/*
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.Windows.Input;
using dnlib.DotNet;
using dnlib.DotNet.Resources;
using dnSpy.AsmEditor.Properties;
using dnSpy.AsmEditor.ViewHelpers;
using dnSpy.Contracts.MVVM;
namespace dnSpy.AsmEditor.Resources {
enum ResourceElementType {
Null = ResourceTypeCode.Null,
String = ResourceTypeCode.String,
Boolean = ResourceTypeCode.Boolean,
Char = ResourceTypeCode.Char,
Byte = ResourceTypeCode.Byte,
SByte = ResourceTypeCode.SByte,
Int16 = ResourceTypeCode.Int16,
UInt16 = ResourceTypeCode.UInt16,
Int32 = ResourceTypeCode.Int32,
UInt32 = ResourceTypeCode.UInt32,
Int64 = ResourceTypeCode.Int64,
UInt64 = ResourceTypeCode.UInt64,
Single = ResourceTypeCode.Single,
Double = ResourceTypeCode.Double,
Decimal = ResourceTypeCode.Decimal,
DateTime = ResourceTypeCode.DateTime,
TimeSpan = ResourceTypeCode.TimeSpan,
ByteArray = ResourceTypeCode.ByteArray,
Stream = ResourceTypeCode.Stream,
SerializedType = ResourceTypeCode.UserTypes,
}
sealed class ResourceElementVM : ViewModelBase {
readonly ResourceElementOptions origOptions;
public IOpenFile OpenFile {
set => openFile = value;
}
IOpenFile? openFile;
public IDnlibTypePicker DnlibTypePicker {
set => UserTypeVM.DnlibTypePicker = value;
}
public ICommand ReinitializeCommand => new RelayCommand(a => Reinitialize());
public ICommand PickRawBytesCommand => new RelayCommand(a => PickRawBytes(), a => IsRawBytes);
public bool CanChangeType {
get => canChangeType;
set {
if (canChangeType != value) {
canChangeType = value;
OnPropertyChanged(nameof(CanChangeType));
}
}
}
bool canChangeType = true;
internal static readonly EnumVM[] resourceElementTypeList = EnumVM.Create(false, typeof(ResourceElementType));
public EnumListVM ResourceElementTypeVM { get; }
public string? Name {
get => name;
set {
if (name != value) {
name = value;
OnPropertyChanged(nameof(Name));
}
}
}
UTF8String? name;
public string? String {
get => @string;
set {
if (@string != value) {
@string = value;
OnPropertyChanged(nameof(String));
}
}
}
string? @string;
public BooleanVM BooleanVM { get; }
public CharVM CharVM { get; }
public ByteVM ByteVM { get; }
public SByteVM SByteVM { get; }
public Int16VM Int16VM { get; }
public UInt16VM UInt16VM { get; }
public Int32VM Int32VM { get; }
public UInt32VM UInt32VM { get; }
public Int64VM Int64VM { get; }
public UInt64VM UInt64VM { get; }
public SingleVM SingleVM { get; }
public DoubleVM DoubleVM { get; }
public DecimalVM DecimalVM { get; }
public DateTimeVM DateTimeVM { get; }
public TimeSpanVM TimeSpanVM { get; }
public byte[]? Data {
get => data;
set {
if (data != value) {
data = value;
OnPropertyChanged(nameof(Data));
OnPropertyChanged(nameof(DataString));
}
}
}
byte[]? data;
public string DataString => string.Format(dnSpy_AsmEditor_Resources.XBytes, Data is null ? 0 : Data.Length);
public UserTypeVM UserTypeVM { get; }
public object? ValueVM {
get {
switch ((ResourceElementType)ResourceElementTypeVM.SelectedItem!) {
case ResourceElementType.Null: return null;
case ResourceElementType.String: return null;
case ResourceElementType.Boolean: return BooleanVM;
case ResourceElementType.Char: return CharVM;
case ResourceElementType.Byte: return ByteVM;
case ResourceElementType.SByte: return SByteVM;
case ResourceElementType.Int16: return Int16VM;
case ResourceElementType.UInt16: return UInt16VM;
case ResourceElementType.Int32: return Int32VM;
case ResourceElementType.UInt32: return UInt32VM;
case ResourceElementType.Int64: return Int64VM;
case ResourceElementType.UInt64: return UInt64VM;
case ResourceElementType.Single: return SingleVM;
case ResourceElementType.Double: return DoubleVM;
case ResourceElementType.Decimal: return DecimalVM;
case ResourceElementType.DateTime: return DateTimeVM;
case ResourceElementType.TimeSpan: return TimeSpanVM;
case ResourceElementType.ByteArray: return null;
case ResourceElementType.Stream: return null;
case ResourceElementType.SerializedType: return UserTypeVM;
default: throw new InvalidOperationException();
}
}
}
public bool IsSerializedType => (ResourceElementType)ResourceElementTypeVM.SelectedItem! == ResourceElementType.SerializedType;
public bool IsSingleLineValue => !IsMultiLineValue && !IsRawBytes && (ResourceElementType)ResourceElementTypeVM.SelectedItem! != ResourceElementType.Null;
public bool IsMultiLineValue => (ResourceElementType)ResourceElementTypeVM.SelectedItem! == ResourceElementType.String;
public bool IsRawBytes {
get {
var code = (ResourceElementType)ResourceElementTypeVM.SelectedItem!;
return code == ResourceElementType.ByteArray || code == ResourceElementType.Stream;
}
}
readonly bool canDeserialize;
public ResourceElementVM(ResourceElementOptions options, ModuleDef ownerModule, bool canDeserialize) {
origOptions = options;
this.canDeserialize = canDeserialize;
BooleanVM = new BooleanVM(a => HasErrorUpdated());
CharVM = new CharVM(a => HasErrorUpdated());
ByteVM = new ByteVM(a => HasErrorUpdated());
SByteVM = new SByteVM(a => HasErrorUpdated());
Int16VM = new Int16VM(a => HasErrorUpdated());
UInt16VM = new UInt16VM(a => HasErrorUpdated());
Int32VM = new Int32VM(a => HasErrorUpdated());
UInt32VM = new UInt32VM(a => HasErrorUpdated());
Int64VM = new Int64VM(a => HasErrorUpdated());
UInt64VM = new UInt64VM(a => HasErrorUpdated());
SingleVM = new SingleVM(a => HasErrorUpdated());
DoubleVM = new DoubleVM(a => HasErrorUpdated());
DecimalVM = new DecimalVM(a => HasErrorUpdated());
DateTimeVM = new DateTimeVM(a => HasErrorUpdated());
TimeSpanVM = new TimeSpanVM(a => HasErrorUpdated());
UserTypeVM = new UserTypeVM(ownerModule, canDeserialize);
ResourceElementTypeVM = new EnumListVM(resourceElementTypeList, (a, b) => OnResourceElementTypeChanged());
UserTypeVM.PropertyChanged += (s, e) => {
if (e.PropertyName == nameof(UserTypeVM.HasError))
HasErrorUpdated();
};
Reinitialize();
}
void PickRawBytes() {
if (openFile is null)
throw new InvalidOperationException();
var newBytes = openFile.Open();
if (newBytes is not null)
Data = newBytes;
}
void OnResourceElementTypeChanged() {
OnPropertyChanged(nameof(ValueVM));
OnPropertyChanged(nameof(IsSerializedType));
OnPropertyChanged(nameof(IsSingleLineValue));
OnPropertyChanged(nameof(IsMultiLineValue));
OnPropertyChanged(nameof(IsRawBytes));
HasErrorUpdated();
}
void Reinitialize() => InitializeFrom(origOptions);
public ResourceElementOptions CreateResourceElementOptions() => CopyTo(new ResourceElementOptions());
void InitializeFrom(ResourceElementOptions options) {
Name = options.Name;
var code = Convert(options.ResourceData!.Code);
var builtin = options.ResourceData as BuiltInResourceData;
ResourceElementTypeVM.SelectedItem = code;
switch (code) {
case ResourceElementType.Null: break;
case ResourceElementType.String: String = (string)builtin!.Data; break;
case ResourceElementType.Boolean: BooleanVM.Value = (bool)builtin!.Data; break;
case ResourceElementType.Char: CharVM.Value = (char)builtin!.Data; break;
case ResourceElementType.Byte: ByteVM.Value = (byte)builtin!.Data; break;
case ResourceElementType.SByte: SByteVM.Value = (sbyte)builtin!.Data; break;
case ResourceElementType.Int16: Int16VM.Value = (short)builtin!.Data; break;
case ResourceElementType.UInt16: UInt16VM.Value = (ushort)builtin!.Data; break;
case ResourceElementType.Int32: Int32VM.Value = (int)builtin!.Data; break;
case ResourceElementType.UInt32: UInt32VM.Value = (uint)builtin!.Data; break;
case ResourceElementType.Int64: Int64VM.Value = (long)builtin!.Data; break;
case ResourceElementType.UInt64: UInt64VM.Value = (ulong)builtin!.Data; break;
case ResourceElementType.Single: SingleVM.Value = (float)builtin!.Data; break;
case ResourceElementType.Double: DoubleVM.Value = (double)builtin!.Data; break;
case ResourceElementType.Decimal: DecimalVM.Value = (decimal)builtin!.Data; break;
case ResourceElementType.DateTime: DateTimeVM.Value = (DateTime)builtin!.Data; break;
case ResourceElementType.TimeSpan: TimeSpanVM.Value = (TimeSpan)builtin!.Data; break;
case ResourceElementType.ByteArray: Data = (byte[])builtin!.Data; break;
case ResourceElementType.Stream: Data = (byte[])builtin!.Data; break;
case ResourceElementType.SerializedType:
var binRes = (BinaryResourceData)options.ResourceData;
UserTypeVM.TypeFullName = binRes.TypeName;
UserTypeVM.SetData(binRes.Data);
break;
default: throw new InvalidOperationException();
}
}
static ResourceElementType Convert(ResourceTypeCode code) {
if (code >= ResourceTypeCode.UserTypes)
return ResourceElementType.SerializedType;
return (ResourceElementType)code;
}
ResourceElementOptions CopyTo(ResourceElementOptions options) {
options.Name = Name;
options.ResourceData = CreateResourceData();
return options;
}
IResourceData CreateResourceData() {
var code = (ResourceElementType)ResourceElementTypeVM.SelectedItem!;
switch (code) {
case ResourceElementType.Null: return new BuiltInResourceData((ResourceTypeCode)code, null);
case ResourceElementType.String: return new BuiltInResourceData((ResourceTypeCode)code, String);
case ResourceElementType.Boolean: return new BuiltInResourceData((ResourceTypeCode)code, BooleanVM.Value);
case ResourceElementType.Char: return new BuiltInResourceData((ResourceTypeCode)code, CharVM.Value);
case ResourceElementType.Byte: return new BuiltInResourceData((ResourceTypeCode)code, ByteVM.Value);
case ResourceElementType.SByte: return new BuiltInResourceData((ResourceTypeCode)code, SByteVM.Value);
case ResourceElementType.Int16: return new BuiltInResourceData((ResourceTypeCode)code, Int16VM.Value);
case ResourceElementType.UInt16: return new BuiltInResourceData((ResourceTypeCode)code, UInt16VM.Value);
case ResourceElementType.Int32: return new BuiltInResourceData((ResourceTypeCode)code, Int32VM.Value);
case ResourceElementType.UInt32: return new BuiltInResourceData((ResourceTypeCode)code, UInt32VM.Value);
case ResourceElementType.Int64: return new BuiltInResourceData((ResourceTypeCode)code, Int64VM.Value);
case ResourceElementType.UInt64: return new BuiltInResourceData((ResourceTypeCode)code, UInt64VM.Value);
case ResourceElementType.Single: return new BuiltInResourceData((ResourceTypeCode)code, SingleVM.Value);
case ResourceElementType.Double: return new BuiltInResourceData((ResourceTypeCode)code, DoubleVM.Value);
case ResourceElementType.Decimal: return new BuiltInResourceData((ResourceTypeCode)code, DecimalVM.Value);
case ResourceElementType.DateTime: return new BuiltInResourceData((ResourceTypeCode)code, DateTimeVM.Value);
case ResourceElementType.TimeSpan: return new BuiltInResourceData((ResourceTypeCode)code, TimeSpanVM.Value);
case ResourceElementType.ByteArray: return new BuiltInResourceData((ResourceTypeCode)code, Data ?? Array.Empty());
case ResourceElementType.Stream: return new BuiltInResourceData((ResourceTypeCode)code, Data ?? Array.Empty());
case ResourceElementType.SerializedType: return new BinaryResourceData(new UserResourceType(UserTypeVM.TypeFullName, ResourceTypeCode.UserTypes), UserTypeVM.GetSerializedData());
default: throw new InvalidOperationException();
}
}
public override bool HasError {
get {
switch ((ResourceElementType)ResourceElementTypeVM.SelectedItem!) {
case ResourceElementType.Null: break;
case ResourceElementType.String: break;
case ResourceElementType.Boolean: if (BooleanVM.HasError) return true; break;
case ResourceElementType.Char: if (CharVM.HasError) return true; break;
case ResourceElementType.Byte: if (ByteVM.HasError) return true; break;
case ResourceElementType.SByte: if (SByteVM.HasError) return true; break;
case ResourceElementType.Int16: if (Int16VM.HasError) return true; break;
case ResourceElementType.UInt16: if (UInt16VM.HasError) return true; break;
case ResourceElementType.Int32: if (Int32VM.HasError) return true; break;
case ResourceElementType.UInt32: if (UInt32VM.HasError) return true; break;
case ResourceElementType.Int64: if (Int64VM.HasError) return true; break;
case ResourceElementType.UInt64: if (UInt64VM.HasError) return true; break;
case ResourceElementType.Single: if (SingleVM.HasError) return true; break;
case ResourceElementType.Double: if (DoubleVM.HasError) return true; break;
case ResourceElementType.Decimal: if (DecimalVM.HasError) return true; break;
case ResourceElementType.DateTime: if (DateTimeVM.HasError) return true; break;
case ResourceElementType.TimeSpan: if (TimeSpanVM.HasError) return true; break;
case ResourceElementType.ByteArray: break;
case ResourceElementType.Stream: break;
case ResourceElementType.SerializedType: if (UserTypeVM.HasError) return true; break;
default: throw new InvalidOperationException();
}
return false;
}
}
}
}