/*
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.Collections.Generic;
using System.Diagnostics;
using VSUTIL = Microsoft.VisualStudio.Utilities;
namespace dnSpy.Contracts.Hex {
///
/// Hex buffer
///
public abstract class HexBuffer : VSUTIL.IPropertyOwner, IDisposable {
///
/// Constructor
///
protected HexBuffer(HexTags tags) {
Properties = new VSUTIL.PropertyCollection();
Tags = tags ?? throw new ArgumentNullException(nameof(tags));
}
///
/// Gets all properties
///
public VSUTIL.PropertyCollection Properties { get; }
///
/// Gets the tags
///
public HexTags Tags { get; }
///
/// true if the underlying stream reads data from some process' memory
///
public bool IsMemory => Tags.Contains(PredefinedHexBufferTags.Memory);
///
/// true if the content can change at any time
///
public abstract bool IsVolatile { get; }
///
/// true if the buffer is read-only
///
public abstract bool IsReadOnly { get; }
///
/// Gets the span
///
public abstract HexSpan Span { get; }
///
/// Gets the name. This could be the filename if the data was read from a file
///
public abstract string Name { get; }
///
/// Raised when a span of data got modified by other code
///
public abstract event EventHandler? BufferSpanInvalidated;
///
/// Clears any read caches and raises if needed
///
public abstract void Refresh();
///
/// Gets the version
///
public abstract HexVersion Version { get; }
///
/// true if an edit is in progress
///
public abstract bool EditInProgress { get; }
///
/// Returns true if the current thread is allowed to modify the buffer
///
///
public abstract bool CheckEditAccess();
///
/// Claims ownership of this buffer for the current thread
///
public abstract void TakeThreadOwnership();
///
/// Gets information about a position in the buffer. The returned info isn't
/// normalized, there may be consecutive spans with the same flags. It's the
/// responsibility of the caller to merge such spans.
///
/// Position
///
public abstract HexSpanInfo GetSpanInfo(HexPosition position);
///
/// Gets the next valid span or null if there's none left. This includes the input
/// () if it happens to lie within this valid span.
/// This method merges all consecutive valid spans.
///
/// Start position to check
/// true if positions before should be included
/// in the returned result. This could result in worse performance.
///
public HexSpan? GetNextValidSpan(HexPosition position, bool fullSpan) => GetNextValidSpan(position, HexPosition.MaxEndPosition, fullSpan);
///
/// Gets the next valid span or null if there's none left. This includes the input
/// () if it happens to lie within this valid span.
/// This method merges all consecutive valid spans.
///
/// Start position to check
/// End position
/// true if positions before should be included
/// in the returned result. This could result in worse performance.
///
public HexSpan? GetNextValidSpan(HexPosition position, HexPosition upperBounds, bool fullSpan) {
if (position >= HexPosition.MaxEndPosition)
throw new ArgumentOutOfRangeException(nameof(position));
if (upperBounds > HexPosition.MaxEndPosition)
throw new ArgumentOutOfRangeException(nameof(upperBounds));
bool checkBackwards = true;
while (position < upperBounds) {
var info = GetSpanInfo(position);
if (info.HasData) {
var start = info.Span.Start;
var end = info.Span.End;
// We use MaxEndPosition and not upperBounds here since we must merge
// all consecutive spans even if some of them happen to be outside the
// requested range.
while (end < HexPosition.MaxEndPosition) {
info = GetSpanInfo(end);
if (!info.HasData)
break;
end = info.Span.End;
}
if (fullSpan && checkBackwards)
start = GetStartOfData(start, validData: true);
return HexSpan.FromBounds(start, end);
}
checkBackwards = false;
position = info.Span.End;
}
return null;
}
///
/// Gets the previous valid span or null if there's none left. This includes the input
/// () if it happens to lie within this valid span.
/// This method merges all consecutive valid spans.
///
/// Start position to check
/// true if positions after should be included
/// in the returned result. This could result in worse performance.
///
public HexSpan? GetPreviousValidSpan(HexPosition position, bool fullSpan) => GetPreviousValidSpan(position, HexPosition.Zero, fullSpan);
///
/// Gets the previous valid span or null if there's none left. This includes the input
/// () if it happens to lie within this valid span.
/// This method merges all consecutive valid spans.
///
/// Start position to check
/// End position
/// true if positions after should be included
/// in the returned result. This could result in worse performance.
///
public HexSpan? GetPreviousValidSpan(HexPosition position, HexPosition lowerBounds, bool fullSpan) {
if (position >= HexPosition.MaxEndPosition)
throw new ArgumentOutOfRangeException(nameof(position));
if (lowerBounds > HexPosition.MaxEndPosition)
throw new ArgumentOutOfRangeException(nameof(lowerBounds));
bool checkForwards = true;
while (position >= lowerBounds) {
var info = GetSpanInfo(position);
if (info.HasData) {
var start = GetStartOfData(info.Span.Start, validData: true);
var end = info.Span.End;
if (fullSpan && checkForwards) {
while (end < HexPosition.MaxEndPosition) {
info = GetSpanInfo(end);
if (!info.HasData)
break;
end = info.Span.End;
}
}
return HexSpan.FromBounds(start, end);
}
checkForwards = false;
position = GetStartOfData(info.Span.Start, validData: false);
if (position == HexPosition.Zero)
break;
position = position - 1;
}
return null;
}
HexPosition GetStartOfData(HexPosition start, bool validData) {
var pos = GetStartOfDataCore(start, validData);
Debug.Assert(pos == HexPosition.Zero || GetSpanInfo(pos - 1).HasData != validData);
return pos;
}
// If the underlying stream is a memory stream, VirtualQueryEx() gets called but it only
// scans forward and a simple loop results in extremely poor PERF, and the code appears
// to have hung if the input position is in a big block with lots of data or lots of no data.
HexPosition GetStartOfDataCore(HexPosition start, bool validData) {
var bestGuess = start;
var pos = start;
ulong d = 0x10000;
while (pos > HexPosition.Zero) {
var testPos = pos >= d ? pos - d : HexPosition.Zero;
var info = GetSpanInfo(testPos);
if (d < 0x8000_0000_0000_0000)
d <<= 1;
if (info.HasData == validData && info.Span.End >= bestGuess) {
bestGuess = info.Span.Start;
pos = info.Span.Start;
continue;
}
if (info.HasData != validData && info.Span.End == bestGuess)
return bestGuess;
pos = info.Span.End;
var lastCorrectDataPos = info.HasData == validData ? info.Span.Start : (HexPosition?)null;
bool foundOppositeData = info.HasData != validData;
for (;;) {
if (pos >= bestGuess)
return lastCorrectDataPos ?? bestGuess;
info = GetSpanInfo(pos);
if (info.HasData == validData) {
if (lastCorrectDataPos is null)
lastCorrectDataPos = info.Span.Start;
}
else {
lastCorrectDataPos = null;
foundOppositeData = true;
}
if (info.Span.End >= bestGuess) {
pos = lastCorrectDataPos ?? bestGuess;
if (foundOppositeData)
return pos;
bestGuess = pos;
break;
}
pos = info.Span.End;
}
}
return bestGuess;
}
///
/// Gets all valid spans. This could be empty if it's a 0-byte buffer stream.
/// This method merges all consecutive valid spans.
///
///
public IEnumerable GetValidSpans() => GetValidSpans(HexSpan.FullSpan, fullSpan: false);
///
/// Gets all valid spans overlapping . This method merges all
/// consecutive valid spans.
///
/// Span
/// true if positions before should be included
/// in the returned result. This could result in worse performance.
///
public IEnumerable GetValidSpans(HexSpan span, bool fullSpan) {
var pos = span.Start;
for (;;) {
var info = GetNextValidSpan(pos, span.End, fullSpan);
if (info is null)
break;
yield return info.Value;
pos = info.Value.End;
fullSpan = false;
}
}
///
/// Creates a object
///
///
public abstract HexEdit CreateEdit();
///
/// Creates a object
///
/// Use by undo/redo to restore a previous version
/// Edit tag, can be anything
///
public abstract HexEdit CreateEdit(int? reiteratedVersionNumber, object? editTag);
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, byte value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, sbyte value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, short value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, ushort value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, int value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, uint value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, long value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, ulong value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, float value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the at with
///
/// Position
/// New value
public void Replace(HexPosition position, double value) {
using (var ed = CreateEdit()) {
ed.Replace(position, value);
ed.Apply();
}
}
///
/// Replaces the data at with
///
/// Position
/// New data
public void Replace(HexPosition position, byte[] data) {
using (var ed = CreateEdit()) {
ed.Replace(position, data);
ed.Apply();
}
}
///
/// Replaces the data at with
///
/// Position
/// New data
/// Index
/// Length
public void Replace(HexPosition position, byte[] data, long index, long length) {
using (var ed = CreateEdit()) {
ed.Replace(position, data, index, length);
ed.Apply();
}
}
///
/// Tries to read a . If there's no data, a value less than 0 is returned.
///
/// Position
///
public abstract int TryReadByte(HexPosition position);
///
/// Reads a
///
/// Position
///
public char ReadChar(HexPosition position) => (char)ReadUInt16(position);
///
/// Reads a
///
/// Position
///
public abstract byte ReadByte(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract sbyte ReadSByte(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract short ReadInt16(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract ushort ReadUInt16(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract int ReadInt32(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract uint ReadUInt32(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract long ReadInt64(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract ulong ReadUInt64(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract float ReadSingle(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract double ReadDouble(HexPosition position);
///
/// Reads a
///
/// Position
///
public char ReadCharBigEndian(HexPosition position) => (char)ReadUInt16BigEndian(position);
///
/// Reads a
///
/// Position
///
public abstract short ReadInt16BigEndian(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract ushort ReadUInt16BigEndian(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract int ReadInt32BigEndian(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract uint ReadUInt32BigEndian(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract long ReadInt64BigEndian(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract ulong ReadUInt64BigEndian(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract float ReadSingleBigEndian(HexPosition position);
///
/// Reads a
///
/// Position
///
public abstract double ReadDoubleBigEndian(HexPosition position);
///
/// Reads bytes
///
/// Position
/// Number of bytes to read
///
public abstract byte[] ReadBytes(HexPosition position, long length);
///
/// Reads bytes
///
/// Position
/// Number of bytes to read
///
public abstract byte[] ReadBytes(HexPosition position, ulong length);
///
/// Reads bytes
///
/// Span
///
public abstract byte[] ReadBytes(HexSpan span);
///
/// Reads bytes
///
/// Position
/// Destination array
public void ReadBytes(HexPosition position, byte[] destination) {
if (destination is null)
throw new ArgumentNullException(nameof(destination));
ReadBytes(position, destination, 0, destination.LongLength);
}
///
/// Reads bytes
///
/// Position
/// Destination array
/// Index
/// Length
public abstract void ReadBytes(HexPosition position, byte[] destination, long destinationIndex, long length);
///
/// Reads bytes
///
/// Position
/// Length
///
public abstract HexBytes ReadHexBytes(HexPosition position, long length);
///
/// Raised before the text buffer gets changed
///
public abstract event EventHandler? Changing;
///
/// Raised when the buffer has changed
///
public abstract event EventHandler? ChangedHighPriority;
///
/// Raised when the buffer has changed
///
public abstract event EventHandler? Changed;
///
/// Raised when the buffer has changed
///
public abstract event EventHandler? ChangedLowPriority;
///
/// Raised after an edit operation has completed or after it has been canceled
///
public abstract event EventHandler? PostChanged;
///
/// Raised after it is disposed
///
public event EventHandler? Disposed;
///
/// true if the instance has been disposed
///
public bool IsDisposed { get; private set; }
///
/// Disposes this instance
///
public void Dispose() {
if (IsDisposed)
return;
IsDisposed = true;
DisposeCore();
Disposed?.Invoke(this, EventArgs.Empty);
}
///
/// Disposes this instance
///
protected virtual void DisposeCore() { }
}
///
/// Invalidated span event args
///
public sealed class HexBufferSpanInvalidatedEventArgs : EventArgs {
///
/// Gets the span
///
public HexSpan Span { get; }
///
/// Constructor
///
/// Span
public HexBufferSpanInvalidatedEventArgs(HexSpan span) => Span = span;
}
}