// 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;
}
}
}
}