Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for loading/editing/saving .NET Core single file publish bundles. (#16) #49

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
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