459 lines
13 KiB
C++
459 lines
13 KiB
C++
/**
|
|
\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 <assert.h>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#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<float32>& 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 <I>n</I> 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 <B>must</b> call the BinaryInput::readInt32 or
|
|
other appropriate function. This is because it would be very hard to
|
|
debug the error sequence: <CODE>serialize(1.0, bo); ... float f; deserialize(f, bi);</CODE>
|
|
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
|
|
<PRE>
|
|
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)
|
|
</PRE>
|
|
\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<lcase>& out, int64 n);\
|
|
void read##ucase(Array<lcase>& 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
|