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