Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,12 @@ sealed class PEFilePETreeNodeDataProvider : PETreeNodeDataProviderBase {
: base(hexBufferService, peStructureProviderFactory, hexBufferFileServiceFactory) {
}
}

[ExportTreeNodeDataProvider(Guid = DocumentTreeViewConstants.BUNDLE_NODE_GUID)]
sealed class BundleFilePETreeNodeDataProvider : PETreeNodeDataProviderBase {
[ImportingConstructor]
BundleFilePETreeNodeDataProvider(Lazy<IHexBufferService> hexBufferService, Lazy<PEStructureProviderFactory> peStructureProviderFactory, Lazy<HexBufferFileServiceFactory> hexBufferFileServiceFactory)
: base(hexBufferService, peStructureProviderFactory, hexBufferFileServiceFactory) {
}
}
}
16 changes: 12 additions & 4 deletions Extensions/dnSpy.AsmEditor/SaveModule/SaveModuleCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ You should have received a copy of the GNU General Public License
using dnSpy.AsmEditor.Hex;
using dnSpy.AsmEditor.Properties;
using dnSpy.AsmEditor.UndoRedo;
using dnSpy.Contracts.App;
using dnSpy.Contracts.Controls;
using dnSpy.Contracts.Documents.Tabs;
using dnSpy.Contracts.Documents.TreeView;
Expand Down Expand Up @@ -87,7 +88,8 @@ sealed class SaveModuleCommand : FileMenuHandler {
this.documentSaver = documentSaver;
}

