BlobReader.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.IO.Compression;
  5. using System.Diagnostics;
  6. namespace SteamKit2.Blob
  7. {
  8. /// <summary>
  9. /// BlobReader that sequentially reads a blob
  10. /// </summary>
  11. public class BlobReader : IDisposable
  12. {
  13. /// <summary>
  14. /// Exception for a Blob that cannot be parsed
  15. /// </summary>
  16. public class InvalidBlobException : Exception
  17. {
  18. internal InvalidBlobException(string message) : base(message) { }
  19. }
  20. private bool ownsSource;
  21. private Stream source;
  22. private Stack<Stream> sourceStack;
  23. private ECacheState cacheState;
  24. private EAutoPreprocessCode processCode;
  25. private int bytesAvailable, spareAvailable;
  26. /// <summary>
  27. /// Cache state of the blob being read
  28. /// </summary>
  29. public ECacheState CacheState { get { return cacheState; } }
  30. /// <summary>
  31. /// Process code of the blob being read
  32. /// </summary>
  33. public EAutoPreprocessCode ProcessCode { get { return processCode; } }
  34. /// <summary>
  35. /// Current bytes available to be read
  36. /// </summary>
  37. public int BytesAvailable { get { return bytesAvailable; } }
  38. /// <summary>
  39. /// Current spare bytes available to be read
  40. /// </summary>
  41. public int SpareAvailable { get { return spareAvailable; } }
  42. private int keyBytes, dataBytes;
  43. /// <summary>
  44. /// Size of the Field Key currently being processed
  45. /// </summary>
  46. public int FieldKeyBytes { get { return keyBytes; } }
  47. /// <summary>
  48. /// Size of the Field Data currently being processed
  49. /// </summary>
  50. public int FieldDataBytes { get { return dataBytes; } }
  51. private byte[] keyBuffer;
  52. private int keyInt;
  53. /// <summary>
  54. /// Byte buffer of the Field Key
  55. /// </summary>
  56. public byte[] ByteKey { get { return keyBuffer; } }
  57. /// <summary>
  58. /// Peekable int of the Field Key
  59. /// </summary>
  60. public int PeekIntKey { get { return keyInt; } }
  61. private const int BlobHeaderLength = 10;
  62. internal const int FieldHeaderLength = 6;
  63. private const int CompressedHeaderLength = 10;
  64. private const int EncryptedHeaderLength = 20;
  65. /// <summary>
  66. /// Create a new BlobReader from a file path
  67. /// </summary>
  68. /// <param name="fileName">Path to blob</param>
  69. /// <returns></returns>
  70. public static BlobReader CreateFrom(string fileName)
  71. {
  72. return new BlobReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None, 0x10000, FileOptions.SequentialScan), true);
  73. }
  74. /// <summary>
  75. /// Create a BlobReader from a Stream. Does not take ownership of the Stream.
  76. /// </summary>
  77. /// <param name="inputSteam">Source</param>
  78. /// <returns></returns>
  79. public static BlobReader CreateFrom(Stream inputSteam)
  80. {
  81. return new BlobReader(inputSteam, false);
  82. }
  83. private BlobReader(Stream blobSource, bool ownsSource = true)
  84. {
  85. this.sourceStack = null;
  86. this.source = blobSource;
  87. this.ownsSource = ownsSource;
  88. this.keyBuffer = new byte[4];
  89. ReadBlobHeader();
  90. }
  91. private void ReadBlobHeader()
  92. {
  93. cacheState = (ECacheState)source.ReadByte();
  94. processCode = (EAutoPreprocessCode)source.ReadByte();
  95. bytesAvailable = source.ReadInt32();
  96. spareAvailable = source.ReadInt32();
  97. if (!BlobUtil.IsValidCacheState(cacheState) || !BlobUtil.IsValidProcess(processCode))
  98. {
  99. throw new InvalidBlobException("Invalid blob header");
  100. }
  101. TakeBytes(BlobHeaderLength);
  102. UnpackBlobIfNeeded();
  103. }
  104. /// <summary>
  105. /// Dispose the BlobReader, releasing any Streams allocated.
  106. /// </summary>
  107. public void Dispose()
  108. {
  109. if(ownsSource)
  110. this.source.Close();
  111. if (sourceStack != null)
  112. foreach (var stream in sourceStack)
  113. stream.Close();
  114. }
  115. private void UnpackBlobIfNeeded()
  116. {
  117. if (processCode == EAutoPreprocessCode.eAutoPreprocessCodeCompressed)
  118. {
  119. Int32 decompressedSize, unknown;
  120. Int16 level;
  121. decompressedSize = source.ReadInt32();
  122. unknown = source.ReadInt32();
  123. level = source.ReadInt16();
  124. if (sourceStack == null)
  125. sourceStack = new Stack<Stream>(1);
  126. sourceStack.Push(source);
  127. source.ReadInt16(); // skip zlib header
  128. source = new BufferedStream(new DeflateStream(source, CompressionMode.Decompress, true), 0x10000);
  129. ReadBlobHeader();
  130. }
  131. else if (processCode == EAutoPreprocessCode.eAutoPreprocessCodeEncrypted)
  132. {
  133. // cryptostream
  134. }
  135. }
  136. private void TakeBytes(int size)
  137. {
  138. Debug.Assert(CanTakeBytes(size));
  139. bytesAvailable -= size;
  140. }
  141. internal bool CanTakeBytes(int size)
  142. {
  143. return bytesAvailable >= size;
  144. }
  145. /// <summary>
  146. /// Read the next Field in the blob
  147. /// </summary>
  148. public void ReadFieldHeader()
  149. {
  150. keyBytes = source.ReadUInt16();
  151. dataBytes = source.ReadInt32();
  152. TakeBytes(FieldHeaderLength);
  153. if (!CanTakeBytes(keyBytes + dataBytes))
  154. {
  155. throw new InvalidBlobException("Invalid field lengths");
  156. }
  157. if (keyBuffer.Length < keyBytes)
  158. {
  159. keyBuffer = new byte[keyBytes];
  160. }
  161. source.Read(keyBuffer, 0, keyBytes);
  162. if (keyBytes == 4)
  163. {
  164. keyInt = BitConverter.ToInt32(keyBuffer, 0);
  165. }
  166. else
  167. {
  168. keyInt = -1;
  169. }
  170. TakeBytes(keyBytes + dataBytes);
  171. }
  172. /// <summary>
  173. /// Reads a field as a Blob
  174. /// </summary>
  175. /// <returns>Blob Reader</returns>
  176. public BlobReader ReadFieldBlob()
  177. {
  178. return new BlobReader(source, false);
  179. }
  180. // magic method to make serializers easier
  181. internal Stream ReadFieldStream()
  182. {
  183. return source;
  184. }
  185. /// <summary>
  186. /// Skip over a field, discarding the contents of a field
  187. /// </summary>
  188. public void SkipField()
  189. {
  190. source.ReadAndDiscard(dataBytes);
  191. }
  192. /// <summary>
  193. /// Skip over the spare, discarding the contents
  194. /// </summary>
  195. public void SkipSpare()
  196. {
  197. Debug.Assert(bytesAvailable == 0);
  198. source.ReadAndDiscard(spareAvailable);
  199. }
  200. }
  201. }