/* 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.Collections.Specialized; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using dnlib.DotNet; using dnlib.DotNet.Emit; using dnSpy.AsmEditor.Commands; using dnSpy.AsmEditor.DnlibDialogs; using dnSpy.AsmEditor.Properties; using dnSpy.AsmEditor.ViewHelpers; using dnSpy.Contracts.Images; using dnSpy.Contracts.MVVM; using dnSpy.Contracts.Search; using dnSpy.Contracts.Text; namespace dnSpy.AsmEditor.MethodBody { sealed class InstructionsListHelper : ListBoxHelperBase, IEditOperand, ISelectItems { CilBodyVM cilBodyVM; protected override string AddNewBeforeSelectionMessage => dnSpy_AsmEditor_Resources.Instr_Command1; protected override string AddNewAfterSelectionMessage => dnSpy_AsmEditor_Resources.Instr_Command2; protected override string AppendNewMessage => dnSpy_AsmEditor_Resources.Instr_Command3; protected override string RemoveSingularMessage => dnSpy_AsmEditor_Resources.Instr_Command4; protected override string RemovePluralMessage => dnSpy_AsmEditor_Resources.Instr_Command5; protected override string RemoveAllMessage => dnSpy_AsmEditor_Resources.Instr_Command6; public InstructionsListHelper(ListView listView, Window ownerWindow) : base(listView) { cilBodyVM = null!; } protected override InstructionVM[] GetSelectedItems() => listBox.SelectedItems.Cast().ToArray(); protected override void OnDataContextChangedInternal(object dataContext) { cilBodyVM = ((MethodBodyVM)dataContext).CilBodyVM; cilBodyVM.SelectItems = this; coll = cilBodyVM.InstructionsListVM; coll.CollectionChanged += coll_CollectionChanged; InitializeInstructions(coll); Add(new ContextMenuHandler { Header = "res:NopInstrCommand", HeaderPlural = "res:NopInstrsCommand", Command = cilBodyVM.ReplaceInstructionWithNopCommand, Icon = null, InputGestureText = "res:ShortCutKeyN", Modifiers = ModifierKeys.None, Key = Key.N, }); Add(new ContextMenuHandler { Header = "res:ReplaceNopsInstrCommand", HeaderPlural = "res:ReplaceNopsInstrsCommand", Command = cilBodyVM.ReplaceInstructionWithMultipleNopsCommand, Icon = null, InputGestureText = "res:ShortCutKeyR", Modifiers = ModifierKeys.None, Key = Key.R, }); Add(new ContextMenuHandler { Header = "res:InvertBranchCommand", HeaderPlural = "res:InvertBranchesCommand", Command = cilBodyVM.InvertBranchCommand, Icon = DsImages.Branch, InputGestureText = "res:ShortCutKeyI", Modifiers = ModifierKeys.None, Key = Key.I, }); Add(new ContextMenuHandler { Header = "res:ConvertUncondBranchCommand", HeaderPlural = "res:ConvertUncondBranchesCommand", Command = cilBodyVM.ConvertBranchToUnconditionalBranchCommand, Icon = null, InputGestureText = "res:ShortCutKeyB", Modifiers = ModifierKeys.None, Key = Key.B, }); Add(new ContextMenuHandler { Header = "res:RemoveAndAddPopsCommand", Command = cilBodyVM.RemoveInstructionAndAddPopsCommand, InputGestureText = "res:ShortCutKeyP", Modifiers = ModifierKeys.None, Key = Key.P, }); AddSeparator(); Add(new ContextMenuHandler { Header = "res:SimplifyAllInstructionsCommand", Command = cilBodyVM.SimplifyAllInstructionsCommand, InputGestureText = "res:ShortCutKeyS", Modifiers = ModifierKeys.None, Key = Key.S, }); Add(new ContextMenuHandler { Header = "res:OptimizeAllInstructionsCommand", Command = cilBodyVM.OptimizeAllInstructionsCommand, InputGestureText = "res:ShortCutKeyO", Modifiers = ModifierKeys.None, Key = Key.O, }); AddSeparator(); AddStandardMenuHandlers(); Add(new ContextMenuHandler { Header = "res:CopyMetaDataToken", HeaderPlural = "res:CopyMetaDataTokens", Command = new RelayCommand(a => CopyOperandMDTokens((InstructionVM[])a!), a => CopyOperandMDTokensCanExecute((InstructionVM[])a!)), InputGestureText = "res:ShortCutKeyCtrlM", Modifiers = ModifierKeys.Control, Key = Key.M, }); Add(new ContextMenuHandler { Header = "res:CopyRVACommand", HeaderPlural = "res:CopyRVAsCommand", Command = new RelayCommand(a => CopyInstructionRVA((InstructionVM[])a!), a => CopyInstructionRVACanExecute((InstructionVM[])a!)), InputGestureText = "res:ShortCutKeyCtrlR", Modifiers = ModifierKeys.Control, Key = Key.R, }); Add(new ContextMenuHandler { Header = "res:CopyFileOffsetCommand", HeaderPlural = "res:CopyFileOffsetsCommand", Command = new RelayCommand(a => CopyInstructionFileOffset((InstructionVM[])a!), a => CopyInstructionFileOffsetCanExecute((InstructionVM[])a!)), InputGestureText = "res:ShortCutKeyCtrlF", Modifiers = ModifierKeys.Control, Key = Key.F, }); } void CopyOffsets(ulong baseOffset, InstructionVM[] instrs) { var sb = new StringBuilder(); int lines = 0; for (int i = 0; i < instrs.Length; i++) { if (lines++ > 0) sb.AppendLine(); sb.Append($"0x{baseOffset + instrs[i].Offset:X8}"); } if (lines > 1) sb.AppendLine(); var text = sb.ToString(); if (text.Length > 0) { try { Clipboard.SetText(text); } catch (ExternalException) { } } } void CopyInstructionRVA(InstructionVM[] instrs) => CopyOffsets(cilBodyVM.RVA.Value, instrs); bool CopyInstructionRVACanExecute(InstructionVM[] instrs) => !cilBodyVM.RVA.HasError && instrs.Length > 0; void CopyInstructionFileOffset(InstructionVM[] instrs) => CopyOffsets(cilBodyVM.FileOffset.Value, instrs); bool CopyInstructionFileOffsetCanExecute(InstructionVM[] instrs) => !cilBodyVM.FileOffset.HasError && instrs.Length > 0; void CopyOperandMDTokens(InstructionVM[] instrs) { var sb = new StringBuilder(); int lines = 0; for (int i = 0; i < instrs.Length; i++) { uint? token = GetOperandMDToken(instrs[i].InstructionOperandVM); if (token is null) continue; if (lines++ > 0) sb.AppendLine(); sb.Append($"0x{token.Value:X8}"); } if (lines > 1) sb.AppendLine(); var text = sb.ToString(); if (text.Length > 0) { try { Clipboard.SetText(text); } catch (ExternalException) { } } } bool CopyOperandMDTokensCanExecute(InstructionVM[] instrs) => instrs.Any(a => GetOperandMDToken(a.InstructionOperandVM) is not null); static uint? GetOperandMDToken(InstructionOperandVM op) { switch (op.InstructionOperandType) { case InstructionOperandType.None: case InstructionOperandType.SByte: case InstructionOperandType.Byte: case InstructionOperandType.Int32: case InstructionOperandType.Int64: case InstructionOperandType.Single: case InstructionOperandType.Double: case InstructionOperandType.String: case InstructionOperandType.BranchTarget: case InstructionOperandType.SwitchTargets: case InstructionOperandType.Local: case InstructionOperandType.Parameter: return null; case InstructionOperandType.Field: case InstructionOperandType.Method: case InstructionOperandType.Token: case InstructionOperandType.Type: var token = op.Other as IMDTokenProvider; return token is null ? (uint?)null : token.MDToken.ToUInt32(); case InstructionOperandType.MethodSig: var msig = op.Other as MethodSig; return msig is null ? (uint?)null : msig.OriginalToken; default: throw new InvalidOperationException(); } } void coll_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems is not null) InitializeInstructions(e.NewItems); } void InitializeInstructions(System.Collections.IList list) { foreach (InstructionVM? instr in list) instr!.InstructionOperandVM.EditOperand = this; } protected override void CopyItemsAsText(InstructionVM[] instrs) { Array.Sort(instrs, (a, b) => a.Index.CompareTo(b.Index)); CopyItemsAsTextToClipboard(instrs); } public static void CopyItemsAsTextToClipboard(InstructionVM[] instrs) { var output = new StringBuilderTextColorOutput(); for (int i = 0; i < instrs.Length; i++) { if (i > 0) output.WriteLine(); var instr = instrs[i]; output.Write(BoxedTextColor.Number, instr.Index.ToString()); output.Write(BoxedTextColor.Text, "\t"); output.Write(BoxedTextColor.Label, instr.Offset.ToString("X4")); output.Write(BoxedTextColor.Text, "\t"); output.Write(BoxedTextColor.OpCode, instr.Code.ToOpCode().ToString()); switch (instr.InstructionOperandVM.InstructionOperandType) { case InstructionOperandType.None: break; case InstructionOperandType.SByte: output.Write(BoxedTextColor.Text, "\t"); output.Write(BoxedTextColor.Number, instr.InstructionOperandVM.SByte.StringValue); break; case InstructionOperandType.Byte: output.Write(BoxedTextColor.Text, "\t"); output.Write(BoxedTextColor.Number, instr.InstructionOperandVM.Byte.StringValue); break; case InstructionOperandType.Int32: output.Write(BoxedTextColor.Text, "\t"); output.Write(BoxedTextColor.Number, instr.InstructionOperandVM.Int32.StringValue); break; case InstructionOperandType.Int64: output.Write(BoxedTextColor.Text, "\t"); output.Write(BoxedTextColor.Number, instr.InstructionOperandVM.Int64.StringValue); break; case InstructionOperandType.Single: output.Write(BoxedTextColor.Text, "\t"); output.Write(BoxedTextColor.Number, instr.InstructionOperandVM.Single.StringValue); break; case InstructionOperandType.Double: output.Write(BoxedTextColor.Text, "\t"); output.Write(BoxedTextColor.Number, instr.InstructionOperandVM.Double.StringValue); break; case InstructionOperandType.String: output.Write(BoxedTextColor.Text, "\t"); output.Write(BoxedTextColor.String, instr.InstructionOperandVM.String.StringValue); break; case InstructionOperandType.Field: case InstructionOperandType.Method: case InstructionOperandType.Token: case InstructionOperandType.Type: case InstructionOperandType.MethodSig: case InstructionOperandType.BranchTarget: case InstructionOperandType.SwitchTargets: case InstructionOperandType.Local: case InstructionOperandType.Parameter: output.Write(BoxedTextColor.Text, "\t"); BodyUtils.WriteObject(output, instr.InstructionOperandVM.Value); break; default: throw new InvalidOperationException(); } } if (instrs.Length > 1) output.WriteLine(); try { Clipboard.SetText(output.ToString()); } catch (ExternalException) { } } [Flags] enum MenuCommandFlags { FieldDef = 0x00000001, FieldMemberRef = 0x00000002, MethodDef = 0x00000004, MethodMemberRef = 0x00000008, MethodSpec = 0x00000010, TypeDef = 0x00000020, TypeRef = 0x00000040, TypeSpec = 0x00000080, } void ShowMenu(object? parameter, InstructionOperandVM opvm, MenuCommandFlags flags) { var ctxMenu = new ContextMenu(); ctxMenu.SetResourceReference(DsImage.BackgroundBrushProperty, "ContextMenuRectangleFill"); MenuItem menuItem; if ((flags & (MenuCommandFlags.TypeDef | MenuCommandFlags.TypeRef)) != 0) { ctxMenu.Items.Add(menuItem = new MenuItem() { Header = dnSpy_AsmEditor_Resources.EditOperand_Type, Command = new RelayCommand(a => AddType(opvm)), }); Add16x16Image(menuItem, DsImages.ClassPublic, true); } if ((flags & MenuCommandFlags.TypeSpec) != 0) { ctxMenu.Items.Add(menuItem = new MenuItem() { Header = dnSpy_AsmEditor_Resources.EditOperand_TypeSpec, Command = new RelayCommand(a => AddTypeSpec(opvm)), }); Add16x16Image(menuItem, DsImages.Template, true); } if ((flags & MenuCommandFlags.MethodDef) != 0) { ctxMenu.Items.Add(menuItem = new MenuItem() { Header = dnSpy_AsmEditor_Resources.EditOperand_Method, Command = new RelayCommand(a => AddMethodDef(opvm)), }); Add16x16Image(menuItem, DsImages.MethodPublic, true); } if ((flags & MenuCommandFlags.MethodMemberRef) != 0) { ctxMenu.Items.Add(new MenuItem() { Header = dnSpy_AsmEditor_Resources.EditOperand_Method_MemberRef, Command = new RelayCommand(a => AddMethodMemberRef(opvm)), }); } if ((flags & MenuCommandFlags.MethodSpec) != 0) { ctxMenu.Items.Add(new MenuItem() { Header = dnSpy_AsmEditor_Resources.EditOperand_MethodSpec, Command = new RelayCommand(a => AddMethodSpec(opvm)), }); } if ((flags & MenuCommandFlags.FieldDef) != 0) { ctxMenu.Items.Add(menuItem = new MenuItem() { Header = dnSpy_AsmEditor_Resources.EditOperand_Field, Command = new RelayCommand(a => AddFieldDef(opvm)), }); Add16x16Image(menuItem, DsImages.FieldPublic, true); } if ((flags & MenuCommandFlags.FieldMemberRef) != 0) { ctxMenu.Items.Add(new MenuItem() { Header = dnSpy_AsmEditor_Resources.EditOperand_Field_MemberRef, Command = new RelayCommand(a => AddFieldMemberRef(opvm)), }); } ctxMenu.Placement = PlacementMode.Bottom; ctxMenu.PlacementTarget = parameter as UIElement; ctxMenu.IsOpen = true; } void AddFieldDef(InstructionOperandVM opvm) { var picker = new DnlibTypePicker(Window.GetWindow(listBox)); var op = opvm.Other as IField ?? (object?)cilBodyVM.TypeSigCreatorOptions.OwnerType; if (picker.GetDnlibType(dnSpy_AsmEditor_Resources.Pick_Field, new FlagsDocumentTreeNodeFilter(VisibleMembersFlags.FieldDef), op, cilBodyVM.OwnerModule) is IField field) opvm.Other = field; } void AddFieldMemberRef(InstructionOperandVM opvm) { MemberRef? mr = opvm.Other as MemberRef; if (opvm.Other is FieldDef fd) mr = cilBodyVM.OwnerModule.Import(fd); if (mr is not null && mr.FieldSig is null) mr = null; AddMemberRef(opvm, mr, true); } void AddMethodDef(InstructionOperandVM opvm) { var picker = new DnlibTypePicker(Window.GetWindow(listBox)); var op = opvm.Other as IMethod ?? (object?)cilBodyVM.TypeSigCreatorOptions.OwnerType; if (picker.GetDnlibType(dnSpy_AsmEditor_Resources.Pick_Method, new FlagsDocumentTreeNodeFilter(VisibleMembersFlags.MethodDef), op, cilBodyVM.OwnerModule) is IMethod method) opvm.Other = method; } void AddMethodMemberRef(InstructionOperandVM opvm) { MemberRef? mr = opvm.Other as MemberRef; var md = opvm.Other as MethodDef; if (opvm.Other is MethodSpec ms) { mr = ms.Method as MemberRef; md = ms.Method as MethodDef; } if (md is not null) mr = cilBodyVM.OwnerModule.Import(md); if (mr is not null && mr.MethodSig is null) mr = null; AddMemberRef(opvm, mr, false); } void AddMemberRef(InstructionOperandVM opvm, MemberRef? mr, bool isField) { var opts = mr is null ? new MemberRefOptions() : new MemberRefOptions(mr); MemberRefVM? vm = new MemberRefVM(opts, cilBodyVM.TypeSigCreatorOptions, isField); var creator = new EditMemberRef(Window.GetWindow(listBox)); var title = isField ? dnSpy_AsmEditor_Resources.EditFieldMemberRef : dnSpy_AsmEditor_Resources.EditMethodMemberRef; vm = creator.Edit(title, vm); if (vm is null) return; opvm.Other = vm.CreateMemberRefOptions().Create(cilBodyVM.OwnerModule); } void AddMethodSpec(InstructionOperandVM opvm) { var ms = opvm.Other as MethodSpec; var opts = ms is null ? new MethodSpecOptions() : new MethodSpecOptions(ms); MethodSpecVM? vm = new MethodSpecVM(opts, cilBodyVM.TypeSigCreatorOptions); var creator = new EditMethodSpec(Window.GetWindow(listBox)); vm = creator.Edit(dnSpy_AsmEditor_Resources.EditMethodSpec, vm); if (vm is null) return; opvm.Other = vm.CreateMethodSpecOptions().Create(cilBodyVM.OwnerModule); } void AddType(InstructionOperandVM opvm) { var picker = new DnlibTypePicker(Window.GetWindow(listBox)); var op = opvm.Other as ITypeDefOrRef ?? (object?)cilBodyVM.TypeSigCreatorOptions.OwnerType; if (picker.GetDnlibType(dnSpy_AsmEditor_Resources.Pick_Type, new FlagsDocumentTreeNodeFilter(VisibleMembersFlags.TypeDef), op, cilBodyVM.OwnerModule) is ITypeDefOrRef type) opvm.Other = type; } void AddTypeSpec(InstructionOperandVM opvm) { var creator = new TypeSigCreator(Window.GetWindow(listBox)); var opts = cilBodyVM.TypeSigCreatorOptions.Clone(dnSpy_AsmEditor_Resources.CreateTypeSpec); var newSig = creator.Create(opts, (opvm.Other as ITypeDefOrRef).ToTypeSig(), out bool canceled); if (canceled) return; opvm.Other = newSig.ToTypeDefOrRef(); } void EditMethodSig(InstructionOperandVM opvm) { var creator = new CreateMethodPropertySig(Window.GetWindow(listBox)); var opts = new MethodSigCreatorOptions(cilBodyVM.TypeSigCreatorOptions.Clone(dnSpy_AsmEditor_Resources.CreateMethodSig)); opts.CanHaveSentinel = true; var sig = (MethodSig?)creator.Create(opts, opvm.Other as MethodSig); if (sig is not null) opvm.Other = sig; } void EditSwitchOperand(InstructionOperandVM opvm) { var data = new SwitchOperandVM(cilBodyVM.InstructionsListVM, opvm.Other as InstructionVM[]); var win = new SwitchOperandDlg(); win.DataContext = data; win.Owner = Window.GetWindow(listBox) ?? Application.Current.MainWindow; if (win.ShowDialog() != true) return; opvm.Other = data.GetSwitchList(); } void IEditOperand.Edit(object? parameter, InstructionOperandVM opvm) { MenuCommandFlags flags; switch (opvm.InstructionOperandType) { case InstructionOperandType.Field: flags = MenuCommandFlags.FieldDef | MenuCommandFlags.FieldMemberRef; ShowMenu(parameter, opvm, flags); break; case InstructionOperandType.Method: flags = MenuCommandFlags.MethodDef | MenuCommandFlags.MethodMemberRef | MenuCommandFlags.MethodSpec; ShowMenu(parameter, opvm, flags); break; case InstructionOperandType.Token: flags = MenuCommandFlags.FieldDef | MenuCommandFlags.FieldMemberRef | MenuCommandFlags.MethodDef | MenuCommandFlags.MethodMemberRef | MenuCommandFlags.MethodSpec | MenuCommandFlags.TypeDef | MenuCommandFlags.TypeRef | MenuCommandFlags.TypeSpec; ShowMenu(parameter, opvm, flags); break; case InstructionOperandType.Type: flags = MenuCommandFlags.TypeDef | MenuCommandFlags.TypeRef | MenuCommandFlags.TypeSpec; ShowMenu(parameter, opvm, flags); break; case InstructionOperandType.MethodSig: EditMethodSig(opvm); break; case InstructionOperandType.SwitchTargets: EditSwitchOperand(opvm); break; default: throw new InvalidOperationException(); } } public void Select(IEnumerable items) { var instrs = items.ToArray(); if (instrs.Length == 0) return; listBox.SelectedItems.Clear(); foreach (var instr in instrs) listBox.SelectedItems.Add(instr); // Select the last one because the selected item is usually the last visible item in the view. listBox.ScrollIntoView(instrs[instrs.Length - 1]); } protected override bool CanUseClipboardData(InstructionVM[] data, bool fromThisInstance) => true; protected override InstructionVM[] BeforeCopyingData(InstructionVM[] data, bool fromThisInstance) { if (fromThisInstance) return data; var newData = new InstructionVM[data.Length]; for (int i = 0; i < data.Length; i++) newData[i] = data[i].Import(cilBodyVM.OwnerModule); return newData; } protected override void AfterCopyingData(InstructionVM[] data, InstructionVM[] origData, bool fromThisInstance) { var dict = new Dictionary(); for (int i = 0; i < data.Length; i++) { if (origData[i] == InstructionVM.Null) continue; Debug.Assert(!dict.ContainsKey(origData[i].Offset)); dict[origData[i].Offset] = data[i]; } var createdLocals = new Dictionary(); createdLocals[LocalVM.Null] = LocalVM.Null; // Need to fix references to instructions and locals InstructionVM oldInstr; InstructionVM? newInstr; for (int i = 0; i < data.Length; i++) { var instr = data[i]; var origInstr = origData[i]; var op = instr.InstructionOperandVM; switch (op.InstructionOperandType) { case MethodBody.InstructionOperandType.BranchTarget: oldInstr = (origInstr.InstructionOperandVM.OperandListItem as InstructionVM) ?? InstructionVM.Null; if (oldInstr == InstructionVM.Null || !dict.TryGetValue(oldInstr.Offset, out newInstr)) newInstr = fromThisInstance ? oldInstr : InstructionVM.Null; op.OperandListItem = newInstr; break; case MethodBody.InstructionOperandType.SwitchTargets: var oldInstrs = (origInstr.InstructionOperandVM.Other as InstructionVM[]) ?? Array.Empty(); var newInstrs = new InstructionVM[oldInstrs.Length]; for (int j = 0; j < oldInstrs.Length; j++) { oldInstr = oldInstrs[j] ?? InstructionVM.Null; if (oldInstr == InstructionVM.Null || !dict.TryGetValue(oldInstr.Offset, out newInstr)) newInstr = fromThisInstance ? oldInstr : InstructionVM.Null; newInstrs[j] = newInstr; } op.Other = newInstrs; break; case MethodBody.InstructionOperandType.Local: if (!fromThisInstance) { var oldLocal = (origInstr.InstructionOperandVM.OperandListItem as LocalVM) ?? LocalVM.Null; if (!createdLocals.TryGetValue(oldLocal, out var newLocal)) { newLocal = oldLocal.Import(cilBodyVM.TypeSigCreatorOptions, cilBodyVM.OwnerModule); cilBodyVM.LocalsListVM.Add(newLocal); createdLocals.Add(oldLocal, newLocal); } op.OperandListItem = newLocal; } break; case MethodBody.InstructionOperandType.Parameter: if (!fromThisInstance) { // Can't reference a parameter in another method op.OperandListItem = BodyUtils.NullParameter; } break; } } } } }