/* 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.IO; using System.Threading; using dnlib.DotNet; using dnlib.DotNet.Resources; using dnlib.IO; using dnSpy.Contracts.Decompiler; using dnSpy.Contracts.DnSpy.Properties; using dnSpy.Contracts.Images; using dnSpy.Contracts.Text; using dnSpy.Contracts.TreeView; using dnSpy.Contracts.Utilities; namespace dnSpy.Contracts.Documents.TreeView.Resources { /// /// Resource element node base class /// public abstract class ResourceElementNode : DocumentTreeNodeData, IResourceNode { /// /// Gets the resource element /// public ResourceElement ResourceElement => resourceElement; ResourceElement resourceElement; // updated by the asm editor, see UpdateData() /// /// Gets the name /// public string Name => resourceElement.Name; /// protected sealed override void WriteCore(ITextColorWriter output, IDecompiler decompiler, DocumentNodeWriteOptions options) { output.WriteFilename(Uri.UnescapeDataString(resourceElement.Name)); if ((options & DocumentNodeWriteOptions.ToolTip) != 0) { if (TreeNode.Parent?.Data is ResourceNode parentNode) { output.WriteLine(); output.WriteFilename(parentNode.Name); } output.WriteLine(); WriteFilename(output); } } /// protected sealed override ImageReference? GetExpandedIcon(IDotNetImageService dnImgMgr) => null; /// protected sealed override ImageReference GetIcon(IDotNetImageService dnImgMgr) { var imgRef = GetIcon(); if (!imgRef.IsDefault) return imgRef; var asm = dnImgMgr.GetType().Assembly; return ResourceUtilities.TryGetImageReference(asm, resourceElement.Name) ?? DsImages.Dialog; } /// /// Gets the icon to use /// /// protected virtual ImageReference GetIcon() => new ImageReference(); /// public sealed override NodePathName NodePathName => new NodePathName(Guid, NameUtilities.CleanName(resourceElement.Name)); /// /// Gets the file offset of the resource /// public uint FileOffset { get { GetModuleOffset(out var fo); return (uint)fo; } } /// /// Gets the length of the resource /// public uint Length => resourceElement.ResourceData.EndOffset - resourceElement.ResourceData.StartOffset; /// /// Gets the RVA of the resource /// public uint RVA { get { var module = GetModuleOffset(out var fo); if (module is null) return 0; return (uint)module.Metadata.PEImage.ToRVA(fo); } } ModuleDefMD? GetModuleOffset(out FileOffset fileOffset) => GetModuleOffset(this, resourceElement, out fileOffset); internal static ModuleDefMD? GetModuleOffset(DocumentTreeNodeData node, ResourceElement resourceElement, out FileOffset fileOffset) { fileOffset = 0; var module = node.GetModule() as ModuleDefMD;//TODO: Support CorModuleDef if (module is null) return null; fileOffset = resourceElement.ResourceData.StartOffset; return module; } /// public override ITreeNodeGroup? TreeNodeGroup => treeNodeGroup; readonly ITreeNodeGroup treeNodeGroup; /// /// Constructor /// /// Treenode group /// Resource element protected ResourceElementNode(ITreeNodeGroup treeNodeGroup, ResourceElement resourceElement) { this.treeNodeGroup = treeNodeGroup ?? throw new ArgumentNullException(nameof(treeNodeGroup)); this.resourceElement = resourceElement ?? throw new ArgumentNullException(nameof(resourceElement)); } /// public virtual void WriteShort(IDecompilerOutput output, IDecompiler decompiler, bool showOffset) { decompiler.WriteCommentBegin(output, true); output.WriteOffsetComment(this, showOffset); const string LTR = "\u200E"; output.Write(NameUtilities.CleanName(Name) + LTR, this, DecompilerReferenceFlags.Local | DecompilerReferenceFlags.Definition, BoxedTextColor.Comment); output.Write($" = {ValueString}", BoxedTextColor.Comment); decompiler.WriteCommentEnd(output, true); output.WriteLine(); } /// /// Gets the value as a string /// protected virtual string ValueString { get { switch (resourceElement.ResourceData.Code) { case ResourceTypeCode.Null: return "null"; case ResourceTypeCode.String: return SimpleTypeConverter.ToString((string)((BuiltInResourceData)resourceElement.ResourceData).Data, false); case ResourceTypeCode.Boolean: return SimpleTypeConverter.ToString((bool)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.Char: return SimpleTypeConverter.ToString((char)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.Byte: return SimpleTypeConverter.ToString((byte)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.SByte: return SimpleTypeConverter.ToString((sbyte)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.Int16: return SimpleTypeConverter.ToString((short)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.UInt16: return SimpleTypeConverter.ToString((ushort)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.Int32: return SimpleTypeConverter.ToString((int)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.UInt32: return SimpleTypeConverter.ToString((uint)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.Int64: return SimpleTypeConverter.ToString((long)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.UInt64: return SimpleTypeConverter.ToString((ulong)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.Single: return SimpleTypeConverter.ToString((float)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.Double: return SimpleTypeConverter.ToString((double)((BuiltInResourceData)resourceElement.ResourceData).Data); case ResourceTypeCode.Decimal: return ((decimal)((BuiltInResourceData)resourceElement.ResourceData).Data).ToString(); case ResourceTypeCode.DateTime: return ((DateTime)((BuiltInResourceData)resourceElement.ResourceData).Data).ToString(); case ResourceTypeCode.TimeSpan: return ((TimeSpan)((BuiltInResourceData)resourceElement.ResourceData).Data).ToString(); case ResourceTypeCode.ByteArray: case ResourceTypeCode.Stream: var ary = (byte[])((BuiltInResourceData)resourceElement.ResourceData).Data; return string.Format(dnSpy_Contracts_DnSpy_Resources.NumberOfBytes, ary.Length); default: var binData = resourceElement.ResourceData as BinaryResourceData; if (binData is not null) return string.Format(dnSpy_Contracts_DnSpy_Resources.NumberOfBytesAndType, binData.Data.Length, binData.TypeName); return resourceElement.ResourceData.ToString() ?? string.Empty; } } } /// /// Converts the value to a string /// /// Cancellation token /// true if the data can be decompiled /// public virtual string? ToString(CancellationToken token, bool canDecompile) => null; /// public IEnumerable GetResourceData(ResourceDataType type) { switch (type) { case ResourceDataType.Deserialized: return GetDeserializedData(); case ResourceDataType.Serialized: return GetSerializedData(); default: throw new InvalidOperationException(); } } /// /// Gets the deserialized data /// /// protected abstract IEnumerable GetDeserializedData(); IEnumerable GetSerializedData() => GetSerializedData(resourceElement); internal static IEnumerable GetSerializedData(ResourceElement resourceElement) { var outStream = new MemoryStream(); var writer = new BinaryWriter(outStream); var builtin = resourceElement.ResourceData as BuiltInResourceData; var bin = resourceElement.ResourceData as BinaryResourceData; switch (resourceElement.ResourceData.Code) { case ResourceTypeCode.Null: break; case ResourceTypeCode.String: writer.Write((string)builtin!.Data); break; case ResourceTypeCode.Boolean: writer.Write((bool)builtin!.Data); break; case ResourceTypeCode.Char: writer.Write((ushort)(char)builtin!.Data); break; case ResourceTypeCode.Byte: writer.Write((byte)builtin!.Data); break; case ResourceTypeCode.SByte: writer.Write((sbyte)builtin!.Data); break; case ResourceTypeCode.Int16: writer.Write((short)builtin!.Data); break; case ResourceTypeCode.UInt16: writer.Write((ushort)builtin!.Data); break; case ResourceTypeCode.Int32: writer.Write((int)builtin!.Data); break; case ResourceTypeCode.UInt32: writer.Write((uint)builtin!.Data); break; case ResourceTypeCode.Int64: writer.Write((long)builtin!.Data); break; case ResourceTypeCode.UInt64: writer.Write((ulong)builtin!.Data); break; case ResourceTypeCode.Single: writer.Write((float)builtin!.Data); break; case ResourceTypeCode.Double: writer.Write((double)builtin!.Data); break; case ResourceTypeCode.Decimal: writer.Write((decimal)builtin!.Data); break; case ResourceTypeCode.DateTime: writer.Write(((DateTime)builtin!.Data).ToBinary()); break; case ResourceTypeCode.TimeSpan: writer.Write(((TimeSpan)builtin!.Data).Ticks); break; case ResourceTypeCode.ByteArray: case ResourceTypeCode.Stream: // Don't write array length, just the data writer.Write((byte[])builtin!.Data); break; default: writer.Write(bin!.Data); break; } outStream.Position = 0; yield return new ResourceData(resourceElement.Name, token => outStream); } /// /// Checks whether can execute. Used by the /// assembly editor. Returns null or an empty string if the data can be updated, else an /// error string that can be shown to the user. /// /// New data /// public virtual string? CheckCanUpdateData(ResourceElement newResElem) { if (resourceElement.ResourceData.Code.FixUserType() != newResElem.ResourceData.Code.FixUserType()) return dnSpy_Contracts_DnSpy_Resources.ResourceTypeCantBeChanged; return string.Empty; } /// /// Updates the internal resource data. Must only be called if /// returned true. Used by the assembly /// editor. /// /// New data public virtual void UpdateData(ResourceElement newResElem) => resourceElement = newResElem; /// public sealed override FilterType GetFilterType(IDocumentTreeNodeFilter filter) => filter.GetResult(this).FilterType; sealed class Data { public readonly ResourceElement ResourceElement; public Data(ResourceElement resourceElement) => ResourceElement = resourceElement; } /// /// Gets the resource element or null /// /// Node /// public static ResourceElement? GetResourceElement(DocumentTreeNodeData node) { if (node is ResourceElementNode resourceElementNode) return resourceElementNode.ResourceElement; if (node.TryGetData(out Data? data)) return data.ResourceElement; return null; } /// /// Adds the resource element to a resource element node /// /// Node /// Resource element public static void AddResourceElement(DocumentTreeNodeData node, ResourceElement resourceElement) { if (node is ResourceElementNode resourceElementNode) { if (resourceElementNode.ResourceElement != resourceElement) throw new InvalidOperationException(); } else { if (node.TryGetData(out _)) throw new InvalidOperationException(); node.AddData(new Data(resourceElement)); } } } }