/** \file G3D/BinaryInput.h \maintainer Morgan McGuire, http://graphics.cs.williams.edu \created 2001-08-09 \edited 2013-01-03 Copyright 2000-2012, Morgan McGuire. All rights reserved. */ #ifndef G3D_BinaryInput_h #define G3D_BinaryInput_h #ifdef _MSC_VER // Disable conditional expression is constant, which occurs incorrectly on inlined functions # pragma warning(push) # pragma warning( disable : 4127 ) #endif #include #include #include #include #include #include #include "G3D/platform.h" #include "G3D/unorm8.h" #include "G3D/Array.h" #include "G3D/Color4.h" #include "G3D/Color3.h" #include "G3D/Vector4.h" #include "G3D/Vector3.h" #include "G3D/Vector2.h" #include "G3D/g3dmath.h" #include "G3D/debug.h" #include "G3D/System.h" namespace G3D { #if defined(G3D_WINDOWS) || defined(G3D_LINUX) // Allow writing of integers to non-word aligned locations. // This is legal on x86, but not on other platforms. #define G3D_ALLOW_UNALIGNED_WRITES #endif /** Sequential or random access byte-order independent binary file access. Files compressed with zlib and beginning with an unsigned 32-bit int size are transparently decompressed when the compressed = true flag is specified to the constructor. For every readX method there are also versions that operate on a whole Array, std::vector, or C-array. e.g. readFloat32(Array& array, n) These methods resize the array or std::vector to the appropriate size before reading. For a C-array, they require the pointer to reference a memory block at least large enough to hold n elements. Most classes define serialize/deserialize methods that use BinaryInput, BinaryOutput, TextInput, and TextOutput. There are text serializer functions for primitive types (e.g. int, std::string, float, double) but not binary serializers-- you must call the BinaryInput::readInt32 or other appropriate function. This is because it would be very hard to debug the error sequence: serialize(1.0, bo); ... float f; deserialize(f, bi); in which a double is serialized and then deserialized as a float. */ class BinaryInput { private: // The initial buffer will be no larger than this, but // may grow if a large memory read occurs. 750 MB static const int64 INITIAL_BUFFER_LENGTH = #ifdef G3D_64BIT 5000000000L // 5 GB #else 750000000 // 750 MB #endif ; /** is the file big or little endian */ G3DEndian m_fileEndian; std::string m_filename; bool m_swapBytes; /** Next position to read from in bitString during readBits. */ int m_bitPos; /** Bits currently being read by readBits. Contains at most 8 (low) bits. Note that beginBits/readBits actually consumes one extra byte, which will be restored by writeBits.*/ uint32 m_bitString; /** 1 when between beginBits and endBits, 0 otherwise. */ int m_beginEndBits; /** When operating on huge files, we cannot load the whole file into memory. This is the file position to which buffer[0] corresponds. Even 32-bit code can load 64-bit files in chunks, so this is not size_t */ int64 m_alreadyRead; /** Length of the entire file, in bytes. For the length of the buffer, see bufferLength */ int64 m_length; /** Length of the array referenced by buffer. May go past the end of the file!*/ int64 m_bufferLength; uint8* m_buffer; /** Next byte in file, relative to buffer. */ int64 m_pos; /** When true, the buffer is freed in the destructor. */ bool m_freeBuffer; /** Ensures that we are able to read at least minLength from startPosition (relative to start of file). */ void loadIntoMemory(int64 startPosition, int64 minLength = 0); /** Verifies that at least this number of bytes can be read.*/ void prepareToRead(int64 nbytes); // Not implemented on purpose, don't use BinaryInput(const BinaryInput&); BinaryInput& operator=(const BinaryInput&); bool operator==(const BinaryInput&); /** Buffer is compressed; replace it with a decompressed version */ void decompress(); public: /** false, constant to use with the copyMemory option */ static const bool NO_COPY; /** If the file cannot be opened, a zero length buffer is presented. Automatically opens files that are inside zipfiles. @param compressed Set to true if and only if the file was compressed using BinaryOutput's zlib compression. This has nothing to do with whether the input is in a zipfile. */ BinaryInput( const std::string& filename, G3DEndian fileEndian, bool compressed = false); /** Creates input stream from an in memory source. Unless you specify copyMemory = false, the data is copied from the pointer, so you may deallocate it as soon as the object is constructed. It is an error to specify copyMemory = false and compressed = true. To decompress part of a file, you can follow the following paradigm: \htmlonly
        BinaryInput master(...);

        // read from master to point where compressed data exists.

        BinaryInput subset(master.getCArray() + master.getPosition(), 
                           master.length() - master.getPosition(),
                           master.endian(), true, true);

        // Now read from subset (it is ok for master to go out of scope)
     
