// dnlib: See LICENSE.txt for more info
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
using dnlib.DotNet.Writer;
namespace dnlib.IO {
///
/// Thrown by a when it can't read data or if the caller tries to set an invalid offset
///
[Serializable]
public sealed class DataReaderException : IOException {
internal DataReaderException(string message) : base(message) { }
internal DataReaderException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
///
/// Reads data
///
[DebuggerDisplay("{StartOffset,h}-{EndOffset,h} Length={Length} BytesLeft={BytesLeft}")]
public struct DataReader {
///
/// Gets the start offset of the data
///
public readonly uint StartOffset => startOffset;
///
/// Gets the end offset of the data, not inclusive
///
public readonly uint EndOffset => endOffset;
///
/// Gets the total length of the data
///
public readonly uint Length => endOffset - startOffset;
///
/// Gets the current offset. This is between and (inclusive)
///
public uint CurrentOffset {
readonly get => currentOffset;
set {
VerifyState();
if (value < startOffset || value > endOffset) {
// Invalid offsets should be an IOException and not an ArgumentException
ThrowDataReaderException($"Invalid new {nameof(CurrentOffset)}");
}
currentOffset = value;
VerifyState();
}
}
///
/// Gets/sets the position relative to
///
public uint Position {
readonly get => currentOffset - startOffset;
set {
VerifyState();
if (value > Length) {
// Invalid positions should be an IOException and not an ArgumentException
ThrowDataReaderException($"Invalid new {nameof(Position)}");
}
currentOffset = startOffset + value;
VerifyState();
}
}
///
/// Gets the number of bytes that can be read without throwing an exception
///
public readonly uint BytesLeft => endOffset - currentOffset;
readonly DataStream stream;
readonly uint startOffset;
readonly uint endOffset;
uint currentOffset;
///
/// Constructor
///
/// Stream
/// Start offset of data
/// Length of data
public DataReader(DataStream stream, uint offset, uint length) {
Debug.Assert(stream is not null || (offset == 0 && length == 0));
Debug.Assert(offset + length >= offset);
this.stream = stream;
startOffset = offset;
endOffset = offset + length;
currentOffset = offset;
VerifyState();
}
[Conditional("DEBUG")]
readonly void VerifyState() {
Debug.Assert(startOffset <= currentOffset);
Debug.Assert(currentOffset <= endOffset);
}
static void ThrowNoMoreBytesLeft() => throw new DataReaderException("There's not enough bytes left to read");
static void ThrowDataReaderException(string message) => throw new DataReaderException(message);
static void ThrowInvalidOperationException() => throw new InvalidOperationException();
static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName);
static void ThrowInvalidArgument(string paramName) => throw new DataReaderException("Invalid argument value");
///
/// Resets the reader so it points to the start of the data
///
public void Reset() => currentOffset = startOffset;
///
/// Creates a new reader that can access a smaller part of this reader
///
/// Start position relative to
/// Length of data
///
public readonly DataReader Slice(uint start, uint length) {
if ((ulong)start + length > Length)
ThrowInvalidArgument(nameof(length));
return new DataReader(stream, startOffset + start, length);
}
///
/// Creates a new reader that can access everything from to the end of the data
///
/// Start position relative to
///
public readonly DataReader Slice(uint start) {
if (start > Length)
ThrowInvalidArgument(nameof(start));
return Slice(start, Length - start);
}
///
/// Creates a new reader that can access a smaller part of this reader
///
/// Start position relative to
/// Length of data
///
public readonly DataReader Slice(int start, int length) {
if (start < 0)
ThrowInvalidArgument(nameof(start));
if (length < 0)
ThrowInvalidArgument(nameof(length));
return Slice((uint)start, (uint)length);
}
///
/// Creates a new reader that can access everything from to the end of the data
///
/// Start position relative to
///
public readonly DataReader Slice(int start) {
if (start < 0)
ThrowInvalidArgument(nameof(start));
if ((uint)start > Length)
ThrowInvalidArgument(nameof(start));
return Slice((uint)start, Length - (uint)start);
}
///
/// Checks if it's possible to read bytes
///
/// Length of data
///
public readonly bool CanRead(int length) => length >= 0 && (uint)length <= BytesLeft;
///
/// Checks if it's possible to read bytes
///
/// Length of data
///
public readonly bool CanRead(uint length) => length <= BytesLeft;
///
/// Reads a
///
///
public bool ReadBoolean() {
VerifyState();
const uint SIZE = 1;
var currentOffset = this.currentOffset;
if (currentOffset == endOffset)
ThrowNoMoreBytesLeft();
var value = stream.ReadBoolean(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public char ReadChar() {
VerifyState();
const uint SIZE = 2;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadChar(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public sbyte ReadSByte() {
VerifyState();
const uint SIZE = 1;
var currentOffset = this.currentOffset;
if (currentOffset == endOffset)
ThrowNoMoreBytesLeft();
var value = stream.ReadSByte(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public byte ReadByte() {
VerifyState();
const uint SIZE = 1;
var currentOffset = this.currentOffset;
if (currentOffset == endOffset)
ThrowNoMoreBytesLeft();
var value = stream.ReadByte(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public short ReadInt16() {
VerifyState();
const uint SIZE = 2;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadInt16(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public ushort ReadUInt16() {
VerifyState();
const uint SIZE = 2;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadUInt16(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public int ReadInt32() {
VerifyState();
const uint SIZE = 4;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadInt32(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public uint ReadUInt32() {
VerifyState();
const uint SIZE = 4;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadUInt32(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
internal byte Unsafe_ReadByte() {
VerifyState();
const uint SIZE = 1;
var currentOffset = this.currentOffset;
Debug.Assert(currentOffset != endOffset);
var value = stream.ReadByte(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
internal ushort Unsafe_ReadUInt16() {
VerifyState();
const uint SIZE = 2;
var currentOffset = this.currentOffset;
Debug.Assert(endOffset - currentOffset >= SIZE);
var value = stream.ReadUInt16(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
internal uint Unsafe_ReadUInt32() {
VerifyState();
const uint SIZE = 4;
var currentOffset = this.currentOffset;
Debug.Assert(endOffset - currentOffset >= SIZE);
var value = stream.ReadUInt32(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public long ReadInt64() {
VerifyState();
const uint SIZE = 8;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadInt64(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public ulong ReadUInt64() {
VerifyState();
const uint SIZE = 8;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadUInt64(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public float ReadSingle() {
VerifyState();
const uint SIZE = 4;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadSingle(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public double ReadDouble() {
VerifyState();
const uint SIZE = 8;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadDouble(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public Guid ReadGuid() {
VerifyState();
const uint SIZE = 16;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadGuid(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a
///
///
public decimal ReadDecimal() {
VerifyState();
const uint SIZE = 16;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < SIZE)
ThrowNoMoreBytesLeft();
var value = stream.ReadDecimal(currentOffset);
this.currentOffset = currentOffset + SIZE;
VerifyState();
return value;
}
///
/// Reads a UTF-16 encoded
///
/// Number of characters to read
///
public string ReadUtf16String(int chars) {
if (chars < 0)
ThrowInvalidArgument(nameof(chars));
if (chars == 0)
return string.Empty;
VerifyState();
uint length = (uint)chars * 2;
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < length)
ThrowNoMoreBytesLeft();
var s = length == 0 ? string.Empty : stream.ReadUtf16String(currentOffset, chars);
this.currentOffset = currentOffset + length;
VerifyState();
return s;
}
///
/// Reads bytes
///
/// Destination pointer
/// Number of bytes to read
public unsafe void ReadBytes(void* destination, int length) {
if (destination is null && length != 0)
ThrowArgumentNullException(nameof(destination));
if (length < 0)
ThrowInvalidArgument(nameof(length));
// This is also true if 'this' is the 'default' instance ('stream' is null)
if (length == 0)
return;
VerifyState();
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < (uint)length)
ThrowNoMoreBytesLeft();
stream.ReadBytes(currentOffset, destination, length);
this.currentOffset = currentOffset + (uint)length;
VerifyState();
}
///
/// Reads bytes
///
/// Destination array
/// Destination index
/// Number of bytes to read
public void ReadBytes(byte[] destination, int destinationIndex, int length) {
if (destination is null)
ThrowArgumentNullException(nameof(destination));
if (destinationIndex < 0)
ThrowInvalidArgument(nameof(destinationIndex));
if (length < 0)
ThrowInvalidArgument(nameof(length));
// This is also true if 'this' is the 'default' instance ('stream' is null)
if (length == 0)
return;
VerifyState();
var currentOffset = this.currentOffset;
if (endOffset - currentOffset < (uint)length)
ThrowNoMoreBytesLeft();
stream.ReadBytes(currentOffset, destination, destinationIndex, length);
this.currentOffset = currentOffset + (uint)length;
VerifyState();
}
///
/// Reads bytes
///
/// Number of bytes to read
///
public byte[] ReadBytes(int length) {
if (length < 0)
ThrowInvalidArgument(nameof(length));
if (length == 0)
return Array2.Empty();
var data = new byte[length];
ReadBytes(data, 0, length);
return data;
}
///
/// Reads a compressed
///
/// Uncompressed
///
public bool TryReadCompressedUInt32(out uint value) {
VerifyState();
var currentOffset = this.currentOffset;
var bytesLeft = endOffset - currentOffset;
if (bytesLeft == 0) {
value = 0;
VerifyState();
return false;
}
var stream = this.stream;
byte b = stream.ReadByte(currentOffset++);
if ((b & 0x80) == 0) {
value = b;
this.currentOffset = currentOffset;
VerifyState();
return true;
}
if ((b & 0xC0) == 0x80) {
if (bytesLeft < 2) {
value = 0;
VerifyState();
return false;
}
value = (uint)(((b & 0x3F) << 8) | stream.ReadByte(currentOffset++));
this.currentOffset = currentOffset;
VerifyState();
return true;
}
// The encoding 111x isn't allowed but the CLR sometimes doesn't verify this
// and just assumes it's 110x. Don't fail if it's 111x, just assume it's 110x.
if (bytesLeft < 4) {
value = 0;
VerifyState();
return false;
}
value = (uint)(((b & 0x1F) << 24) | (stream.ReadByte(currentOffset++) << 16) |
(stream.ReadByte(currentOffset++) << 8) | stream.ReadByte(currentOffset++));
this.currentOffset = currentOffset;
VerifyState();
return true;
}
///
/// Reads a compressed
///
///
public uint ReadCompressedUInt32() {
if (!TryReadCompressedUInt32(out uint value))
ThrowNoMoreBytesLeft();
return value;
}
///
/// Reads a compressed
///
/// Uncompressed
///
public bool TryReadCompressedInt32(out int value) {
VerifyState();
var currentOffset = this.currentOffset;
var bytesLeft = endOffset - currentOffset;
if (bytesLeft == 0) {
value = 0;
VerifyState();
return false;
}
var stream = this.stream;
byte b = stream.ReadByte(currentOffset++);
if ((b & 0x80) == 0) {
if ((b & 1) != 0)
value = -0x40 | (b >> 1);
else
value = (b >> 1);
this.currentOffset = currentOffset;
VerifyState();
return true;
}
if ((b & 0xC0) == 0x80) {
if (bytesLeft < 2) {
value = 0;
VerifyState();
return false;
}
uint tmp = (uint)(((b & 0x3F) << 8) | stream.ReadByte(currentOffset++));
if ((tmp & 1) != 0)
value = -0x2000 | (int)(tmp >> 1);
else
value = (int)(tmp >> 1);
this.currentOffset = currentOffset;
VerifyState();
return true;
}
if ((b & 0xE0) == 0xC0) {
if (bytesLeft < 4) {
value = 0;
VerifyState();
return false;
}
uint tmp = (uint)(((b & 0x1F) << 24) | (stream.ReadByte(currentOffset++) << 16) |
(stream.ReadByte(currentOffset++) << 8) | stream.ReadByte(currentOffset++));
if ((tmp & 1) != 0)
value = -0x10000000 | (int)(tmp >> 1);
else
value = (int)(tmp >> 1);
this.currentOffset = currentOffset;
VerifyState();
return true;
}
value = 0;
VerifyState();
return false;
}
///
/// Reads a compressed
///
///
public int ReadCompressedInt32() {
if (!TryReadCompressedInt32(out int value))
ThrowNoMoreBytesLeft();
return value;
}
///
/// Reads a 7-bit encoded integer
///
///
public uint Read7BitEncodedUInt32() {
uint val = 0;
int bits = 0;
for (int i = 0; i < 5; i++) {
byte b = ReadByte();
val |= (uint)(b & 0x7F) << bits;
if ((b & 0x80) == 0)
return val;
bits += 7;
}
ThrowDataReaderException("Invalid encoded UInt32");
return 0;
}
///
/// Reads a 7-bit encoded integer
///
///
public int Read7BitEncodedInt32() => (int)Read7BitEncodedUInt32();
///
/// Reads a serialized UTF-8 string
///
///
public string ReadSerializedString() => ReadSerializedString(Encoding.UTF8);
///
/// Reads a serialized string
///
/// Encoding
///
public string ReadSerializedString(Encoding encoding) {
if (encoding is null)
ThrowArgumentNullException(nameof(encoding));
int length = Read7BitEncodedInt32();
if (length < 0)
ThrowNoMoreBytesLeft();
if (length == 0)
return string.Empty;
return ReadString(length, encoding);
}
///
/// Returns all data without updating the current position
///
///
public readonly byte[] ToArray() {
int length = (int)Length;
if (length < 0)
ThrowInvalidOperationException();
// This is also true if 'this' is the 'default' instance ('stream' is null)
if (length == 0)
return Array2.Empty();
var data = new byte[length];
stream.ReadBytes(startOffset, data, 0, data.Length);
return data;
}
///
/// Returns the remaining data
///
///
public byte[] ReadRemainingBytes() {
int length = (int)BytesLeft;
if (length < 0)
ThrowInvalidOperationException();
return ReadBytes(length);
}
///
/// Reads all bytes until a terminating byte or returns null if wasn't found.
/// If found, the current offset is incremented by the length of the returned data
///
/// Terminating byte value
///
public byte[] TryReadBytesUntil(byte value) {
var currentOffset = this.currentOffset;
var endOffset = this.endOffset;
// This is also true if 'this' is the 'default' instance ('stream' is null)
if (currentOffset == endOffset)
return null;
if (!stream.TryGetOffsetOf(currentOffset, endOffset, value, out var valueOffset))
return null;
int length = (int)(valueOffset - currentOffset);
if (length < 0)
return null;
return ReadBytes(length);
}
///
/// Reads a zero-terminated UTF-8 string or returns null if the string couldn't be read.
/// If successful, the current offset is incremented past the terminating zero.
///
///
public string TryReadZeroTerminatedUtf8String() => TryReadZeroTerminatedString(Encoding.UTF8);
///
/// Reads a zero-terminated string or returns null if the string couldn't be read.
/// If successful, the current offset is incremented past the terminating zero.
///
/// Encoding
///
public string TryReadZeroTerminatedString(Encoding encoding) {
if (encoding is null)
ThrowArgumentNullException(nameof(encoding));
VerifyState();
var currentOffset = this.currentOffset;
var endOffset = this.endOffset;
// This is also true if 'this' is the 'default' instance ('stream' is null)
if (currentOffset == endOffset)
return null;
if (!stream.TryGetOffsetOf(currentOffset, endOffset, 0, out var valueOffset))
return null;
int length = (int)(valueOffset - currentOffset);
if (length < 0)
return null;
var value = length == 0 ? string.Empty : stream.ReadString(currentOffset, length, encoding);
this.currentOffset = valueOffset + 1;
VerifyState();
return value;
}
///
/// Reads a UTF-8 encoded string
///
/// Number of bytes to read (not characters)
///
public string ReadUtf8String(int byteCount) => ReadString(byteCount, Encoding.UTF8);
///
/// Reads a string
///
/// Number of bytes to read (not characters)
/// Encoding
///
public string ReadString(int byteCount, Encoding encoding) {
if (byteCount < 0)
ThrowInvalidArgument(nameof(byteCount));
if (encoding is null)
ThrowArgumentNullException(nameof(encoding));
if (byteCount == 0)
return string.Empty;
if ((uint)byteCount > Length)
ThrowInvalidArgument(nameof(byteCount));
VerifyState();
var currentOffset = this.currentOffset;
var value = stream.ReadString(currentOffset, byteCount, encoding);
this.currentOffset = currentOffset + (uint)byteCount;
VerifyState();
return value;
}
///
/// Creates a that can access this content. The caller doesn't have to dispose of the returned stream.
///
///
public readonly Stream AsStream() => new DataReaderStream(this);
readonly byte[] AllocTempBuffer() => new byte[(int)Math.Min(0x2000, BytesLeft)];
///
/// Copies the data, starting from , to
///
/// Destination
/// Number of bytes written
public void CopyTo(DataWriter destination) {
if (destination is null)
ThrowArgumentNullException(nameof(destination));
if (Position >= Length)
return;
CopyTo(destination.InternalStream, AllocTempBuffer());
}
///
/// Copies the data, starting from , to
///
/// Destination
/// Temp buffer during writing
/// Number of bytes written
public void CopyTo(DataWriter destination, byte[] dataBuffer) {
if (destination is null)
ThrowArgumentNullException(nameof(destination));
CopyTo(destination.InternalStream, dataBuffer);
}
///
/// Copies the data, starting from , to
///
/// Destination
/// Number of bytes written
public void CopyTo(BinaryWriter destination) {
if (destination is null)
ThrowArgumentNullException(nameof(destination));
if (Position >= Length)
return;
CopyTo(destination.BaseStream, AllocTempBuffer());
}
///
/// Copies the data, starting from , to
///
/// Destination
/// Temp buffer during writing
/// Number of bytes written
public void CopyTo(BinaryWriter destination, byte[] dataBuffer) {
if (destination is null)
ThrowArgumentNullException(nameof(destination));
CopyTo(destination.BaseStream, dataBuffer);
}
///
/// Copies the data, starting from , to
///
/// Destination
/// Number of bytes written
public void CopyTo(Stream destination) {
if (destination is null)
ThrowArgumentNullException(nameof(destination));
if (Position >= Length)
return;
CopyTo(destination, AllocTempBuffer());
}
///
/// Copies the data, starting from , to
///
/// Destination
/// Temp buffer during writing
/// Number of bytes written
public void CopyTo(Stream destination, byte[] dataBuffer) {
if (destination is null)
ThrowArgumentNullException(nameof(destination));
if (dataBuffer is null)
ThrowArgumentNullException(nameof(dataBuffer));
if (Position >= Length)
return;
if (dataBuffer.Length == 0)
ThrowInvalidArgument(nameof(dataBuffer));
uint lenLeft = BytesLeft;
while (lenLeft > 0) {
int num = (int)Math.Min((uint)dataBuffer.Length, lenLeft);
lenLeft -= (uint)num;
ReadBytes(dataBuffer, 0, num);
destination.Write(dataBuffer, 0, num);
}
}
}
}