HashSet<object> GetDocuments(DocumentTreeNodeData[] nodes) {
HashSet<object> GetDocuments(DocumentTreeNodeData[] nodes, out bool hitBundle) {
hitBundle = false;
var hash = new HashSet<object>();

foreach (var node in nodes) {
Expand All @@ -100,6 +102,9 @@ HashSet<object> GetDocuments(DocumentTreeNodeData[] nodes) {
if (topNode is null || topNode.TreeNode.Parent is null)
continue;

if (fileNode.Document.SingleFileBundle is not null)
hitBundle = true;

bool added = false;

if (fileNode.Document.ModuleDef is not null) {
Expand Down Expand Up @@ -128,13 +133,16 @@ HashSet<object> GetDocuments(DocumentTreeNodeData[] nodes) {
}

public override bool IsVisible(AsmEditorContext context) => true;
public override bool IsEnabled(AsmEditorContext context) => GetDocuments(context.Nodes).Count > 0;
public override bool IsEnabled(AsmEditorContext context) => GetDocuments(context.Nodes, out _).Count > 0;

public override void Execute(AsmEditorContext context) {
var asmNodes = GetDocuments(context.Nodes);
var asmNodes = GetDocuments(context.Nodes, out bool bundle);
if (bundle)
MsgBox.Instance.Show("Warning: Entries inside bundles will not be updated!"); //TODO: localize

documentSaver.Value.Save(asmNodes);
}

public override string? GetHeader(AsmEditorContext context) => GetDocuments(context.Nodes).Count <= 1 ? dnSpy_AsmEditor_Resources.SaveModuleCommand : dnSpy_AsmEditor_Resources.SaveModulesCommand;
public override string? GetHeader(AsmEditorContext context) => GetDocuments(context.Nodes, out _).Count <= 1 ? dnSpy_AsmEditor_Resources.SaveModuleCommand : dnSpy_AsmEditor_Resources.SaveModulesCommand;
}
}
82 changes: 81 additions & 1 deletion dnSpy/dnSpy.Contracts.DnSpy/Documents/DsDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ You should have received a copy of the GNU General Public License
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using dnlib.DotNet;
using dnlib.PE;
using dnSpy.Contracts.Bundles;
using dnSpy.Contracts.Utilities;

namespace dnSpy.Contracts.Documents {
Expand All @@ -39,6 +41,10 @@ public abstract class DsDocument : IDsDocument2 {
public virtual ModuleDef? ModuleDef => null;
/// <inheritdoc/>
public virtual IPEImage? PEImage => (ModuleDef as ModuleDefMD)?.Metadata?.PEImage;
/// <inheritdoc/>
public virtual SingleFileBundle? SingleFileBundle => null;
/// <inheritdoc/>
public virtual BundleEntry? BundleEntry => null;

/// <inheritdoc/>
public string Filename {
Expand Down Expand Up @@ -135,6 +141,9 @@ public sealed class DsPEDocument : DsDocument, IDsPEDocument, IDisposable {
public override IDsDocumentNameKey Key => FilenameKey.CreateFullPath(Filename);
/// <inheritdoc/>
public override IPEImage? PEImage { get; }
/// <inheritdoc/>
public override BundleEntry? BundleEntry => bundleEntry;
BundleEntry? bundleEntry;

/// <summary>
/// Constructor
Expand All @@ -145,6 +154,8 @@ public DsPEDocument(IPEImage peImage) {
Filename = peImage.Filename ?? string.Empty;
}

internal void SetBundleEntry(BundleEntry bundleEntry) => this.bundleEntry = bundleEntry;

/// <inheritdoc/>
public void Dispose() => PEImage!.Dispose();
}
Expand Down Expand Up @@ -226,6 +237,10 @@ public class DsDotNetDocument : DsDotNetDocumentBase, IDisposable {
public override DsDocumentInfo? SerializedDocument => documentInfo;
DsDocumentInfo documentInfo;

/// <inheritdoc/>
public override BundleEntry? BundleEntry => bundleEntry;
BundleEntry? bundleEntry;

/// <summary>
/// Constructor
/// </summary>
Expand Down Expand Up @@ -289,6 +304,8 @@ protected override TList<IDsDocument> CreateChildren() {
return list;
}

internal void SetBundleEntry(BundleEntry bundleEntry) => this.bundleEntry = bundleEntry;

/// <inheritdoc/>
public void Dispose() => ModuleDef!.Dispose();
}
Expand All @@ -309,6 +326,69 @@ protected override TList<IDsDocument> CreateChildren() {
}
}

/// <summary>
/// .NET single file bundle
/// </summary>
public class DsBundleDocument : DsDocument, IDsPEDocument, IDisposable {
/// <inheritdoc/>
public override DsDocumentInfo? SerializedDocument => DsDocumentInfo.CreateDocument(Filename);
/// <inheritdoc/>
public override IDsDocumentNameKey Key => FilenameKey.CreateFullPath(Filename);
/// <inheritdoc/>
public override IPEImage? PEImage { get; }
/// <summary>
/// The bundle represented by this document.
/// </summary>
public override SingleFileBundle? SingleFileBundle { get; }

readonly string directoryOfBundle;

/// <summary>
/// Constructor
/// </summary>
/// <param name="peImage">PE image</param>
/// <param name="bundle">Parsed bundle</param>
public DsBundleDocument(IPEImage peImage, SingleFileBundle bundle) {
PEImage = peImage;
Filename = peImage.Filename ?? string.Empty;
directoryOfBundle = Path.GetDirectoryName(Filename) ?? string.Empty;
SingleFileBundle = bundle;
}

/// <inheritdoc/>
protected override TList<IDsDocument> CreateChildren() {
var list = new TList<IDsDocument>();
foreach (var entry in SingleFileBundle!.Entries) {
if (entry is AssemblyBundleEntry asmEntry) {
var mod = asmEntry.Module;
mod.Location = Path.Combine(directoryOfBundle, asmEntry.RelativePath);

var data = asmEntry.GetEntryData();

DsDocumentInfo documentInfo;
if (data is not null)
documentInfo = DsDocumentInfo.CreateInMemory(() => (data, true), asmEntry.FileName);
else
documentInfo = DsDocumentInfo.CreateDocument(string.Empty);

var document = DsDotNetDocument.CreateAssembly(documentInfo, mod, true);
document.SetBundleEntry(entry);
list.Add(document);
}
else if (entry is NativeBinaryBundleEntry nativeEntry) {
var peDocument = new DsPEDocument(nativeEntry.PEImage);
peDocument.SetBundleEntry(entry);
list.Add(peDocument);
}
}

return list;
}

/// <inheritdoc/>
public void Dispose() => PEImage!.Dispose();
}

/// <summary>
/// mmap'd I/O helper methods
/// </summary>
Expand All @@ -317,7 +397,7 @@ static class MemoryMappedIOHelper {
/// Disable memory mapped I/O
/// </summary>
/// <param name="document">Document</param>
public static void DisableMemoryMappedIO(IDsDocument document) {
public static void DisableMemoryMappedIO(IDsDocument? document) {
if (document is null)
return;
DisableMemoryMappedIO(document.PEImage);
Expand Down
11 changes: 11 additions & 0 deletions dnSpy/dnSpy.Contracts.DnSpy/Documents/IDsDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License
using System.Linq;
using dnlib.DotNet;
using dnlib.PE;
using dnSpy.Contracts.Bundles;

namespace dnSpy.Contracts.Documents {
/// <summary>
Expand Down Expand Up @@ -55,6 +56,16 @@ public interface IDsDocument : IAnnotations {
/// </summary>
IPEImage? PEImage { get; }

/// <summary>
/// Gets the single file bundle descriptor or null if it's not a single file bundle.
/// </summary>
SingleFileBundle? SingleFileBundle { get; }

/// <summary>
/// Gets the single file bundle entry or null if it's not inside a bundle.
/// </summary>
BundleEntry? BundleEntry { get; }

/// <summary>
/// Gets/sets the filename
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ You should have received a copy of the GNU General Public License

using dnlib.DotNet;
using dnlib.PE;
using dnSpy.Contracts.Bundles;
using dnSpy.Contracts.TreeView;

namespace dnSpy.Contracts.Documents.TreeView {
/// <summary>
/// A .NET assembly file
/// </summary>
public abstract class AssemblyDocumentNode : DsDocumentNode, IMDTokenNode {
public abstract class AssemblyDocumentNode : DsDocumentNode, IMDTokenNode, IBundleEntryNode {
/// <summary>
/// Gets the <see cref="IDsDocument"/> instance
/// </summary>
Expand All @@ -37,6 +38,7 @@ public abstract class AssemblyDocumentNode : DsDocumentNode, IMDTokenNode {
public bool IsExe => (Document.ModuleDef!.Characteristics & Characteristics.Dll) == 0;

IMDTokenProvider? IMDTokenNode.Reference => Document.AssemblyDef;
BundleEntry? IBundleEntryNode.BundleEntry => Document.BundleEntry;

/// <summary>
/// Constructor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Diagnostics;

namespace dnSpy.Contracts.Documents.TreeView {
/// <summary>
/// A .NEt single file bundle.
/// </summary>
public abstract class BundleDocumentNode : DsDocumentNode {
/// <summary>
/// Constructor
/// </summary>
/// <param name="document">Document</param>
protected BundleDocumentNode(IDsDocument document) : base(document) => Debug2.Assert(document.SingleFileBundle is not null);
}
}
14 changes: 14 additions & 0 deletions dnSpy/dnSpy.Contracts.DnSpy/Documents/TreeView/BundleFolderNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Diagnostics;
using dnSpy.Contracts.Bundles;

namespace dnSpy.Contracts.Documents.TreeView {
/// <summary>
/// Bundle folder node
/// </summary>
public abstract class BundleFolderNode : DocumentTreeNodeData {
/// <summary>
/// Constructor
/// </summary>
protected BundleFolderNode(BundleFolder bundleFolder) => Debug2.Assert(bundleFolder is not null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ public static class DocumentTreeViewConstants {
/// <summary><see cref="ModuleDocumentNode"/></summary>
public const string MODULE_NODE_GUID = "597B3358-A6F5-47EA-B0D2-57EDD1208333";

/// <summary><see cref="BundleDocumentNode"/></summary>
public const string BUNDLE_NODE_GUID = "56ADC6DE-146D-4967-AE15-1F561CB61DFC";

/// <summary><see cref="BundleFolderNode"/></summary>
public const string BUNDLE_FOLDER_NODE_GUID = "BCF6AA92-94FF-4837-9E55-0C770FCB3BB4";

/// <summary><see cref="UnknownBundleEntryNode"/></summary>
public const string BUNDLE_UNKNOWN_ENTRY_NODE_GUID = "582A8F1D-2D9E-476A-84B6-6053B983C374";

/// <summary><see cref="JsonBundleEntryNode"/></summary>
public const string BUNDLE_JSON_ENTRY_NODE_GUID = "9C972EA7-9E52-4283-B38A-7C876A50F897";

/// <summary><see cref="ResourcesFolderNode"/></summary>
public const string RESOURCES_FOLDER_NODE_GUID = "1DD75445-9DED-482F-B6EB-4FD13E4A2197";

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Diagnostics;
using dnSpy.Contracts.Bundles;
using dnSpy.Contracts.TreeView;

namespace dnSpy.Contracts.Documents.TreeView {
/// <summary>
/// JSON bundle entry node
/// </summary>
public abstract class JsonBundleEntryNode : DocumentTreeNodeData, IBundleEntryNode {
/// <inheritdoc/>
public BundleEntry BundleEntry { get; }

/// <summary>
/// Constructor
/// </summary>
protected JsonBundleEntryNode(BundleEntry bundleEntry) {
Debug2.Assert(bundleEntry is not null);
BundleEntry = bundleEntry;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,21 @@ You should have received a copy of the GNU General Public License

using System.Diagnostics;
using dnlib.DotNet;
using dnSpy.Contracts.Bundles;
using dnSpy.Contracts.TreeView;

namespace dnSpy.Contracts.Documents.TreeView {
/// <summary>
/// A .NET module file
/// </summary>
public abstract class ModuleDocumentNode : DsDocumentNode, IMDTokenNode {
public abstract class ModuleDocumentNode : DsDocumentNode, IMDTokenNode, IBundleEntryNode {
/// <summary>
/// Gets the <see cref="IDsDocument"/> instance
/// </summary>
public new IDsDotNetDocument Document => (IDsDotNetDocument)base.Document;

IMDTokenProvider? IMDTokenNode.Reference => Document.ModuleDef;
BundleEntry? IBundleEntryNode.BundleEntry => Document.BundleEntry;

/// <summary>
/// Constructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,21 @@ You should have received a copy of the GNU General Public License

using System.Diagnostics;
using dnlib.PE;
using dnSpy.Contracts.Bundles;
using dnSpy.Contracts.TreeView;

namespace dnSpy.Contracts.Documents.TreeView {
/// <summary>
/// A PE file (but not a .NET file)
/// </summary>
public abstract class PEDocumentNode : DsDocumentNode {
public abstract class PEDocumentNode : DsDocumentNode, IBundleEntryNode {
/// <summary>
/// true if it's an .exe file, false if it's a .dll file
/// </summary>
public bool IsExe => (Document.PEImage!.ImageNTHeaders.FileHeader.Characteristics & Characteristics.Dll) == 0;

BundleEntry? IBundleEntryNode.BundleEntry => Document.BundleEntry;

/// <summary>
/// Constructor
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Diagnostics;
using dnSpy.Contracts.Bundles;
using dnSpy.Contracts.TreeView;

namespace dnSpy.Contracts.Documents.TreeView {
/// <summary>
/// Unknown bundle entry node
/// </summary>
public abstract class UnknownBundleEntryNode : DocumentTreeNodeData, IBundleEntryNode {
/// <inheritdoc/>
public BundleEntry BundleEntry { get; }

/// <summary>
/// Constructor
/// </summary>
protected UnknownBundleEntryNode(BundleEntry bundleEntry) {
Debug2.Assert(bundleEntry is not null);
BundleEntry = bundleEntry;
}
}
}
Loading