\endhtmlonly */ BinaryInput( const uint8* data, int64 dataLen, G3DEndian dataEndian, bool compressed = false, bool copyMemory = true); virtual ~BinaryInput(); /** Change the endian-ness of the file. This only changes the interpretation of the file for future read calls; the underlying data is unmodified.*/ void setEndian(G3DEndian endian); G3DEndian endian() const { return m_fileEndian; } std::string getFilename() const { return m_filename; } /** Performs bounds checks in debug mode. [] are relative to the start of the file, not the current position. Seeks to the new position before reading (and leaves that as the current position) */ uint8 operator[](int64 n) { setPosition(n); return readUInt8(); } /** Returns the length of the file in bytes. */ int64 getLength() const { return m_length; } int64 size() const { return getLength(); } /** Returns the current byte position in the file, where 0 is the beginning and getLength() - 1 is the end. */ int64 getPosition() const { return m_pos + m_alreadyRead; } /** Returns a pointer to the internal memory buffer. May throw an exception for huge files. */ const uint8* getCArray() { if (m_alreadyRead > 0 || m_bufferLength < m_length) { throw "Cannot getCArray for a huge file"; } return m_buffer; } /** Sets the position. Cannot set past length. May throw a char* when seeking backwards more than 10 MB on a huge file. */ void setPosition(int64 p) { debugAssertM(p <= m_length, "Read past end of file"); m_pos = p - m_alreadyRead; if ((m_pos < 0) || (m_pos > m_bufferLength)) { loadIntoMemory(m_pos + m_alreadyRead); } } /** Goes back to the beginning of the file. */ void reset() { setPosition(0); } void readBytes(void* bytes, int64 n); int8 readInt8() { prepareToRead(1); return m_buffer[m_pos++]; } bool readBool8() { return (readInt8() != 0); } uint8 readUInt8() { prepareToRead(1); return ((uint8*)m_buffer)[m_pos++]; } unorm8 readUNorm8() { return unorm8::fromBits(readUInt8()); } uint16 readUInt16() { prepareToRead(2); m_pos += 2; if (m_swapBytes) { uint8 out[2]; out[0] = m_buffer[m_pos - 1]; out[1] = m_buffer[m_pos - 2]; return *(uint16*)out; } else { #ifdef G3D_ALLOW_UNALIGNED_WRITES return *(uint16*)(&m_buffer[m_pos - 2]); #else uint8 out[2]; out[0] = m_buffer[m_pos - 2]; out[1] = m_buffer[m_pos - 1]; return *(uint16*)out; #endif } } int16 readInt16() { uint16 a = readUInt16(); return *(int16*)&a; } uint32 readUInt32() { prepareToRead(4); m_pos += 4; if (m_swapBytes) { uint8 out[4]; out[0] = m_buffer[m_pos - 1]; out[1] = m_buffer[m_pos - 2]; out[2] = m_buffer[m_pos - 3]; out[3] = m_buffer[m_pos - 4]; return *(uint32*)out; } else { #ifdef G3D_ALLOW_UNALIGNED_WRITES return *(uint32*)(&m_buffer[m_pos - 4]); #else uint8 out[4]; out[0] = m_buffer[m_pos - 4]; out[1] = m_buffer[m_pos - 3]; out[2] = m_buffer[m_pos - 2]; out[3] = m_buffer[m_pos - 1]; return *(uint32*)out; #endif } } int32 readInt32() { uint32 a = readUInt32(); return *(int32*)&a; } uint64 readUInt64(); int64 readInt64() { uint64 a = readUInt64(); return *(int64*)&a; } float32 readFloat32() { union { uint32 a; float32 b; }; a = readUInt32(); return b; } float64 readFloat64() { union { uint64 a; float64 b; }; a = readUInt64(); return b; } /** Always consumes \a maxLength characters. Reads a string until NULL or \a maxLength characters. Does not require NULL termination. */ std::string readString(int64 maxLength); /** Reads a string until NULL or end of file. */ std::string readString(); /** Read a string (which may contain NULLs) of exactly numBytes bytes, including the final terminator if there is one. If there is a NULL in the string before the end, then only the part up to the first NULL is returned although all bytes are read.*/ std::string readFixedLengthString(int numBytes); /** Reads a string until NULL, newline ("\r", "\n", "\r\n", "\n\r") or the end of the file is encountered. Consumes the newline. */ std::string readStringNewline(); /** Reads until NULL or the end of the file is encountered. If the string has odd length (including NULL), reads another byte. This is a common format for 16-bit alignment in files. */ std::string readStringEven(); /** Reads a uint32 and then calls readString(maxLength) with that value as the length. */ std::string readString32(); Vector4 readVector4(); Vector3 readVector3(); Vector2 readVector2(); Color4 readColor4(); Color3 readColor3(); /** Skips ahead n bytes. */ void skip(int64 n) { setPosition(m_pos + m_alreadyRead + n); } /** Returns true if the position is not at the end of the file */ bool hasMore() const { return m_pos + m_alreadyRead < m_length; } /** Prepares for bit reading via readBits. Only readBits can be called between beginBits and endBits without corrupting the data stream. */ void beginBits(); /** Can only be called between beginBits and endBits */ uint32 readBits(int numBits); /** Ends bit-reading. */ void endBits(); # define DECLARE_READER(ucase, lcase)\ void read##ucase(lcase* out, int64 n);\ void read##ucase(std::vector& out, int64 n);\ void read##ucase(Array& out, int64 n); DECLARE_READER(Bool8, bool) DECLARE_READER(UInt8, uint8) DECLARE_READER(Int8, int8) DECLARE_READER(UInt16, uint16) DECLARE_READER(Int16, int16) DECLARE_READER(UInt32, uint32) DECLARE_READER(Int32, int32) DECLARE_READER(UInt64, uint64) DECLARE_READER(Int64, int64) DECLARE_READER(Float32, float32) DECLARE_READER(Float64, float64) # undef DECLARE_READER }; } #ifdef _MSC_VER # pragma warning(pop) #endif #endif