using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using MiscUtil.Conversion;
|
|
|
|
namespace MiscUtil.IO
|
|
{
|
|
/// <summary>
|
|
/// Equivalent of System.IO.BinaryReader, but with either endianness, depending on
|
|
/// the EndianBitConverter it is constructed with. No data is buffered in the
|
|
/// reader; the client may seek within the stream at will.
|
|
/// </summary>
|
|
public class EndianBinaryReader : IDisposable
|
|
{
|
|
#region Fields not directly related to properties
|
|
/// <summary>
|
|
/// Whether or not this reader has been disposed yet.
|
|
/// </summary>
|
|
bool disposed=false;
|
|
/// <summary>
|
|
/// Decoder to use for string conversions.
|
|
/// </summary>
|
|
Decoder decoder;
|
|
/// <summary>
|
|
/// Buffer used for temporary storage before conversion into primitives
|
|
/// </summary>
|
|
byte[] buffer = new byte[16];
|
|
/// <summary>
|
|
/// Buffer used for temporary storage when reading a single character
|
|
/// </summary>
|
|
char[] charBuffer = new char[1];
|
|
/// <summary>
|
|
/// Minimum number of bytes used to encode a character
|
|
/// </summary>
|
|
int minBytesPerChar;
|
|
#endregion
|
|
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on
|
|
/// the EndianBitConverter it is constructed with.
|
|
/// </summary>
|
|
/// <param name="bitConverter">Converter to use when reading data</param>
|
|
/// <param name="stream">Stream to read data from</param>
|
|
public EndianBinaryReader (EndianBitConverter bitConverter,
|
|
Stream stream) : this (bitConverter, stream, Encoding.UTF8)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a new binary reader with the given bit converter, reading
|
|
/// to the given stream, using the given encoding.
|
|
/// </summary>
|
|
/// <param name="bitConverter">Converter to use when reading data</param>
|
|
/// <param name="stream">Stream to read data from</param>
|
|
/// <param name="encoding">Encoding to use when reading character data</param>
|
|
public EndianBinaryReader (EndianBitConverter bitConverter, Stream stream, Encoding encoding)
|
|
{
|
|
if (bitConverter==null)
|
|
{
|
|
throw new ArgumentNullException("bitConverter");
|
|
}
|
|
if (stream==null)
|
|
{
|
|
throw new ArgumentNullException("stream");
|
|
}
|
|
if (encoding==null)
|
|
{
|
|
throw new ArgumentNullException("encoding");
|
|
}
|
|
if (!stream.CanRead)
|
|
{
|
|
throw new ArgumentException("Stream isn't writable", "stream");
|
|
}
|
|
this.stream = stream;
|
|
this.bitConverter = bitConverter;
|
|
this.encoding = encoding;
|
|
this.decoder = encoding.GetDecoder();
|
|
this.minBytesPerChar = 1;
|
|
|
|
if (encoding is UnicodeEncoding)
|
|
{
|
|
minBytesPerChar = 2;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Properties
|
|
EndianBitConverter bitConverter;
|
|
/// <summary>
|
|
/// The bit converter used to read values from the stream
|
|
/// </summary>
|
|
public EndianBitConverter BitConverter
|
|
{
|
|
get { return bitConverter; }
|
|
}
|
|
|
|
Encoding encoding;
|
|
/// <summary>
|
|
/// The encoding used to read strings
|
|
/// </summary>
|
|
public Encoding Encoding
|
|
{
|
|
get { return encoding; }
|
|
}
|
|
|
|
Stream stream;
|
|
/// <summary>
|
|
/// Gets the underlying stream of the EndianBinaryReader.
|
|
/// </summary>
|
|
public Stream BaseStream
|
|
{
|
|
get { return stream; }
|
|
}
|
|
#endregion
|
|
|
|
#region Public methods
|
|
/// <summary>
|
|
/// Closes the reader, including the underlying stream..
|
|
/// </summary>
|
|
public void Close()
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Seeks within the stream.
|
|
/// </summary>
|
|
/// <param name="offset">Offset to seek to.</param>
|
|
/// <param name="origin">Origin of seek operation.</param>
|
|
public void Seek (int offset, SeekOrigin origin)
|
|
{
|
|
CheckDisposed();
|
|
stream.Seek (offset, origin);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a single byte from the stream.
|
|
/// </summary>
|
|
/// <returns>The byte read</returns>
|
|
public byte ReadByte()
|
|
{
|
|
ReadInternal(buffer, 1);
|
|
return buffer[0];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a single signed byte from the stream.
|
|
/// </summary>
|
|
/// <returns>The byte read</returns>
|
|
public sbyte ReadSByte()
|
|
{
|
|
ReadInternal(buffer, 1);
|
|
return unchecked((sbyte)buffer[0]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a boolean from the stream. 1 byte is read.
|
|
/// </summary>
|
|
/// <returns>The boolean read</returns>
|
|
public bool ReadBoolean()
|
|
{
|
|
ReadInternal(buffer, 1);
|
|
return bitConverter.ToBoolean(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 16-bit signed integer from the stream, using the bit converter
|
|
/// for this reader. 2 bytes are read.
|
|
/// </summary>
|
|
/// <returns>The 16-bit integer read</returns>
|
|
public short ReadInt16()
|
|
{
|
|
ReadInternal(buffer, 2);
|
|
return bitConverter.ToInt16(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 32-bit signed integer from the stream, using the bit converter
|
|
/// for this reader. 4 bytes are read.
|
|
/// </summary>
|
|
/// <returns>The 32-bit integer read</returns>
|
|
public int ReadInt32()
|
|
{
|
|
ReadInternal(buffer, 4);
|
|
return bitConverter.ToInt32(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 64-bit signed integer from the stream, using the bit converter
|
|
/// for this reader. 8 bytes are read.
|
|
/// </summary>
|
|
/// <returns>The 64-bit integer read</returns>
|
|
public long ReadInt64()
|
|
{
|
|
ReadInternal(buffer, 8);
|
|
return bitConverter.ToInt64(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 16-bit unsigned integer from the stream, using the bit converter
|
|
/// for this reader. 2 bytes are read.
|
|
/// </summary>
|
|
/// <returns>The 16-bit unsigned integer read</returns>
|
|
public ushort ReadUInt16()
|
|
{
|
|
ReadInternal(buffer, 2);
|
|
return bitConverter.ToUInt16(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 32-bit unsigned integer from the stream, using the bit converter
|
|
/// for this reader. 4 bytes are read.
|
|
/// </summary>
|
|
/// <returns>The 32-bit unsigned integer read</returns>
|
|
public uint ReadUInt32()
|
|
{
|
|
ReadInternal(buffer, 4);
|
|
return bitConverter.ToUInt32(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 64-bit unsigned integer from the stream, using the bit converter
|
|
/// for this reader. 8 bytes are read.
|
|
/// </summary>
|
|
/// <returns>The 64-bit unsigned integer read</returns>
|
|
public ulong ReadUInt64()
|
|
{
|
|
ReadInternal(buffer, 8);
|
|
return bitConverter.ToUInt64(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a single-precision floating-point value from the stream, using the bit converter
|
|
/// for this reader. 4 bytes are read.
|
|
/// </summary>
|
|
/// <returns>The floating point value read</returns>
|
|
public float ReadSingle()
|
|
{
|
|
ReadInternal(buffer, 4);
|
|
return bitConverter.ToSingle(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a double-precision floating-point value from the stream, using the bit converter
|
|
/// for this reader. 8 bytes are read.
|
|
/// </summary>
|
|
/// <returns>The floating point value read</returns>
|
|
public double ReadDouble()
|
|
{
|
|
ReadInternal(buffer, 8);
|
|
return bitConverter.ToDouble(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a decimal value from the stream, using the bit converter
|
|
/// for this reader. 16 bytes are read.
|
|
/// </summary>
|
|
/// <returns>The decimal value read</returns>
|
|
public decimal ReadDecimal()
|
|
{
|
|
ReadInternal(buffer, 16);
|
|
return bitConverter.ToDecimal(buffer, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a single character from the stream, using the character encoding for
|
|
/// this reader. If no characters have been fully read by the time the stream ends,
|
|
/// -1 is returned.
|
|
/// </summary>
|
|
/// <returns>The character read, or -1 for end of stream.</returns>
|
|
public int Read()
|
|
{
|
|
int charsRead = Read(charBuffer, 0, 1);
|
|
if (charsRead==0)
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return charBuffer[0];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the specified number of characters into the given buffer, starting at
|
|
/// the given index.
|
|
/// </summary>
|
|
/// <param name="data">The buffer to copy data into</param>
|
|
/// <param name="index">The first index to copy data into</param>
|
|
/// <param name="count">The number of characters to read</param>
|
|
/// <returns>The number of characters actually read. This will only be less than
|
|
/// the requested number of characters if the end of the stream is reached.
|
|
/// </returns>
|
|
public int Read(char[] data, int index, int count)
|
|
{
|
|
CheckDisposed();
|
|
if (buffer==null)
|
|
{
|
|
throw new ArgumentNullException("buffer");
|
|
}
|
|
if (index < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException("index");
|
|
}
|
|
if (count < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException("index");
|
|
}
|
|
if (count+index > data.Length)
|
|
{
|
|
throw new ArgumentException
|
|
("Not enough space in buffer for specified number of characters starting at specified index");
|
|
}
|
|
|
|
int read=0;
|
|
bool firstTime=true;
|
|
|
|
// Use the normal buffer if we're only reading a small amount, otherwise
|
|
// use at most 4K at a time.
|
|
byte[] byteBuffer = buffer;
|
|
|
|
if (byteBuffer.Length < count*minBytesPerChar)
|
|
{
|
|
byteBuffer = new byte[4096];
|
|
}
|
|
|
|
while (read < count)
|
|
{
|
|
int amountToRead;
|
|
// First time through we know we haven't previously read any data
|
|
if (firstTime)
|
|
{
|
|
amountToRead = count*minBytesPerChar;
|
|
firstTime=false;
|
|
}
|
|
// After that we can only assume we need to fully read "chars left -1" characters
|
|
// and a single byte of the character we may be in the middle of
|
|
else
|
|
{
|
|
amountToRead = ((count-read-1)*minBytesPerChar)+1;
|
|
}
|
|
if (amountToRead > byteBuffer.Length)
|
|
{
|
|
amountToRead = byteBuffer.Length;
|
|
}
|
|
int bytesRead = TryReadInternal(byteBuffer, amountToRead);
|
|
if (bytesRead==0)
|
|
{
|
|
return read;
|
|
}
|
|
int decoded = decoder.GetChars(byteBuffer, 0, bytesRead, data, index);
|
|
read += decoded;
|
|
index += decoded;
|
|
}
|
|
return read;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the specified number of bytes into the given buffer, starting at
|
|
/// the given index.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to copy data into</param>
|
|
/// <param name="index">The first index to copy data into</param>
|
|
/// <param name="count">The number of bytes to read</param>
|
|
/// <returns>The number of bytes actually read. This will only be less than
|
|
/// the requested number of bytes if the end of the stream is reached.
|
|
/// </returns>
|
|
public int Read(byte[] buffer, int index, int count)
|
|
{
|
|
CheckDisposed();
|
|
if (buffer==null)
|
|
{
|
|
throw new ArgumentNullException("buffer");
|
|
}
|
|
if (index < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException("index");
|
|
}
|
|
if (count < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException("index");
|
|
}
|
|
if (count+index > buffer.Length)
|
|
{
|
|
throw new ArgumentException
|
|
("Not enough space in buffer for specified number of bytes starting at specified index");
|
|
}
|
|
int read=0;
|
|
while (count > 0)
|
|
{
|
|
int block = stream.Read(buffer, index, count);
|
|
if (block==0)
|
|
{
|
|
return read;
|
|
}
|
|
index += block;
|
|
read += block;
|
|
count -= block;
|
|
}
|
|
return read;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the specified number of bytes, returning them in a new byte array.
|
|
/// If not enough bytes are available before the end of the stream, this
|
|
/// method will return what is available.
|
|
/// </summary>
|
|
/// <param name="count">The number of bytes to read</param>
|
|
/// <returns>The bytes read</returns>
|
|
public byte[] ReadBytes(int count)
|
|
{
|
|
CheckDisposed();
|
|
if (count < 0)
|
|
{
|
|
throw new ArgumentOutOfRangeException("count");
|
|
}
|
|
byte[] ret = new byte[count];
|
|
int index=0;
|
|
while (index < count)
|
|
{
|
|
int read = stream.Read(ret, index, count-index);
|
|
// Stream has finished half way through. That's fine, return what we've got.
|
|
if (read==0)
|
|
{
|
|
byte[] copy = new byte[index];
|
|
Buffer.BlockCopy(ret, 0, copy, 0, index);
|
|
return copy;
|
|
}
|
|
index += read;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the specified number of bytes, returning them in a new byte array.
|
|
/// If not enough bytes are available before the end of the stream, this
|
|
/// method will throw an IOException.
|
|
/// </summary>
|
|
/// <param name="count">The number of bytes to read</param>
|
|
/// <returns>The bytes read</returns>
|
|
public byte[] ReadBytesOrThrow(int count)
|
|
{
|
|
byte[] ret = new byte[count];
|
|
ReadInternal(ret, count);
|
|
return ret;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 7-bit encoded integer from the stream. This is stored with the least significant
|
|
/// information first, with 7 bits of information per byte of value, and the top
|
|
/// bit as a continuation flag. This method is not affected by the endianness
|
|
/// of the bit converter.
|
|
/// </summary>
|
|
/// <returns>The 7-bit encoded integer read from the stream.</returns>
|
|
public int Read7BitEncodedInt()
|
|
{
|
|
CheckDisposed();
|
|
|
|
int ret=0;
|
|
for (int shift = 0; shift < 35; shift+=7)
|
|
{
|
|
int b = stream.ReadByte();
|
|
if (b==-1)
|
|
{
|
|
throw new EndOfStreamException();
|
|
}
|
|
ret = ret | ((b&0x7f) << shift);
|
|
if ((b & 0x80) == 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
// Still haven't seen a byte with the high bit unset? Dodgy data.
|
|
throw new IOException("Invalid 7-bit encoded integer in stream.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 7-bit encoded integer from the stream. This is stored with the most significant
|
|
/// information first, with 7 bits of information per byte of value, and the top
|
|
/// bit as a continuation flag. This method is not affected by the endianness
|
|
/// of the bit converter.
|
|
/// </summary>
|
|
/// <returns>The 7-bit encoded integer read from the stream.</returns>
|
|
public int ReadBigEndian7BitEncodedInt()
|
|
{
|
|
CheckDisposed();
|
|
|
|
int ret=0;
|
|
for (int i=0; i < 5; i++)
|
|
{
|
|
int b = stream.ReadByte();
|
|
if (b==-1)
|
|
{
|
|
throw new EndOfStreamException();
|
|
}
|
|
ret = (ret << 7) | (b&0x7f);
|
|
if ((b & 0x80) == 0)
|
|
{
|
|
return ret;
|
|
}
|
|
}
|
|
// Still haven't seen a byte with the high bit unset? Dodgy data.
|
|
throw new IOException("Invalid 7-bit encoded integer in stream.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a length-prefixed string from the stream, using the encoding for this reader.
|
|
/// A 7-bit encoded integer is first read, which specifies the number of bytes
|
|
/// to read from the stream. These bytes are then converted into a string with
|
|
/// the encoding for this reader.
|
|
/// </summary>
|
|
/// <returns>The string read from the stream.</returns>
|
|
public string ReadString()
|
|
{
|
|
int bytesToRead = Read7BitEncodedInt();
|
|
|
|
byte[] data = new byte[bytesToRead];
|
|
ReadInternal(data, bytesToRead);
|
|
return encoding.GetString(data, 0, data.Length);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private methods
|
|
/// <summary>
|
|
/// Checks whether or not the reader has been disposed, throwing an exception if so.
|
|
/// </summary>
|
|
void CheckDisposed()
|
|
{
|
|
if (disposed)
|
|
{
|
|
throw new ObjectDisposedException("EndianBinaryReader");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the given number of bytes from the stream, throwing an exception
|
|
/// if they can't all be read.
|
|
/// </summary>
|
|
/// <param name="data">Buffer to read into</param>
|
|
/// <param name="size">Number of bytes to read</param>
|
|
void ReadInternal (byte[] data, int size)
|
|
{
|
|
CheckDisposed();
|
|
int index=0;
|
|
while (index < size)
|
|
{
|
|
int read = stream.Read(data, index, size-index);
|
|
if (read==0)
|
|
{
|
|
throw new EndOfStreamException
|
|
(String.Format("End of stream reached with {0} byte{1} left to read.", size-index,
|
|
size-index==1 ? "s" : ""));
|
|
}
|
|
index += read;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the given number of bytes from the stream if possible, returning
|
|
/// the number of bytes actually read, which may be less than requested if
|
|
/// (and only if) the end of the stream is reached.
|
|
/// </summary>
|
|
/// <param name="data">Buffer to read into</param>
|
|
/// <param name="size">Number of bytes to read</param>
|
|
/// <returns>Number of bytes actually read</returns>
|
|
int TryReadInternal (byte[] data, int size)
|
|
{
|
|
CheckDisposed();
|
|
int index=0;
|
|
while (index < size)
|
|
{
|
|
int read = stream.Read(data, index, size-index);
|
|
if (read==0)
|
|
{
|
|
return index;
|
|
}
|
|
index += read;
|
|
}
|
|
return index;
|
|
}
|
|
#endregion
|
|
|
|
#region IDisposable Members
|
|
/// <summary>
|
|
/// Disposes of the underlying stream.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
if (!disposed)
|
|
{
|
|
disposed = true;
|
|
((IDisposable)stream).Dispose();
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|