/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Diagnostics; namespace dnSpy.Contracts.Hex.Files { /// /// Any data /// public abstract class BufferData { /// /// Gets the span /// public HexBufferSpan Span { get; } /// /// Constructor /// /// Data span protected BufferData(HexBufferSpan span) { if (span.IsDefault) throw new ArgumentException(); Span = span; } } /// /// Simple data that contains no fields /// public abstract class SimpleData : BufferData { /// /// Constructor /// /// Data span protected SimpleData(HexBufferSpan span) : base(span) { } /// /// Writes the value /// /// Formatter public abstract void WriteValue(HexFieldFormatter formatter); /// /// Returns the span the field value references or null. The span can be empty. /// /// File /// public virtual HexSpan? GetFieldReferenceSpan(HexBufferFile file) => null; } /// /// Base class of structures and arrays /// public abstract class ComplexData : BufferData { /// /// Gets the name or an empty string /// public string Name { get; } /// /// Constructor /// /// Name /// Span protected ComplexData(string name, HexBufferSpan span) : base(span) => Name = name ?? throw new ArgumentNullException(nameof(name)); /// /// Gets a field /// /// Index /// public BufferField this[int index] => GetFieldByIndex(index); /// /// Gets a field /// /// Name /// public BufferField? this[string name] => GetFieldByName(name); /// /// Gets the field count /// public abstract int FieldCount { get; } /// /// Gets a field by index /// /// Index /// public abstract BufferField GetFieldByIndex(int index); /// /// Gets a field by position /// /// Position /// public abstract BufferField? GetFieldByPosition(HexPosition position); /// /// Gets a field /// /// Name of field /// public abstract BufferField? GetFieldByName(string name); /// /// Writes the name /// /// Formatter public abstract void WriteName(HexFieldFormatter formatter); /// /// Returns the first field (recursively) that contains a or null if none was found. /// This field could be contained in a nested instance. /// /// Position /// public BufferField? GetSimpleField(HexPosition position) { ComplexData? structure = this; for (;;) { var field = structure.GetFieldByPosition(position); if (field is null) return null; structure = field.Data as ComplexData; if (structure is null) { Debug.Assert(field.Data is SimpleData); return field.Data is SimpleData ? field : null; } } } } /// /// A structure /// public abstract class StructureData : ComplexData { /// /// Constructor /// /// Name /// Span protected StructureData(string name, HexBufferSpan span) : base(name, span) { } /// /// Gets the fields /// protected abstract BufferField[] Fields { get; } /// /// Gets the field count /// public sealed override int FieldCount => Fields.Length; /// /// Gets a field by index /// /// Index /// public sealed override BufferField GetFieldByIndex(int index) => Fields[index]; /// /// Gets a field by position /// /// Position /// public sealed override BufferField? GetFieldByPosition(HexPosition position) { foreach (var field in Fields) { if (field.Data.Span.Span.Contains(position)) return field; } return null; } /// /// Gets a field /// /// Name of field /// public override BufferField? GetFieldByName(string name) { if (name is null) throw new ArgumentNullException(nameof(name)); foreach (var field in Fields) { if (field.Name == name) return field; } return null; } /// /// Writes the name /// /// Formatter public override void WriteName(HexFieldFormatter formatter) => formatter.WriteStructure(Name); } /// /// An array /// public abstract class ArrayData : ComplexData { /// /// Constructor /// /// Name /// Span protected ArrayData(string name, HexBufferSpan span) : base(name, span) { } /// /// Creates a virtual array /// /// Span /// Array name or null /// public static VirtualArrayData CreateVirtualByteArray(HexBufferSpan span, string? name = null) => new VirtualArrayData(name ?? string.Empty, span, 1, createByteData); static readonly Func createByteData = p => new ByteData(p.Buffer, p.Position); /// /// Creates a virtual array /// /// Span /// Array name or null /// public static VirtualArrayData CreateVirtualUInt16Array(HexBufferSpan span, string? name = null) { if ((span.Length.ToUInt64() & 1) != 0) throw new ArgumentOutOfRangeException(nameof(span)); return new VirtualArrayData(name ?? string.Empty, span, 2, createUInt16Data); } static readonly Func createUInt16Data = p => new UInt16Data(p.Buffer, p.Position); /// /// Creates a virtual array /// /// Span /// Array name or null /// public static VirtualArrayData CreateVirtualUInt32Array(HexBufferSpan span, string? name = null) { if ((span.Length.ToUInt64() & 3) != 0) throw new ArgumentOutOfRangeException(nameof(span)); return new VirtualArrayData(name ?? string.Empty, span, 4, createUInt32Data); } static readonly Func createUInt32Data = p => new UInt32Data(p.Buffer, p.Position); /// /// Creates a array /// /// Buffer /// Position /// Number of elements /// Array name or null /// public static ArrayData CreateByteArray(HexBuffer buffer, HexPosition position, int elements, string? name = null) { var fields = new ArrayField[elements]; var currPos = position; for (int i = 0; i < fields.Length; i++) { var field = new ArrayField(new ByteData(buffer, currPos), (uint)i); fields[i] = field; currPos = field.Data.Span.End; } return new ArrayData(name ?? string.Empty, new HexBufferSpan(buffer, HexSpan.FromBounds(position, currPos)), fields); } /// /// Creates a array /// /// Buffer /// Position /// Number of elements /// Array name or null /// public static ArrayData CreateUInt16Array(HexBuffer buffer, HexPosition position, int elements, string? name = null) { var fields = new ArrayField[elements]; var currPos = position; for (int i = 0; i < fields.Length; i++) { var field = new ArrayField(new UInt16Data(buffer, currPos), (uint)i); fields[i] = field; currPos = field.Data.Span.End; } return new ArrayData(name ?? string.Empty, new HexBufferSpan(buffer, HexSpan.FromBounds(position, currPos)), fields); } /// /// Creates a array /// /// Buffer /// Position /// Number of elements /// Array name or null /// public static ArrayData CreateUInt32Array(HexBuffer buffer, HexPosition position, int elements, string? name = null) { var fields = new ArrayField[elements]; var currPos = position; for (int i = 0; i < fields.Length; i++) { var field = new ArrayField(new UInt32Data(buffer, currPos), (uint)i); fields[i] = field; currPos = field.Data.Span.End; } return new ArrayData(name ?? string.Empty, new HexBufferSpan(buffer, HexSpan.FromBounds(position, currPos)), fields); } /// /// Creates a array /// /// Buffer /// Position /// Number of elements /// Array name or null /// public static ArrayData CreateUInt64Array(HexBuffer buffer, HexPosition position, int elements, string? name = null) { var fields = new ArrayField[elements]; var currPos = position; for (int i = 0; i < fields.Length; i++) { var field = new ArrayField(new UInt64Data(buffer, currPos), (uint)i); fields[i] = field; currPos = field.Data.Span.End; } return new ArrayData(name ?? string.Empty, new HexBufferSpan(buffer, HexSpan.FromBounds(position, currPos)), fields); } /// /// Creates a array /// /// Buffer /// Position /// Number of elements /// Array name or null /// public static ArrayData CreateSByteArray(HexBuffer buffer, HexPosition position, int elements, string? name = null) { var fields = new ArrayField[elements]; var currPos = position; for (int i = 0; i < fields.Length; i++) { var field = new ArrayField(new SByteData(buffer, currPos), (uint)i); fields[i] = field; currPos = field.Data.Span.End; } return new ArrayData(name ?? string.Empty, new HexBufferSpan(buffer, HexSpan.FromBounds(position, currPos)), fields); } /// /// Creates a array /// /// Buffer /// Position /// Number of elements /// Array name or null /// public static ArrayData CreateInt16Array(HexBuffer buffer, HexPosition position, int elements, string? name = null) { var fields = new ArrayField[elements]; var currPos = position; for (int i = 0; i < fields.Length; i++) { var field = new ArrayField(new Int16Data(buffer, currPos), (uint)i); fields[i] = field; currPos = field.Data.Span.End; } return new ArrayData(name ?? string.Empty, new HexBufferSpan(buffer, HexSpan.FromBounds(position, currPos)), fields); } /// /// Creates a array /// /// Buffer /// Position /// Number of elements /// Array name or null /// public static ArrayData CreateInt32Array(HexBuffer buffer, HexPosition position, int elements, string? name = null) { var fields = new ArrayField[elements]; var currPos = position; for (int i = 0; i < fields.Length; i++) { var field = new ArrayField(new Int32Data(buffer, currPos), (uint)i); fields[i] = field; currPos = field.Data.Span.End; } return new ArrayData(name ?? string.Empty, new HexBufferSpan(buffer, HexSpan.FromBounds(position, currPos)), fields); } /// /// Creates a array /// /// Buffer /// Position /// Number of elements /// Array name or null /// public static ArrayData CreateInt64Array(HexBuffer buffer, HexPosition position, int elements, string? name = null) { var fields = new ArrayField[elements]; var currPos = position; for (int i = 0; i < fields.Length; i++) { var field = new ArrayField(new Int64Data(buffer, currPos), (uint)i); fields[i] = field; currPos = field.Data.Span.End; } return new ArrayData(name ?? string.Empty, new HexBufferSpan(buffer, HexSpan.FromBounds(position, currPos)), fields); } /// /// Gets a field /// /// Name of field /// public override BufferField? GetFieldByName(string name) { if (name is null) throw new ArgumentNullException(nameof(name)); if (!int.TryParse(name, out int index)) return null; // Don't throw if it's outside the range, it's a look up by name that should return null if the name doesn't exist if ((uint)index >= (uint)FieldCount) return null; return GetFieldByIndex(index); } /// /// Writes the name /// /// Formatter public override void WriteName(HexFieldFormatter formatter) => formatter.WriteArray(Name); } /// /// An array whose elements all have the same size /// /// Type of data public class ArrayData : ArrayData where TData : BufferData { readonly ArrayField[] fields; /// /// Gets the field at /// /// Index /// public new ArrayField this[int index] => fields[index]; /// /// Gets the field count /// public override int FieldCount => fields.Length; /// /// Constructor, see eg. /// /// Name /// Array span /// Array elements public ArrayData(string name, HexBufferSpan span, ArrayField[] fields) : base(name, span) { if (fields is null) throw new ArgumentNullException(nameof(fields)); #if DEBUG for (int i = 1; i < fields.Length; i++) { if (fields[i - 1].Data.Span.Length != fields[i].Data.Span.Length) throw new ArgumentException(); if (fields[i - 1].Data.Span.End != fields[i].Data.Span.Start) throw new ArgumentException(); } if (fields.Length > 0) { if (fields[0].Data.Span.Start != span.Start || fields[fields.Length - 1].Data.Span.End != span.End) throw new ArgumentException(); } #endif this.fields = fields; } /// /// Gets a field by index /// /// Index /// public override BufferField GetFieldByIndex(int index) => fields[index]; /// /// Gets a field by position /// /// Position /// public override BufferField? GetFieldByPosition(HexPosition position) { if (!Span.Contains(position)) return null; int index = (int)((position - Span.Start).ToUInt64() / fields[0].Data.Span.Length.ToUInt64()); return fields[index]; } } /// /// An array whose elements have different sizes /// /// Type of data public class VariableLengthArrayData : ArrayData where TData : BufferData { readonly ArrayField[] fields; /// /// Gets the field at /// /// Index /// public new ArrayField this[int index] => fields[index]; /// /// Gets the field count /// public override int FieldCount => fields.Length; /// /// Constructor, see eg. /// /// Name /// Array span /// Array elements public VariableLengthArrayData(string name, HexBufferSpan span, ArrayField[] fields) : base(name, span) => this.fields = fields ?? throw new ArgumentNullException(nameof(fields)); /// /// Gets a field by index /// /// Index /// public override BufferField GetFieldByIndex(int index) => fields[index]; /// /// Gets a field by position /// /// Position /// public override BufferField? GetFieldByPosition(HexPosition position) { if (!Span.Contains(position)) return null; foreach (var field in fields) { if (field.Data.Span.Span.Contains(position)) return field; } return null; } } /// /// An array whose elements all have the same size and where each element is only created when needed. /// The elements aren't cached so calling eg. multiple times with /// the same input will always return new instances. /// /// Type of data public class VirtualArrayData : ArrayData where TData : BufferData { /// /// Gets the field at /// /// Index /// public new ArrayField this[int index] { get { if ((uint)index >= (uint)FieldCount) throw new ArgumentOutOfRangeException(nameof(index)); return new ArrayField(createElement(Span.Start + elementLength * (uint)index), (uint)index); } } /// /// Gets the field count /// public override int FieldCount { get; } readonly ulong elementLength; readonly Func createElement; /// /// Constructor, see eg. /// /// Name /// Array span /// Size of each element in bytes /// Creates new elements; input parameter is the position of the data public VirtualArrayData(string name, HexBufferSpan span, ulong elementLength, Func createElement) : base(name, span) { if (elementLength == 0) throw new ArgumentOutOfRangeException(nameof(elementLength)); this.elementLength = elementLength; this.createElement = createElement ?? throw new ArgumentNullException(nameof(createElement)); ulong fieldCount = span.Length.ToUInt64() / elementLength; if (fieldCount * elementLength != span.Length) throw new ArgumentOutOfRangeException(nameof(span)); if (fieldCount > int.MaxValue) throw new ArgumentOutOfRangeException(nameof(span)); FieldCount = (int)fieldCount; } /// /// Gets a field by index /// /// Index /// public override BufferField GetFieldByIndex(int index) => this[index]; /// /// Gets a field by position /// /// Position /// public override BufferField? GetFieldByPosition(HexPosition position) { if (!Span.Contains(position)) return null; int index = (int)((position - Span.Start).ToUInt64() / elementLength); return this[index]; } } }