/* 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.Text; using System.Windows.Input; using dnlib.DotNet; using dnSpy.AsmEditor.Properties; using dnSpy.AsmEditor.ViewHelpers; using dnSpy.Contracts.MVVM; using dnSpy.Contracts.Search; namespace dnSpy.AsmEditor.DnlibDialogs { struct EnumInfo { public ITypeDefOrRef? EnumType; public object? Value; public bool IsArray; public static EnumInfo CreateNullArray(ITypeDefOrRef? type) => new EnumInfo() { EnumType = type, IsArray = true, }; public override string ToString() { var td = EnumType.ResolveTypeDef(); if (td is not null) { var s = ModelUtils.GetEnumFieldName(td, Value); if (s is not null) return $"{EnumType}.{s}"; } if (!IsArray) return $"({(EnumType ?? (object)dnSpy_AsmEditor_Resources.UnknownEnum)}){Value}"; var list = Value as System.Collections.IList; if (list is null) return $"({(EnumType ?? (object)dnSpy_AsmEditor_Resources.UnknownEnum)}[])null"; var sb = new StringBuilder(); sb.Append($"new {(EnumType ?? (object)dnSpy_AsmEditor_Resources.UnknownEnum)}[] {{"); for (int i = 0; i < list.Count; i++) { if (i > 0) sb.Append(','); sb.Append(' '); var s = ModelUtils.GetEnumFieldName(td, list[i]); sb.Append(s ?? (Value is null ? "null" : Value.ToString())); } sb.Append(" }"); return sb.ToString(); } } abstract class EnumDataFieldVMBase : DataFieldVM { EnumInfo enumInfo; DataFieldVM? enumUnderlyingTypeField; public IDnlibTypePicker DnlibTypePicker { set => dnlibTypePicker = value; } IDnlibTypePicker? dnlibTypePicker; public ICommand PickEnumTypeCommand => new RelayCommand(a => PickEnumType()); public ITypeDefOrRef? EnumType { get => enumInfo.EnumType; set { enumInfo.EnumType = value; var td = value.ResolveTypeDef(); if (td is null || !td.IsEnum) enumUnderlyingTypeField = null; else { enumUnderlyingTypeField = CreateEnumUnderlyingTypeField(td.GetEnumUnderlyingType().RemovePinnedAndModifiers().GetElementType()); if (enumUnderlyingTypeField is not null) { enumUnderlyingTypeField.StringValue = StringValue; ForceWriteStringValue(enumUnderlyingTypeField.StringValue); } } OnPropertyChanged(nameof(PickEnumToolTip)); } } public string PickEnumToolTip { get { if (enumInfo.EnumType is null) return dnSpy_AsmEditor_Resources.Pick_EnumType; return string.Format(dnSpy_AsmEditor_Resources.EnumType, enumInfo.EnumType.FullName); } } public EnumInfo NullValue => EnumInfo.CreateNullArray(enumInfo.EnumType); readonly ModuleDef ownerModule; protected EnumDataFieldVMBase(ModuleDef ownerModule, EnumInfo value, Action onUpdated) : base(onUpdated) { this.ownerModule = ownerModule; SetValueFromConstructor(value); } protected override void OnStringValueChanged() { if (enumUnderlyingTypeField is not null) enumUnderlyingTypeField.StringValue = StringValue; } protected override string OnNewValue(EnumInfo value) { InitializeEnumUnderlyingTypeField(value); if (enumUnderlyingTypeField is null) return string.Empty; else { enumUnderlyingTypeField.ObjectValue = value.Value; return enumUnderlyingTypeField.StringValue; } } protected override string? ConvertToValue(out EnumInfo value) { string? error = null; value = enumInfo; if (enumUnderlyingTypeField is not null) error = enumUnderlyingTypeField.ConvertToObjectValue(out value.Value); return error; } void InitializeEnumUnderlyingTypeField(EnumInfo enumInfo) { this.enumInfo = enumInfo; enumUnderlyingTypeField = null; if (enumInfo.Value is not null) enumUnderlyingTypeField = CreateEnumUnderlyingTypeFieldFromValue(enumInfo.Value); else { var td = enumInfo.EnumType.ResolveTypeDef(); if (td is not null && td.IsEnum) enumUnderlyingTypeField = CreateEnumUnderlyingTypeField(td.GetEnumUnderlyingType().RemovePinnedAndModifiers().GetElementType()); } } void PickEnumType() { if (dnlibTypePicker is null) throw new InvalidOperationException(); var type = dnlibTypePicker.GetDnlibType(dnSpy_AsmEditor_Resources.Pick_EnumType, new FlagsDocumentTreeNodeFilter(VisibleMembersFlags.EnumTypeDef), EnumType, ownerModule); if (type is not null) EnumType = type; } protected abstract DataFieldVM? CreateEnumUnderlyingTypeFieldFromValue(object value); protected abstract DataFieldVM? CreateEnumUnderlyingTypeField(ElementType elementType); } sealed class EnumDataFieldVM : EnumDataFieldVMBase { public EnumDataFieldVM(ModuleDef ownerModule, Action onUpdated) : this(ownerModule, new EnumInfo(), onUpdated) { } public EnumDataFieldVM(ModuleDef ownerModule, EnumInfo value, Action onUpdated) : base(ownerModule, value, onUpdated) { } protected override DataFieldVM? CreateEnumUnderlyingTypeFieldFromValue(object value) { if (value is bool) return new BooleanVM((bool)value, a => { }); if (value is char) return new CharVM((char)value, a => { }); if (value is sbyte) return new SByteVM((sbyte)value, a => { }); if (value is byte) return new ByteVM((byte)value, a => { }); if (value is short) return new Int16VM((short)value, a => { }); if (value is ushort) return new UInt16VM((ushort)value, a => { }); if (value is int) return new Int32VM((int)value, a => { }); if (value is uint) return new UInt32VM((uint)value, a => { }); if (value is long) return new Int64VM((long)value, a => { }); if (value is ulong) return new UInt64VM((ulong)value, a => { }); if (value is float) return new SingleVM((float)value, a => { }); if (value is double) return new DoubleVM((double)value, a => { }); return null; } protected override DataFieldVM? CreateEnumUnderlyingTypeField(ElementType elementType) { switch (elementType) { case ElementType.Boolean: return new BooleanVM(a => { }); case ElementType.Char: return new CharVM(a => { }); case ElementType.I1: return new SByteVM(a => { }); case ElementType.U1: return new ByteVM(a => { }); case ElementType.I2: return new Int16VM(a => { }); case ElementType.U2: return new UInt16VM(a => { }); case ElementType.I4: return new Int32VM(a => { }); case ElementType.U4: return new UInt32VM(a => { }); case ElementType.I8: return new Int64VM(a => { }); case ElementType.U8: return new UInt64VM(a => { }); case ElementType.R4: return new SingleVM(a => { }); case ElementType.R8: return new DoubleVM(a => { }); } return null; } } sealed class EnumListDataFieldVM : EnumDataFieldVMBase { public EnumListDataFieldVM(ModuleDef ownerModule, Action onUpdated) : this(ownerModule, EnumInfo.CreateNullArray(null), onUpdated) { } public EnumListDataFieldVM(ModuleDef ownerModule, EnumInfo value, Action onUpdated) : base(ownerModule, value, onUpdated) { } protected override DataFieldVM? CreateEnumUnderlyingTypeFieldFromValue(object value) { if (value is IList) return new BooleanListDataFieldVM((IList)value, a => { }); if (value is IList) return new CharListDataFieldVM((IList)value, a => { }); if (value is IList) return new SByteListDataFieldVM((IList)value, a => { }); if (value is IList) return new ByteListDataFieldVM((IList)value, a => { }); if (value is IList) return new Int16ListDataFieldVM((IList)value, a => { }); if (value is IList) return new UInt16ListDataFieldVM((IList)value, a => { }); if (value is IList) return new Int32ListDataFieldVM((IList)value, a => { }); if (value is IList) return new UInt32ListDataFieldVM((IList)value, a => { }); if (value is IList) return new Int64ListDataFieldVM((IList)value, a => { }); if (value is IList) return new UInt64ListDataFieldVM((IList)value, a => { }); if (value is IList) return new SingleListDataFieldVM((IList)value, a => { }); if (value is IList) return new DoubleListDataFieldVM((IList)value, a => { }); return null; } protected override DataFieldVM? CreateEnumUnderlyingTypeField(ElementType elementType) { switch (elementType) { case ElementType.Boolean: return new BooleanListDataFieldVM(a => { }); case ElementType.Char: return new CharListDataFieldVM(a => { }); case ElementType.I1: return new SByteListDataFieldVM(a => { }); case ElementType.U1: return new ByteListDataFieldVM(a => { }); case ElementType.I2: return new Int16ListDataFieldVM(a => { }); case ElementType.U2: return new UInt16ListDataFieldVM(a => { }); case ElementType.I4: return new Int32ListDataFieldVM(a => { }); case ElementType.U4: return new UInt32ListDataFieldVM(a => { }); case ElementType.I8: return new Int64ListDataFieldVM(a => { }); case ElementType.U8: return new UInt64ListDataFieldVM(a => { }); case ElementType.R4: return new SingleListDataFieldVM(a => { }); case ElementType.R8: return new DoubleListDataFieldVM(a => { }); } return null; } } }