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