/*
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.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using dnlib.PE;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.Documents.Tabs;
using dnSpy.Contracts.Documents.Tabs.DocViewer;
using dnSpy.Contracts.Documents.TreeView;
using dnSpy.Contracts.Hex;
using dnSpy.Contracts.Hex.Editor;
using VSTE = Microsoft.VisualStudio.Text.Editor;
namespace dnSpy.AsmEditor.Hex.Nodes {
[ExportReferenceDocumentTabContentProvider(Order = TabConstants.ORDER_CONTENTPROVIDER_HEXADDRREF)]
sealed class HexAddressReferenceFileTabContentCreator : IReferenceDocumentTabContentProvider {
readonly Lazy hexViewDocumentTabContentCreator;
[ImportingConstructor]
HexAddressReferenceFileTabContentCreator(Lazy hexViewDocumentTabContentCreator, IDocumentTreeView documentTreeView) => this.hexViewDocumentTabContentCreator = hexViewDocumentTabContentCreator;
public DocumentTabReferenceResult? Create(IDocumentTabService documentTabService, DocumentTabContent? sourceContent, object? @ref) {
var addrRef = @ref as AddressReference;
if (addrRef is null)
addrRef = (@ref as TextReference)?.Reference as AddressReference;
if (addrRef is not null)
return Create(addrRef, documentTabService.DocumentTreeView);
return null;
}
DocumentTabReferenceResult? Create(AddressReference addrRef, IDocumentTreeView documentTreeView) {
var content = hexViewDocumentTabContentCreator.Value.TryCreate(addrRef.Filename);
if (content is null)
return null;
var fileOffset = GetFileOffset(addrRef, documentTreeView);
return new DocumentTabReferenceResult(content, null, e => CreateHandler(e, content, fileOffset, addrRef));
}
void CreateHandler(ShowTabContentEventArgs e, HexViewDocumentTabContent content, HexPosition? fileOffset, AddressReference addrRef) {
if (!e.Success)
return;
Debug.Assert(e.Tab.Content == content);
var uiContext = e.Tab.UIContext as HexViewDocumentTabUIContext;
Debug2.Assert(uiContext is not null);
if (uiContext is null || fileOffset is null)
return;
var start = fileOffset.Value;
var end = HexPosition.Min(start + addrRef.Length, HexPosition.MaxEndPosition);
if (!IsVisible(uiContext.HexView, start, end)) {
uiContext.HexView.Options.SetOptionValue(DefaultHexViewOptions.StartPositionId, uiContext.HexView.Buffer.Span.Start);
uiContext.HexView.Options.SetOptionValue(DefaultHexViewOptions.EndPositionId, uiContext.HexView.Buffer.Span.End);
RedisplayHexLines(uiContext.HexView);
if (!IsVisible(uiContext.HexView, start, end))
return;
}
if (e.HasMovedCaret)
return;
if (!uiContext.HexView.VisualElement.IsLoaded) {
RoutedEventHandler? loaded = null;
loaded = (s, e2) => {
uiContext.HexView.VisualElement.Loaded -= loaded;
InitializeHexView(uiContext.HexView, start, end);
};
uiContext.HexView.VisualElement.Loaded += loaded;
}
else
InitializeHexView(uiContext.HexView, start, end);
e.HasMovedCaret = true;
}
static void InitializeHexView(HexView hexView, HexPosition start, HexPosition end) {
if (!IsVisible(hexView, start, end))
return;
var span = new HexBufferSpan(new HexBufferPoint(hexView.Buffer, start), new HexBufferPoint(hexView.Buffer, end));
hexView.Selection.Select(span.Start, span.End, alignPoints: false);
var column = hexView.Caret.IsValuesCaretPresent ? HexColumnType.Values : HexColumnType.Ascii;
hexView.Caret.MoveTo(column, span.Start);
var flags = column == HexColumnType.Values ? HexSpanSelectionFlags.Values : HexSpanSelectionFlags.Ascii;
hexView.ViewScroller.EnsureSpanVisible(span, flags, VSTE.EnsureSpanVisibleOptions.ShowStart);
}
static void RedisplayHexLines(HexView hexView) {
var line = hexView.HexViewLines.FirstVisibleLine;
var verticalDistance = line.Top - hexView.ViewportTop;
var bufferPosition = line.BufferStart;
hexView.DisplayHexLineContainingBufferPosition(bufferPosition, verticalDistance, VSTE.ViewRelativePosition.Top, null, null, DisplayHexLineOptions.CanRecreateBufferLines);
}
static bool IsVisible(HexView hexView, HexPosition start, HexPosition end) =>
start < HexPosition.MaxEndPosition && start >= hexView.BufferLines.StartPosition && end <= hexView.BufferLines.EndPosition;
HexPosition? GetFileOffset(AddressReference addrRef, IDocumentTreeView documentTreeView) {
if (!addrRef.IsRVA)
return addrRef.Address;
if (string.IsNullOrEmpty(addrRef.Filename))
return null;
var file = documentTreeView.GetAllCreatedDocumentNodes().FirstOrDefault(a => StringComparer.OrdinalIgnoreCase.Equals(a.Document.Filename, addrRef.Filename));
if (file is null)
return null;
var pe = file.Document.PEImage;
if (pe is null)
return null;
return (ulong)pe.ToFileOffset((RVA)addrRef.Address);
}
}
}