/*
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.IO;
using System.Windows;
using dnSpy.Contracts.Controls;
using dnSpy.Contracts.Documents.Tabs;
using dnSpy.Contracts.Hex;
using dnSpy.Contracts.Hex.Editor;
using dnSpy.Contracts.Hex.Editor.HexGroups;
using dnSpy.Contracts.Hex.Editor.OptionsExtensionMethods;
using dnSpy.Contracts.Menus;
using dnSpy.Contracts.Settings;
using VSTE = Microsoft.VisualStudio.Text.Editor;
namespace dnSpy.AsmEditor.Hex {
[ExportDocumentTabContentFactory(Order = TabConstants.ORDER_ASMED_HEXVIEWDOCUMENTTABCONTENTFACTORY)]
sealed class HexViewDocumentTabContentFactory : IDocumentTabContentFactory {
readonly Lazy hexViewDocumentTabContentCreator;
[ImportingConstructor]
HexViewDocumentTabContentFactory(Lazy hexViewDocumentTabContentCreator) => this.hexViewDocumentTabContentCreator = hexViewDocumentTabContentCreator;
public DocumentTabContent? Create(IDocumentTabContentFactoryContext context) => null;
static readonly Guid GUID_SerializedContent = new Guid("3125CEDA-98DE-447E-9363-8583A45BDE8C");
public Guid? Serialize(DocumentTabContent content, ISettingsSection section) {
var hb = content as HexViewDocumentTabContent;
if (hb is null)
return null;
section.Attribute("filename", hb.Filename);
return GUID_SerializedContent;
}
public DocumentTabContent? Deserialize(Guid guid, ISettingsSection section, IDocumentTabContentFactoryContext context) {
if (guid != GUID_SerializedContent)
return null;
var filename = section.Attribute("filename");
return hexViewDocumentTabContentCreator.Value.TryCreate(filename);
}
}
interface IHexViewDocumentTabContentCreator {
HexViewDocumentTabContent? TryCreate(string filename);
}
[Export(typeof(IHexViewDocumentTabContentCreator))]
sealed class HexViewDocumentTabContentCreator : IHexViewDocumentTabContentCreator {
readonly Lazy hexBufferService;
readonly Lazy hexEditorGroupFactoryService;
[ImportingConstructor]
HexViewDocumentTabContentCreator(Lazy hexBufferService, Lazy hexEditorGroupFactoryService) {
this.hexBufferService = hexBufferService;
this.hexEditorGroupFactoryService = hexEditorGroupFactoryService;
}
public HexViewDocumentTabContent? TryCreate(string filename) {
var buffer = hexBufferService.Value.GetOrCreate(filename);
if (buffer is null)
return null;
return new HexViewDocumentTabContent(hexEditorGroupFactoryService, buffer);
}
}
sealed class HexViewDocumentTabContent : DocumentTabContent {
public override string Title {
get {
var filename = Filename;
try {
return Path.GetFileName(filename);
}
catch {
}
return filename;
}
}
public override object? ToolTip => Filename;
public string Filename => buffer.Name;
readonly HexBuffer buffer;
readonly Lazy hexEditorGroupFactoryService;
public HexViewDocumentTabContent(Lazy hexEditorGroupFactoryService, HexBuffer buffer) {
this.buffer = buffer ?? throw new ArgumentNullException(nameof(buffer));
this.hexEditorGroupFactoryService = hexEditorGroupFactoryService;
}
public override DocumentTabContent Clone() =>
new HexViewDocumentTabContent(hexEditorGroupFactoryService, buffer);
public override DocumentTabUIContext CreateUIContext(IDocumentTabUIContextLocator locator) =>
locator.Get(buffer, useStrongReference: true, creator: () => new HexViewDocumentTabUIContext(hexEditorGroupFactoryService.Value, buffer));
}
sealed class HexViewDocumentTabUIContext : DocumentTabUIContext, IDisposable, IZoomable {
public override IInputElement? FocusedElement => hexViewHost.HexView.VisualElement;
public override FrameworkElement? ZoomElement => null;
public override object? UIObject => hexViewHost.HostControl;
public WpfHexView HexView => hexViewHost.HexView;
double IZoomable.ZoomValue => hexViewHost.HexView.ZoomLevel / 100;
readonly WpfHexViewHost hexViewHost;
public HexViewDocumentTabUIContext(HexEditorGroupFactoryService hexEditorGroupFactoryService, HexBuffer buffer) => hexViewHost = hexEditorGroupFactoryService.Create(buffer, PredefinedHexViewRoles.HexEditorGroup, PredefinedHexViewRoles.HexEditorGroupDefault, new Guid(MenuConstants.GUIDOBJ_ASMEDITOR_HEXVIEW_GUID));
public override object? CreateUIState() {
if (cachedHexViewUIState is not null)
return cachedHexViewUIState;
var state = new HexViewUIState(HexView);
state.ShowOffsetColumn = HexView.Options.ShowOffsetColumn();
state.ShowValuesColumn = HexView.Options.ShowValuesColumn();
state.ShowAsciiColumn = HexView.Options.ShowAsciiColumn();
state.StartPosition = HexView.Options.GetStartPosition();
state.EndPosition = HexView.Options.GetEndPosition();
state.BasePosition = HexView.Options.GetBasePosition();
state.UseRelativePositions = HexView.Options.UseRelativePositions();
state.OffsetBitSize = HexView.Options.GetOffsetBitSize();
state.HexValuesDisplayFormat = HexView.Options.GetValuesDisplayFormat();
state.BytesPerLine = HexView.Options.GetBytesPerLine();
return state;
}
public override void RestoreUIState(object? obj) {
var state = obj as HexViewUIState;
if (state is null)
return;
if (!HexView.VisualElement.IsLoaded) {
bool start = cachedHexViewUIState is null;
cachedHexViewUIState = state;
if (start)
HexView.VisualElement.Loaded += VisualElement_Loaded;
}
else
InitializeState(state);
}
HexViewUIState? cachedHexViewUIState;
void InitializeState(HexViewUIState state) {
if (IsValid(state)) {
HexView.Options.SetOptionValue(DefaultHexViewOptions.ShowOffsetColumnId, state.ShowOffsetColumn);
HexView.Options.SetOptionValue(DefaultHexViewOptions.ShowValuesColumnId, state.ShowValuesColumn);
HexView.Options.SetOptionValue(DefaultHexViewOptions.ShowAsciiColumnId, state.ShowAsciiColumn);
HexView.Options.SetOptionValue(DefaultHexViewOptions.StartPositionId, state.StartPosition);
HexView.Options.SetOptionValue(DefaultHexViewOptions.EndPositionId, state.EndPosition);
HexView.Options.SetOptionValue(DefaultHexViewOptions.BasePositionId, state.BasePosition);
HexView.Options.SetOptionValue(DefaultHexViewOptions.UseRelativePositionsId, state.UseRelativePositions);
HexView.Options.SetOptionValue(DefaultHexViewOptions.OffsetBitSizeId, state.OffsetBitSize);
HexView.Options.SetOptionValue(DefaultHexViewOptions.HexValuesDisplayFormatId, state.HexValuesDisplayFormat);
HexView.Options.SetOptionValue(DefaultHexViewOptions.BytesPerLineId, state.BytesPerLine);
HexView.ViewportLeft = state.ViewportLeft;
HexView.DisplayHexLineContainingBufferPosition(new HexBufferPoint(HexView.Buffer, state.TopLinePosition), state.TopLineVerticalDistance, VSTE.ViewRelativePosition.Top, null, null, DisplayHexLineOptions.CanRecreateBufferLines);
var valuesPos = new HexCellPosition(HexColumnType.Values, new HexBufferPoint(HexView.Buffer, state.ValuesPosition), state.ValuesCellPosition);
var asciiPos = new HexCellPosition(HexColumnType.Ascii, new HexBufferPoint(HexView.Buffer, state.AsciiPosition), 0);
var newPos = new HexColumnPosition(state.ActiveColumn, valuesPos, asciiPos);
// BufferLines could've been recreated, re-verify the new position
if (HexView.BufferLines.IsValidPosition(newPos.ValuePosition.BufferPosition) && HexView.BufferLines.IsValidPosition(newPos.AsciiPosition.BufferPosition))
HexView.Caret.MoveTo(newPos);
var anchorPoint = new HexBufferPoint(HexView.Buffer, state.AnchorPoint);
var activePoint = new HexBufferPoint(HexView.Buffer, state.ActivePoint);
if (HexView.BufferLines.IsValidPosition(anchorPoint) && HexView.BufferLines.IsValidPosition(activePoint))
HexView.Selection.Select(anchorPoint, activePoint, alignPoints: false);
else
HexView.Selection.Clear();
}
else {
HexView.Caret.MoveTo(HexView.BufferLines.BufferStart);
HexView.Selection.Clear();
}
}
bool IsValid(HexViewUIState state) {
if (state.ActiveColumn != HexColumnType.Values && state.ActiveColumn != HexColumnType.Ascii)
return false;
if (state.StartPosition >= HexPosition.MaxEndPosition)
return false;
if (state.EndPosition > HexPosition.MaxEndPosition)
return false;
if (state.BasePosition >= HexPosition.MaxEndPosition)
return false;
if (state.EndPosition < state.StartPosition)
return false;
if (state.OffsetBitSize < HexBufferLineFormatterOptions.MinOffsetBitSize || state.OffsetBitSize > HexBufferLineFormatterOptions.MaxOffsetBitSize)
return false;
if (state.HexValuesDisplayFormat < HexBufferLineFormatterOptions.HexValuesDisplayFormat_First || state.HexValuesDisplayFormat > HexBufferLineFormatterOptions.HexValuesDisplayFormat_Last)
return false;
if (state.BytesPerLine < HexBufferLineFormatterOptions.MinBytesPerLine || state.BytesPerLine > HexBufferLineFormatterOptions.MaxBytesPerLine)
return false;
if (state.ValuesPosition >= HexPosition.MaxEndPosition)
return false;
if (state.AsciiPosition >= HexPosition.MaxEndPosition)
return false;
if (state.TopLinePosition >= HexPosition.MaxEndPosition)
return false;
if (state.ValuesPosition < state.StartPosition || state.ValuesPosition > state.EndPosition)
return false;
if (state.AsciiPosition < state.StartPosition || state.AsciiPosition > state.EndPosition)
return false;
if (state.ValuesCellPosition < 0 || state.ValuesCellPosition > 1000)
return false;
if (state.TopLinePosition < state.StartPosition || state.TopLinePosition > state.EndPosition)
return false;
if (state.AnchorPoint < state.ActivePoint) {
if (state.AnchorPoint >= HexPosition.MaxEndPosition)
return false;
if (state.ActivePoint > HexPosition.MaxEndPosition)
return false;
}
else {
if (state.AnchorPoint > HexPosition.MaxEndPosition)
return false;
if (state.ActivePoint >= HexPosition.MaxEndPosition)
return false;
}
if (double.IsNaN(state.ViewportLeft) || state.ViewportLeft < 0 || state.ViewportLeft > 100000)
return false;
if (double.IsNaN(state.TopLineVerticalDistance) || Math.Abs(state.TopLineVerticalDistance) > 10000)
return false;
return true;
}
void VisualElement_Loaded(object? sender, RoutedEventArgs e) {
HexView.VisualElement.Loaded -= VisualElement_Loaded;
if (cachedHexViewUIState is null)
return;
InitializeState(cachedHexViewUIState);
cachedHexViewUIState = null;
}
public override object? DeserializeUIState(ISettingsSection section) => HexViewUIStateSerializer.Read(section, new HexViewUIState());
public override void SerializeUIState(ISettingsSection section, object? obj) {
var state = obj as HexViewUIState;
if (state is null)
return;
HexViewUIStateSerializer.Write(section, state);
}
public void Dispose() => hexViewHost.Close();
}
}