1098 lines
26 KiB
C
1098 lines
26 KiB
C
|
/*
|
||
|
* mptIO.h
|
||
|
* -------
|
||
|
* Purpose: Basic functions for reading/writing binary and endian safe data to/from files/streams.
|
||
|
* Notes : Some useful functions for reading and writing are still missing.
|
||
|
* Authors: Joern Heusipp
|
||
|
* OpenMPT Devs
|
||
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
||
|
*/
|
||
|
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#include "BuildSettings.h"
|
||
|
|
||
|
|
||
|
#include "../common/Endianness.h"
|
||
|
#include <algorithm>
|
||
|
#include <array>
|
||
|
#include <iosfwd>
|
||
|
#include <limits>
|
||
|
#include <cstring>
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_BEGIN
|
||
|
|
||
|
|
||
|
namespace mpt {
|
||
|
|
||
|
namespace IO {
|
||
|
|
||
|
typedef int64 Offset;
|
||
|
|
||
|
static constexpr std::size_t BUFFERSIZE_TINY = 1 * 1024; // on stack usage
|
||
|
static constexpr std::size_t BUFFERSIZE_SMALL = 4 * 1024; // on heap
|
||
|
static constexpr std::size_t BUFFERSIZE_NORMAL = 64 * 1024; // FILE I/O
|
||
|
static constexpr std::size_t BUFFERSIZE_LARGE = 1024 * 1024;
|
||
|
|
||
|
|
||
|
|
||
|
// Returns true iff 'off' fits into 'Toff'.
|
||
|
template < typename Toff >
|
||
|
inline bool OffsetFits(IO::Offset off)
|
||
|
{
|
||
|
return (static_cast<IO::Offset>(mpt::saturate_cast<Toff>(off)) == off);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
bool IsValid(std::ostream & f);
|
||
|
bool IsValid(std::istream & f);
|
||
|
bool IsValid(std::iostream & f);
|
||
|
bool IsReadSeekable(std::istream& f);
|
||
|
bool IsWriteSeekable(std::ostream& f);
|
||
|
IO::Offset TellRead(std::istream & f);
|
||
|
IO::Offset TellWrite(std::ostream & f);
|
||
|
bool SeekBegin(std::ostream & f);
|
||
|
bool SeekBegin(std::istream & f);
|
||
|
bool SeekBegin(std::iostream & f);
|
||
|
bool SeekEnd(std::ostream & f);
|
||
|
bool SeekEnd(std::istream & f);
|
||
|
bool SeekEnd(std::iostream & f);
|
||
|
bool SeekAbsolute(std::ostream & f, IO::Offset pos);
|
||
|
bool SeekAbsolute(std::istream & f, IO::Offset pos);
|
||
|
bool SeekAbsolute(std::iostream & f, IO::Offset pos);
|
||
|
bool SeekRelative(std::ostream & f, IO::Offset off);
|
||
|
bool SeekRelative(std::istream & f, IO::Offset off);
|
||
|
bool SeekRelative(std::iostream & f, IO::Offset off);
|
||
|
IO::Offset ReadRawImpl(std::istream & f, mpt::byte_span data);
|
||
|
bool WriteRawImpl(std::ostream & f, mpt::const_byte_span data);
|
||
|
bool IsEof(std::istream & f);
|
||
|
bool Flush(std::ostream & f);
|
||
|
|
||
|
|
||
|
|
||
|
template <typename Tfile> class WriteBuffer;
|
||
|
|
||
|
template <typename Tfile> bool IsValid(WriteBuffer<Tfile> & f) { return IsValid(f.file()); }
|
||
|
template <typename Tfile> bool IsReadSeekable(WriteBuffer<Tfile> & f) { return IsReadSeekable(f.file()); }
|
||
|
template <typename Tfile> bool IsWriteSeekable(WriteBuffer<Tfile> & f) { return IsWriteSeekable(f.file()); }
|
||
|
template <typename Tfile> IO::Offset TellRead(WriteBuffer<Tfile> & f) { f.FlushLocal(); return TellRead(f.file()); }
|
||
|
template <typename Tfile> IO::Offset TellWrite(WriteBuffer<Tfile> & f) { return TellWrite(f.file()) + f.GetCurrentSize(); }
|
||
|
template <typename Tfile> bool SeekBegin(WriteBuffer<Tfile> & f) { f.FlushLocal(); return SeekBegin(f.file()); }
|
||
|
template <typename Tfile> bool SeekEnd(WriteBuffer<Tfile> & f) { f.FlushLocal(); return SeekEnd(f.file()); }
|
||
|
template <typename Tfile> bool SeekAbsolute(WriteBuffer<Tfile> & f, IO::Offset pos) { f.FlushLocal(); return SeekAbsolute(f.file(), pos); }
|
||
|
template <typename Tfile> bool SeekRelative(WriteBuffer<Tfile> & f, IO::Offset off) { f.FlushLocal(); return SeekRelative(f.file(), off); }
|
||
|
template <typename Tfile> IO::Offset ReadRawImpl(WriteBuffer<Tfile> & f, mpt::byte_span data) { f.FlushLocal(); return ReadRawImpl(f.file(), data); }
|
||
|
template <typename Tfile> bool WriteRawImpl(WriteBuffer<Tfile> & f, mpt::const_byte_span data) { return f.Write(data); }
|
||
|
template <typename Tfile> bool IsEof(WriteBuffer<Tfile> & f) { f.FlushLocal(); return IsEof(f.file()); }
|
||
|
template <typename Tfile> bool Flush(WriteBuffer<Tfile> & f) { f.FlushLocal(); return Flush(f.file()); }
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
template <typename Tbyte> bool IsValid(std::pair<mpt::span<Tbyte>, IO::Offset> & f)
|
||
|
{
|
||
|
return (f.second >= 0);
|
||
|
}
|
||
|
template <typename Tbyte> IO::Offset TellRead(std::pair<mpt::span<Tbyte>, IO::Offset> & f)
|
||
|
{
|
||
|
return f.second;
|
||
|
}
|
||
|
template <typename Tbyte> IO::Offset TellWrite(std::pair<mpt::span<Tbyte>, IO::Offset> & f)
|
||
|
{
|
||
|
return f.second;
|
||
|
}
|
||
|
template <typename Tbyte> bool SeekBegin(std::pair<mpt::span<Tbyte>, IO::Offset> & f)
|
||
|
{
|
||
|
f.second = 0;
|
||
|
return true;
|
||
|
}
|
||
|
template <typename Tbyte> bool SeekEnd(std::pair<mpt::span<Tbyte>, IO::Offset> & f)
|
||
|
{
|
||
|
f.second = f.first.size();
|
||
|
return true;
|
||
|
}
|
||
|
template <typename Tbyte> bool SeekAbsolute(std::pair<mpt::span<Tbyte>, IO::Offset> & f, IO::Offset pos)
|
||
|
{
|
||
|
f.second = pos;
|
||
|
return true;
|
||
|
}
|
||
|
template <typename Tbyte> bool SeekRelative(std::pair<mpt::span<Tbyte>, IO::Offset> & f, IO::Offset off)
|
||
|
{
|
||
|
if(f.second < 0)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
f.second += off;
|
||
|
return true;
|
||
|
}
|
||
|
template <typename Tbyte> IO::Offset ReadRawImpl(std::pair<mpt::span<Tbyte>, IO::Offset> & f, mpt::byte_span data)
|
||
|
{
|
||
|
if(f.second < 0)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
if(f.second >= static_cast<IO::Offset>(f.first.size()))
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
std::size_t num = mpt::saturate_cast<std::size_t>(std::min(static_cast<IO::Offset>(f.first.size()) - f.second, static_cast<IO::Offset>(data.size())));
|
||
|
std::copy(mpt::byte_cast<const std::byte*>(f.first.data() + f.second), mpt::byte_cast<const std::byte*>(f.first.data() + f.second + num), data.data());
|
||
|
f.second += num;
|
||
|
return num;
|
||
|
}
|
||
|
template <typename Tbyte> bool WriteRawImpl(std::pair<mpt::span<Tbyte>, IO::Offset> & f, mpt::const_byte_span data)
|
||
|
{
|
||
|
if(f.second < 0)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if(f.second > static_cast<IO::Offset>(f.first.size()))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
std::size_t num = mpt::saturate_cast<std::size_t>(std::min(static_cast<IO::Offset>(f.first.size()) - f.second, static_cast<IO::Offset>(data.size())));
|
||
|
if(num != data.size())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
std::copy(data.data(), data.data() + num, mpt::byte_cast<std::byte*>(f.first.data() + f.second));
|
||
|
f.second += num;
|
||
|
return true;
|
||
|
}
|
||
|
template <typename Tbyte> bool IsEof(std::pair<mpt::span<Tbyte>, IO::Offset> & f)
|
||
|
{
|
||
|
return (f.second >= static_cast<IO::Offset>(f.first.size()));
|
||
|
}
|
||
|
template <typename Tbyte> bool Flush(std::pair<mpt::span<Tbyte>, IO::Offset> & f)
|
||
|
{
|
||
|
MPT_UNREFERENCED_PARAMETER(f);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
template <typename Tbyte, typename Tfile>
|
||
|
inline IO::Offset ReadRaw(Tfile & f, Tbyte * data, std::size_t size)
|
||
|
{
|
||
|
return IO::ReadRawImpl(f, mpt::as_span(mpt::byte_cast<std::byte*>(data), size));
|
||
|
}
|
||
|
|
||
|
template <typename Tbyte, typename Tfile>
|
||
|
inline IO::Offset ReadRaw(Tfile & f, mpt::span<Tbyte> data)
|
||
|
{
|
||
|
return IO::ReadRawImpl(f, mpt::byte_cast<mpt::byte_span>(data));
|
||
|
}
|
||
|
|
||
|
template <typename Tbyte, typename Tfile>
|
||
|
inline bool WriteRaw(Tfile & f, const Tbyte * data, std::size_t size)
|
||
|
{
|
||
|
return IO::WriteRawImpl(f, mpt::as_span(mpt::byte_cast<const std::byte*>(data), size));
|
||
|
}
|
||
|
|
||
|
template <typename Tbyte, typename Tfile>
|
||
|
inline bool WriteRaw(Tfile & f, mpt::span<Tbyte> data)
|
||
|
{
|
||
|
return IO::WriteRawImpl(f, mpt::byte_cast<mpt::const_byte_span>(data));
|
||
|
}
|
||
|
|
||
|
template <typename Tbinary, typename Tfile>
|
||
|
inline bool Read(Tfile & f, Tbinary & v)
|
||
|
{
|
||
|
return IO::ReadRaw(f, mpt::as_raw_memory(v)) == mpt::saturate_cast<mpt::IO::Offset>(mpt::as_raw_memory(v).size());
|
||
|
}
|
||
|
|
||
|
template <typename Tbinary, typename Tfile>
|
||
|
inline bool Write(Tfile & f, const Tbinary & v)
|
||
|
{
|
||
|
return IO::WriteRaw(f, mpt::as_raw_memory(v));
|
||
|
}
|
||
|
|
||
|
template <typename Tbinary, typename Tfile>
|
||
|
inline bool Write(Tfile & f, const std::vector<Tbinary> & v)
|
||
|
{
|
||
|
static_assert(mpt::is_binary_safe<Tbinary>::value);
|
||
|
return IO::WriteRaw(f, mpt::as_raw_memory(v));
|
||
|
}
|
||
|
|
||
|
template <typename T, typename Tfile>
|
||
|
inline bool WritePartial(Tfile & f, const T & v, size_t size = sizeof(T))
|
||
|
{
|
||
|
MPT_ASSERT(size <= sizeof(T));
|
||
|
return IO::WriteRaw(f, mpt::as_span(mpt::as_raw_memory(v).data(), size));
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool ReadByte(Tfile & f, std::byte & v)
|
||
|
{
|
||
|
bool result = false;
|
||
|
std::byte byte = mpt::as_byte(0);
|
||
|
const IO::Offset readResult = IO::ReadRaw(f, &byte, sizeof(std::byte));
|
||
|
if(readResult < 0)
|
||
|
{
|
||
|
result = false;
|
||
|
} else
|
||
|
{
|
||
|
result = (static_cast<uint64>(readResult) == sizeof(std::byte));
|
||
|
}
|
||
|
v = byte;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
template <typename T, typename Tfile>
|
||
|
inline bool ReadBinaryTruncatedLE(Tfile & f, T & v, std::size_t size)
|
||
|
{
|
||
|
bool result = false;
|
||
|
static_assert(std::numeric_limits<T>::is_integer);
|
||
|
uint8 bytes[sizeof(T)];
|
||
|
std::memset(bytes, 0, sizeof(T));
|
||
|
const IO::Offset readResult = IO::ReadRaw(f, bytes, std::min(size, sizeof(T)));
|
||
|
if(readResult < 0)
|
||
|
{
|
||
|
result = false;
|
||
|
} else
|
||
|
{
|
||
|
result = (static_cast<uint64>(readResult) == std::min(size, sizeof(T)));
|
||
|
}
|
||
|
typename mpt::make_le<T>::type val;
|
||
|
std::memcpy(&val, bytes, sizeof(T));
|
||
|
v = val;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
template <typename T, typename Tfile>
|
||
|
inline bool ReadIntLE(Tfile & f, T & v)
|
||
|
{
|
||
|
bool result = false;
|
||
|
static_assert(std::numeric_limits<T>::is_integer);
|
||
|
uint8 bytes[sizeof(T)];
|
||
|
std::memset(bytes, 0, sizeof(T));
|
||
|
const IO::Offset readResult = IO::ReadRaw(f, bytes, sizeof(T));
|
||
|
if(readResult < 0)
|
||
|
{
|
||
|
result = false;
|
||
|
} else
|
||
|
{
|
||
|
result = (static_cast<uint64>(readResult) == sizeof(T));
|
||
|
}
|
||
|
typename mpt::make_le<T>::type val;
|
||
|
std::memcpy(&val, bytes, sizeof(T));
|
||
|
v = val;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
template <typename T, typename Tfile>
|
||
|
inline bool ReadIntBE(Tfile & f, T & v)
|
||
|
{
|
||
|
bool result = false;
|
||
|
static_assert(std::numeric_limits<T>::is_integer);
|
||
|
uint8 bytes[sizeof(T)];
|
||
|
std::memset(bytes, 0, sizeof(T));
|
||
|
const IO::Offset readResult = IO::ReadRaw(f, bytes, sizeof(T));
|
||
|
if(readResult < 0)
|
||
|
{
|
||
|
result = false;
|
||
|
} else
|
||
|
{
|
||
|
result = (static_cast<uint64>(readResult) == sizeof(T));
|
||
|
}
|
||
|
typename mpt::make_be<T>::type val;
|
||
|
std::memcpy(&val, bytes, sizeof(T));
|
||
|
v = val;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool ReadAdaptiveInt16LE(Tfile & f, uint16 & v)
|
||
|
{
|
||
|
bool result = true;
|
||
|
uint8 byte = 0;
|
||
|
std::size_t additionalBytes = 0;
|
||
|
v = 0;
|
||
|
byte = 0;
|
||
|
if(!IO::ReadIntLE<uint8>(f, byte)) result = false;
|
||
|
additionalBytes = (byte & 0x01);
|
||
|
v = byte >> 1;
|
||
|
for(std::size_t i = 0; i < additionalBytes; ++i)
|
||
|
{
|
||
|
byte = 0;
|
||
|
if(!IO::ReadIntLE<uint8>(f, byte)) result = false;
|
||
|
v |= (static_cast<uint16>(byte) << (((i+1)*8) - 1));
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool ReadAdaptiveInt32LE(Tfile & f, uint32 & v)
|
||
|
{
|
||
|
bool result = true;
|
||
|
uint8 byte = 0;
|
||
|
std::size_t additionalBytes = 0;
|
||
|
v = 0;
|
||
|
byte = 0;
|
||
|
if(!IO::ReadIntLE<uint8>(f, byte)) result = false;
|
||
|
additionalBytes = (byte & 0x03);
|
||
|
v = byte >> 2;
|
||
|
for(std::size_t i = 0; i < additionalBytes; ++i)
|
||
|
{
|
||
|
byte = 0;
|
||
|
if(!IO::ReadIntLE<uint8>(f, byte)) result = false;
|
||
|
v |= (static_cast<uint32>(byte) << (((i+1)*8) - 2));
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool ReadAdaptiveInt64LE(Tfile & f, uint64 & v)
|
||
|
{
|
||
|
bool result = true;
|
||
|
uint8 byte = 0;
|
||
|
std::size_t additionalBytes = 0;
|
||
|
v = 0;
|
||
|
byte = 0;
|
||
|
if(!IO::ReadIntLE<uint8>(f, byte)) result = false;
|
||
|
additionalBytes = (1 << (byte & 0x03)) - 1;
|
||
|
v = byte >> 2;
|
||
|
for(std::size_t i = 0; i < additionalBytes; ++i)
|
||
|
{
|
||
|
byte = 0;
|
||
|
if(!IO::ReadIntLE<uint8>(f, byte)) result = false;
|
||
|
v |= (static_cast<uint64>(byte) << (((i+1)*8) - 2));
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
template <typename Tsize, typename Tfile>
|
||
|
inline bool ReadSizedStringLE(Tfile & f, std::string & str, Tsize maxSize = std::numeric_limits<Tsize>::max())
|
||
|
{
|
||
|
static_assert(std::numeric_limits<Tsize>::is_integer);
|
||
|
str.clear();
|
||
|
Tsize size = 0;
|
||
|
if(!mpt::IO::ReadIntLE(f, size))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if(size > maxSize)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
for(Tsize i = 0; i != size; ++i)
|
||
|
{
|
||
|
char c = '\0';
|
||
|
if(!mpt::IO::ReadIntLE(f, c))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
str.push_back(c);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
template <typename T, typename Tfile>
|
||
|
inline bool WriteIntLE(Tfile & f, const T v)
|
||
|
{
|
||
|
static_assert(std::numeric_limits<T>::is_integer);
|
||
|
return IO::Write(f, mpt::as_le(v));
|
||
|
}
|
||
|
|
||
|
template <typename T, typename Tfile>
|
||
|
inline bool WriteIntBE(Tfile & f, const T v)
|
||
|
{
|
||
|
static_assert(std::numeric_limits<T>::is_integer);
|
||
|
return IO::Write(f, mpt::as_be(v));
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool WriteAdaptiveInt16LE(Tfile & f, const uint16 v, std::size_t fixedSize = 0)
|
||
|
{
|
||
|
std::size_t minSize = fixedSize;
|
||
|
std::size_t maxSize = fixedSize;
|
||
|
MPT_ASSERT(minSize == 0 || minSize == 1 || minSize == 2);
|
||
|
MPT_ASSERT(maxSize == 0 || maxSize == 1 || maxSize == 2);
|
||
|
MPT_ASSERT(maxSize == 0 || maxSize >= minSize);
|
||
|
if(maxSize == 0)
|
||
|
{
|
||
|
maxSize = 2;
|
||
|
}
|
||
|
if(v < 0x80 && minSize <= 1 && 1 <= maxSize)
|
||
|
{
|
||
|
return IO::WriteIntLE<uint8>(f, static_cast<uint8>(v << 1) | 0x00);
|
||
|
} else if(v < 0x8000 && minSize <= 2 && 2 <= maxSize)
|
||
|
{
|
||
|
return IO::WriteIntLE<uint16>(f, static_cast<uint16>(v << 1) | 0x01);
|
||
|
} else
|
||
|
{
|
||
|
MPT_ASSERT_NOTREACHED();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool WriteAdaptiveInt32LE(Tfile & f, const uint32 v, std::size_t fixedSize = 0)
|
||
|
{
|
||
|
std::size_t minSize = fixedSize;
|
||
|
std::size_t maxSize = fixedSize;
|
||
|
MPT_ASSERT(minSize == 0 || minSize == 1 || minSize == 2 || minSize == 3 || minSize == 4);
|
||
|
MPT_ASSERT(maxSize == 0 || maxSize == 1 || maxSize == 2 || maxSize == 3 || maxSize == 4);
|
||
|
MPT_ASSERT(maxSize == 0 || maxSize >= minSize);
|
||
|
if(maxSize == 0)
|
||
|
{
|
||
|
maxSize = 4;
|
||
|
}
|
||
|
if(v < 0x40 && minSize <= 1 && 1 <= maxSize)
|
||
|
{
|
||
|
return IO::WriteIntLE<uint8>(f, static_cast<uint8>(v << 2) | 0x00);
|
||
|
} else if(v < 0x4000 && minSize <= 2 && 2 <= maxSize)
|
||
|
{
|
||
|
return IO::WriteIntLE<uint16>(f, static_cast<uint16>(v << 2) | 0x01);
|
||
|
} else if(v < 0x400000 && minSize <= 3 && 3 <= maxSize)
|
||
|
{
|
||
|
uint32 value = static_cast<uint32>(v << 2) | 0x02;
|
||
|
std::byte bytes[3];
|
||
|
bytes[0] = static_cast<std::byte>(value >> 0);
|
||
|
bytes[1] = static_cast<std::byte>(value >> 8);
|
||
|
bytes[2] = static_cast<std::byte>(value >> 16);
|
||
|
return IO::WriteRaw(f, bytes, 3);
|
||
|
} else if(v < 0x40000000 && minSize <= 4 && 4 <= maxSize)
|
||
|
{
|
||
|
return IO::WriteIntLE<uint32>(f, static_cast<uint32>(v << 2) | 0x03);
|
||
|
} else
|
||
|
{
|
||
|
MPT_ASSERT_NOTREACHED();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool WriteAdaptiveInt64LE(Tfile & f, const uint64 v, std::size_t fixedSize = 0)
|
||
|
{
|
||
|
std::size_t minSize = fixedSize;
|
||
|
std::size_t maxSize = fixedSize;
|
||
|
MPT_ASSERT(minSize == 0 || minSize == 1 || minSize == 2 || minSize == 4 || minSize == 8);
|
||
|
MPT_ASSERT(maxSize == 0 || maxSize == 1 || maxSize == 2 || maxSize == 4 || maxSize == 8);
|
||
|
MPT_ASSERT(maxSize == 0 || maxSize >= minSize);
|
||
|
if(maxSize == 0)
|
||
|
{
|
||
|
maxSize = 8;
|
||
|
}
|
||
|
if(v < 0x40 && minSize <= 1 && 1 <= maxSize)
|
||
|
{
|
||
|
return IO::WriteIntLE<uint8>(f, static_cast<uint8>(v << 2) | 0x00);
|
||
|
} else if(v < 0x4000 && minSize <= 2 && 2 <= maxSize)
|
||
|
{
|
||
|
return IO::WriteIntLE<uint16>(f, static_cast<uint16>(v << 2) | 0x01);
|
||
|
} else if(v < 0x40000000 && minSize <= 4 && 4 <= maxSize)
|
||
|
{
|
||
|
return IO::WriteIntLE<uint32>(f, static_cast<uint32>(v << 2) | 0x02);
|
||
|
} else if(v < 0x4000000000000000ull && minSize <= 8 && 8 <= maxSize)
|
||
|
{
|
||
|
return IO::WriteIntLE<uint64>(f, static_cast<uint64>(v << 2) | 0x03);
|
||
|
} else
|
||
|
{
|
||
|
MPT_ASSERT_NOTREACHED();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Write a variable-length integer, as found in MIDI files. The number of written bytes is placed in the bytesWritten parameter.
|
||
|
template <typename Tfile, typename T>
|
||
|
bool WriteVarInt(Tfile & f, const T v, size_t *bytesWritten = nullptr)
|
||
|
{
|
||
|
static_assert(std::numeric_limits<T>::is_integer);
|
||
|
static_assert(!std::numeric_limits<T>::is_signed);
|
||
|
std::byte out[(sizeof(T) * 8 + 6) / 7];
|
||
|
size_t numBytes = 0;
|
||
|
for(uint32 n = (sizeof(T) * 8) / 7; n > 0; n--)
|
||
|
{
|
||
|
if(v >= (static_cast<T>(1) << (n * 7u)))
|
||
|
{
|
||
|
out[numBytes++] = static_cast<std::byte>(((v >> (n * 7u)) & 0x7F) | 0x80);
|
||
|
}
|
||
|
}
|
||
|
out[numBytes++] = static_cast<std::byte>(v & 0x7F);
|
||
|
MPT_ASSERT(numBytes <= std::size(out));
|
||
|
if(bytesWritten != nullptr) *bytesWritten = numBytes;
|
||
|
return mpt::IO::WriteRaw(f, out, numBytes);
|
||
|
}
|
||
|
|
||
|
template <typename Tsize, typename Tfile>
|
||
|
inline bool WriteSizedStringLE(Tfile & f, const std::string & str)
|
||
|
{
|
||
|
static_assert(std::numeric_limits<Tsize>::is_integer);
|
||
|
if(str.size() > std::numeric_limits<Tsize>::max())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
Tsize size = static_cast<Tsize>(str.size());
|
||
|
if(!mpt::IO::WriteIntLE(f, size))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if(!mpt::IO::WriteRaw(f, str.data(), str.size()))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool WriteText(Tfile &f, const std::string &s)
|
||
|
{
|
||
|
return mpt::IO::WriteRaw(f, s.data(), s.size());
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool WriteTextCRLF(Tfile &f)
|
||
|
{
|
||
|
return mpt::IO::WriteText(f, "\r\n");
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool WriteTextLF(Tfile &f)
|
||
|
{
|
||
|
return mpt::IO::WriteText(f, "\n");
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool WriteTextCRLF(Tfile &f, const std::string &s)
|
||
|
{
|
||
|
return mpt::IO::WriteText(f, s) && mpt::IO::WriteTextCRLF(f);
|
||
|
}
|
||
|
|
||
|
template <typename Tfile>
|
||
|
inline bool WriteTextLF(Tfile &f, const std::string &s)
|
||
|
{
|
||
|
return mpt::IO::WriteText(f, s) && mpt::IO::WriteTextLF(f);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
// WriteBuffer class that avoids calling to the underlying file writing
|
||
|
// functions for every operation, which would involve rather slow un-inlinable
|
||
|
// virtual calls in the iostream and FILE* cases. It is the users responabiliy
|
||
|
// to call HasWriteError() to check for writeback errors at this buffering
|
||
|
// level.
|
||
|
|
||
|
template <typename Tfile>
|
||
|
class WriteBuffer
|
||
|
{
|
||
|
private:
|
||
|
mpt::byte_span buffer;
|
||
|
std::size_t size = 0;
|
||
|
Tfile & f;
|
||
|
bool writeError = false;
|
||
|
public:
|
||
|
WriteBuffer(const WriteBuffer &) = delete;
|
||
|
WriteBuffer & operator=(const WriteBuffer &) = delete;
|
||
|
public:
|
||
|
inline WriteBuffer(Tfile & f_, mpt::byte_span buffer_)
|
||
|
: buffer(buffer_)
|
||
|
, f(f_)
|
||
|
{
|
||
|
}
|
||
|
inline ~WriteBuffer() noexcept(false)
|
||
|
{
|
||
|
if(!writeError)
|
||
|
{
|
||
|
FlushLocal();
|
||
|
}
|
||
|
}
|
||
|
public:
|
||
|
inline Tfile & file() const
|
||
|
{
|
||
|
if(IsDirty())
|
||
|
{
|
||
|
FlushLocal();
|
||
|
}
|
||
|
return f;
|
||
|
}
|
||
|
public:
|
||
|
inline bool HasWriteError() const
|
||
|
{
|
||
|
return writeError;
|
||
|
}
|
||
|
inline void ClearError()
|
||
|
{
|
||
|
writeError = false;
|
||
|
}
|
||
|
inline bool IsDirty() const
|
||
|
{
|
||
|
return size > 0;
|
||
|
}
|
||
|
inline bool IsClean() const
|
||
|
{
|
||
|
return size == 0;
|
||
|
}
|
||
|
inline bool IsFull() const
|
||
|
{
|
||
|
return size == buffer.size();
|
||
|
}
|
||
|
inline std::size_t GetCurrentSize() const
|
||
|
{
|
||
|
return size;
|
||
|
}
|
||
|
inline bool Write(mpt::const_byte_span data)
|
||
|
{
|
||
|
bool result = true;
|
||
|
for(std::size_t i = 0; i < data.size(); ++i)
|
||
|
{
|
||
|
buffer[size] = data[i];
|
||
|
size++;
|
||
|
if(IsFull())
|
||
|
{
|
||
|
FlushLocal();
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
inline void FlushLocal()
|
||
|
{
|
||
|
if(IsClean())
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
try
|
||
|
{
|
||
|
if(!mpt::IO::WriteRaw(f, mpt::as_span(buffer.data(), size)))
|
||
|
{
|
||
|
writeError = true;
|
||
|
}
|
||
|
} catch (const std::exception &)
|
||
|
{
|
||
|
writeError = true;
|
||
|
throw;
|
||
|
}
|
||
|
size = 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
} // namespace IO
|
||
|
|
||
|
|
||
|
} // namespace mpt
|
||
|
|
||
|
|
||
|
|
||
|
class IFileDataContainer {
|
||
|
public:
|
||
|
typedef std::size_t off_t;
|
||
|
protected:
|
||
|
IFileDataContainer() = default;
|
||
|
public:
|
||
|
IFileDataContainer(const IFileDataContainer&) = default;
|
||
|
IFileDataContainer & operator=(const IFileDataContainer&) = default;
|
||
|
virtual ~IFileDataContainer() = default;
|
||
|
public:
|
||
|
virtual bool IsValid() const = 0;
|
||
|
virtual bool HasFastGetLength() const = 0;
|
||
|
virtual bool HasPinnedView() const = 0;
|
||
|
virtual const std::byte *GetRawData() const = 0;
|
||
|
virtual off_t GetLength() const = 0;
|
||
|
virtual off_t Read(std::byte *dst, off_t pos, off_t count) const = 0;
|
||
|
|
||
|
virtual off_t Read(off_t pos, mpt::byte_span dst) const
|
||
|
{
|
||
|
return Read(dst.data(), pos, dst.size());
|
||
|
}
|
||
|
|
||
|
virtual bool CanRead(off_t pos, off_t length) const
|
||
|
{
|
||
|
off_t dataLength = GetLength();
|
||
|
if((pos == dataLength) && (length == 0))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
if(pos >= dataLength)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return length <= dataLength - pos;
|
||
|
}
|
||
|
|
||
|
virtual off_t GetReadableLength(off_t pos, off_t length) const
|
||
|
{
|
||
|
off_t dataLength = GetLength();
|
||
|
if(pos >= dataLength)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
return std::min(length, dataLength - pos);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
class FileDataContainerDummy : public IFileDataContainer {
|
||
|
public:
|
||
|
FileDataContainerDummy() { }
|
||
|
public:
|
||
|
bool IsValid() const override
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool HasFastGetLength() const override
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool HasPinnedView() const override
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
const std::byte *GetRawData() const override
|
||
|
{
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
off_t GetLength() const override
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
off_t Read(std::byte * /*dst*/, off_t /*pos*/, off_t /*count*/) const override
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
class FileDataContainerWindow : public IFileDataContainer
|
||
|
{
|
||
|
private:
|
||
|
std::shared_ptr<const IFileDataContainer> data;
|
||
|
const off_t dataOffset;
|
||
|
const off_t dataLength;
|
||
|
public:
|
||
|
FileDataContainerWindow(std::shared_ptr<const IFileDataContainer> src, off_t off, off_t len) : data(src), dataOffset(off), dataLength(len) { }
|
||
|
|
||
|
bool IsValid() const override
|
||
|
{
|
||
|
return data->IsValid();
|
||
|
}
|
||
|
bool HasFastGetLength() const override
|
||
|
{
|
||
|
return data->HasFastGetLength();
|
||
|
}
|
||
|
bool HasPinnedView() const override
|
||
|
{
|
||
|
return data->HasPinnedView();
|
||
|
}
|
||
|
const std::byte *GetRawData() const override
|
||
|
{
|
||
|
return data->GetRawData() + dataOffset;
|
||
|
}
|
||
|
off_t GetLength() const override
|
||
|
{
|
||
|
return dataLength;
|
||
|
}
|
||
|
off_t Read(std::byte *dst, off_t pos, off_t count) const override
|
||
|
{
|
||
|
if(pos >= dataLength)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
return data->Read(dst, dataOffset + pos, std::min(count, dataLength - pos));
|
||
|
}
|
||
|
bool CanRead(off_t pos, off_t length) const override
|
||
|
{
|
||
|
if((pos == dataLength) && (length == 0))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
if(pos >= dataLength)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return (length <= dataLength - pos);
|
||
|
}
|
||
|
off_t GetReadableLength(off_t pos, off_t length) const override
|
||
|
{
|
||
|
if(pos >= dataLength)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
return std::min(length, dataLength - pos);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
class FileDataContainerSeekable : public IFileDataContainer {
|
||
|
|
||
|
private:
|
||
|
|
||
|
off_t streamLength;
|
||
|
|
||
|
mutable bool cached;
|
||
|
mutable std::vector<std::byte> cache;
|
||
|
|
||
|
private:
|
||
|
|
||
|
mutable bool m_Buffered;
|
||
|
enum : std::size_t {
|
||
|
CHUNK_SIZE = mpt::IO::BUFFERSIZE_SMALL,
|
||
|
BUFFER_SIZE = mpt::IO::BUFFERSIZE_NORMAL
|
||
|
};
|
||
|
enum : std::size_t {
|
||
|
NUM_CHUNKS = BUFFER_SIZE / CHUNK_SIZE
|
||
|
};
|
||
|
struct chunk_info {
|
||
|
off_t ChunkOffset = 0;
|
||
|
off_t ChunkLength = 0;
|
||
|
bool ChunkValid = false;
|
||
|
};
|
||
|
mutable std::vector<std::byte> m_Buffer;
|
||
|
mpt::byte_span chunk_data(std::size_t chunkIndex) const
|
||
|
{
|
||
|
return mpt::byte_span(m_Buffer.data() + (chunkIndex * CHUNK_SIZE), CHUNK_SIZE);
|
||
|
}
|
||
|
mutable std::array<chunk_info, NUM_CHUNKS> m_ChunkInfo;
|
||
|
mutable std::array<std::size_t, NUM_CHUNKS> m_ChunkIndexLRU;
|
||
|
|
||
|
std::size_t InternalFillPageAndReturnIndex(off_t pos) const;
|
||
|
|
||
|
protected:
|
||
|
|
||
|
FileDataContainerSeekable(off_t length, bool buffered);
|
||
|
|
||
|
private:
|
||
|
|
||
|
void CacheStream() const;
|
||
|
|
||
|
public:
|
||
|
|
||
|
bool IsValid() const override;
|
||
|
bool HasFastGetLength() const override;
|
||
|
bool HasPinnedView() const override;
|
||
|
const std::byte *GetRawData() const override;
|
||
|
off_t GetLength() const override;
|
||
|
off_t Read(std::byte *dst, off_t pos, off_t count) const override;
|
||
|
|
||
|
private:
|
||
|
|
||
|
off_t InternalReadBuffered(std::byte* dst, off_t pos, off_t count) const;
|
||
|
|
||
|
virtual off_t InternalRead(std::byte *dst, off_t pos, off_t count) const = 0;
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
class FileDataContainerStdStreamSeekable : public FileDataContainerSeekable {
|
||
|
|
||
|
private:
|
||
|
|
||
|
std::istream *stream;
|
||
|
|
||
|
public:
|
||
|
|
||
|
FileDataContainerStdStreamSeekable(std::istream *s);
|
||
|
|
||
|
static bool IsSeekable(std::istream *stream);
|
||
|
static off_t GetLength(std::istream *stream);
|
||
|
|
||
|
private:
|
||
|
|
||
|
off_t InternalRead(std::byte *dst, off_t pos, off_t count) const override;
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
class FileDataContainerUnseekable : public IFileDataContainer {
|
||
|
|
||
|
private:
|
||
|
|
||
|
mutable std::vector<std::byte> cache;
|
||
|
mutable std::size_t cachesize;
|
||
|
mutable bool streamFullyCached;
|
||
|
|
||
|
protected:
|
||
|
|
||
|
FileDataContainerUnseekable();
|
||
|
|
||
|
private:
|
||
|
|
||
|
enum : std::size_t {
|
||
|
QUANTUM_SIZE = mpt::IO::BUFFERSIZE_SMALL,
|
||
|
BUFFER_SIZE = mpt::IO::BUFFERSIZE_NORMAL
|
||
|
};
|
||
|
|
||
|
void EnsureCacheBuffer(std::size_t requiredbuffersize) const;
|
||
|
void CacheStream() const;
|
||
|
void CacheStreamUpTo(off_t pos, off_t length) const;
|
||
|
|
||
|
private:
|
||
|
|
||
|
void ReadCached(std::byte *dst, off_t pos, off_t count) const;
|
||
|
|
||
|
public:
|
||
|
|
||
|
bool IsValid() const override;
|
||
|
bool HasFastGetLength() const override;
|
||
|
bool HasPinnedView() const override;
|
||
|
const std::byte *GetRawData() const override;
|
||
|
off_t GetLength() const override;
|
||
|
off_t Read(std::byte *dst, off_t pos, off_t count) const override;
|
||
|
bool CanRead(off_t pos, off_t length) const override;
|
||
|
off_t GetReadableLength(off_t pos, off_t length) const override;
|
||
|
|
||
|
private:
|
||
|
|
||
|
virtual bool InternalEof() const = 0;
|
||
|
virtual off_t InternalRead(std::byte *dst, off_t count) const = 0;
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
class FileDataContainerStdStream : public FileDataContainerUnseekable {
|
||
|
|
||
|
private:
|
||
|
|
||
|
std::istream *stream;
|
||
|
|
||
|
public:
|
||
|
|
||
|
FileDataContainerStdStream(std::istream *s);
|
||
|
|
||
|
private:
|
||
|
|
||
|
bool InternalEof() const override;
|
||
|
off_t InternalRead(std::byte *dst, off_t count) const override;
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
#if defined(MPT_FILEREADER_CALLBACK_STREAM)
|
||
|
|
||
|
|
||
|
struct CallbackStream
|
||
|
{
|
||
|
enum : int {
|
||
|
SeekSet = 0,
|
||
|
SeekCur = 1,
|
||
|
SeekEnd = 2
|
||
|
};
|
||
|
void *stream;
|
||
|
std::size_t (*read)( void * stream, void * dst, std::size_t bytes );
|
||
|
int (*seek)( void * stream, int64 offset, int whence );
|
||
|
int64 (*tell)( void * stream );
|
||
|
};
|
||
|
|
||
|
|
||
|
class FileDataContainerCallbackStreamSeekable : public FileDataContainerSeekable
|
||
|
{
|
||
|
private:
|
||
|
CallbackStream stream;
|
||
|
public:
|
||
|
static bool IsSeekable(CallbackStream stream);
|
||
|
static off_t GetLength(CallbackStream stream);
|
||
|
FileDataContainerCallbackStreamSeekable(CallbackStream s);
|
||
|
private:
|
||
|
off_t InternalRead(std::byte *dst, off_t pos, off_t count) const override;
|
||
|
};
|
||
|
|
||
|
|
||
|
class FileDataContainerCallbackStream : public FileDataContainerUnseekable
|
||
|
{
|
||
|
private:
|
||
|
CallbackStream stream;
|
||
|
mutable bool eof_reached;
|
||
|
public:
|
||
|
FileDataContainerCallbackStream(CallbackStream s);
|
||
|
private:
|
||
|
bool InternalEof() const override;
|
||
|
off_t InternalRead(std::byte *dst, off_t count) const override;
|
||
|
};
|
||
|
|
||
|
|
||
|
#endif // MPT_FILEREADER_CALLBACK_STREAM
|
||
|
|
||
|
|
||
|
class FileDataContainerMemory
|
||
|
: public IFileDataContainer
|
||
|
{
|
||
|
|
||
|
private:
|
||
|
|
||
|
const std::byte *streamData; // Pointer to memory-mapped file
|
||
|
off_t streamLength; // Size of memory-mapped file in bytes
|
||
|
|
||
|
public:
|
||
|
FileDataContainerMemory() : streamData(nullptr), streamLength(0) { }
|
||
|
FileDataContainerMemory(mpt::const_byte_span data) : streamData(data.data()), streamLength(data.size()) { }
|
||
|
|
||
|
public:
|
||
|
|
||
|
bool IsValid() const override
|
||
|
{
|
||
|
return streamData != nullptr;
|
||
|
}
|
||
|
|
||
|
bool HasFastGetLength() const override
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool HasPinnedView() const override
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
const std::byte *GetRawData() const override
|
||
|
{
|
||
|
return streamData;
|
||
|
}
|
||
|
|
||
|
off_t GetLength() const override
|
||
|
{
|
||
|
return streamLength;
|
||
|
}
|
||
|
|
||
|
off_t Read(std::byte *dst, off_t pos, off_t count) const override
|
||
|
{
|
||
|
if(pos >= streamLength)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
off_t avail = std::min(streamLength - pos, count);
|
||
|
std::copy(streamData + pos, streamData + pos + avail, dst);
|
||
|
return avail;
|
||
|
}
|
||
|
|
||
|
off_t Read(off_t pos, mpt::byte_span dst) const override
|
||
|
{
|
||
|
return Read(dst.data(), pos, dst.size());
|
||
|
}
|
||
|
|
||
|
bool CanRead(off_t pos, off_t length) const override
|
||
|
{
|
||
|
if((pos == streamLength) && (length == 0))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
if(pos >= streamLength)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return (length <= streamLength - pos);
|
||
|
}
|
||
|
|
||
|
off_t GetReadableLength(off_t pos, off_t length) const override
|
||
|
{
|
||
|
if(pos >= streamLength)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
return std::min(length, streamLength - pos);
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_END
|