// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using System.IO; using dnlib.IO; using dnlib.DotNet.MD; using dnlib.Protection; namespace dnlib.DotNet.Writer { /// /// #Blob heap /// public sealed class BlobHeap : HeapBase, IOffsetHeap { readonly Dictionary cachedDict = new Dictionary(ByteArrayEqualityComparer.Instance); readonly List cached = new List(); uint nextOffset = 1; byte[] originalData; Dictionary userRawData; /// public override string Name => "#Blob"; /// /// Populates blobs from an existing (eg. to preserve /// blob offsets) /// /// The #Blob stream with the original content public void Populate(BlobStream blobStream) { if (isReadOnly) throw new ModuleWriterException("Trying to modify #Blob when it's read-only"); if (originalData is not null) throw new InvalidOperationException("Can't call method twice"); if (nextOffset != 1) throw new InvalidOperationException("Add() has already been called"); if (blobStream is null || blobStream.StreamLength == 0) return; var reader = blobStream.CreateReader(); originalData = reader.ToArray(); nextOffset = (uint)originalData.Length; Populate(ref reader); } void Populate(ref DataReader reader) { reader.Position = 1; while (reader.Position < reader.Length) { uint offset = reader.Position; if (!reader.TryReadCompressedUInt32(out uint len)) { if (offset == reader.Position) reader.Position++; continue; } if (len == 0 || (ulong)reader.Position + len > reader.Length) continue; var data = reader.ReadBytes((int)len); if (!cachedDict.ContainsKey(data)) cachedDict[data] = offset; } } /// /// Adds data to the #Blob heap /// /// The data /// The offset of the data in the #Blob heap public uint Add(byte[] data) { if (isReadOnly) throw new ModuleWriterException("Trying to modify #Blob when it's read-only"); if (data is null || data.Length == 0) return 0; if (cachedDict.TryGetValue(data, out uint offset)) return offset; return AddToCache(data); } /// /// Adds data to the #Blob heap, but does not re-use an existing position /// /// The data /// The offset of the data in the #Blob heap public uint Create(byte[] data) { if (isReadOnly) throw new ModuleWriterException("Trying to modify #Blob when it's read-only"); return AddToCache(data ?? Array2.Empty()); } uint AddToCache(byte[] data) { uint offset; cached.Add(data); cachedDict[data] = offset = nextOffset; nextOffset += (uint)GetRawDataSize(data); return offset; } /// public override uint GetRawLength() => nextOffset; protected override EncryptionMethod GetEncryptionMethod(IEncryption e) => e.BlobEnc; /// protected override void WriteToImpl(DataWriter writer) { if (originalData is not null) writer.WriteBytes(originalData); else writer.WriteByte(0); uint offset = originalData is not null ? (uint)originalData.Length : 1; foreach (var data in cached) { int rawLen = GetRawDataSize(data); if (userRawData is not null && userRawData.TryGetValue(offset, out var rawData)) { if (rawData.Length != rawLen) throw new InvalidOperationException("Invalid length of raw data"); writer.WriteBytes(rawData); } else { writer.WriteCompressedUInt32((uint)data.Length); writer.WriteBytes(data); } offset += (uint)rawLen; } } /// public int GetRawDataSize(byte[] data) => DataWriter.GetCompressedUInt32Length((uint)data.Length) + data.Length; /// public void SetRawData(uint offset, byte[] rawData) { if (userRawData is null) userRawData = new Dictionary(); userRawData[offset] = rawData ?? throw new ArgumentNullException(nameof(rawData)); } /// public IEnumerable> GetAllRawData() { var memStream = new MemoryStream(); var writer = new DataWriter(memStream); uint offset = originalData is not null ? (uint)originalData.Length : 1; foreach (var data in cached) { memStream.Position = 0; memStream.SetLength(0); writer.WriteCompressedUInt32((uint)data.Length); writer.WriteBytes(data); yield return new KeyValuePair(offset, memStream.ToArray()); offset += (uint)memStream.Length; } } } }