907 lines
27 KiB
C#
907 lines
27 KiB
C#
// 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 {
|
|
/// <summary>
|
|
/// Thrown by a <see cref="DataReader"/> when it can't read data or if the caller tries to set an invalid offset
|
|
/// </summary>
|
|
[Serializable]
|
|
public sealed class DataReaderException : IOException {
|
|
internal DataReaderException(string message) : base(message) { }
|
|
internal DataReaderException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads data
|
|
/// </summary>
|
|
[DebuggerDisplay("{StartOffset,h}-{EndOffset,h} Length={Length} BytesLeft={BytesLeft}")]
|
|
public struct DataReader {
|
|
/// <summary>
|
|
/// Gets the start offset of the data
|
|
/// </summary>
|
|
public readonly uint StartOffset => startOffset;
|
|
|
|
/// <summary>
|
|
/// Gets the end offset of the data, not inclusive
|
|
/// </summary>
|
|
public readonly uint EndOffset => endOffset;
|
|
|
|
/// <summary>
|
|
/// Gets the total length of the data
|
|
/// </summary>
|
|
public readonly uint Length => endOffset - startOffset;
|
|
|
|
/// <summary>
|
|
/// Gets the current offset. This is between <see cref="StartOffset"/> and <see cref="EndOffset"/> (inclusive)
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the position relative to <see cref="StartOffset"/>
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the number of bytes that can be read without throwing an exception
|
|
/// </summary>
|
|
public readonly uint BytesLeft => endOffset - currentOffset;
|
|
|
|
readonly DataStream stream;
|
|
readonly uint startOffset;
|
|
readonly uint endOffset;
|
|
uint currentOffset;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="stream">Stream</param>
|
|
/// <param name="offset">Start offset of data</param>
|
|
/// <param name="length">Length of data</param>
|
|
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");
|
|
|
|
/// <summary>
|
|
/// Resets the reader so it points to the start of the data
|
|
/// </summary>
|
|
public void Reset() => currentOffset = startOffset;
|
|
|
|
/// <summary>
|
|
/// Creates a new reader that can access a smaller part of this reader
|
|
/// </summary>
|
|
/// <param name="start">Start position relative to <see cref="StartOffset"/></param>
|
|
/// <param name="length">Length of data</param>
|
|
/// <returns></returns>
|
|
public readonly DataReader Slice(uint start, uint length) {
|
|
if ((ulong)start + length > Length)
|
|
ThrowInvalidArgument(nameof(length));
|
|
return new DataReader(stream, startOffset + start, length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new reader that can access everything from <paramref name="start"/> to the end of the data
|
|
/// </summary>
|
|
/// <param name="start">Start position relative to <see cref="StartOffset"/></param>
|
|
/// <returns></returns>
|
|
public readonly DataReader Slice(uint start) {
|
|
if (start > Length)
|
|
ThrowInvalidArgument(nameof(start));
|
|
return Slice(start, Length - start);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new reader that can access a smaller part of this reader
|
|
/// </summary>
|
|
/// <param name="start">Start position relative to <see cref="StartOffset"/></param>
|
|
/// <param name="length">Length of data</param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new reader that can access everything from <paramref name="start"/> to the end of the data
|
|
/// </summary>
|
|
/// <param name="start">Start position relative to <see cref="StartOffset"/></param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if it's possible to read <paramref name="length"/> bytes
|
|
/// </summary>
|
|
/// <param name="length">Length of data</param>
|
|
/// <returns></returns>
|
|
public readonly bool CanRead(int length) => length >= 0 && (uint)length <= BytesLeft;
|
|
|
|
/// <summary>
|
|
/// Checks if it's possible to read <paramref name="length"/> bytes
|
|
/// </summary>
|
|
/// <param name="length">Length of data</param>
|
|
/// <returns></returns>
|
|
public readonly bool CanRead(uint length) => length <= BytesLeft;
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="bool"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="char"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="sbyte"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="byte"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="short"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="ushort"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="int"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="uint"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="long"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="ulong"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="float"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="double"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="Guid"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a <see cref="decimal"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a UTF-16 encoded <see cref="string"/>
|
|
/// </summary>
|
|
/// <param name="chars">Number of characters to read</param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads bytes
|
|
/// </summary>
|
|
/// <param name="destination">Destination pointer</param>
|
|
/// <param name="length">Number of bytes to read</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads bytes
|
|
/// </summary>
|
|
/// <param name="destination">Destination array</param>
|
|
/// <param name="destinationIndex">Destination index</param>
|
|
/// <param name="length">Number of bytes to read</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads bytes
|
|
/// </summary>
|
|
/// <param name="length">Number of bytes to read</param>
|
|
/// <returns></returns>
|
|
public byte[] ReadBytes(int length) {
|
|
if (length < 0)
|
|
ThrowInvalidArgument(nameof(length));
|
|
if (length == 0)
|
|
return Array2.Empty<byte>();
|
|
var data = new byte[length];
|
|
ReadBytes(data, 0, length);
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a compressed <see cref="uint"/>
|
|
/// </summary>
|
|
/// <param name="value">Uncompressed <see cref="uint"/></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a compressed <see cref="uint"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public uint ReadCompressedUInt32() {
|
|
if (!TryReadCompressedUInt32(out uint value))
|
|
ThrowNoMoreBytesLeft();
|
|
return value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a compressed <see cref="int"/>
|
|
/// </summary>
|
|
/// <param name="value">Uncompressed <see cref="int"/></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a compressed <see cref="int"/>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public int ReadCompressedInt32() {
|
|
if (!TryReadCompressedInt32(out int value))
|
|
ThrowNoMoreBytesLeft();
|
|
return value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 7-bit encoded integer
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a 7-bit encoded integer
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public int Read7BitEncodedInt32() => (int)Read7BitEncodedUInt32();
|
|
|
|
/// <summary>
|
|
/// Reads a serialized UTF-8 string
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public string ReadSerializedString() => ReadSerializedString(Encoding.UTF8);
|
|
|
|
/// <summary>
|
|
/// Reads a serialized string
|
|
/// </summary>
|
|
/// <param name="encoding">Encoding</param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns all data without updating the current position
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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<byte>();
|
|
var data = new byte[length];
|
|
stream.ReadBytes(startOffset, data, 0, data.Length);
|
|
return data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the remaining data
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public byte[] ReadRemainingBytes() {
|
|
int length = (int)BytesLeft;
|
|
if (length < 0)
|
|
ThrowInvalidOperationException();
|
|
return ReadBytes(length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads all bytes until a terminating byte or returns null if <paramref name="value"/> wasn't found.
|
|
/// If found, the current offset is incremented by the length of the returned data
|
|
/// </summary>
|
|
/// <param name="value">Terminating byte value</param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public string TryReadZeroTerminatedUtf8String() => TryReadZeroTerminatedString(Encoding.UTF8);
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="encoding">Encoding</param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a UTF-8 encoded string
|
|
/// </summary>
|
|
/// <param name="byteCount">Number of bytes to read (not characters)</param>
|
|
/// <returns></returns>
|
|
public string ReadUtf8String(int byteCount) => ReadString(byteCount, Encoding.UTF8);
|
|
|
|
/// <summary>
|
|
/// Reads a string
|
|
/// </summary>
|
|
/// <param name="byteCount">Number of bytes to read (not characters)</param>
|
|
/// <param name="encoding">Encoding</param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a <see cref="Stream"/> that can access this content. The caller doesn't have to dispose of the returned stream.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public readonly Stream AsStream() => new DataReaderStream(this);
|
|
|
|
readonly byte[] AllocTempBuffer() => new byte[(int)Math.Min(0x2000, BytesLeft)];
|
|
|
|
/// <summary>
|
|
/// Copies the data, starting from <see cref="Position"/>, to <paramref name="destination"/>
|
|
/// </summary>
|
|
/// <param name="destination">Destination</param>
|
|
/// <returns>Number of bytes written</returns>
|
|
public void CopyTo(DataWriter destination) {
|
|
if (destination is null)
|
|
ThrowArgumentNullException(nameof(destination));
|
|
if (Position >= Length)
|
|
return;
|
|
CopyTo(destination.InternalStream, AllocTempBuffer());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the data, starting from <see cref="Position"/>, to <paramref name="destination"/>
|
|
/// </summary>
|
|
/// <param name="destination">Destination</param>
|
|
/// <param name="dataBuffer">Temp buffer during writing</param>
|
|
/// <returns>Number of bytes written</returns>
|
|
public void CopyTo(DataWriter destination, byte[] dataBuffer) {
|
|
if (destination is null)
|
|
ThrowArgumentNullException(nameof(destination));
|
|
CopyTo(destination.InternalStream, dataBuffer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the data, starting from <see cref="Position"/>, to <paramref name="destination"/>
|
|
/// </summary>
|
|
/// <param name="destination">Destination</param>
|
|
/// <returns>Number of bytes written</returns>
|
|
public void CopyTo(BinaryWriter destination) {
|
|
if (destination is null)
|
|
ThrowArgumentNullException(nameof(destination));
|
|
if (Position >= Length)
|
|
return;
|
|
CopyTo(destination.BaseStream, AllocTempBuffer());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the data, starting from <see cref="Position"/>, to <paramref name="destination"/>
|
|
/// </summary>
|
|
/// <param name="destination">Destination</param>
|
|
/// <param name="dataBuffer">Temp buffer during writing</param>
|
|
/// <returns>Number of bytes written</returns>
|
|
public void CopyTo(BinaryWriter destination, byte[] dataBuffer) {
|
|
if (destination is null)
|
|
ThrowArgumentNullException(nameof(destination));
|
|
CopyTo(destination.BaseStream, dataBuffer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the data, starting from <see cref="Position"/>, to <paramref name="destination"/>
|
|
/// </summary>
|
|
/// <param name="destination">Destination</param>
|
|
/// <returns>Number of bytes written</returns>
|
|
public void CopyTo(Stream destination) {
|
|
if (destination is null)
|
|
ThrowArgumentNullException(nameof(destination));
|
|
if (Position >= Length)
|
|
return;
|
|
CopyTo(destination, AllocTempBuffer());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the data, starting from <see cref="Position"/>, to <paramref name="destination"/>
|
|
/// </summary>
|
|
/// <param name="destination">Destination</param>
|
|
/// <param name="dataBuffer">Temp buffer during writing</param>
|
|
/// <returns>Number of bytes written</returns>
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|