| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.IO.Compression;
- using System.Diagnostics;
- namespace SteamKit2.Blob
- {
- /// <summary>
- /// BlobReader that sequentially reads a blob
- /// </summary>
- public class BlobReader : IDisposable
- {
- /// <summary>
- /// Exception for a Blob that cannot be parsed
- /// </summary>
- public class InvalidBlobException : Exception
- {
- internal InvalidBlobException(string message) : base(message) { }
- }
- private bool ownsSource;
- private Stream source;
- private Stack<Stream> sourceStack;
- private ECacheState cacheState;
- private EAutoPreprocessCode processCode;
- private int bytesAvailable, spareAvailable;
- /// <summary>
- /// Cache state of the blob being read
- /// </summary>
- public ECacheState CacheState { get { return cacheState; } }
- /// <summary>
- /// Process code of the blob being read
- /// </summary>
- public EAutoPreprocessCode ProcessCode { get { return processCode; } }
- /// <summary>
- /// Current bytes available to be read
- /// </summary>
- public int BytesAvailable { get { return bytesAvailable; } }
- /// <summary>
- /// Current spare bytes available to be read
- /// </summary>
- public int SpareAvailable { get { return spareAvailable; } }
- private int keyBytes, dataBytes;
- /// <summary>
- /// Size of the Field Key currently being processed
- /// </summary>
- public int FieldKeyBytes { get { return keyBytes; } }
- /// <summary>
- /// Size of the Field Data currently being processed
- /// </summary>
- public int FieldDataBytes { get { return dataBytes; } }
- private byte[] keyBuffer;
- private int keyInt;
- /// <summary>
- /// Byte buffer of the Field Key
- /// </summary>
- public byte[] ByteKey { get { return keyBuffer; } }
- /// <summary>
- /// Peekable int of the Field Key
- /// </summary>
- public int PeekIntKey { get { return keyInt; } }
- private const int BlobHeaderLength = 10;
- internal const int FieldHeaderLength = 6;
- private const int CompressedHeaderLength = 10;
- private const int EncryptedHeaderLength = 20;
- /// <summary>
- /// Create a new BlobReader from a file path
- /// </summary>
- /// <param name="fileName">Path to blob</param>
- /// <returns></returns>
- public static BlobReader CreateFrom(string fileName)
- {
- return new BlobReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None, 0x10000, FileOptions.SequentialScan), true);
- }
- /// <summary>
- /// Create a BlobReader from a Stream. Does not take ownership of the Stream.
- /// </summary>
- /// <param name="inputSteam">Source</param>
- /// <returns></returns>
- public static BlobReader CreateFrom(Stream inputSteam)
- {
- return new BlobReader(inputSteam, false);
- }
- private BlobReader(Stream blobSource, bool ownsSource = true)
- {
- this.sourceStack = null;
- this.source = blobSource;
- this.ownsSource = ownsSource;
- this.keyBuffer = new byte[4];
- ReadBlobHeader();
- }
- private void ReadBlobHeader()
- {
- cacheState = (ECacheState)source.ReadByte();
- processCode = (EAutoPreprocessCode)source.ReadByte();
- bytesAvailable = source.ReadInt32();
- spareAvailable = source.ReadInt32();
- if (!BlobUtil.IsValidCacheState(cacheState) || !BlobUtil.IsValidProcess(processCode))
- {
- throw new InvalidBlobException("Invalid blob header");
- }
- TakeBytes(BlobHeaderLength);
- UnpackBlobIfNeeded();
- }
- /// <summary>
- /// Dispose the BlobReader, releasing any Streams allocated.
- /// </summary>
- public void Dispose()
- {
- if(ownsSource)
- this.source.Close();
- if (sourceStack != null)
- foreach (var stream in sourceStack)
- stream.Close();
- }
- private void UnpackBlobIfNeeded()
- {
- if (processCode == EAutoPreprocessCode.eAutoPreprocessCodeCompressed)
- {
- Int32 decompressedSize, unknown;
- Int16 level;
- decompressedSize = source.ReadInt32();
- unknown = source.ReadInt32();
- level = source.ReadInt16();
- if (sourceStack == null)
- sourceStack = new Stack<Stream>(1);
- sourceStack.Push(source);
- source.ReadInt16(); // skip zlib header
- source = new BufferedStream(new DeflateStream(source, CompressionMode.Decompress, true), 0x10000);
- ReadBlobHeader();
- }
- else if (processCode == EAutoPreprocessCode.eAutoPreprocessCodeEncrypted)
- {
- // cryptostream
- }
- }
- private void TakeBytes(int size)
- {
- Debug.Assert(CanTakeBytes(size));
- bytesAvailable -= size;
- }
- internal bool CanTakeBytes(int size)
- {
- return bytesAvailable >= size;
- }
- /// <summary>
- /// Read the next Field in the blob
- /// </summary>
- public void ReadFieldHeader()
- {
- keyBytes = source.ReadUInt16();
- dataBytes = source.ReadInt32();
- TakeBytes(FieldHeaderLength);
- if (!CanTakeBytes(keyBytes + dataBytes))
- {
- throw new InvalidBlobException("Invalid field lengths");
- }
- if (keyBuffer.Length < keyBytes)
- {
- keyBuffer = new byte[keyBytes];
- }
- source.Read(keyBuffer, 0, keyBytes);
- if (keyBytes == 4)
- {
- keyInt = BitConverter.ToInt32(keyBuffer, 0);
- }
- else
- {
- keyInt = -1;
- }
- TakeBytes(keyBytes + dataBytes);
- }
- /// <summary>
- /// Reads a field as a Blob
- /// </summary>
- /// <returns>Blob Reader</returns>
- public BlobReader ReadFieldBlob()
- {
- return new BlobReader(source, false);
- }
- // magic method to make serializers easier
- internal Stream ReadFieldStream()
- {
- return source;
- }
- /// <summary>
- /// Skip over a field, discarding the contents of a field
- /// </summary>
- public void SkipField()
- {
- source.ReadAndDiscard(dataBytes);
- }
- /// <summary>
- /// Skip over the spare, discarding the contents
- /// </summary>
- public void SkipSpare()
- {
- Debug.Assert(bytesAvailable == 0);
- source.ReadAndDiscard(spareAvailable);
- }
- }
- }
|