/* * FileReader.h * ------------ * Purpose: A basic class for transparent reading of memory-based 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 "mptStringBuffer.h" #include "misc_util.h" #include "Endianness.h" #include "mptIO.h" #include #include #include #include #include "FileReaderFwd.h" OPENMPT_NAMESPACE_BEGIN // change to show warnings for functions which trigger pre-caching the whole file for unseekable streams //#define FILEREADER_DEPRECATED [[deprecated]] #define FILEREADER_DEPRECATED class FileReaderTraitsMemory { public: using off_t = FileDataContainerMemory::off_t; using data_type = FileDataContainerMemory; using ref_data_type = const FileDataContainerMemory &; using shared_data_type = const FileDataContainerMemory &; using value_data_type = FileDataContainerMemory; static shared_data_type get_shared(const data_type & data) { return data; } static ref_data_type get_ref(const data_type & data) { return data; } static value_data_type make_data() { return mpt::const_byte_span(); } static value_data_type make_data(mpt::const_byte_span data) { return data; } static value_data_type make_chunk(shared_data_type data, off_t position, off_t size) { return mpt::as_span(data.GetRawData() + position, size); } }; class FileReaderTraitsStdStream { public: using off_t = IFileDataContainer::off_t; using data_type = std::shared_ptr; using ref_data_type = const IFileDataContainer &; using shared_data_type = std::shared_ptr; using value_data_type = std::shared_ptr; static shared_data_type get_shared(const data_type & data) { return data; } static ref_data_type get_ref(const data_type & data) { return *data; } static value_data_type make_data() { return std::make_shared(); } static value_data_type make_data(mpt::const_byte_span data) { return std::make_shared(data); } static value_data_type make_chunk(shared_data_type data, off_t position, off_t size) { return std::static_pointer_cast(std::make_shared(data, position, size)); } }; using FileReaderTraitsDefault = FileReaderTraitsStdStream; namespace mpt { namespace FileReader { // Read a "T" object from the stream. // If not enough bytes can be read, false is returned. // If successful, the file cursor is advanced by the size of "T". template bool Read(TFileCursor &f, T &target) { // cppcheck false-positive // cppcheck-suppress uninitvar mpt::byte_span dst = mpt::as_raw_memory(target); if(dst.size() != f.GetRaw(dst)) { return false; } f.Skip(dst.size()); return true; } // Read some kind of integer in little-endian format. // If successful, the file cursor is advanced by the size of the integer. template T ReadIntLE(TFileCursor &f) { static_assert(std::numeric_limits::is_integer == true, "Target type is a not an integer"); typename mpt::make_le::type target; if(Read(f, target)) { return target; } else { return 0; } } // Read some kind of integer in big-endian format. // If successful, the file cursor is advanced by the size of the integer. template T ReadIntBE(TFileCursor &f) { static_assert(std::numeric_limits::is_integer == true, "Target type is a not an integer"); typename mpt::make_be::type target; if(Read(f, target)) { return target; } else { return 0; } } // Read a integer in little-endian format which has some of its higher bytes not stored in file. // If successful, the file cursor is advanced by the given size. template T ReadTruncatedIntLE(TFileCursor &f, typename TFileCursor::off_t size) { static_assert(std::numeric_limits::is_integer == true, "Target type is a not an integer"); MPT_ASSERT(sizeof(T) >= size); if(size == 0) { return 0; } if(!f.CanRead(size)) { return 0; } uint8 buf[sizeof(T)]; bool negative = false; for(std::size_t i = 0; i < sizeof(T); ++i) { uint8 byte = 0; if(i < size) { Read(f, byte); negative = std::numeric_limits::is_signed && ((byte & 0x80) != 0x00); } else { // sign or zero extend byte = negative ? 0xff : 0x00; } buf[i] = byte; } typename mpt::make_le::type target; std::memcpy(&target, buf, sizeof(T)); return target; } // Read a supplied-size little endian integer to a fixed size variable. // The data is properly sign-extended when fewer bytes are stored. // If more bytes are stored, higher order bytes are silently ignored. // If successful, the file cursor is advanced by the given size. template T ReadSizedIntLE(TFileCursor &f, typename TFileCursor::off_t size) { static_assert(std::numeric_limits::is_integer == true, "Target type is a not an integer"); if(size == 0) { return 0; } if(!f.CanRead(size)) { return 0; } if(size < sizeof(T)) { return ReadTruncatedIntLE(f, size); } T retval = ReadIntLE(f); f.Skip(size - sizeof(T)); return retval; } // Read unsigned 32-Bit integer in little-endian format. // If successful, the file cursor is advanced by the size of the integer. template uint32 ReadUint32LE(TFileCursor &f) { return ReadIntLE(f); } // Read unsigned 32-Bit integer in big-endian format. // If successful, the file cursor is advanced by the size of the integer. template uint32 ReadUint32BE(TFileCursor &f) { return ReadIntBE(f); } // Read signed 32-Bit integer in little-endian format. // If successful, the file cursor is advanced by the size of the integer. template int32 ReadInt32LE(TFileCursor &f) { return ReadIntLE(f); } // Read signed 32-Bit integer in big-endian format. // If successful, the file cursor is advanced by the size of the integer. template int32 ReadInt32BE(TFileCursor &f) { return ReadIntBE(f); } // Read unsigned 16-Bit integer in little-endian format. // If successful, the file cursor is advanced by the size of the integer. template uint16 ReadUint16LE(TFileCursor &f) { return ReadIntLE(f); } // Read unsigned 16-Bit integer in big-endian format. // If successful, the file cursor is advanced by the size of the integer. template uint16 ReadUint16BE(TFileCursor &f) { return ReadIntBE(f); } // Read signed 16-Bit integer in little-endian format. // If successful, the file cursor is advanced by the size of the integer. template int16 ReadInt16LE(TFileCursor &f) { return ReadIntLE(f); } // Read signed 16-Bit integer in big-endian format. // If successful, the file cursor is advanced by the size of the integer. template int16 ReadInt16BE(TFileCursor &f) { return ReadIntBE(f); } // Read a single 8bit character. // If successful, the file cursor is advanced by the size of the integer. template char ReadChar(TFileCursor &f) { char target; if(Read(f, target)) { return target; } else { return 0; } } // Read unsigned 8-Bit integer. // If successful, the file cursor is advanced by the size of the integer. template uint8 ReadUint8(TFileCursor &f) { uint8 target; if(Read(f, target)) { return target; } else { return 0; } } // Read signed 8-Bit integer. If successful, the file cursor is advanced by the size of the integer. template int8 ReadInt8(TFileCursor &f) { int8 target; if(Read(f, target)) { return target; } else { return 0; } } // Read 32-Bit float in little-endian format. // If successful, the file cursor is advanced by the size of the float. template float ReadFloatLE(TFileCursor &f) { IEEE754binary32LE target; if(Read(f, target)) { return target; } else { return 0.0f; } } // Read 32-Bit float in big-endian format. // If successful, the file cursor is advanced by the size of the float. template float ReadFloatBE(TFileCursor &f) { IEEE754binary32BE target; if(Read(f, target)) { return target; } else { return 0.0f; } } // Read 64-Bit float in little-endian format. // If successful, the file cursor is advanced by the size of the float. template double ReadDoubleLE(TFileCursor &f) { IEEE754binary64LE target; if(Read(f, target)) { return target; } else { return 0.0; } } // Read 64-Bit float in big-endian format. // If successful, the file cursor is advanced by the size of the float. template double ReadDoubleBE(TFileCursor &f) { IEEE754binary64BE target; if(Read(f, target)) { return target; } else { return 0.0; } } // Read a struct. // If successful, the file cursor is advanced by the size of the struct. Otherwise, the target is zeroed. template bool ReadStruct(TFileCursor &f, T &target) { static_assert(mpt::is_binary_safe::value); if(Read(f, target)) { return true; } else { Clear(target); return false; } } // Allow to read a struct partially (if there's less memory available than the struct's size, fill it up with zeros). // The file cursor is advanced by "partialSize" bytes. template typename TFileCursor::off_t ReadStructPartial(TFileCursor &f, T &target, typename TFileCursor::off_t partialSize = sizeof(T)) { static_assert(mpt::is_binary_safe::value); typename TFileCursor::off_t copyBytes = std::min(partialSize, sizeof(T)); if(!f.CanRead(copyBytes)) { copyBytes = f.BytesLeft(); } f.GetRaw(mpt::as_raw_memory(target).data(), copyBytes); std::memset(mpt::as_raw_memory(target).data() + copyBytes, 0, sizeof(target) - copyBytes); f.Skip(partialSize); return copyBytes; } // Read a string of length srcSize into fixed-length char array destBuffer using a given read mode. // The file cursor is advanced by "srcSize" bytes. // Returns true if at least one byte could be read or 0 bytes were requested. template bool ReadString(TFileCursor &f, char (&destBuffer)[destSize], const typename TFileCursor::off_t srcSize) { typename TFileCursor::PinnedRawDataView source = f.ReadPinnedRawDataView(srcSize); // Make sure the string is cached properly. typename TFileCursor::off_t realSrcSize = source.size(); // In case fewer bytes are available mpt::String::WriteAutoBuf(destBuffer) = mpt::String::ReadBuf(mode, mpt::byte_cast(source.data()), realSrcSize); return (realSrcSize > 0 || srcSize == 0); } // Read a string of length srcSize into a std::string dest using a given read mode. // The file cursor is advanced by "srcSize" bytes. // Returns true if at least one character could be read or 0 characters were requested. template bool ReadString(TFileCursor &f, std::string &dest, const typename TFileCursor::off_t srcSize) { dest.clear(); typename TFileCursor::PinnedRawDataView source = f.ReadPinnedRawDataView(srcSize); // Make sure the string is cached properly. typename TFileCursor::off_t realSrcSize = source.size(); // In case fewer bytes are available dest = mpt::String::ReadBuf(mode, mpt::byte_cast(source.data()), realSrcSize); return (realSrcSize > 0 || srcSize == 0); } // Read a string of length srcSize into a mpt::charbuf dest using a given read mode. // The file cursor is advanced by "srcSize" bytes. // Returns true if at least one character could be read or 0 characters were requested. template bool ReadString(TFileCursor &f, mpt::charbuf &dest, const typename TFileCursor::off_t srcSize) { typename TFileCursor::PinnedRawDataView source = f.ReadPinnedRawDataView(srcSize); // Make sure the string is cached properly. typename TFileCursor::off_t realSrcSize = source.size(); // In case fewer bytes are available dest = mpt::String::ReadBuf(mode, mpt::byte_cast(source.data()), realSrcSize); return (realSrcSize > 0 || srcSize == 0); } // Read a charset encoded string of length srcSize into a mpt::ustring dest using a given read mode. // The file cursor is advanced by "srcSize" bytes. // Returns true if at least one character could be read or 0 characters were requested. template bool ReadString(TFileCursor &f, mpt::ustring &dest, mpt::Charset charset, const typename TFileCursor::off_t srcSize) { dest.clear(); typename TFileCursor::PinnedRawDataView source = f.ReadPinnedRawDataView(srcSize); // Make sure the string is cached properly. typename TFileCursor::off_t realSrcSize = source.size(); // In case fewer bytes are available dest = mpt::ToUnicode(charset, mpt::String::ReadBuf(mode, mpt::byte_cast(source.data()), realSrcSize)); return (realSrcSize > 0 || srcSize == 0); } // Read a string with a preprended length field of type Tsize (must be a packed<*,*> type) into a std::string dest using a given read mode. // The file cursor is advanced by the string length. // Returns true if the size field could be read and at least one character could be read or 0 characters were requested. template bool ReadSizedString(TFileCursor &f, char (&destBuffer)[destSize], const typename TFileCursor::off_t maxLength = std::numeric_limits::max()) { packed srcSize; // Enforce usage of a packed type by ensuring that the passed type has the required typedefs if(!Read(f, srcSize)) return false; return ReadString(f, destBuffer, std::min(static_cast(srcSize), maxLength)); } // Read a string with a preprended length field of type Tsize (must be a packed<*,*> type) into a std::string dest using a given read mode. // The file cursor is advanced by the string length. // Returns true if the size field could be read and at least one character could be read or 0 characters were requested. template bool ReadSizedString(TFileCursor &f, std::string &dest, const typename TFileCursor::off_t maxLength = std::numeric_limits::max()) { packed srcSize; // Enforce usage of a packed type by ensuring that the passed type has the required typedefs if(!Read(f, srcSize)) return false; return ReadString(f, dest, std::min(static_cast(srcSize), maxLength)); } // Read a string with a preprended length field of type Tsize (must be a packed<*,*> type) into a mpt::charbuf dest using a given read mode. // The file cursor is advanced by the string length. // Returns true if the size field could be read and at least one character could be read or 0 characters were requested. template bool ReadSizedString(TFileCursor &f, mpt::charbuf &dest, const typename TFileCursor::off_t maxLength = std::numeric_limits::max()) { packed srcSize; // Enforce usage of a packed type by ensuring that the passed type has the required typedefs if(!Read(f, srcSize)) return false; return ReadString(f, dest, std::min(static_cast(srcSize), maxLength)); } // Read a null-terminated string into a std::string template bool ReadNullString(TFileCursor &f, std::string &dest, const typename TFileCursor::off_t maxLength = std::numeric_limits::max()) { dest.clear(); if(!f.CanRead(1)) return false; try { char buffer[64]; typename TFileCursor::off_t avail = 0; while((avail = std::min(f.GetRaw(buffer, std::size(buffer)), maxLength - dest.length())) != 0) { auto end = std::find(buffer, buffer + avail, '\0'); dest.insert(dest.end(), buffer, end); f.Skip(end - buffer); if(end < buffer + avail) { // Found null char f.Skip(1); break; } } } MPT_EXCEPTION_CATCH_OUT_OF_MEMORY(e) { MPT_EXCEPTION_DELETE_OUT_OF_MEMORY(e); } return dest.length() != 0; } // Read a string up to the next line terminator into a std::string template bool ReadLine(TFileCursor &f, std::string &dest, const typename TFileCursor::off_t maxLength = std::numeric_limits::max()) { dest.clear(); if(!f.CanRead(1)) return false; try { char buffer[64]; char c = '\0'; typename TFileCursor::off_t avail = 0; while((avail = std::min(f.GetRaw(buffer, std::size(buffer)), maxLength - dest.length())) != 0) { auto end = std::find_if(buffer, buffer + avail, mpt::String::Traits::IsLineEnding); dest.insert(dest.end(), buffer, end); f.Skip(end - buffer); if(end < buffer + avail) { // Found line ending f.Skip(1); // Handle CRLF line ending if(*end == '\r') { if(Read(f, c) && c != '\n') f.SkipBack(1); } break; } } } MPT_EXCEPTION_CATCH_OUT_OF_MEMORY(e) { MPT_EXCEPTION_DELETE_OUT_OF_MEMORY(e); } return true; } // Read an array of binary-safe T values. // If successful, the file cursor is advanced by the size of the array. // Otherwise, the target is zeroed. template bool ReadArray(TFileCursor &f, T (&destArray)[destSize]) { static_assert(mpt::is_binary_safe::value); if(f.CanRead(sizeof(destArray))) { f.ReadRaw(mpt::as_raw_memory(destArray)); return true; } else { Clear(destArray); return false; } } // Read an array of binary-safe T values. // If successful, the file cursor is advanced by the size of the array. // Otherwise, the target is zeroed. template bool ReadArray(TFileCursor &f, std::array &destArray) { static_assert(mpt::is_binary_safe::value); if(f.CanRead(sizeof(destArray))) { f.ReadRaw(mpt::as_raw_memory(destArray)); return true; } else { destArray.fill(T()); return false; } } // Read destSize elements of binary-safe type T into a vector. // If successful, the file cursor is advanced by the size of the vector. // Otherwise, the vector is resized to destSize, but possibly existing contents are not cleared. template bool ReadVector(TFileCursor &f, std::vector &destVector, size_t destSize) { static_assert(mpt::is_binary_safe::value); destVector.resize(destSize); if(f.CanRead(sizeof(T) * destSize)) { f.ReadRaw(mpt::as_raw_memory(destVector)); return true; } else { return false; } } template std::array ReadArray(TFileCursor &f) { std::array destArray; ReadArray(f, destArray); return destArray; } // Compare a magic string with the current stream position. // Returns true if they are identical and advances the file cursor by the the length of the "magic" string. // Returns false if the string could not be found. The file cursor is not advanced in this case. template bool ReadMagic(TFileCursor &f, const char *const magic, typename TFileCursor::off_t magicLength) { std::byte buffer[16] = { std::byte(0) }; typename TFileCursor::off_t bytesRead = 0; typename TFileCursor::off_t bytesRemain = magicLength; while(bytesRemain) { typename TFileCursor::off_t numBytes = std::min(static_cast(sizeof(buffer)), bytesRemain); if(f.GetRawWithOffset(bytesRead, buffer, numBytes) != numBytes) return false; if(memcmp(buffer, magic + bytesRead, numBytes)) return false; bytesRemain -= numBytes; bytesRead += numBytes; } f.Skip(magicLength); return true; } template bool ReadMagic(TFileCursor &f, const char (&magic)[N]) { MPT_ASSERT(magic[N - 1] == '\0'); for(std::size_t i = 0; i < N - 1; ++i) { MPT_ASSERT(magic[i] != '\0'); } return ReadMagic(f, static_cast(magic), static_cast(N - 1)); } // Read variable-length unsigned integer (as found in MIDI files). // If successful, the file cursor is advanced by the size of the integer and true is returned. // False is returned if not enough bytes were left to finish reading of the integer or if an overflow happened (source doesn't fit into target integer). // In case of an overflow, the target is also set to the maximum value supported by its data type. template bool ReadVarInt(TFileCursor &f, T &target) { static_assert(std::numeric_limits::is_integer == true && std::numeric_limits::is_signed == false, "Target type is not an unsigned integer"); if(f.NoBytesLeft()) { target = 0; return false; } std::byte bytes[16]; // More than enough for any valid VarInt typename TFileCursor::off_t avail = f.GetRaw(bytes, sizeof(bytes)); typename TFileCursor::off_t readPos = 1; uint8 b = mpt::byte_cast(bytes[0]); target = (b & 0x7F); size_t writtenBits = static_cast(mpt::bit_width(target)); // Bits used in the most significant byte while(readPos < avail && (b & 0x80) != 0) { b = mpt::byte_cast(bytes[readPos++]); target <<= 7; target |= (b & 0x7F); writtenBits += 7; if(readPos == avail) { f.Skip(readPos); avail = f.GetRaw(bytes, sizeof(bytes)); readPos = 0; } } f.Skip(readPos); if(writtenBits > sizeof(target) * 8u) { // Overflow target = Util::MaxValueOfType(target); return false; } else if((b & 0x80) != 0) { // Reached EOF return false; } return true; } } // namespace FileReader } // namespace mpt namespace FR = mpt::FileReader; namespace detail { template class FileReader { private: using traits_type = Ttraits; public: using off_t = typename traits_type::off_t; using data_type = typename traits_type::data_type; using ref_data_type = typename traits_type::ref_data_type; using shared_data_type = typename traits_type::shared_data_type; using value_data_type = typename traits_type::value_data_type; protected: shared_data_type SharedDataContainer() const { return traits_type::get_shared(m_data); } ref_data_type DataContainer() const { return traits_type::get_ref(m_data); } static value_data_type DataInitializer() { return traits_type::make_data(); } static value_data_type DataInitializer(mpt::const_byte_span data) { return traits_type::make_data(data); } static value_data_type CreateChunkImpl(shared_data_type data, off_t position, off_t size) { return traits_type::make_chunk(data, position, size); } private: data_type m_data; off_t streamPos; // Cursor location in the file const mpt::PathString *fileName; // Filename that corresponds to this FileReader. It is only set if this FileReader represents the whole contents of fileName. May be nullptr. Lifetime is managed outside of FileReader. public: // Initialize invalid file reader object. FileReader() : m_data(DataInitializer()), streamPos(0), fileName(nullptr) { } // Initialize file reader object with pointer to data and data length. template FileReader(mpt::span bytedata, const mpt::PathString *filename = nullptr) : m_data(DataInitializer(mpt::byte_cast(bytedata))), streamPos(0), fileName(filename) { } // Initialize file reader object based on an existing file reader object window. explicit FileReader(value_data_type other, const mpt::PathString *filename = nullptr) : m_data(other), streamPos(0), fileName(filename) { } public: mpt::PathString GetFileName() const { if(!fileName) { return mpt::PathString(); } return *fileName; } // Returns true if the object points to a valid (non-empty) stream. operator bool() const { return IsValid(); } // Returns true if the object points to a valid (non-empty) stream. bool IsValid() const { return DataContainer().IsValid(); } // Reset cursor to first byte in file. void Rewind() { streamPos = 0; } // Seek to a position in the mapped file. // Returns false if position is invalid. bool Seek(off_t position) { if(position <= streamPos) { streamPos = position; return true; } if(position <= DataContainer().GetLength()) { streamPos = position; return true; } else { return false; } } // Increases position by skipBytes. // Returns true if skipBytes could be skipped or false if the file end was reached earlier. bool Skip(off_t skipBytes) { if(CanRead(skipBytes)) { streamPos += skipBytes; return true; } else { streamPos = DataContainer().GetLength(); return false; } } // Decreases position by skipBytes. // Returns true if skipBytes could be skipped or false if the file start was reached earlier. bool SkipBack(off_t skipBytes) { if(streamPos >= skipBytes) { streamPos -= skipBytes; return true; } else { streamPos = 0; return false; } } // Returns cursor position in the mapped file. off_t GetPosition() const { return streamPos; } // Return true IFF seeking and GetLength() is fast. // In particular, it returns false for unseekable stream where GetLength() // requires pre-caching. bool HasFastGetLength() const { return DataContainer().HasFastGetLength(); } // Returns size of the mapped file in bytes. FILEREADER_DEPRECATED off_t GetLength() const { // deprecated because in case of an unseekable std::istream, this triggers caching of the whole file return DataContainer().GetLength(); } // Return byte count between cursor position and end of file, i.e. how many bytes can still be read. FILEREADER_DEPRECATED off_t BytesLeft() const { // deprecated because in case of an unseekable std::istream, this triggers caching of the whole file return DataContainer().GetLength() - streamPos; } bool EndOfFile() const { return !CanRead(1); } bool NoBytesLeft() const { return !CanRead(1); } // Check if "amount" bytes can be read from the current position in the stream. bool CanRead(off_t amount) const { return DataContainer().CanRead(streamPos, amount); } // Check if file size is at least size, without potentially caching the whole file to query the exact file length. bool LengthIsAtLeast(off_t size) const { return DataContainer().CanRead(0, size); } // Check if file size is exactly size, without potentially caching the whole file if it is larger. bool LengthIs(off_t size) const { return DataContainer().CanRead(0, size) && !DataContainer().CanRead(size, 1); } protected: FileReader CreateChunk(off_t position, off_t length) const { off_t readableLength = DataContainer().GetReadableLength(position, length); if(readableLength == 0) { return FileReader(); } return FileReader(CreateChunkImpl(SharedDataContainer(), position, std::min(length, DataContainer().GetLength() - position))); } public: // Create a new FileReader object for parsing a sub chunk at a given position with a given length. // The file cursor is not modified. FileReader GetChunkAt(off_t position, off_t length) const { return CreateChunk(position, length); } // Create a new FileReader object for parsing a sub chunk at the current position with a given length. // The file cursor is not advanced. FileReader GetChunk(off_t length) { return CreateChunk(streamPos, length); } // Create a new FileReader object for parsing a sub chunk at the current position with a given length. // The file cursor is advanced by "length" bytes. FileReader ReadChunk(off_t length) { off_t position = streamPos; Skip(length); return CreateChunk(position, length); } class PinnedRawDataView { private: std::size_t size_; const std::byte *pinnedData; std::vector cache; private: void Init(const FileReader &file, std::size_t size) { size_ = 0; pinnedData = nullptr; if(!file.CanRead(size)) { size = file.BytesLeft(); } size_ = size; if(file.DataContainer().HasPinnedView()) { pinnedData = file.DataContainer().GetRawData() + file.GetPosition(); } else { cache.resize(size_); if(!cache.empty()) { // cppcheck false-positive // cppcheck-suppress containerOutOfBounds file.GetRaw(&(cache[0]), size); } } } public: PinnedRawDataView() : size_(0) , pinnedData(nullptr) { } PinnedRawDataView(const FileReader &file) { Init(file, file.BytesLeft()); } PinnedRawDataView(const FileReader &file, std::size_t size) { Init(file, size); } PinnedRawDataView(FileReader &file, bool advance) { Init(file, file.BytesLeft()); if(advance) { file.Skip(size_); } } PinnedRawDataView(FileReader &file, std::size_t size, bool advance) { Init(file, size); if(advance) { file.Skip(size_); } } public: mpt::const_byte_span GetSpan() const { if(pinnedData) { return mpt::as_span(pinnedData, size_); } else if(!cache.empty()) { return mpt::as_span(cache); } else { return mpt::const_byte_span(); } } mpt::const_byte_span span() const { return GetSpan(); } void invalidate() { size_ = 0; pinnedData = nullptr; cache = std::vector(); } const std::byte *data() const { return span().data(); } std::size_t size() const { return size_; } mpt::const_byte_span::pointer begin() const { return span().data(); } mpt::const_byte_span::pointer end() const { return span().data() + span().size(); } mpt::const_byte_span::const_pointer cbegin() const { return span().data(); } mpt::const_byte_span::const_pointer cend() const { return span().data() + span().size(); } }; // Returns a pinned view into the remaining raw data from cursor position. PinnedRawDataView GetPinnedRawDataView() const { return PinnedRawDataView(*this); } // Returns a pinned view into the remeining raw data from cursor position, clamped at size. PinnedRawDataView GetPinnedRawDataView(std::size_t size) const { return PinnedRawDataView(*this, size); } // Returns a pinned view into the remeining raw data from cursor position. // File cursor is advaned by the size of the returned pinned view. PinnedRawDataView ReadPinnedRawDataView() { return PinnedRawDataView(*this, true); } // Returns a pinned view into the remeining raw data from cursor position, clamped at size. // File cursor is advaned by the size of the returned pinned view. PinnedRawDataView ReadPinnedRawDataView(std::size_t size) { return PinnedRawDataView(*this, size, true); } // Returns raw stream data at cursor position. // Should only be used if absolutely necessary, for example for sample reading, or when used with a small chunk of the file retrieved by ReadChunk(). // Use GetPinnedRawDataView(size) whenever possible. FILEREADER_DEPRECATED const std::byte *GetRawData() const { // deprecated because in case of an unseekable std::istream, this triggers caching of the whole file return DataContainer().GetRawData() + streamPos; } template FILEREADER_DEPRECATED const T *GetRawData() const { // deprecated because in case of an unseekable std::istream, this triggers caching of the whole file return mpt::byte_cast(DataContainer().GetRawData() + streamPos); } template std::size_t GetRawWithOffset(std::size_t offset, T *dst, std::size_t count) const { return static_cast(DataContainer().Read(mpt::byte_cast(dst), streamPos + offset, count)); } std::size_t GetRawWithOffset(std::size_t offset, mpt::byte_span dst) const { return static_cast(DataContainer().Read(streamPos + offset, dst)); } template std::size_t GetRaw(T *dst, std::size_t count) const { return static_cast(DataContainer().Read(mpt::byte_cast(dst), streamPos, count)); } std::size_t GetRaw(mpt::byte_span dst) const { return static_cast(DataContainer().Read(streamPos, dst)); } template std::size_t ReadRaw(T *dst, std::size_t count) { std::size_t result = static_cast(DataContainer().Read(mpt::byte_cast(dst), streamPos, count)); streamPos += result; return result; } std::size_t ReadRaw(mpt::byte_span dst) { std::size_t result = static_cast(DataContainer().Read(streamPos, dst)); streamPos += result; return result; } std::vector GetRawDataAsByteVector() const { PinnedRawDataView view = GetPinnedRawDataView(); return mpt::make_vector(view.span()); } std::vector ReadRawDataAsByteVector() { PinnedRawDataView view = ReadPinnedRawDataView(); return mpt::make_vector(view.span()); } std::vector GetRawDataAsByteVector(std::size_t size) const { PinnedRawDataView view = GetPinnedRawDataView(size); return mpt::make_vector(view.span()); } std::vector ReadRawDataAsByteVector(std::size_t size) { PinnedRawDataView view = ReadPinnedRawDataView(size); return mpt::make_vector(view.span()); } std::string GetRawDataAsString() const { PinnedRawDataView view = GetPinnedRawDataView(); mpt::span data = mpt::byte_cast>(view.span()); return std::string(data.begin(), data.end()); } std::string ReadRawDataAsString() { PinnedRawDataView view = ReadPinnedRawDataView(); mpt::span data = mpt::byte_cast>(view.span()); return std::string(data.begin(), data.end()); } std::string GetRawDataAsString(std::size_t size) const { PinnedRawDataView view = GetPinnedRawDataView(size); mpt::span data = mpt::byte_cast>(view.span()); return std::string(data.begin(), data.end()); } std::string ReadRawDataAsString(std::size_t size) { PinnedRawDataView view = ReadPinnedRawDataView(size); mpt::span data = mpt::byte_cast>(view.span()); return std::string(data.begin(), data.end()); } template bool Read(T &target) { return mpt::FileReader::Read(*this, target); } template T ReadIntLE() { return mpt::FileReader::ReadIntLE(*this); } template T ReadIntBE() { return mpt::FileReader::ReadIntLE(*this); } template T ReadTruncatedIntLE(off_t size) { return mpt::FileReader::ReadTruncatedIntLE(*this, size); } template T ReadSizedIntLE(off_t size) { return mpt::FileReader::ReadSizedIntLE(*this, size); } uint32 ReadUint32LE() { return mpt::FileReader::ReadUint32LE(*this); } uint32 ReadUint32BE() { return mpt::FileReader::ReadUint32BE(*this); } int32 ReadInt32LE() { return mpt::FileReader::ReadInt32LE(*this); } int32 ReadInt32BE() { return mpt::FileReader::ReadInt32BE(*this); } uint16 ReadUint16LE() { return mpt::FileReader::ReadUint16LE(*this); } uint16 ReadUint16BE() { return mpt::FileReader::ReadUint16BE(*this); } int16 ReadInt16LE() { return mpt::FileReader::ReadInt16LE(*this); } int16 ReadInt16BE() { return mpt::FileReader::ReadInt16BE(*this); } char ReadChar() { return mpt::FileReader::ReadChar(*this); } uint8 ReadUint8() { return mpt::FileReader::ReadUint8(*this); } int8 ReadInt8() { return mpt::FileReader::ReadInt8(*this); } float ReadFloatLE() { return mpt::FileReader::ReadFloatLE(*this); } float ReadFloatBE() { return mpt::FileReader::ReadFloatBE(*this); } double ReadDoubleLE() { return mpt::FileReader::ReadDoubleLE(*this); } double ReadDoubleBE() { return mpt::FileReader::ReadDoubleBE(*this); } template bool ReadStruct(T &target) { return mpt::FileReader::ReadStruct(*this, target); } template size_t ReadStructPartial(T &target, size_t partialSize = sizeof(T)) { return mpt::FileReader::ReadStructPartial(*this, target, partialSize); } template bool ReadString(char (&destBuffer)[destSize], const off_t srcSize) { return mpt::FileReader::ReadString(*this, destBuffer, srcSize); } template bool ReadString(std::string &dest, const off_t srcSize) { return mpt::FileReader::ReadString(*this, dest, srcSize); } template bool ReadString(mpt::charbuf &dest, const off_t srcSize) { return mpt::FileReader::ReadString(*this, dest, srcSize); } template bool ReadString(mpt::ustring &dest, mpt::Charset charset, const off_t srcSize) { return mpt::FileReader::ReadString(*this, dest, charset, srcSize); } template bool ReadSizedString(char (&destBuffer)[destSize], const off_t maxLength = std::numeric_limits::max()) { return mpt::FileReader::ReadSizedString(*this, destBuffer, maxLength); } template bool ReadSizedString(std::string &dest, const off_t maxLength = std::numeric_limits::max()) { return mpt::FileReader::ReadSizedString(*this, dest, maxLength); } template bool ReadSizedString(mpt::charbuf &dest, const off_t maxLength = std::numeric_limits::max()) { return mpt::FileReader::ReadSizedString(*this, dest, maxLength); } bool ReadNullString(std::string &dest, const off_t maxLength = std::numeric_limits::max()) { return mpt::FileReader::ReadNullString(*this, dest, maxLength); } bool ReadLine(std::string &dest, const off_t maxLength = std::numeric_limits::max()) { return mpt::FileReader::ReadLine(*this, dest, maxLength); } template bool ReadArray(T (&destArray)[destSize]) { return mpt::FileReader::ReadArray(*this, destArray); } template bool ReadArray(std::array &destArray) { return mpt::FileReader::ReadArray(*this, destArray); } template std::array ReadArray() { return mpt::FileReader::ReadArray(*this); } template bool ReadVector(std::vector &destVector, size_t destSize) { return mpt::FileReader::ReadVector(*this, destVector, destSize); } template bool ReadMagic(const char (&magic)[N]) { return mpt::FileReader::ReadMagic(*this, magic); } bool ReadMagic(const char *const magic, off_t magicLength) { return mpt::FileReader::ReadMagic(*this, magic, magicLength); } template bool ReadVarInt(T &target) { return mpt::FileReader::ReadVarInt(*this, target); } }; } // namespace detail using FileReader = detail::FileReader; using MemoryFileReader = detail::FileReader; // Initialize file reader object with pointer to data and data length. template inline FileReader make_FileReader(mpt::span bytedata, const mpt::PathString *filename = nullptr) { return FileReader(mpt::byte_cast(bytedata), filename); } #if defined(MPT_FILEREADER_CALLBACK_STREAM) // Initialize file reader object with a CallbackStream. inline FileReader make_FileReader(CallbackStream s, const mpt::PathString *filename = nullptr) { return FileReader( FileDataContainerCallbackStreamSeekable::IsSeekable(s) ? std::static_pointer_cast(std::make_shared(s)) : std::static_pointer_cast(std::make_shared(s)) , filename ); } #endif // MPT_FILEREADER_CALLBACK_STREAM // Initialize file reader object with a std::istream. inline FileReader make_FileReader(std::istream *s, const mpt::PathString *filename = nullptr) { return FileReader( FileDataContainerStdStreamSeekable::IsSeekable(s) ? std::static_pointer_cast(std::make_shared(s)) : std::static_pointer_cast(std::make_shared(s)) , filename ); } #if defined(MPT_ENABLE_FILEIO) // templated in order to reduce header inter-dependencies template FileReader GetFileReader(TInputFile &file) { if(!file.IsValid()) { return FileReader(); } if(file.IsCached()) { return make_FileReader(file.GetCache(), &file.GetFilenameRef()); } else { return make_FileReader(file.GetStream(), &file.GetFilenameRef()); } } #endif // MPT_ENABLE_FILEIO #if defined(MPT_ENABLE_TEMPFILE) && MPT_OS_WINDOWS class OnDiskFileWrapper { private: mpt::PathString m_Filename; bool m_IsTempFile; public: OnDiskFileWrapper(FileReader &file, const mpt::PathString &fileNameExtension = P_("tmp")); ~OnDiskFileWrapper(); public: bool IsValid() const; mpt::PathString GetFilename() const; }; // class OnDiskFileWrapper #endif // MPT_ENABLE_TEMPFILE && MPT_OS_WINDOWS OPENMPT_NAMESPACE_END