diff --git a/.editorconfig b/.editorconfig
index b1533ed3f..3c5663e27 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,7 +1,7 @@
[*]
charset = utf-8
-end_of_line = lf
+end_of_line = crlf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index e95f10964..000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-name: Bug report
-about: Create a report to help improve AsmResolver
-title: ''
-labels: bug
-assignees: ''
-
----
-
-## Describe the bug
-A clear and concise description of what the bug is.
-
-## To Reproduce
-Steps to reproduce the behavior. Preferably with sample code and sample input/output files.
-
-## Expected behavior
-A clear and concise description of what you expected to happen.
-
-## Screenshots
-If applicable, add screenshots to help explain your problem.
-
-## Platform
- - OS: [e.g. Windows 10, Linux]
- - AsmResolver Version: [e.g. 4.5.1]
-
-**Additional context**
-Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
new file mode 100644
index 000000000..6548e9f62
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -0,0 +1,61 @@
+name: Bug Report
+description: File a bug or crash report
+labels: ["bug"]
+body:
+ - type: input
+ id: asmresolver_version
+ attributes:
+ label: AsmResolver Version
+ placeholder: 4.11.0
+ validations:
+ required: true
+ - type: input
+ id: dotnet_version
+ attributes:
+ label: .NET Version
+ placeholder: .NET 6.0
+ - type: dropdown
+ attributes:
+ label: Operating System
+ options:
+ - Windows
+ - Linux
+ - MacOS
+ - Other
+ validations:
+ required: true
+ - type: textarea
+ id: description
+ attributes:
+ label: Describe the Bug
+ description: A clear and concise description of what the bug is.
+ validations:
+ required: true
+ - type: textarea
+ id: how_to_reproduce
+ attributes:
+ label: How To Reproduce
+ description: The steps on how to reproduce the bug. Preferably with sample code and/or sample input files.
+ validations:
+ required: true
+ - type: textarea
+ id: expected_behavior
+ attributes:
+ label: Expected Behavior
+ description: Describe the result that you expect to get after performing the steps.
+ validations:
+ required: true
+ - type: textarea
+ id: actual_behavior
+ attributes:
+ label: Actual Behavior
+ description: Describe the actual behavior that you observed after performing the steps.
+ validations:
+ required: true
+ - type: textarea
+ id: context
+ attributes:
+ label: Additional Context
+ description: Any other information that may help us fix the issue goes here.
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 3e0c05e63..000000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for AsmResolver
-title: ''
-labels: enhancement
-assignees: ''
-
----
-
-## Problem Description
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-## Proposal
-A clear and concise description of what you want to happen.
-
-## Alternatives
-A clear and concise description of any alternative solutions or features you've considered.
-
-## Additional context
-Add any other context or screenshots about the feature request here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml
new file mode 100644
index 000000000..679f68408
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yaml
@@ -0,0 +1,36 @@
+name: Feature Request
+description: Suggest a new feature
+labels: ["enhancement"]
+body:
+ - type: textarea
+ id: description
+ attributes:
+ label: Problem Description
+ description: A clear and concise description of what the problem is.
+ placeholder: |
+ Example: "I'm always frustrated when [...]", or "I often need to [...]"
+ validations:
+ required: true
+ - type: textarea
+ id: proposal
+ attributes:
+ label: Proposal
+ description: |
+ A clear and concise description of what you want to happen. You can be relatively open-ended, but including
+ syntax suggestions or pseudo code is appreciated as it may help conveying your idea.
+ validations:
+ required: true
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: Alternatives
+ description: A clear and concise description of any alternative solutions or features you've considered.
+ validations:
+ required: false
+ - type: textarea
+ id: context
+ attributes:
+ label: Additional Context
+ description: Add any other context or screenshots about the feature request here.
+ validations:
+ required: false
diff --git a/.github/ISSUE_TEMPLATE/other.yaml b/.github/ISSUE_TEMPLATE/other.yaml
new file mode 100644
index 000000000..b0da1e372
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/other.yaml
@@ -0,0 +1,8 @@
+name: Other
+description: Something else?
+body:
+ - type: textarea
+ id: description
+ attributes:
+ label: Description
+ description: Please describe your issue.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..0006a8e24
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,20 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "nuget" # See documentation for possible values
+ target-branch: "development"
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "daily"
+
+ - package-ecosystem: "github-actions"
+ target-branch: "development"
+ # Workflow files stored in the
+ # default location of `.github/workflows`
+ directory: "/"
+ schedule:
+ interval: "daily"
diff --git a/AsmResolver.sln b/AsmResolver.sln
index 77d8e6856..21b8f5456 100644
--- a/AsmResolver.sln
+++ b/AsmResolver.sln
@@ -81,9 +81,24 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TlsTest", "test\TestBinarie
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CallManagedExport", "test\TestBinaries\Native\CallManagedExport\CallManagedExport.vcxproj", "{40483E28-C703-4933-BA5B-9512EF6E6A21}"
EndProject
-Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "HelloWorldVB", "test\TestBinaries\DotNet\HelloWorldVB\HelloWorldVB.vbproj", "{CF6A7E02-37DC-4963-AC14-76D74ADCD87A}"
+Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "HelloWorldVB", "test\TestBinaries\DotNet\HelloWorldVB\HelloWorldVB.vbproj", "{CF6A7E02-37DC-4963-AC14-76D74ADCD87A}"
EndProject
-Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "ClassLibraryVB", "test\TestBinaries\DotNet\ClassLibraryVB\ClassLibraryVB.vbproj", "{2D1DF5DA-7367-4490-B3F0-B996348E150B}"
+Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "ClassLibraryVB", "test\TestBinaries\DotNet\ClassLibraryVB\ClassLibraryVB.vbproj", "{2D1DF5DA-7367-4490-B3F0-B996348E150B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{66C7E95F-0C1A-466E-988A-C84D5542458B}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ .gitignore = .gitignore
+ CONTRIBUTING.md = CONTRIBUTING.md
+ LICENSE.md = LICENSE.md
+ README.md = README.md
+ Directory.Build.props = Directory.Build.props
+ appveyor.yml = appveyor.yml
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.Pdb", "src\AsmResolver.Symbols.Pdb\AsmResolver.Symbols.Pdb.csproj", "{9E311832-D0F2-42CA-84DD-9A91B88F0287}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsmResolver.Symbols.Pdb.Tests", "test\AsmResolver.Symbols.Pdb.Tests\AsmResolver.Symbols.Pdb.Tests.csproj", "{AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -483,6 +498,30 @@ Global
{2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x64.Build.0 = Release|Any CPU
{2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x86.ActiveCfg = Release|Any CPU
{2D1DF5DA-7367-4490-B3F0-B996348E150B}.Release|x86.Build.0 = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x64.Build.0 = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Debug|x86.Build.0 = Debug|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x64.ActiveCfg = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x64.Build.0 = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x86.ActiveCfg = Release|Any CPU
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287}.Release|x86.Build.0 = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x64.Build.0 = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Debug|x86.Build.0 = Debug|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.ActiveCfg = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x64.Build.0 = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.ActiveCfg = Release|Any CPU
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -525,6 +564,8 @@ Global
{40483E28-C703-4933-BA5B-9512EF6E6A21} = {EA971BB0-94BA-44DB-B16C-212D2DB27E17}
{CF6A7E02-37DC-4963-AC14-76D74ADCD87A} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0}
{2D1DF5DA-7367-4490-B3F0-B996348E150B} = {B3AF102B-ABE1-41B2-AE48-C40702F45AB0}
+ {9E311832-D0F2-42CA-84DD-9A91B88F0287} = {34A95168-A162-4F6A-803B-B6F221FE9EA6}
+ {AAD604B6-ABE5-4DBC-A2D9-4EF8E815B2EE} = {786C1732-8C96-45DD-97BB-639C9AA7F45B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3302AC79-6D23-4E7D-8C5F-C0C7261044D0}
diff --git a/Directory.Build.props b/Directory.Build.props
index bc687b4c7..da8160f3a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,13 +1,13 @@
- Copyright © Washi 2016-2021
+ Copyright © Washi 2016-2022
https://github.com/Washi1337/AsmResolver
https://github.com/Washi1337/AsmResolver/LICENSE.md
https://github.com/Washi1337/AsmResolver
git
- 9
- 4.8.0
+ 10
+ 5.0.0-beta.1
diff --git a/README.md b/README.md
index db28f348a..9bf2ec12f 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,54 @@
-AsmResolver
-===========
+# AsmResolver
- [![Master branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/master.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/master) [![Nuget feed](https://img.shields.io/nuget/v/AsmResolver.svg)](https://www.nuget.org/packages/AsmResolver/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Documentation Status](https://readthedocs.org/projects/asmresolver/badge/?version=latest)](https://asmresolver.readthedocs.io/en/latest/?badge=latest)
+ [![Master branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/master.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/master)
+ [![Nuget feed](https://img.shields.io/nuget/v/AsmResolver.svg)](https://www.nuget.org/packages/AsmResolver/)
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
+ [![Documentation Status](https://readthedocs.org/projects/asmresolver/badge/?version=latest)](https://asmresolver.readthedocs.io/en/latest/?badge=latest)
+ [![Discord](https://img.shields.io/discord/961647807591243796.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/Y7DTBkbhJJ)
AsmResolver is a PE inspection library allowing .NET programmers to read, modify and write executable files. This includes .NET as well as native images. The library exposes high-level representations of the PE, while still allowing the user to access low-level structures.
AsmResolver is released under the MIT license.
-Binaries
---------
+## Features
+
+AsmResolver has a lot of features. Below a non-exhaustive list:
+
+- [x] Create, read and write PE files
+ - [x] Inspect and update PE headers.
+ - [x] Create, read and write sections.
+- [x] Create, read and write various data directories
+ - [x] Debug Directory (CodeView)
+ - [x] .NET Directory
+ - [x] CIL assembler and disassemblers
+ - [x] Metadata Directory (tables, strings, user-strings, blobs, GUIDs)
+ - [x] Resources Directory
+ - [x] Strong Name Signing
+ - [x] VTable Fixup Directory
+ - [x] Exception Directory (AMD64)
+ - [x] Export Directory
+ - [x] Import Directory
+ - [x] Base Relocation Directory
+ - [x] TLS Directory
+ - [x] Win32 Resources Directory
+- [x] Fully mutable object model for .NET modules that is similar to System.Reflection
+ - [x] Strong type-system with many useful factory methods for quickly constructing new metadata.
+ - [x] Full metadata importing and cloning (useful for injecting metadata into another assembly)
+ - [x] .NET Framework 2.0+, .NET Core and .NET 5+ binary file support.
+ - [x] Infer memory layout of types statically.
+ - [x] Create, read and write managed resource sets (`.resources` files)
+ - [x] Create new method bodies containing native code.
+ - [x] Highly configurable reader and writer options and custom error handling for both.
+ - [x] Rich support for AppHost and SingleFileHost bundled files.
+
+
+## Documentation
+
+Check out the [wiki](https://asmresolver.readthedocs.org/) for guides and information on how to use the library.
+
+
+## Binaries
Stable builds:
@@ -26,13 +65,7 @@ Nightly builds:
| development | [![Development branch build status](https://img.shields.io/appveyor/ci/Washi1337/AsmResolver/development.svg)](https://ci.appveyor.com/project/Washi1337/asmresolver/branch/development)
-Documentation
--------------
-Check out the [wiki](https://asmresolver.readthedocs.org/) for guides and information on how to use the library.
-
-
-Compiling
----------
+## Compiling
The solution can be build using the .NET SDK or an IDE that works with the .NET SDK (such as Visual Studio and JetBrains Rider). The main packages target .NET Standard 2.0, and the xUnit test projects target .NET Core 3.1.
@@ -47,25 +80,23 @@ To run all tests, simply run:
$ dotnet test
```
+## Contributing
-Contributing
-------------
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on general workflow and code style.
-Acknowledgements
-----------------
+## Found a bug or have questions?
-AsmResolver started out as a hobby project, but has grown into a community project with various contributors. Without these people, AsmResolver would not have been where it is today!
+Please use the [issue tracker](https://github.com/Washi1337/AsmResolver/issues). Try to be as descriptive as possible.
-- Special thanks to all the people who contributed [directly with code commits](https://github.com/Washi1337/AsmResolver/graphs/contributors).
+You can also join the [Discord](https://discord.gg/Y7DTBkbhJJ) to engage more directly with the community.
-- Another big thank you to all the people that suggested new features, provided feedback on the API design, have done extensive testing, and/or reported bugs on the [issue board](https://github.com/Washi1337/AsmResolver/issues), by e-mail, or through DMs.
+## Acknowledgements
-If you feel you have been under-represented in these acknowledgements, feel free to contact me.
+AsmResolver started out as a hobby project, but has grown into a community project with various contributors. Without these people, AsmResolver would not have been where it is today!
+- Special thanks to all the people who contributed [directly with code commits](https://github.com/Washi1337/AsmResolver/graphs/contributors).
-Found a bug or have questions?
-------------------------------
-Please use the [issue tracker](https://github.com/Washi1337/AsmResolver/issues). Try to be as descriptive as possible.
+- Another big thank you to all the people that suggested new features, provided feedback on the API design, have done extensive testing, and/or reported bugs on the [issue board](https://github.com/Washi1337/AsmResolver/issues), by e-mail, or through DMs.
+If you feel you have been under-represented in these acknowledgements, feel free to contact me.
diff --git a/appveyor.yml b/appveyor.yml
index 9b205fb48..6c4e999bc 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -3,8 +3,8 @@
only:
- master
- image: Visual Studio 2019
- version: 4.8.0-master-build.{build}
+ image: Visual Studio 2022
+ version: 4.11.2-master-build.{build}
configuration: Release
skip_commits:
@@ -23,7 +23,7 @@
deploy:
provider: NuGet
api_key:
- secure: DCeHUu0aAsOjRnoi2DpcuXpj0apD7dxHzglSamP7LGzcZjhIvTBi1ONnjIa7L2zm
+ secure: L3fXsS7umzD8zwAvTsdGxOg/E6tQ4IR4MfwBAcO8elE7ZwjZ8HO8UPwjiWbp4RMw
skip_symbols: false
artifact: /.*\.nupkg/
@@ -32,8 +32,8 @@
only:
- development
- image: Visual Studio 2019
- version: 4.8.0-dev-build.{build}
+ image: Visual Studio 2022
+ version: 5.0.0-dev-build.{build}
configuration: Release
skip_commits:
diff --git a/docs/dotnet/bundles.rst b/docs/dotnet/bundles.rst
new file mode 100644
index 000000000..670b9eb3d
--- /dev/null
+++ b/docs/dotnet/bundles.rst
@@ -0,0 +1,176 @@
+AppHost / SingleFileHost Bundles
+================================
+
+Since the release of .NET Core 3.1, it is possible to deploy .NET assemblies as a single binary. These files are executables that do not contain a traditional .NET metadata header, and run natively on the underlying operating system via a platform-specific application host bootstrapper.
+
+AsmResolver supports extracting the embedded files from these types of binaries. Additionally, given an application host template provided by the .NET SDK, AsmResolver also supports constructing new bundles as well. All relevant code is found in the following namespace:
+
+.. code-block:: csharp
+
+ using AsmResolver.DotNet.Bundles;
+
+
+Creating Bundles
+----------------
+
+.NET bundles are represented using the ``BundleManifest`` class. Creating new bundles can be done using any of the constructors:
+
+.. code-block:: csharp
+
+ var manifest = new BundleManifest(majorVersionNumber: 6);
+
+
+The major version number refers to the file format that should be used when saving the manifest. Below an overview of the values that are recognized by the CLR:
+
++----------------------+----------------------------+
+| .NET Version Number | Bundle File Format Version |
++======================+============================+
+| .NET Core 3.1 | 1 |
++----------------------+----------------------------+
+| .NET 5.0 | 2 |
++----------------------+----------------------------+
+| .NET 6.0 | 6 |
++----------------------+----------------------------+
+
+To create a new bundle with a specific bundle identifier, use the overloaded constructor
+
+.. code-block:: csharp
+
+ var manifest = new BundleManifest(6, "MyBundleID");
+
+
+It is also possible to change the version number as well as the bundle ID later, since these values are exposed as mutable properties ``MajorVersion`` and ``BundleID``
+
+.. code-block:: csharp
+
+ manifest.MajorVersion = 6;
+ manifest.BundleID = manifest.GenerateDeterministicBundleID();
+
+.. note::
+
+ If ``BundleID`` is left unset (``null``), it will be automatically assigned a new one using ``GenerateDeterministicBundleID`` upon writing.
+
+
+Reading Bundles
+---------------
+
+Reading and extracting existing bundle manifests from an executable can be done by using one of the ``FromXXX`` methods:
+
+.. code-block:: csharp
+
+ var manifest = BundleManifest.FromFile(@"C:\Path\To\Executable.exe");
+
+.. code-block:: csharp
+
+ byte[] contents = ...
+ var manifest = BundleManifest.FromBytes(contents);
+
+.. code-block:: csharp
+
+ IDataSource contents = ...
+ var manifest = BundleManifest.FromDataSource(contents);
+
+
+Similar to the official .NET bundler and extractor, the methods above locate the bundle in the file by looking for a specific signature first. However, official implementations of the application hosting program itself actually do not verify or use this signature in any shape or form. This means that a third party can replace or remove this signature, or write their own implementation of an application host that does not adhere to this standard, and thus throw off static analysis of the file.
+
+AsmResolver does not provide built-in alternative heuristics for finding the right start address of the bundle header. However, it is possible to implement one yourself and provide the resulting start address in one of the overloads of the ``FromXXX`` methods:
+
+.. code-block:: csharp
+
+ byte[] contents = ...
+ ulong bundleAddress = ...
+ var manifest = BundleManifest.FromBytes(contents, bundleAddress);
+
+.. code-block:: csharp
+
+ IDataSource contents = ...
+ ulong bundleAddress = ...
+ var manifest = BundleManifest.FromDataSource(contents, bundleAddress);
+
+
+Writing Bundles
+---------------
+
+Constructing new bundled executable files requires a template file that AsmResolver can base the final output on. This is similar how .NET compilers themselves do this as well. By default, the .NET SDK installs template binaries in one of the following directories:
+
+- ``/sdk//AppHostTemplate``
+- ``/packs/Microsoft.NETCore.App.Host.//runtimes//native``
+
+Using this template file, it is then possible to write a new bundled executable file using ``WriteUsingTemplate``:
+
+.. code-block:: csharp
+
+ BundleManifest manifest = ...
+ manifest.WriteUsingTemplate(
+ @"C:\Path\To\Output\File.exe",
+ new BundlerParameters(
+ appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe",
+ appBinaryPath: @"HelloWorld.dll"));
+
+
+Typically on Windows, use an ``apphost.exe`` template if you want to construct a native binary that is framework dependent, and ``singlefilehost.exe`` for a fully self-contained binary. On Linux, use the ``apphost`` and ``singlefilehost`` ELF equivalents.
+
+For bundle executable files targeting Windows, it may be required to copy over some values from the original PE file into the final bundle executable file. Usually these values include fields from the PE headers (such as the executable's sub-system target) and Win32 resources (such as application icons and version information). AsmResolver can automatically update these headers by specifying a source image to pull this data from in the ``BundlerParameters``:
+
+.. code-block:: csharp
+
+ BundleManifest manifest = ...
+ manifest.WriteUsingTemplate(
+ @"C:\Path\To\Output\File.exe",
+ new BundlerParameters(
+ appHostTemplatePath: @"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Host.win-x64\6.0.0\runtimes\win-x64\native\apphost.exe",
+ appBinaryPath: @"HelloWorld.dll",
+ imagePathToCopyHeadersFrom: @"C:\Path\To\Original\HelloWorld.exe"));
+
+``BundleManifest`` also defines other ```WriteUsingTemplate`` overloads taking ``byte[]``, ``IDataSource`` or ``IPEImage`` instances instead of paths.
+
+
+Managing Files
+--------------
+
+Files in a bundle are represented using the ``BundleFile`` class, and are exposed by the ``BundleManifest.Files`` property. Both the class as well as the list itself is fully mutable, and thus can be used to add, remove or modify files in the bundle.
+
+Creating a new file can be done using the constructors:
+
+.. code-block:: csharp
+
+ var newFile = new BundleFile(
+ relativePath: "HelloWorld.dll",
+ type: BundleFileType.Assembly,
+ contents: System.IO.File.ReadAllBytes(@"C:\Binaries\HelloWorld.dll"));
+
+ manifest.Files.Add(newFile);
+
+
+It is also possible to iterate over all files and inspect their contents using ``GetData``:
+
+.. code-block:: csharp
+
+ foreach (var file in manifest.Files)
+ {
+ string path = file.RelativePath;
+ byte[] contents = file.GetData();
+
+ Console.WriteLine($"Extracting {path}...");
+ System.IO.File.WriteAllBytes(path, contents);
+ }
+
+
+Changing the contents of an existing file can be done using the ``Contents`` property.
+
+.. code-block:: csharp
+
+ BundleFile file = ...
+ file.Contents = new DataSegment(new byte[] { 1, 2, 3, 4 });
+
+
+If the bundle manifest is put into a single-file host template (e.g. ``singlefilehost.exe``), then files can also be compressed or decompressed:
+
+.. code-block:: csharp
+
+ file.Compress();
+ // file.Contents now contains the compressed version of the data and file.IsCompressed = true
+
+ file.Decompress();
+ // file.Contents now contains the decompressed version of the data and file.IsCompressed = false
+
diff --git a/docs/dotnet/importing.rst b/docs/dotnet/importing.rst
index 24e4e21ad..7b1bb8dd2 100644
--- a/docs/dotnet/importing.rst
+++ b/docs/dotnet/importing.rst
@@ -5,16 +5,27 @@ Reference Importing
.NET modules use entries in the TypeRef or MemberRef tables to reference types or members from external assemblies. Importing references into the current module therefore form a key role when creating new- or modifying existing .NET modules. When a member is not imported into the current module, a ``MemberNotImportedException`` will be thrown when you are trying to create a PE image or write the module to the disk.
-AsmResolver provides the ``ReferenceImporter`` class that does most of the heavy lifting.
+AsmResolver provides the ``ReferenceImporter`` class that does most of the heavy lifting. Obtaining an instance of ``ReferenceImporter`` can be done in two ways.
-All samples in this document assume there is an instance of ``ReferenceImporter`` created using the following code:
+Either instantiate one yourself:
.. code-block:: csharp
+ ModuleDefinition module = ...
var importer = new ReferenceImporter(module);
+Or obtain the default instance that comes with every ``ModuleDefinition`` object. This avoids allocating new reference importers every time.
-Importing metadata members
+.. code-block:: csharp
+
+ ModuleDefinition module = ...
+ var importer = module.DefaultImporter;
+
+
+The example snippets that will follow in this articule assume that there is such a ``ReferenceImporter`` object instantiated using either of these two methods, and is stored in an ``importer`` variable.
+
+
+Importing existing members
--------------------------
Metadata members from external modules can be imported using the ``ReferenceImporter`` class using one of the following members:
@@ -59,18 +70,28 @@ Below an example of how to import a type definition called ``SomeType``:
ITypeDefOrRef importedType = importer.ImportType(typeToImport);
-Importing type signatures
--------------------------
+These types also implement the ``IImportable`` interface. This means you can also use the ``member.ImportWith`` method instead:
+
+.. code-block:: csharp
+
+ ModuleDefinition externalModule = ModuleDefinition.FromFile(...);
+ TypeDefinition typeToImport = externalModule.TopLevelTypes.First(t => t.Name == "SomeType");
+
+ ITypeDefOrRef importedType = typeToImport.ImportWith(importer);
+
+
+Importing existing type signatures
+----------------------------------
Type signatures can also be imported using the ``ReferenceImporter`` class, but these should be imported using the ``ImportTypeSignature`` method instead.
-.. note::
+.. note::
If a corlib type signature is imported, the appropriate type from the ``CorLibTypeFactory`` of the target module will be selected, regardless of whether CorLib versions are compatible with each other.
-Importing using reflection
---------------------------
+Importing using System.Reflection
+---------------------------------
Types and members can also be imported by passing on an instance of various ``System.Reflection`` classes.
@@ -90,22 +111,72 @@ Types and members can also be imported by passing on an instance of various ``Sy
| ``FieldInfo`` | ``ImportScope`` | ``MemberReference`` |
+---------------------------+------------------------+----------------------+
-
-There is limited support for importing compound types. Types that can be imported through reflection include:
+There is limited support for importing complex types. Types that can be imported through reflection include:
- Pointer types.
- By-reference types.
-- Array types:
- - If an array contains only one dimension, a ``SzArrayTypeSignature`` is returned. Otherwise a ``ArrayTypeSignature`` is created.
+- Array types (If an array contains only one dimension, a ``SzArrayTypeSignature`` is returned. Otherwise a ``ArrayTypeSignature`` is created).
- Generic parameters.
- Generic type instantiations.
-Instantiations of generic methods are supported.
+Instantiations of generic methods are also supported.
+
+
+Creating new references
+-----------------------
+
+Member references can also be created and imported without having direct access to its member definition or ``System.Reflection`` instance. It is possible to create new instances of ``TypeReference`` and ``MemberReference`` using the constructors, but the preferred way is to use the factory methods that allow for a more fluent syntax. Below an example on how to create a fully imported reference to ``void System.Console.WriteLine(string)``:
+
+.. code-block:: csharp
+
+ var factory = module.CorLibTypeFactory;
+ var importedMethod = factory.CorLibScope
+ .CreateTypeReference("System", "Console")
+ .CreateMemberReference("WriteLine", MethodSignature.CreateStatic(
+ factory.Void, factory.String))
+ .ImportWith(importer);
+
+ // importedMethod now references "void System.Console.WriteLine(string)"
+
+Generic type instantiations can also be created using ``MakeGenericInstanceType``:
+
+.. code-block:: csharp
+
+ ModuleDefinition module = ...
+
+ var factory = module.CorLibTypeFactory;
+ var importedMethod = factory.CorLibScope
+ .CreateTypeReference("System.Collections.Generic", "List`1")
+ .MakeGenericInstanceType(factory.Int32)
+ .ToTypeDefOrRef()
+ .CreateMemberReference("Add", MethodSignature.CreateInstance(
+ factory.Void,
+ new GenericParameterSignature(GenericParameterType.Type, 0)))
+ .ImportWith(importer);
+
+ // importedMethod now references "System.Collections.Generic.List`1.Add(!0)"
+
+
+Similarly, generic method instantiations can be constructed using ``MakeGenericInstanceMethod``:
+
+.. code-block:: csharp
+
+ ModuleDefinition module = ...
+
+ var factory = module.CorLibTypeFactory;
+ var importedMethod = factory.CorLibScope
+ .CreateTypeReference("System", "Array")
+ .CreateMemberReference("Empty", MethodSignature.CreateStatic(
+ new GenericParameterSignature(GenericParameterType.Method, 0).MakeSzArrayType(), 1))
+ .MakeGenericInstanceMethod(factory.String)
+ .ImportWith(importer);
+
+ // importedMethod now references "!0[] System.Array.Empty()"
.. _dotnet-importer-common-caveats:
-Common Caveats using the Importer
+Common Caveats using the Importer
---------------------------------
Caching and reuse of instances
@@ -116,20 +187,17 @@ The default implementation of ``ReferenceImporter`` does not maintain a cache. E
Importing cross-framework versions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The ``ReferenceImporter`` does not support importing across different versions of the target framework. Members are being imported as-is, and are not automatically adjusted to conform with other versions of a library.
+The ``ReferenceImporter`` does not support importing across different versions of the target framework. Members are being imported as-is, and are not automatically adjusted to conform with other versions of a library.
-As a result, trying to import from for example a library part of the .NET Framework into a module targeting .NET Core or vice versa has a high chance of producing an invalid .NET binary that cannot be executed by the runtime. For example, attempting to import a reference to ``[System.Runtime] System.DateTime`` into a module targeting .NET Framework will result in a new reference targeting a .NET Core library (``System.Runtime``) as opposed to the appropriate .NET Framework library (``mscorlib``).
+As a result, trying to import from for example a library part of the .NET Framework into a module targeting .NET Core or vice versa has a high chance of producing an invalid .NET binary that cannot be executed by the runtime. For example, attempting to import a reference to ``[System.Runtime] System.DateTime`` into a module targeting .NET Framework will result in a new reference targeting a .NET Core library (``System.Runtime``) as opposed to the appropriate .NET Framework library (``mscorlib``).
This is a common mistake when trying to import using metadata provided by ``System.Reflection``. For example, if the host application that uses AsmResolver targets .NET Core but the input file is targeting .NET Framework, then you will run in the exact issue described in the above.
.. code-block:: csharp
- var targetModule = ModuleDefinition.FromFile(...);
- var importer = new ReferenceImporter(targetModule);
-
var reference = importer.ImportType(typeof(DateTime));
// `reference` will target `[mscorlib] System.DateTime` when running on .NET Framework, and `[System.Runtime] System.DateTime` when running on .NET Core.
-Therefore, always make sure you are importing from a .NET module that is compatible with the target .NET module.
\ No newline at end of file
+Therefore, always make sure you are importing from a .NET module that is compatible with the target .NET module.
diff --git a/docs/dotnet/managed-method-bodies.rst b/docs/dotnet/managed-method-bodies.rst
index db72bba4b..281be6f31 100644
--- a/docs/dotnet/managed-method-bodies.rst
+++ b/docs/dotnet/managed-method-bodies.rst
@@ -90,14 +90,14 @@ By default, depending on the value of ``OpCode.OperandType``, ``Operand`` contai
+----------------------------------------+----------------------------------------------+
.. warning::
-
+
Providing an incorrect operand type will result in the CIL assembler to fail assembling the method body upon writing the module to the disk.
Creating a new instruction can be done using one of the constructors, together with the ``CilOpCodes`` static class:
-.. code-block:: csharp
+.. code-block:: csharp
- body.Instructions.AddRange(new[]
+ body.Instructions.AddRange(new[]
{
new CilInstruction(CilOpCodes.Ldstr, "Hello, World!"),
new CilInstruction(CilOpCodes.Ret),
@@ -105,7 +105,7 @@ Creating a new instruction can be done using one of the constructors, together w
However, the preferred way of adding instructions to add or insert new instructions is to use one of the ``Add`` or ``Insert`` overloads that directly take an opcode and operand. This is because it avoids an allocation of an array, and the overloads perform immediate validation on the created instruction.
-.. code-block:: csharp
+.. code-block:: csharp
var instructions = body.Instructions;
instructions.Add(CilOpCodes.Ldstr, "Hello, World!");
@@ -115,16 +115,16 @@ However, the preferred way of adding instructions to add or insert new instructi
Pushing 32-bit integer constants onto the stack
-----------------------------------------------
-In CIL, pushing integer constants onto the stack is done using one of the ``ldc.i4`` instruction variants.
+In CIL, pushing integer constants onto the stack is done using one of the ``ldc.i4`` instruction variants.
The recommended way to create such an instruction is not to use the constructor, but instead use the ``CilInstruction.CreateLdcI4(int)`` method instead. This automatically selects the smallest possible opcode possible and sets the operand accordingly:
-.. code-block:: csharp
+.. code-block:: csharp
CilInstruction push1 = CilInstruction.CreateLdcI4(1); // Returns "ldc.i4.1" macro
CilInstruction pushShort = CilInstruction.CreateLdcI4(123); // Returns "ldc.i4.s 123" macro
CilInstruction pushLarge = CilInstruction.CreateLdcI4(12345678); // Returns "ldc.i4 12345678"
-
+
If we want to get the pushed value, we can use the ``CilInstruction.GetLdcI4Constant()`` method. This method works on any of the ``ldc.i4`` variants, including all the macro opcodes that do not explicitly define an operand such as ``ldc.i4.1``.
@@ -133,7 +133,7 @@ Branching Instructions
Branch instructions are instructions that (might) transfer control to another part of the method body. To reference the instruction to jump to (the branch target), ``ICilLabel`` is used. The easiest way to create such a label is to use the ``CreateLabel()`` function on the instruction to reference:
-.. code-block:: csharp
+.. code-block:: csharp
CilInstruction targetInstruction = ...
ICilLabel label = targetInstruction.CreateLabel();
@@ -142,7 +142,7 @@ Branch instructions are instructions that (might) transfer control to another pa
Alternatively, when using the ``Add`` or ``Insert`` overloads, it is possible to use the return value of these overloads.
-.. code-block:: csharp
+.. code-block:: csharp
var instructions = body.Instructions;
var label = new CilInstructionLabel();
@@ -150,8 +150,8 @@ Alternatively, when using the ``Add`` or ``Insert`` overloads, it is possible to
instructions.Add(CilOpCodes.Br, label);
/* ... */
label.Instruction = instruction.Add(CilOpCodes.Ret);
-
-
+
+
The ``switch`` operation uses a ``IList`` instead.
.. note::
@@ -159,10 +159,10 @@ The ``switch`` operation uses a ``IList`` instead.
When a branching instruction contains a ``null`` label or a label that references an instruction that is not present in the method body, AsmResolver will by default report an exception upon serializing the code stream. This can be disabled by setting ``VerifyLabelsOnBuild`` to ``false``.
-Finding instructions by offset
+Finding instructions by offset
------------------------------
-Instructions stored in a method body are indexed not by offset, but by order of occurrence. If it is required to find an instruction by offset, it is possible to use the ``Instructions.GetByOffset(int)`` method, which performs a binary search (O(log(n))) and is faster than a linear search (O(n)) such as a for loop or using a construction like ``.First(i => i.Offset == offset)`` provided by ``System.Linq``.
+Instructions stored in a method body are indexed not by offset, but by order of occurrence. If it is required to find an instruction by offset, it is possible to use the ``Instructions.GetByOffset(int)`` method, which performs a binary search (O(log(n))) and is faster than a linear search (O(n)) such as a for loop or using a construction like ``.First(i => i.Offset == offset)`` provided by ``System.Linq``.
For ``GetByOffset`` to work, it is required that all offsets in the instruction collection are up to date. Recalculating all offsets within an instruction collection can be done through ``Instructions.CalculateOffsets()``.
@@ -180,12 +180,12 @@ For ``GetByOffset`` to work, it is required that all offsets in the instruction
instruction1 = body.Instructions[index];
-Referencing members
+Referencing members
-------------------
As specified by the table above, operations such as a ``call`` require a member as operand.
-It is important that the member referenced in the operand of such an instruction is imported in the module. This can be done using the ``ReferenceImporter`` class.
+It is important that the member referenced in the operand of such an instruction is imported in the module. This can be done using the ``ReferenceImporter`` class.
Below an example on how to use the ``ReferenceImporter`` to emit a call to ``Console::WriteLine(string)`` using reflection:
@@ -238,7 +238,44 @@ Instructions can be formatted using e.g. an instance of the ``CilInstructionForm
Console.WriteLine(formatter.FormatInstruction(instruction));
-Exception handlers
+Patching CIL instructions
+-------------------------
+
+Instructions can be added or removed using the ``Add``, ``Insert``, ``Remove`` and ``RemoveAt`` methods:
+
+.. code-block:: csharp
+
+ body.Instructions.Add(CilOpCodes.Ldstr, "Hello, world!");
+ body.Instructions.Insert(i, CilOpCodes.Ldc_I4, 1234);
+ body.Instructions.RemoveAt(i);
+
+... or by using the indexer to replace existing instructions:
+
+.. code-block:: csharp
+
+ body.Instructions[i] = new CilInstruction(CilOpCodes.Ret);
+
+Removing or replacing instructions may not always be favourable. The original ``CilInstruction`` object might be used as a reference for a branch target or exception handler boundary. Removing or replacing these ``CilInstruction`` objects would therefore break these kinds of references, rendering the body invalid. Rather than updating all references manually, it may therefore be wiser to reuse the ``CilInstruction`` object and simply modify the ``OpCode`` and ``Operand`` properties instead:
+
+.. code-block:: csharp
+
+ body.Instructions[i].OpCode = CilOpCodes.Ldc_I4;
+ body.Instructions[i].Operand = 1234;
+
+AsmResolver provides a helper function ``ReplaceWith`` that shortens the code into a single line:
+
+.. code-block:: csharp
+
+ body.Instructions[i].ReplaceWith(CilOpCodes.Ldc_I4, 1234);
+
+Since it is very common to replace instructions with a `nop`, AsmResolver also defines a special ``ReplaceWithNop`` helper function:
+
+.. code-block:: csharp
+
+ body.Instructions[i].ReplaceWithNop();
+
+
+Exception handlers
------------------
Exception handlers are regions in the method body that are protected from exceptions. In AsmResolver, they are represented by the ``CilExceptionHandler`` class, and define the following properties:
@@ -267,4 +304,4 @@ The max stack can be computed by using the ``ComputeMaxStack`` method. By defaul
.. note::
- If a ``StackImbalanceException`` is thrown upon writing the module to the disk, or upon calling ``ComputeMaxStack``, it means that not all execution paths in the provided CIL code push or pop the expected amount of values. It is a good indication that the provided CIL code is invalid.
\ No newline at end of file
+ If a ``StackImbalanceException`` is thrown upon writing the module to the disk, or upon calling ``ComputeMaxStack``, it means that not all execution paths in the provided CIL code push or pop the expected amount of values. It is a good indication that the provided CIL code is invalid.
diff --git a/docs/dotnet/unmanaged-method-bodies.rst b/docs/dotnet/unmanaged-method-bodies.rst
index dee5d9c5e..c5edc20d3 100644
--- a/docs/dotnet/unmanaged-method-bodies.rst
+++ b/docs/dotnet/unmanaged-method-bodies.rst
@@ -20,7 +20,7 @@ Allowing native code in modules
To make the CLR treat the output file as a mixed mode application, the ``ILOnly`` flag needs to be unset:
-.. code-block:: csharp
+.. code-block:: csharp
ModuleDefinition module = ...
module.Attributes &= ~DotNetDirectoryFlags.ILOnly;
@@ -80,7 +80,7 @@ In the following sections, we will briefly go over each of them.
Writing native code
-------------------
-The contents of a native method body can be set through the ``Code`` property. This is a ``byte[]`` that represents the raw code stream to be executed. Below an example of a simple method body written in x86 64-bit assembly code, that returns the constant ``0x1337``:
+The contents of a native method body can be set through the ``Code`` property. This is a ``byte[]`` that represents the raw code stream to be executed. Below an example of a simple method body written in x86 64-bit assembly code, that returns the constant ``1337``:
.. code-block:: csharp
@@ -92,14 +92,20 @@ The contents of a native method body can be set through the ``Code`` property. T
.. note::
-
+
Since native method bodies are platform dependent, AsmResolver does not provide a standard way to encode these instructions. To construct the byte array that you need for a particular implementation of a method body, consider using a third-party assembler or assembler library.
-References to external symbols
-------------------------------
+Symbols and Address Fixups
+--------------------------
+
+In a lot of cases, native method bodies that references symbols (such as imported functions) require direct addresses to be referenced within its instructions. Since the addresses of these symbols are not known yet upon creating a ``NativeMethodBody``, it is not possible to encode such an operand directly in the ``Code`` byte array. To support these kinds of references regardless, AsmResolver can be instructed to apply address fixups just before writing the body to the disk. These instructions are essentially small pieces of information that tell AsmResolver that at a particular offset the bytes should be replaced with a reference to a symbol in the final PE. This can be applied to any object that implements ``ISymbol``. In the following, two of the most commonly used symbols will be discussed.
+
+
+Imported Symbols
+~~~~~~~~~~~~~~~~
-In a lot of cases, methods require making calls to functions defined in external libraries and native method bodies are no exception. In the PE file format, these kinds of symbols are often put into the imports directory. This is essentially a table of names that the Windows PE loader will go through, look up the actual address of each name, and put it in the import address table. Typically, when a piece of code is meant to make a call to an external function, the code will make an indirect call to an entry stored in this table. In x86 64-bit, using nasm syntax, a call to the ``puts`` function might look like the following snippet:
+In the PE file format, symbols from external modules are often imported by placing an entry into the imports directory. This is essentially a table of names that the Windows PE loader will go through, look up the actual address of each name, and put it in the import address table. Typically, when a piece of code is meant to make a call to an external function, the code will make an indirect call to an entry stored in this table. In x86 64-bit, using nasm syntax, a call to the ``puts`` function might look like the following snippet:
.. code-block:: csharp
@@ -108,10 +114,6 @@ In a lot of cases, methods require making calls to functions defined in external
call qword [rel puts]
...
-Since the import directory is not constructed yet when we are operating on the abstraction level of a ``ModuleDefinition``, the address of the import address entry is still unknown. Therefore, it is not possible to encode an operand like the one in the call instruction of the above example.
-
-To support these kinds of references in native method bodies regardless, it is possible to instruct AsmResolver to apply address fixups just before writing the body to the disk. These are essentially small pieces of information that tell AsmResolver that at a particular offset the bytes should be replaced with a reference to a symbol in the final PE.
-
Consider the following example x86 64-bit code, that is printing the text ``Hello from the unmanaged world!`` to the standard output stream using the ``puts`` function.
.. code-block:: csharp
@@ -135,16 +137,16 @@ Consider the following example x86 64-bit code, that is printing the text ``Hell
0x67, 0x65, 0x64, 0x20, 0x77, 0x6f, 0x72, // "ged wor"
0x6c, 0x64, 0x21, 0x00 // "ld!"
};
-
-Notice how the operand of the call instruction is left at zero (`0x00`) bytes. To let AsmResolver know that these 4 bytes are to be replaced by an address to an entry in the import address table, we first create a new instance of ``ImportedSymbol``, representing the ``puts`` symbol:
+
+Notice how the operand of the ``call`` instruction is left at zero (``0x00``) bytes. To let AsmResolver know that these 4 bytes are to be replaced by an address to an entry in the import address table, we first create a new instance of ``ImportedSymbol``, representing the ``puts`` symbol:
.. code-block:: csharp
var ucrtbased = new ImportedModule("ucrtbased.dll");
var puts = new ImportedSymbol(0x4fc, "puts");
ucrtbased.Symbols.Add(puts);
-
+
We can then add it as a fixup to the method body:
@@ -155,12 +157,49 @@ We can then add it as a fixup to the method body:
));
-The type of fixup that is required will depend on the architecture and instruction that is used. Below an overview:
+Local Symbols
+~~~~~~~~~~~~~
+
+If a native body is supposed to process or return some data that is defined within the body itself, the ``NativeLocalSymbol`` class can be used.
+
+Consider the following example x86 32-bit snippet, that returns the virtual address of a string.
+
+.. code-block:: csharp
+
+ 0xB8, 0x00, 0x00, 0x00, 0x00 // mov eax, message
+ 0xc3, // ret
+
+ // message (unicode):
+ 0x48, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x2c, 0x00, 0x20, 0x00, // "Hello, "
+ 0x77, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6c, 0x00, 0x64, 0x00, 0x21, 0x00, 0x00, 0x00 // "world!."
+
+
+Notice how the operand of the ``mov`` instruction is left at zero (``0x00``) bytes. To let AsmResolver know that these 4 bytes are to be replaced by the actual virtual address to ``message``, we can define a local symbol and register an address fixup in the following manner:
+
+.. code-block:: csharp
+
+ var message = new NativeLocalSymbol(body, offset: 0x6);
+ body.AddressFixups.Add(new AddressFixup(
+ 0x1, AddressFixupType.Absolute32BitAddress, message
+ ));
+
+
+.. warning::
+
+ The ``NativeLocalSymbol`` can only be used within the code of the native method body itself. This is due to the fact that these types of symbols are not processed further after serializing a ``NativeMethodBody`` to a ``CodeSegment`` by the default method body serializer.
+
+
+Fixup Types
+~~~~~~~~~~~
+
+The type of fixup that is required will depend on the architecture and instruction that is used. Below an overview of all fixups that AsmResolver is able to apply:
+--------------------------+-----------------------------------------------------------------------+---------------------------------+
| Fixup type | Description | Example instructions |
+==========================+=======================================================================+=================================+
-| ``Absolute32BitAddress`` | The operand is an absolute virtual address | ``call dword [address]`` |
+| ``Absolute32BitAddress`` | The operand is a 32-bit absolute virtual address | ``call dword [address]`` |
++--------------------------+-----------------------------------------------------------------------+---------------------------------+
+| ``Absolute64BitAddress`` | The operand is a 64-bit absolute virtual address | ``mov rax, address`` |
+--------------------------+-----------------------------------------------------------------------+---------------------------------+
| ``Relative32BitAddress`` | The operand is an address relative to the current instruction pointer | ``call qword [rip+offset]`` |
+--------------------------+-----------------------------------------------------------------------+---------------------------------+
diff --git a/docs/index.rst b/docs/index.rst
index b3fee3b95..89f3b0aeb 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -55,10 +55,12 @@ Table of Contents:
dotnet/member-tree
dotnet/type-signatures
dotnet/importing
+ dotnet/methods
dotnet/managed-method-bodies
dotnet/unmanaged-method-bodies
dotnet/managed-resources
dotnet/cloning
dotnet/token-allocation
dotnet/type-memory-layout
- dotnet/advanced-pe-image-building.rst
\ No newline at end of file
+ dotnet/bundles
+ dotnet/advanced-pe-image-building.rst
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 000000000..bebbea9fa
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,2 @@
+!AsmResolver.Symbols.Pdb/
+
diff --git a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj
index 46d0b6a32..456ce3119 100644
--- a/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj
+++ b/src/AsmResolver.DotNet/AsmResolver.DotNet.csproj
@@ -1,14 +1,13 @@
-
+
AsmResolver.DotNet
High level .NET image models for the AsmResolver executable file inspection toolsuite.
exe pe directories imports exports resources dotnet cil inspection manipulation assembly disassembly
- netstandard2.0
true
1701;1702;NU5105
- 9
enable
+ net6.0;netcoreapp3.1;netstandard2.0
@@ -28,7 +27,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/src/AsmResolver.DotNet/AssemblyDefinition.cs b/src/AsmResolver.DotNet/AssemblyDefinition.cs
index e99d44242..fabe500a6 100644
--- a/src/AsmResolver.DotNet/AssemblyDefinition.cs
+++ b/src/AsmResolver.DotNet/AssemblyDefinition.cs
@@ -222,6 +222,13 @@ protected virtual IList GetModules()
return _publicKeyToken;
}
+ ///
+ public override bool IsImportedInModule(ModuleDefinition module) => ManifestModule == module;
+
+ ///
+ public override AssemblyReference ImportWith(ReferenceImporter importer) =>
+ (AssemblyReference) importer.ImportScope(new AssemblyReference(this));
+
///
public override AssemblyDefinition Resolve() => this;
@@ -280,11 +287,14 @@ public void Write(string filePath, IPEImageBuilder imageBuilder, IPEFileBuilder
if (directory is null || !Directory.Exists(directory))
throw new DirectoryNotFoundException();
- foreach (var module in Modules)
+ for (int i = 0; i < Modules.Count; i++)
{
- string modulePath = module == ManifestModule
- ? filePath
- : Path.Combine(directory, module.Name);
+ var module = Modules[i];
+ string modulePath;
+ if (module == ManifestModule)
+ modulePath = filePath;
+ else
+ modulePath = Path.Combine(directory, module.Name ?? $"module{i}.bin");
module.Write(modulePath, imageBuilder, fileBuilder);
}
diff --git a/src/AsmResolver.DotNet/AssemblyDescriptor.cs b/src/AsmResolver.DotNet/AssemblyDescriptor.cs
index 248812ba8..5d19e5b42 100644
--- a/src/AsmResolver.DotNet/AssemblyDescriptor.cs
+++ b/src/AsmResolver.DotNet/AssemblyDescriptor.cs
@@ -2,18 +2,20 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using System.Reflection;
using System.Security.Cryptography;
using System.Threading;
using AsmResolver.Collections;
using AsmResolver.PE.DotNet.Metadata.Tables;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
+using AssemblyHashAlgorithm = AsmResolver.PE.DotNet.Metadata.Tables.Rows.AssemblyHashAlgorithm;
namespace AsmResolver.DotNet
{
///
/// Provides a base implementation for describing a self-describing .NET assembly hosted by a common language runtime (CLR).
///
- public abstract class AssemblyDescriptor : MetadataMember, IHasCustomAttribute, IFullNameProvider
+ public abstract class AssemblyDescriptor : MetadataMember, IHasCustomAttribute, IFullNameProvider, IImportable
{
private const int PublicKeyTokenLength = 8;
@@ -29,7 +31,7 @@ protected AssemblyDescriptor(MetadataToken token)
: base(token)
{
_name = new LazyVariable(GetName);
- _culture = new LazyVariable(() => GetCulture());
+ _culture = new LazyVariable(GetCulture);
Version = new Version(0, 0, 0, 0);
}
@@ -212,6 +214,19 @@ public IList CustomAttributes
///
public override string ToString() => FullName;
+ ///
+ public abstract bool IsImportedInModule(ModuleDefinition module);
+
+ ///
+ /// Imports the assembly descriptor using the provided reference importer.
+ ///
+ /// The importer object to use.
+ /// The imported assembly reference.
+ public abstract AssemblyReference ImportWith(ReferenceImporter importer);
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer);
+
///
/// Computes the token of a public key using the provided hashing algorithm.
///
diff --git a/src/AsmResolver.DotNet/AssemblyReference.cs b/src/AsmResolver.DotNet/AssemblyReference.cs
index b5548144e..d81b2b9c9 100644
--- a/src/AsmResolver.DotNet/AssemblyReference.cs
+++ b/src/AsmResolver.DotNet/AssemblyReference.cs
@@ -147,6 +147,13 @@ public AssemblyReference(AssemblyDescriptor descriptor)
///
protected virtual byte[]? GetHashValue() => null;
+ ///
+ public override bool IsImportedInModule(ModuleDefinition module) => Module == module;
+
+ ///
+ public override AssemblyReference ImportWith(ReferenceImporter importer) =>
+ (AssemblyReference) importer.ImportScope(this);
+
///
public override AssemblyDefinition? Resolve() => Module?.MetadataResolver.AssemblyResolver.Resolve(this);
diff --git a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs
index d6f233046..d6eeac3c6 100644
--- a/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs
+++ b/src/AsmResolver.DotNet/Builder/Discovery/MemberDiscoverer.cs
@@ -343,7 +343,7 @@ private FieldDefinition AddPlaceHolderField(TypeDefinition placeHolderType, Meta
var placeHolderField = new FieldDefinition(
$"PlaceHolderField_{token.Rid.ToString()}",
FieldPlaceHolderAttributes,
- FieldSignature.CreateStatic(_module.CorLibTypeFactory.Object));
+ _module.CorLibTypeFactory.Object);
// Add the field to the type.
placeHolderType.Fields.Add(placeHolderField);
diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs
index 37b5c136b..2aeee551a 100644
--- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs
+++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.CodedIndices.cs
@@ -26,16 +26,16 @@ private void AddCustomAttribute(MetadataToken ownerToken, CustomAttribute attrib
table.Add(attribute, row);
}
- private uint AddResolutionScope(IResolutionScope? scope)
+ private uint AddResolutionScope(IResolutionScope? scope, bool allowDuplicates, bool preserveRid)
{
if (!AssertIsImported(scope))
return 0;
var token = scope.MetadataToken.Table switch
{
- TableIndex.AssemblyRef => GetAssemblyReferenceToken(scope as AssemblyReference),
- TableIndex.TypeRef => GetTypeReferenceToken(scope as TypeReference),
- TableIndex.ModuleRef => GetModuleReferenceToken(scope as ModuleReference),
+ TableIndex.AssemblyRef => AddAssemblyReference(scope as AssemblyReference, allowDuplicates, preserveRid),
+ TableIndex.TypeRef => AddTypeReference(scope as TypeReference, allowDuplicates, preserveRid),
+ TableIndex.ModuleRef => AddModuleReference(scope as ModuleReference, allowDuplicates, preserveRid),
TableIndex.Module => 0,
_ => throw new ArgumentOutOfRangeException(nameof(scope))
};
diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs
index ff010957a..617253eef 100644
--- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs
+++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.MemberTree.cs
@@ -132,6 +132,9 @@ public void DefineTypes(IEnumerable types)
var typeDefTable = Metadata.TablesStream.GetTable(TableIndex.TypeDef);
var nestedClassTable = Metadata.TablesStream.GetSortedTable(TableIndex.NestedClass);
+ if (types is ICollection collection)
+ typeDefTable.EnsureCapacity(typeDefTable.Count + collection.Count);
+
foreach (var type in types)
{
// At this point, we might not have added all type defs/refs/specs yet, so we cannot determine
@@ -177,6 +180,8 @@ public void DefineTypes(IEnumerable types)
public void DefineFields(IEnumerable fields)
{
var table = Metadata.TablesStream.GetTable(TableIndex.Field);
+ if (fields is ICollection collection)
+ table.EnsureCapacity(table.Count + collection.Count);
foreach (var field in fields)
{
@@ -197,6 +202,8 @@ public void DefineFields(IEnumerable fields)
public void DefineMethods(IEnumerable methods)
{
var table = Metadata.TablesStream.GetTable(TableIndex.Method);
+ if (methods is ICollection collection)
+ table.EnsureCapacity(table.Count + collection.Count);
foreach (var method in methods)
{
@@ -227,6 +234,8 @@ public void DefineMethods(IEnumerable methods)
public void DefineParameters(IEnumerable parameters)
{
var table = Metadata.TablesStream.GetTable(TableIndex.Param);
+ if (parameters is ICollection collection)
+ table.EnsureCapacity(table.Count + collection.Count);
foreach (var parameter in parameters)
{
@@ -247,6 +256,8 @@ public void DefineParameters(IEnumerable parameters)
public void DefineProperties(IEnumerable properties)
{
var table = Metadata.TablesStream.GetTable(TableIndex.Property);
+ if (properties is ICollection collection)
+ table.EnsureCapacity(table.Count + collection.Count);
foreach (var property in properties)
{
@@ -267,6 +278,8 @@ public void DefineProperties(IEnumerable properties)
public void DefineEvents(IEnumerable events)
{
var table = Metadata.TablesStream.GetTable(TableIndex.Event);
+ if (events is ICollection collection)
+ table.EnsureCapacity(table.Count + collection.Count);
foreach (var @event in events)
{
@@ -299,10 +312,21 @@ public void FinalizeTypes()
uint propertyList = 1;
uint eventList = 1;
+ tablesStream.GetTable(TableIndex.FieldPtr)
+ .EnsureCapacity(tablesStream.GetTable(TableIndex.Field).Count);
+ tablesStream.GetTable(TableIndex.MethodPtr)
+ .EnsureCapacity(tablesStream.GetTable(TableIndex.Method).Count);
+ tablesStream.GetTable(TableIndex.ParamPtr)
+ .EnsureCapacity(tablesStream.GetTable(TableIndex.Param).Count);
+ tablesStream.GetTable(TableIndex.PropertyPtr)
+ .EnsureCapacity(tablesStream.GetTable(TableIndex.Property).Count);
+ tablesStream.GetTable(TableIndex.EventPtr)
+ .EnsureCapacity(tablesStream.GetTable(TableIndex.Event).Count);
+
for (uint rid = 1; rid <= typeDefTable.Count; rid++)
{
var typeToken = new MetadataToken(TableIndex.TypeDef, rid);
- var type = _tokenMapping.GetTypeByToken(typeToken);
+ var type = _tokenMapping.GetTypeByToken(typeToken)!;
// Update extends, field list and method list columns.
ref var typeRow = ref typeDefTable.GetRowRef(rid);
@@ -409,7 +433,7 @@ private void FinalizeMethods(ref bool paramPtrRequired)
for (uint rid = 1; rid <= definitionTable.Count; rid++)
{
var newToken = new MetadataToken(TableIndex.Method, rid);
- var method = _tokenMapping.GetMethodByToken(newToken);
+ var method = _tokenMapping.GetMethodByToken(newToken)!;
// Serialize method body and update column.
ref var row = ref definitionTable.GetRowRef(rid);
diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs
index 4615f1005..b3eca662b 100644
--- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs
+++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryBuffer.TokenProvider.cs
@@ -11,17 +11,37 @@ public partial class DotNetDirectoryBuffer : IMetadataTokenProvider
///
public MetadataToken GetTypeReferenceToken(TypeReference? type)
+ {
+ return AddTypeReference(type, false, false);
+ }
+
+ ///
+ /// Adds a type reference to the buffer.
+ ///
+ /// The reference to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ ///
+ ///
+ /// true if the metadata token of the type should be preserved, false otherwise.
+ ///
+ /// The newly assigned metadata token.
+ public MetadataToken AddTypeReference(TypeReference? type, bool allowDuplicates, bool preserveRid)
{
if (!AssertIsImported(type))
return MetadataToken.Zero;
- var table = Metadata.TablesStream.GetTable(TableIndex.TypeRef);
+ var table = Metadata.TablesStream.GetDistinctTable(TableIndex.TypeRef);
var row = new TypeReferenceRow(
- AddResolutionScope(type.Scope),
+ AddResolutionScope(type.Scope, allowDuplicates, preserveRid),
Metadata.StringsStream.GetStringIndex(type.Name),
Metadata.StringsStream.GetStringIndex(type.Namespace));
- var token = table.Add(row);
+ var token = preserveRid
+ ? table.Insert(type.MetadataToken.Rid, row, allowDuplicates)
+ : table.Add(row, allowDuplicates);
+
_tokenMapping.Register(type, token);
AddCustomAttributes(token, type);
return token;
@@ -90,17 +110,31 @@ public MetadataToken GetEventDefinitionToken(EventDefinition? @event)
///
public MetadataToken GetMemberReferenceToken(MemberReference? member)
+ {
+ return AddMemberReference(member, false);
+ }
+
+ ///
+ /// Adds a member reference to the buffer.
+ ///
+ /// The reference to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ ///
+ /// The newly assigned metadata token.
+ public MetadataToken AddMemberReference(MemberReference? member, bool allowDuplicates)
{
if (!AssertIsImported(member))
return MetadataToken.Zero;
- var table = Metadata.TablesStream.GetTable(TableIndex.MemberRef);
+ var table = Metadata.TablesStream.GetDistinctTable(TableIndex.MemberRef);
var row = new MemberReferenceRow(
AddMemberRefParent(member.Parent),
Metadata.StringsStream.GetStringIndex(member.Name),
Metadata.BlobStream.GetBlobIndex(this, member.Signature, ErrorListener));
- var token = table.Add(row);
+ var token = table.Add(row, allowDuplicates);
_tokenMapping.Register(member, token);
AddCustomAttributes(token, member);
return token;
@@ -108,15 +142,29 @@ public MetadataToken GetMemberReferenceToken(MemberReference? member)
///
public MetadataToken GetStandAloneSignatureToken(StandAloneSignature? signature)
+ {
+ return AddStandAloneSignature(signature, false);
+ }
+
+ ///
+ /// Adds a stand-alone signature to the buffer.
+ ///
+ /// The signature to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ ///
+ /// The newly assigned metadata token.
+ public MetadataToken AddStandAloneSignature(StandAloneSignature? signature, bool allowDuplicates)
{
if (signature is null)
return MetadataToken.Zero;
- var table = Metadata.TablesStream.GetTable(TableIndex.StandAloneSig);
+ var table = Metadata.TablesStream.GetDistinctTable(TableIndex.StandAloneSig);
var row = new StandAloneSignatureRow(
Metadata.BlobStream.GetBlobIndex(this, signature.Signature, ErrorListener));
- var token = table.Add(row);
+ var token = table.Add(row, allowDuplicates);
_tokenMapping.Register(signature, token);
AddCustomAttributes(token, signature);
return token;
@@ -124,11 +172,28 @@ public MetadataToken GetStandAloneSignatureToken(StandAloneSignature? signature)
///
public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly)
+ {
+ return AddAssemblyReference(assembly, false, false);
+ }
+
+ ///
+ /// Adds an assembly reference to the buffer.
+ ///
+ /// The reference to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ ///
+ ///
+ /// true if the metadata token of the assembly should be preserved, false otherwise.
+ ///
+ /// The newly assigned metadata token.
+ public MetadataToken AddAssemblyReference(AssemblyReference? assembly, bool allowDuplicates, bool preserveRid)
{
if (assembly is null || !AssertIsImported(assembly))
return MetadataToken.Zero;
- var table = Metadata.TablesStream.GetTable(TableIndex.AssemblyRef);
+ var table = Metadata.TablesStream.GetDistinctTable(TableIndex.AssemblyRef);
var row = new AssemblyReferenceRow((ushort) assembly.Version.Major,
(ushort) assembly.Version.Minor,
@@ -140,7 +205,10 @@ public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly)
Metadata.StringsStream.GetStringIndex(assembly.Culture),
Metadata.BlobStream.GetBlobIndex(assembly.HashValue));
- var token = table.Add(row);
+ var token = preserveRid
+ ? table.Insert(assembly.MetadataToken.Rid, row, allowDuplicates)
+ : table.Add(row, allowDuplicates);
+
AddCustomAttributes(token, assembly);
return token;
}
@@ -151,28 +219,62 @@ public MetadataToken GetAssemblyReferenceToken(AssemblyReference? assembly)
/// The reference to add.
/// The new metadata token assigned to the module reference.
public MetadataToken GetModuleReferenceToken(ModuleReference? reference)
+ {
+ return AddModuleReference(reference, false, false);
+ }
+
+ ///
+ /// Adds a module reference to the buffer.
+ ///
+ /// The reference to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ ///
+ ///
+ /// true if the metadata token of the module should be preserved, false otherwise.
+ ///
+ /// The newly assigned metadata token.
+ public MetadataToken AddModuleReference(ModuleReference? reference, bool allowDuplicates, bool preserveRid)
{
if (!AssertIsImported(reference))
return MetadataToken.Zero;
- var table = Metadata.TablesStream.GetTable(TableIndex.ModuleRef);
+ var table = Metadata.TablesStream.GetDistinctTable(TableIndex.ModuleRef);
var row = new ModuleReferenceRow(Metadata.StringsStream.GetStringIndex(reference.Name));
- var token = table.Add(row);
+ var token = preserveRid
+ ? table.Insert(reference.MetadataToken.Rid, row, allowDuplicates)
+ : table.Add(row, allowDuplicates);
+
AddCustomAttributes(token, reference);
return token;
}
///
public MetadataToken GetTypeSpecificationToken(TypeSpecification? type)
+ {
+ return AddTypeSpecification(type, false);
+ }
+
+ ///
+ /// Adds a type specification to the buffer.
+ ///
+ /// The specification to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ ///
+ /// The newly assigned metadata token.
+ public MetadataToken AddTypeSpecification(TypeSpecification? type, bool allowDuplicates)
{
if (!AssertIsImported(type))
return MetadataToken.Zero;
- var table = Metadata.TablesStream.GetTable(TableIndex.TypeSpec);
+ var table = Metadata.TablesStream.GetDistinctTable(TableIndex.TypeSpec);
var row = new TypeSpecificationRow(Metadata.BlobStream.GetBlobIndex(this, type.Signature, ErrorListener));
- var token = table.Add(row);
+ var token = table.Add(row, allowDuplicates);
_tokenMapping.Register(type, token);
AddCustomAttributes(token, type);
return token;
@@ -180,16 +282,30 @@ public MetadataToken GetTypeSpecificationToken(TypeSpecification? type)
///
public MetadataToken GetMethodSpecificationToken(MethodSpecification? method)
+ {
+ return AddMethodSpecification(method, false);
+ }
+
+ ///
+ /// Adds a method specification to the buffer.
+ ///
+ /// The specification to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ ///
+ /// The newly assigned metadata token.
+ public MetadataToken AddMethodSpecification(MethodSpecification? method, bool allowDuplicates)
{
if (!AssertIsImported(method))
return MetadataToken.Zero;
- var table = Metadata.TablesStream.GetTable(TableIndex.MethodSpec);
+ var table = Metadata.TablesStream.GetDistinctTable(TableIndex.MethodSpec);
var row = new MethodSpecificationRow(
AddMethodDefOrRef(method.Method),
Metadata.BlobStream.GetBlobIndex(this, method.Signature, ErrorListener));
- var token = table.Add(row);
+ var token = table.Add(row, allowDuplicates);
_tokenMapping.Register(method, token);
AddCustomAttributes(token, method);
return token;
diff --git a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs
index 8def5c196..7c0619f9f 100644
--- a/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs
+++ b/src/AsmResolver.DotNet/Builder/DotNetDirectoryFactory.cs
@@ -84,7 +84,7 @@ public IMethodBodySerializer MethodBodySerializer
// When specified, import existing AssemblyRef, ModuleRef, TypeRef and MemberRef prior to adding any other
// member reference or definition, to ensure that they are assigned their original RIDs.
- ImportBasicTablesIntoTableBuffersIfSpecified(module, buffer);
+ ImportBasicTablesIfSpecified(module, buffer);
// Define all types defined in the module.
buffer.DefineTypes(discoveryResult.Types);
@@ -102,7 +102,7 @@ public IMethodBodySerializer MethodBodySerializer
// Import remaining preservable tables (Type specs, method specs, signatures etc).
// We do this before finalizing any member to ensure that they are assigned their original RIDs.
- ImportRemainingTablesIntoTableBuffersIfSpecified(module, buffer);
+ ImportRemainingTablesIfSpecified(module, buffer);
// Finalize member definitions.
buffer.FinalizeTypes();
@@ -197,7 +197,7 @@ private IMetadataBuffer CreateMetadataBuffer(ModuleDefinition module)
return metadataBuffer;
}
- private void ImportBasicTablesIntoTableBuffersIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer)
+ private void ImportBasicTablesIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer)
{
if (module.DotNetDirectory is null)
return;
@@ -210,37 +210,58 @@ private void ImportBasicTablesIntoTableBuffersIfSpecified(ModuleDefinition modul
// and type reference tokens are still preserved, we need to prioritize these.
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveAssemblyReferenceIndices) != 0)
- ImportTableIntoTableBuffers(module, TableIndex.AssemblyRef, buffer.GetAssemblyReferenceToken);
+ {
+ ImportTables(module, TableIndex.AssemblyRef,
+ r => buffer.AddAssemblyReference(r, true, true));
+ }
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveModuleReferenceIndices) != 0)
- ImportTableIntoTableBuffers(module, TableIndex.ModuleRef, buffer.GetModuleReferenceToken);
+ {
+ ImportTables(module, TableIndex.ModuleRef,
+ r => buffer.AddModuleReference(r, true, true));
+ }
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeReferenceIndices) != 0)
- ImportTableIntoTableBuffers(module, TableIndex.TypeRef, buffer.GetTypeReferenceToken);
+ {
+ ImportTables(module, TableIndex.TypeRef,
+ r => buffer.AddTypeReference(r, true, true));
+ }
}
private void ImportTypeSpecsAndMemberRefsIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer)
{
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveTypeSpecificationIndices) != 0)
- ImportTableIntoTableBuffers(module, TableIndex.TypeSpec, buffer.GetTypeSpecificationToken);
+ {
+ ImportTables(module, TableIndex.TypeSpec,
+ s => buffer.AddTypeSpecification(s, true));
+ }
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveMemberReferenceIndices) != 0)
- ImportTableIntoTableBuffers(module, TableIndex.MemberRef, buffer.GetMemberReferenceToken);
+ {
+ ImportTables(module, TableIndex.MemberRef,
+ r => buffer.AddMemberReference(r, true));
+ }
}
- private void ImportRemainingTablesIntoTableBuffersIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer)
+ private void ImportRemainingTablesIfSpecified(ModuleDefinition module, DotNetDirectoryBuffer buffer)
{
if (module.DotNetDirectory is null)
return;
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveStandAloneSignatureIndices) != 0)
- ImportTableIntoTableBuffers(module, TableIndex.StandAloneSig, buffer.GetStandAloneSignatureToken);
+ {
+ ImportTables(module, TableIndex.StandAloneSig,
+ s => buffer.AddStandAloneSignature(s, true));
+ }
if ((MetadataBuilderFlags & MetadataBuilderFlags.PreserveMethodSpecificationIndices) != 0)
- ImportTableIntoTableBuffers(module, TableIndex.MethodSpec, buffer.GetMethodSpecificationToken);
+ {
+ ImportTables(module, TableIndex.MethodSpec,
+ s => buffer.AddMethodSpecification(s, true));
+ }
}
- private static void ImportTableIntoTableBuffers(ModuleDefinition module, TableIndex tableIndex,
+ private static void ImportTables(ModuleDefinition module, TableIndex tableIndex,
Func importAction)
{
int count = module.DotNetDirectory!.Metadata
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs
index b9283f323..4074289d4 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Blob/BlobStreamBuffer.cs
@@ -14,9 +14,11 @@ namespace AsmResolver.DotNet.Builder.Metadata.Blob
public class BlobStreamBuffer : IMetadataStreamBuffer
{
private readonly MemoryStream _rawStream = new();
- private readonly IBinaryStreamWriter _writer;
+ private readonly BinaryStreamWriter _writer;
private readonly Dictionary _blobs = new(ByteArrayEqualityComparer.Instance);
+ private readonly MemoryStreamWriterPool _blobWriterPool = new();
+
///
/// Creates a new blob stream buffer with the default blob stream name.
///
@@ -116,11 +118,11 @@ public uint GetBlobIndex(ITypeCodedIndexProvider provider, BlobSignature? signat
return 0u;
// Serialize blob.
- using var stream = new MemoryStream();
- var writer = new BinaryStreamWriter(stream);
- signature.Write(new BlobSerializationContext(writer, provider, errorListener));
- return GetBlobIndex(stream.ToArray());
+ using var rentedWriter = _blobWriterPool.Rent();
+ signature.Write(new BlobSerializationContext(rentedWriter.Writer, provider, errorListener));
+
+ return GetBlobIndex(rentedWriter.GetData());
}
///
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Guid/GuidStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Guid/GuidStreamBuffer.cs
index 710ff651a..aacf2ba7d 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Guid/GuidStreamBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Guid/GuidStreamBuffer.cs
@@ -13,7 +13,7 @@ namespace AsmResolver.DotNet.Builder.Metadata.Guid
public class GuidStreamBuffer : IMetadataStreamBuffer
{
private readonly MemoryStream _rawStream = new();
- private readonly IBinaryStreamWriter _writer;
+ private readonly BinaryStreamWriter _writer;
private readonly Dictionary _guids = new();
///
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Strings/StringsStreamBlobSuffixComparer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Strings/StringsStreamBlobSuffixComparer.cs
index 47da9a1a0..3faccd70c 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Strings/StringsStreamBlobSuffixComparer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Strings/StringsStreamBlobSuffixComparer.cs
@@ -29,8 +29,15 @@ public int Compare(KeyValuePair x, KeyValuePair
- public int Compare(byte[] x, byte[] y)
+ public int Compare(byte[]? x, byte[]? y)
{
+ if (ReferenceEquals(x, y))
+ return 0;
+ if (x is null)
+ return -1;
+ if (y is null)
+ return 1;
+
for (int i = x.Length - 1, j = y.Length - 1; i >= 0 && j >= 0; i--, j--)
{
int charComparison = x[i].CompareTo(y[j]);
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs
index 1c1f7f796..ac966743d 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/DistinctMetadataTableBuffer.cs
@@ -44,17 +44,68 @@ public DistinctMetadataTableBuffer(IMetadataTableBuffer underlyingBuffer)
}
}
+ ///
+ public void EnsureCapacity(int capacity)
+ {
+ _underlyingBuffer.EnsureCapacity(capacity);
+
+#if NETSTANDARD2_1_OR_GREATER
+ _entries.EnsureCapacity(capacity);
+#endif
+ }
+
///
public ref TRow GetRowRef(uint rid) => ref _underlyingBuffer.GetRowRef(rid);
///
- public MetadataToken Add(in TRow row)
+ public MetadataToken Add(in TRow row) => Add(row, false);
+
+ ///
+ public MetadataToken Insert(uint rid, in TRow row) => Insert(rid, row, false);
+
+ ///
+ /// Inserts a row into the metadata table at the provided row identifier.
+ ///
+ /// The row identifier.
+ /// The row to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ /// The metadata token that this row was assigned to.
+ public MetadataToken Insert(uint rid, in TRow row, bool allowDuplicates)
+ {
+ if (!_entries.TryGetValue(row, out var token))
+ {
+ token = _underlyingBuffer.Insert(rid, in row);
+ _entries.Add(row, token);
+ }
+ else if (allowDuplicates)
+ {
+ token = _underlyingBuffer.Insert(rid, in row);
+ }
+
+ return token;
+ }
+
+ ///
+ /// Adds a row to the metadata table buffer.
+ ///
+ /// The row to add.
+ ///
+ /// true if the row is always to be added to the end of the buffer, false if a duplicated row
+ /// is supposed to be removed and the token of the original should be returned instead.
+ /// The metadata token that this row was assigned to.
+ public MetadataToken Add(in TRow row, bool allowDuplicates)
{
if (!_entries.TryGetValue(row, out var token))
{
token = _underlyingBuffer.Add(in row);
_entries.Add(row, token);
}
+ else if (allowDuplicates)
+ {
+ token = _underlyingBuffer.Add(in row);
+ }
return token;
}
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs
index bcf1b8bfd..b73b5061a 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/IMetadataTableBuffer.cs
@@ -44,6 +44,12 @@ public interface IMetadataTableBuffer : IMetadataTableBuffer
set;
}
+ ///
+ /// Ensures the capacity of the table buffer is at least the provided amount of elements.
+ ///
+ /// The number of elements to store.
+ void EnsureCapacity(int capacity);
+
///
/// Gets or sets a reference to a row in the metadata table.
///
@@ -56,5 +62,13 @@ public interface IMetadataTableBuffer : IMetadataTableBuffer
/// The row to add.
/// The metadata token that this row was assigned to.
MetadataToken Add(in TRow row);
+
+ ///
+ /// Inserts a row into the metadata table at the provided row identifier.
+ ///
+ /// The row identifier.
+ /// The row to add.
+ /// The metadata token that this row was assigned to.
+ MetadataToken Insert(uint rid, in TRow row);
}
}
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/SortedMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/SortedMetadataTableBuffer.cs
index d8d9b9aa4..7f33b1694 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/SortedMetadataTableBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/SortedMetadataTableBuffer.cs
@@ -12,6 +12,7 @@ namespace AsmResolver.DotNet.Builder.Metadata.Tables
/// The type of members that are assigned new metadata rows.
/// The type of rows to store.
public class SortedMetadataTableBuffer : ISortedMetadataTableBuffer
+ where TKey : notnull
where TRow : struct, IMetadataRow
{
private readonly List<(TKey Key, TRow Row)> _entries = new();
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs
index e379139e1..49651b052 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/TablesStreamBuffer.cs
@@ -120,6 +120,18 @@ public IMetadataTableBuffer GetTable(TableIndex table)
return (IMetadataTableBuffer) _tableBuffers[(int) table];
}
+ ///
+ /// Gets a table buffer by its table index.
+ ///
+ /// The index of the table to get.
+ /// The type of rows the table stores.
+ /// The metadata table.
+ public DistinctMetadataTableBuffer GetDistinctTable(TableIndex table)
+ where TRow : struct, IMetadataRow
+ {
+ return (DistinctMetadataTableBuffer) _tableBuffers[(int) table];
+ }
+
///
/// Gets a table buffer by its table index.
///
@@ -171,6 +183,7 @@ private IMetadataTableBuffer Distinct(TableIndex table)
}
private ISortedMetadataTableBuffer Sorted(TableIndex table, int primaryColumn)
+ where TKey : notnull
where TRow : struct, IMetadataRow
{
return new SortedMetadataTableBuffer(
@@ -179,6 +192,7 @@ private IMetadataTableBuffer Distinct(TableIndex table)
}
private ISortedMetadataTableBuffer Sorted(TableIndex table, int primaryColumn, int secondaryColumn)
+ where TKey : notnull
where TRow : struct, IMetadataRow
{
return new SortedMetadataTableBuffer(
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs
index e94a81d44..17717b185 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/Tables/UnsortedMetadataTableBuffer.cs
@@ -15,7 +15,9 @@ public class UnsortedMetadataTableBuffer : IMetadataTableBuffer
where TRow : struct, IMetadataRow
{
private readonly RefList _entries = new();
+ private readonly BitList _available = new();
private readonly MetadataTable _table;
+ private uint _currentRid;
///
/// Creates a new unsorted metadata table buffer.
@@ -33,7 +35,18 @@ public UnsortedMetadataTableBuffer(MetadataTable table)
public virtual TRow this[uint rid]
{
get => _entries[(int) (rid - 1)];
- set => _entries[(int) (rid - 1)] = value;
+ set
+ {
+ _entries[(int) (rid - 1)] = value;
+ _available[(int) (rid - 1)] = false;
+ }
+ }
+
+ ///
+ public void EnsureCapacity(int capacity)
+ {
+ if (_entries.Capacity < capacity)
+ _entries.Capacity = capacity;
}
///
@@ -42,13 +55,53 @@ public UnsortedMetadataTableBuffer(MetadataTable table)
///
public virtual MetadataToken Add(in TRow row)
{
- _entries.Add(row);
- return new MetadataToken(_table.TableIndex, (uint) _entries.Count);
+ // Move over unavailable slots.
+ while (_currentRid < _available.Count && !_available[(int) _currentRid])
+ _currentRid++;
+
+ // If we moved over all entries, we're adding to the end.
+ if (_currentRid == _entries.Count)
+ {
+ _currentRid++;
+ }
+
+ return Insert(_currentRid++, row);
+ }
+
+ ///
+ public MetadataToken Insert(uint rid, in TRow row)
+ {
+ EnsureRowsAllocated(rid);
+
+ var token = new MetadataToken(_table.TableIndex, rid);
+
+ if (!_available[(int) (rid - 1)])
+ {
+ if (EqualityComparer.Default.Equals(row, _entries[(int) (rid - 1)]))
+ return token;
+
+ throw new InvalidOperationException($"Token 0x{token.ToString()} is already in use.");
+ }
+
+ this[rid] = row;
+ return token;
+ }
+
+ private void EnsureRowsAllocated(uint rid)
+ {
+ while (_entries.Count < rid)
+ {
+ _entries.Add(default);
+ _available.Add(true);
+ }
}
///
public void FlushToTable()
{
+ if (_table.Capacity < _entries.Count)
+ _table.Capacity = _entries.Count;
+
foreach (var row in _entries)
_table.Add(row);
}
diff --git a/src/AsmResolver.DotNet/Builder/Metadata/UserStrings/UserStringsStreamBuffer.cs b/src/AsmResolver.DotNet/Builder/Metadata/UserStrings/UserStringsStreamBuffer.cs
index 15df10919..c6706b3cf 100644
--- a/src/AsmResolver.DotNet/Builder/Metadata/UserStrings/UserStringsStreamBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Metadata/UserStrings/UserStringsStreamBuffer.cs
@@ -14,7 +14,7 @@ namespace AsmResolver.DotNet.Builder.Metadata.UserStrings
public class UserStringsStreamBuffer : IMetadataStreamBuffer
{
private readonly MemoryStream _rawStream = new();
- private readonly IBinaryStreamWriter _writer;
+ private readonly BinaryStreamWriter _writer;
private readonly Dictionary _strings = new();
///
diff --git a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs
index faec68be5..95aef83eb 100644
--- a/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs
+++ b/src/AsmResolver.DotNet/Builder/PEImageBuildResult.cs
@@ -34,7 +34,7 @@ public PEImageBuildResult(IPEImage? image, DiagnosticBag diagnosticBag, ITokenMa
/// Gets a value indicating whether the image was constructed successfully or not.
///
[MemberNotNullWhen(false, nameof(ConstructedImage))]
- public bool HasFailed => ConstructedImage is null;
+ public bool HasFailed => DiagnosticBag.IsFatal;
///
/// Gets the bag containing the diagnostics that were collected during the construction of the image.
diff --git a/src/AsmResolver.DotNet/Builder/Resources/DotNetResourcesDirectoryBuffer.cs b/src/AsmResolver.DotNet/Builder/Resources/DotNetResourcesDirectoryBuffer.cs
index 524b55ae2..073df1eec 100644
--- a/src/AsmResolver.DotNet/Builder/Resources/DotNetResourcesDirectoryBuffer.cs
+++ b/src/AsmResolver.DotNet/Builder/Resources/DotNetResourcesDirectoryBuffer.cs
@@ -12,7 +12,7 @@ namespace AsmResolver.DotNet.Builder.Resources
public class DotNetResourcesDirectoryBuffer
{
private readonly MemoryStream _rawStream = new();
- private readonly IBinaryStreamWriter _writer;
+ private readonly BinaryStreamWriter _writer;
private readonly Dictionary _dataOffsets = new(ByteArrayEqualityComparer.Instance);
///
diff --git a/src/AsmResolver.DotNet/Builder/TokenMapping.cs b/src/AsmResolver.DotNet/Builder/TokenMapping.cs
index 9ae079ba4..678dc1753 100644
--- a/src/AsmResolver.DotNet/Builder/TokenMapping.cs
+++ b/src/AsmResolver.DotNet/Builder/TokenMapping.cs
@@ -102,13 +102,13 @@ public void Register(IMetadataMember member, MetadataToken newToken)
///
/// The new token.
/// The type, or null if no type is assigned to the provided token.
- public TypeDefinition GetTypeByToken(MetadataToken newToken) => _typeDefTokens.GetKey(newToken);
+ public TypeDefinition? GetTypeByToken(MetadataToken newToken) => _typeDefTokens.GetKey(newToken);
///
/// Gets the method assigned to the provided metadata token.
///
/// The new token.
/// The type, or null if no method is assigned to the provided token.
- public MethodDefinition GetMethodByToken(MetadataToken newToken) => _methodTokens.GetKey(newToken);
+ public MethodDefinition? GetMethodByToken(MetadataToken newToken) => _methodTokens.GetKey(newToken);
}
}
diff --git a/src/AsmResolver.DotNet/Bundles/BundleFile.cs b/src/AsmResolver.DotNet/Bundles/BundleFile.cs
new file mode 100644
index 000000000..958eeba16
--- /dev/null
+++ b/src/AsmResolver.DotNet/Bundles/BundleFile.cs
@@ -0,0 +1,221 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using AsmResolver.Collections;
+using AsmResolver.IO;
+
+namespace AsmResolver.DotNet.Bundles
+{
+ ///
+ /// Represents a single file in a .NET bundle manifest.
+ ///
+ public class BundleFile : IOwnedCollectionElement
+ {
+ private readonly LazyVariable _contents;
+
+ ///
+ /// Creates a new empty bundle file.
+ ///
+ /// The path of the file, relative to the root of the bundle.
+ public BundleFile(string relativePath)
+ {
+ RelativePath = relativePath;
+ _contents = new LazyVariable(GetContents);
+ }
+
+ ///
+ /// Creates a new bundle file.
+ ///
+ /// The path of the file, relative to the root of the bundle.
+ /// The type of the file.
+ /// The contents of the file.
+ public BundleFile(string relativePath, BundleFileType type, byte[] contents)
+ : this(relativePath, type, new DataSegment(contents))
+ {
+ }
+
+ ///
+ /// Creates a new empty bundle file.
+ ///
+ /// The path of the file, relative to the root of the bundle.
+ /// The type of the file.
+ /// The contents of the file.
+ public BundleFile(string relativePath, BundleFileType type, ISegment contents)
+ {
+ RelativePath = relativePath;
+ Type = type;
+ _contents = new LazyVariable(contents);
+ }
+
+ ///
+ /// Gets the parent manifest this file was added to.
+ ///
+ public BundleManifest? ParentManifest
+ {
+ get;
+ private set;
+ }
+
+ ///
+ BundleManifest? IOwnedCollectionElement.Owner
+ {
+ get => ParentManifest;
+ set => ParentManifest = value;
+ }
+
+ ///
+ /// Gets or sets the path to the file, relative to the root directory of the bundle.
+ ///
+ public string RelativePath
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the type of the file.
+ ///
+ public BundleFileType Type
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the data stored in is compressed or not.
+ ///
+ ///
+ /// The default implementation of the application host by Microsoft only supports compressing files if it is
+ /// a fully self-contained binary and the file is not the .deps.json nor the .runtmeconfig.json
+ /// file. This property does not do validation on any of these conditions. As such, if the file is supposed to be
+ /// compressed with any of these conditions not met, a custom application host template needs to be provided
+ /// upon serializing the bundle for it to be runnable.
+ ///
+ public bool IsCompressed
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the raw contents of the file.
+ ///
+ public ISegment Contents
+ {
+ get => _contents.Value;
+ set => _contents.Value = value;
+ }
+
+ ///
+ /// Gets a value whether the contents of the file can be read using a .
+ ///
+ public bool CanRead => Contents is IReadableSegment;
+
+ ///
+ /// Obtains the raw contents of the file.
+ ///
+ /// The contents.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual ISegment? GetContents() => null;
+
+ ///
+ /// Attempts to create a that points to the start of the raw contents of the file.
+ ///
+ /// The reader.
+ /// true if the reader was constructed successfully, false otherwise.
+ public bool TryGetReader(out BinaryStreamReader reader)
+ {
+ if (Contents is IReadableSegment segment)
+ {
+ reader = segment.CreateReader();
+ return true;
+ }
+
+ reader = default;
+ return false;
+ }
+
+ ///
+ /// Reads (and decompresses if necessary) the contents of the file.
+ ///
+ /// The contents.
+ public byte[] GetData() => GetData(true);
+
+ ///
+ /// Reads the contents of the file.
+ ///
+ /// true if the contents should be decompressed or not when necessary.
+ /// The contents.
+ public byte[] GetData(bool decompressIfRequired)
+ {
+ if (TryGetReader(out var reader))
+ {
+ byte[] contents = reader.ReadToEnd();
+ if (decompressIfRequired && IsCompressed)
+ {
+ using var outputStream = new MemoryStream();
+
+ using var inputStream = new MemoryStream(contents);
+ using (var deflate = new DeflateStream(inputStream, CompressionMode.Decompress))
+ {
+ deflate.CopyTo(outputStream);
+ }
+
+ contents = outputStream.ToArray();
+ }
+
+ return contents;
+ }
+
+ throw new InvalidOperationException("Contents of file is not readable.");
+ }
+
+ ///
+ /// Marks the file as compressed, compresses the file contents, and replaces the value of
+ /// with the result.
+ ///
+ /// Occurs when the file was already compressed.
+ ///
+ /// The default implementation of the application host by Microsoft only supports compressing files if it is
+ /// a fully self-contained binary and the file is not the .deps.json nor the .runtmeconfig.json
+ /// file. This method does not do validation on any of these conditions. As such, if the file is supposed to be
+ /// compressed with any of these conditions not met, a custom application host template needs to be provided
+ /// upon serializing the bundle for it to be runnable.
+ ///
+ public void Compress()
+ {
+ if (IsCompressed)
+ throw new InvalidOperationException("File is already compressed.");
+
+ using var inputStream = new MemoryStream(GetData());
+
+ using var outputStream = new MemoryStream();
+ using (var deflate = new DeflateStream(outputStream, CompressionLevel.Optimal))
+ {
+ inputStream.CopyTo(deflate);
+ }
+
+ Contents = new DataSegment(outputStream.ToArray());
+ IsCompressed = true;
+ }
+
+ ///
+ /// Marks the file as uncompressed, decompresses the file contents, and replaces the value of
+ /// with the result.
+ ///
+ /// Occurs when the file was not compressed.
+ public void Decompress()
+ {
+ if (!IsCompressed)
+ throw new InvalidOperationException("File is not compressed.");
+
+ Contents = new DataSegment(GetData(true));
+ IsCompressed = false;
+ }
+
+ ///
+ public override string ToString() => RelativePath;
+ }
+}
diff --git a/src/AsmResolver.DotNet/Bundles/BundleFileType.cs b/src/AsmResolver.DotNet/Bundles/BundleFileType.cs
new file mode 100644
index 000000000..8ac16b290
--- /dev/null
+++ b/src/AsmResolver.DotNet/Bundles/BundleFileType.cs
@@ -0,0 +1,38 @@
+namespace AsmResolver.DotNet.Bundles
+{
+ ///
+ /// Provides members defining all possible file types that can be stored in a bundled .NET application.
+ ///
+ public enum BundleFileType
+ {
+ ///
+ /// Indicates the file type is unknown.
+ ///
+ Unknown,
+
+ ///
+ /// Indicates the file is a .NET assembly.
+ ///
+ Assembly,
+
+ ///
+ /// Indicates the file is a native binary.
+ ///
+ NativeBinary,
+
+ ///
+ /// Indicates the file is the deps.json file associated to a .NET assembly.
+ ///
+ DepsJson,
+
+ ///
+ /// Indicates the file is the runtimeconfig.json file associated to a .NET assembly.
+ ///
+ RuntimeConfigJson,
+
+ ///
+ /// Indicates the file contains symbols.
+ ///
+ Symbols
+ }
+}
diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifest.cs b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs
new file mode 100644
index 000000000..2acc2a57b
--- /dev/null
+++ b/src/AsmResolver.DotNet/Bundles/BundleManifest.cs
@@ -0,0 +1,503 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using AsmResolver.Collections;
+using AsmResolver.IO;
+using AsmResolver.PE.File;
+using AsmResolver.PE.File.Headers;
+using AsmResolver.PE.Win32Resources.Builder;
+
+namespace AsmResolver.DotNet.Bundles
+{
+ ///
+ /// Represents a set of bundled files embedded in a .NET application host or single-file host.
+ ///
+ public class BundleManifest
+ {
+ private const int DefaultBundleIDLength = 12;
+
+ private static readonly byte[] BundleSignature =
+ {
+ 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
+ 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
+ 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
+ 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae
+ };
+
+ private static readonly byte[] AppBinaryPathPlaceholder =
+ Encoding.UTF8.GetBytes("c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2");
+
+ private IList? _files;
+
+ ///
+ /// Initializes an empty bundle manifest.
+ ///
+ protected BundleManifest()
+ {
+ }
+
+ ///
+ /// Creates a new bundle manifest.
+ ///
+ /// The file format version.
+ public BundleManifest(uint majorVersionNumber)
+ {
+ MajorVersion = majorVersionNumber;
+ MinorVersion = 0;
+ }
+
+ ///
+ /// Creates a new bundle manifest with a specific bundle identifier.
+ ///
+ /// The file format version.
+ /// The unique bundle manifest identifier.
+ public BundleManifest(uint majorVersionNumber, string bundleId)
+ {
+ MajorVersion = majorVersionNumber;
+ MinorVersion = 0;
+ BundleID = bundleId;
+ }
+
+ ///
+ /// Gets or sets the major file format version of the bundle.
+ ///
+ ///
+ /// Version numbers recognized by the CLR are:
+ ///
+ /// - 1 for .NET Core 3.1
+ /// - 2 for .NET 5.0
+ /// - 6 for .NET 6.0
+ ///
+ ///
+ public uint MajorVersion
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the minor file format version of the bundle.
+ ///
+ ///
+ /// This value is ignored by the CLR and should be set to 0.
+ ///
+ public uint MinorVersion
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the unique identifier for the bundle manifest.
+ ///
+ ///
+ /// When this property is set to null, the bundle identifier will be generated upon writing the manifest
+ /// based on the contents of the manifest.
+ ///
+ public string? BundleID
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets flags associated to the bundle.
+ ///
+ public BundleManifestFlags Flags
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets a collection of files stored in the bundle.
+ ///
+ public IList Files
+ {
+ get
+ {
+ if (_files is null)
+ Interlocked.CompareExchange(ref _files, GetFiles(), null);
+ return _files;
+ }
+ }
+
+ ///
+ /// Attempts to automatically locate and parse the bundle header in the provided file.
+ ///
+ /// The path to the file to read.
+ /// The read manifest.
+ public static BundleManifest FromFile(string filePath)
+ {
+ return FromBytes(File.ReadAllBytes(filePath));
+ }
+
+ ///
+ /// Attempts to automatically locate and parse the bundle header in the provided file.
+ ///
+ /// The raw contents of the file to read.
+ /// The read manifest.
+ public static BundleManifest FromBytes(byte[] data)
+ {
+ return FromDataSource(new ByteArrayDataSource(data));
+ }
+
+ ///
+ /// Parses the bundle header in the provided file at the provided address.
+ ///
+ /// The raw contents of the file to read.
+ /// The address within the file to start reading the bundle at.
+ /// The read manifest.
+ public static BundleManifest FromBytes(byte[] data, ulong offset)
+ {
+ return FromDataSource(new ByteArrayDataSource(data), offset);
+ }
+
+ ///
+ /// Attempts to automatically locate and parse the bundle header in the provided file.
+ ///
+ /// The raw contents of the file to read.
+ /// The read manifest.
+ public static BundleManifest FromDataSource(IDataSource source)
+ {
+ long address = FindBundleManifestAddress(source);
+ if (address == -1)
+ throw new BadImageFormatException("File does not contain an AppHost bundle signature.");
+
+ return FromDataSource(source, (ulong) address);
+ }
+
+ ///
+ /// Parses the bundle header in the provided file at the provided address.
+ ///
+ /// The raw contents of the file to read.
+ /// The address within the file to start reading the bundle at.
+ /// The read manifest.
+ public static BundleManifest FromDataSource(IDataSource source, ulong offset)
+ {
+ var reader = new BinaryStreamReader(source, 0, 0, (uint) source.Length)
+ {
+ Offset = offset
+ };
+
+ return FromReader(reader);
+ }
+
+ ///
+ /// Parses the bundle header from the provided input stream.
+ ///
+ /// The input stream pointing to the start of the bundle to read.
+ /// The read manifest.
+ public static BundleManifest FromReader(BinaryStreamReader reader) => new SerializedBundleManifest(reader);
+
+ private static long FindInFile(IDataSource source, byte[] data)
+ {
+ // Note: For performance reasons, we read data from the data source in blocks, such that we avoid
+ // virtual-dispatch calls and do the searching directly on a byte array instead.
+
+ byte[] buffer = new byte[0x1000];
+
+ ulong start = 0;
+ while (start < source.Length)
+ {
+ int read = source.ReadBytes(start, buffer, 0, buffer.Length);
+
+ for (int i = sizeof(ulong); i < read - data.Length; i++)
+ {
+ bool fullMatch = true;
+ for (int j = 0; fullMatch && j < data.Length; j++)
+ {
+ if (buffer[i + j] != data[j])
+ fullMatch = false;
+ }
+
+ if (fullMatch)
+ return (long) start + i;
+ }
+
+ start += (ulong) read;
+ }
+
+ return -1;
+ }
+
+ private static long ReadBundleManifestAddress(IDataSource source, long signatureAddress)
+ {
+ var reader = new BinaryStreamReader(source, (ulong) signatureAddress - sizeof(ulong), 0, 8);
+ ulong manifestAddress = reader.ReadUInt64();
+
+ return source.IsValidAddress(manifestAddress)
+ ? (long) manifestAddress
+ : -1;
+ }
+
+ ///
+ /// Attempts to find the start of the bundle header in the provided file.
+ ///
+ /// The file to locate the bundle header in.
+ /// The offset, or -1 if none was found.
+ public static long FindBundleManifestAddress(IDataSource source)
+ {
+ long signatureAddress = FindInFile(source, BundleSignature);
+ if (signatureAddress == -1)
+ return -1;
+
+ return ReadBundleManifestAddress(source, signatureAddress);
+ }
+
+ ///
+ /// Gets a value indicating whether the provided data source contains a conventional bundled assembly signature.
+ ///
+ /// The file to locate the bundle header in.
+ /// true if a bundle signature was found, false otherwise.
+ public static bool IsBundledAssembly(IDataSource source) => FindBundleManifestAddress(source) != -1;
+
+ ///
+ /// Obtains the list of files stored in the bundle.
+ ///
+ /// The files
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual IList GetFiles() => new OwnedCollection(this);
+
+ ///
+ /// Generates a bundle identifier based on the SHA-256 hashes of all files in the manifest.
+ ///
+ /// The generated bundle identifier.
+ public string GenerateDeterministicBundleID()
+ {
+ using var manifestHasher = SHA256.Create();
+
+ for (int i = 0; i < Files.Count; i++)
+ {
+ var file = Files[i];
+ using var fileHasher = SHA256.Create();
+ byte[] fileHash = fileHasher.ComputeHash(file.GetData());
+ manifestHasher.TransformBlock(fileHash, 0, fileHash.Length, fileHash, 0);
+ }
+
+ manifestHasher.TransformFinalBlock(Array.Empty(), 0, 0);
+ byte[] manifestHash = manifestHasher.Hash!;
+
+ return Convert.ToBase64String(manifestHash)
+ .Substring(DefaultBundleIDLength)
+ .Replace('/', '_');
+ }
+
+ ///
+ /// Constructs a new application host file based on the bundle manifest.
+ ///
+ /// The path of the file to write to.
+ /// The parameters to use for bundling all files into a single executable.
+ public void WriteUsingTemplate(string outputPath, in BundlerParameters parameters)
+ {
+ using var fs = File.Create(outputPath);
+ WriteUsingTemplate(fs, parameters);
+ }
+
+ ///
+ /// Constructs a new application host file based on the bundle manifest.
+ ///
+ /// The output stream to write to.
+ /// The parameters to use for bundling all files into a single executable.
+ public void WriteUsingTemplate(Stream outputStream, in BundlerParameters parameters)
+ {
+ WriteUsingTemplate(new BinaryStreamWriter(outputStream), parameters);
+ }
+
+ ///
+ /// Constructs a new application host file based on the bundle manifest.
+ ///
+ /// The output stream to write to.
+ /// The parameters to use for bundling all files into a single executable.
+ public void WriteUsingTemplate(IBinaryStreamWriter writer, BundlerParameters parameters)
+ {
+ var appBinaryEntry = Files.FirstOrDefault(f => f.RelativePath == parameters.ApplicationBinaryPath);
+ if (appBinaryEntry is null)
+ throw new ArgumentException($"Application {parameters.ApplicationBinaryPath} does not exist within the bundle.");
+
+ byte[] appBinaryPathBytes = Encoding.UTF8.GetBytes(parameters.ApplicationBinaryPath);
+ if (appBinaryPathBytes.Length > 1024)
+ throw new ArgumentException("Application binary path cannot exceed 1024 bytes.");
+
+ if (!parameters.IsArm64Linux)
+ EnsureAppHostPEHeadersAreUpToDate(ref parameters);
+
+ var appHostTemplateSource = new ByteArrayDataSource(parameters.ApplicationHostTemplate);
+ long signatureAddress = FindInFile(appHostTemplateSource, BundleSignature);
+ if (signatureAddress == -1)
+ throw new ArgumentException("AppHost template does not contain the bundle signature.");
+
+ long appBinaryPathAddress = FindInFile(appHostTemplateSource, AppBinaryPathPlaceholder);
+ if (appBinaryPathAddress == -1)
+ throw new ArgumentException("AppHost template does not contain the application binary path placeholder.");
+
+ writer.WriteBytes(parameters.ApplicationHostTemplate);
+ writer.Offset = writer.Length;
+ ulong headerAddress = WriteManifest(writer, parameters.IsArm64Linux);
+
+ writer.Offset = (ulong) signatureAddress - sizeof(ulong);
+ writer.WriteUInt64(headerAddress);
+
+ writer.Offset = (ulong) appBinaryPathAddress;
+ writer.WriteBytes(appBinaryPathBytes);
+ if (AppBinaryPathPlaceholder.Length > appBinaryPathBytes.Length)
+ writer.WriteZeroes(AppBinaryPathPlaceholder.Length - appBinaryPathBytes.Length);
+ }
+
+ private static void EnsureAppHostPEHeadersAreUpToDate(ref BundlerParameters parameters)
+ {
+ PEFile file;
+ try
+ {
+ file = PEFile.FromBytes(parameters.ApplicationHostTemplate);
+ }
+ catch (BadImageFormatException)
+ {
+ // Template is not a PE file.
+ return;
+ }
+
+ bool changed = false;
+
+ // Ensure same Windows subsystem is used (typically required for GUI applications).
+ if (file.OptionalHeader.SubSystem != parameters.SubSystem)
+ {
+ file.OptionalHeader.SubSystem = parameters.SubSystem;
+ changed = true;
+ }
+
+ // If the app binary has resources (such as an icon or version info), we need to copy it into the
+ // AppHost template so that they are also visible from the final packed executable.
+ if (parameters.Resources is { } directory)
+ {
+ // Put original resource directory in a new .rsrc section.
+ var buffer = new ResourceDirectoryBuffer();
+ buffer.AddDirectory(directory);
+ var rsrc = new PESection(".rsrc", SectionFlags.MemoryRead | SectionFlags.ContentInitializedData);
+ rsrc.Contents = buffer;
+
+ // Find .reloc section, and insert .rsrc before it if it is present. Otherwise just append to the end.
+ int sectionIndex = file.Sections.Count - 1;
+ for (int i = file.Sections.Count - 1; i >= 0; i--)
+ {
+ if (file.Sections[i].Name == ".reloc")
+ {
+ sectionIndex = i;
+ break;
+ }
+ }
+
+ file.Sections.Insert(sectionIndex, rsrc);
+
+ // Update resource data directory va + size.
+ file.AlignSections();
+ file.OptionalHeader.DataDirectories[(int) DataDirectoryIndex.ResourceDirectory] = new DataDirectory(
+ buffer.Rva,
+ buffer.GetPhysicalSize());
+
+ changed = true;
+ }
+
+ // Rebuild AppHost PE file if necessary.
+ if (changed)
+ {
+ using var stream = new MemoryStream();
+ file.Write(stream);
+ parameters.ApplicationHostTemplate = stream.ToArray();
+ }
+ }
+
+ ///
+ /// Writes the manifest to an output stream.
+ ///
+ /// The output stream to write to.
+ /// true if the application host is a Linux ELF binary targeting ARM64.
+ /// The address of the bundle header.
+ ///
+ /// This does not necessarily produce a working executable file, it only writes the contents of the entire manifest,
+ /// without a host application that invokes the manifest. If you want to produce a runnable executable, use one
+ /// of the WriteUsingTemplate methods instead.
+ ///
+ public ulong WriteManifest(IBinaryStreamWriter writer, bool isArm64Linux)
+ {
+ WriteFileContents(writer, isArm64Linux
+ ? 4096u
+ : 16u);
+
+ ulong headerAddress = writer.Offset;
+ WriteManifestHeader(writer);
+
+ return headerAddress;
+ }
+
+ private void WriteFileContents(IBinaryStreamWriter writer, uint alignment)
+ {
+ for (int i = 0; i < Files.Count; i++)
+ {
+ var file = Files[i];
+
+ if (file.Type == BundleFileType.Assembly)
+ writer.Align(alignment);
+
+ file.Contents.UpdateOffsets(writer.Offset, (uint) writer.Offset);
+ file.Contents.Write(writer);
+ }
+ }
+
+ private void WriteManifestHeader(IBinaryStreamWriter writer)
+ {
+ writer.WriteUInt32(MajorVersion);
+ writer.WriteUInt32(MinorVersion);
+ writer.WriteInt32(Files.Count);
+
+ BundleID ??= GenerateDeterministicBundleID();
+ writer.WriteBinaryFormatterString(BundleID);
+
+ if (MajorVersion >= 2)
+ {
+ WriteFileOffsetSizePair(writer, Files.FirstOrDefault(f => f.Type == BundleFileType.DepsJson));
+ WriteFileOffsetSizePair(writer, Files.FirstOrDefault(f => f.Type == BundleFileType.RuntimeConfigJson));
+ writer.WriteUInt64((ulong) Flags);
+ }
+
+ WriteFileHeaders(writer);
+ }
+
+ private void WriteFileHeaders(IBinaryStreamWriter writer)
+ {
+ for (int i = 0; i < Files.Count; i++)
+ {
+ var file = Files[i];
+
+ WriteFileOffsetSizePair(writer, file);
+
+ if (MajorVersion >= 6)
+ writer.WriteUInt64(file.IsCompressed ? file.Contents.GetPhysicalSize() : 0);
+
+ writer.WriteByte((byte) file.Type);
+ writer.WriteBinaryFormatterString(file.RelativePath);
+ }
+ }
+
+ private static void WriteFileOffsetSizePair(IBinaryStreamWriter writer, BundleFile? file)
+ {
+ if (file is not null)
+ {
+ writer.WriteUInt64(file.Contents.Offset);
+ writer.WriteUInt64((ulong) file.GetData().Length);
+ }
+ else
+ {
+ writer.WriteUInt64(0);
+ writer.WriteUInt64(0);
+ }
+ }
+
+ }
+}
diff --git a/src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs b/src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs
new file mode 100644
index 000000000..0a2a9a277
--- /dev/null
+++ b/src/AsmResolver.DotNet/Bundles/BundleManifestFlags.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace AsmResolver.DotNet.Bundles
+{
+ ///
+ /// Provides members defining all flags that can be assigned to a bundle manifest.
+ ///
+ [Flags]
+ public enum BundleManifestFlags : ulong
+ {
+ ///
+ /// Indicates no flags were assigned.
+ ///
+ None = 0,
+
+ ///
+ /// Indicates the bundle was compiled in .NET Core 3 compatibility mode.
+ ///
+ NetCoreApp3CompatibilityMode = 1
+ }
+}
diff --git a/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs b/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs
new file mode 100644
index 000000000..2e83c3c16
--- /dev/null
+++ b/src/AsmResolver.DotNet/Bundles/BundlerParameters.cs
@@ -0,0 +1,221 @@
+using System.IO;
+using AsmResolver.IO;
+using AsmResolver.PE;
+using AsmResolver.PE.File.Headers;
+using AsmResolver.PE.Win32Resources;
+
+namespace AsmResolver.DotNet.Bundles
+{
+ ///
+ /// Defines parameters for the .NET application bundler.
+ ///
+ public struct BundlerParameters
+ {
+ ///
+ /// Initializes new bundler parameters.
+ ///
+ ///
+ /// The path to the application host file template to use. By default this is stored in
+ /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or
+ /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native.
+ ///
+ ///
+ /// The name of the file in the bundle that contains the entry point of the application.
+ ///
+ public BundlerParameters(string appHostTemplatePath, string appBinaryPath)
+ : this(File.ReadAllBytes(appHostTemplatePath), appBinaryPath)
+ {
+ }
+
+ ///
+ /// Initializes new bundler parameters.
+ ///
+ /// The application host template file to use.
+ ///
+ /// The name of the file in the bundle that contains the entry point of the application.
+ ///
+ public BundlerParameters(byte[] appHostTemplate, string appBinaryPath)
+ {
+ ApplicationHostTemplate = appHostTemplate;
+ ApplicationBinaryPath = appBinaryPath;
+ IsArm64Linux = false;
+ Resources = null;
+ SubSystem = SubSystem.WindowsCui;
+ }
+
+ ///
+ /// Initializes new bundler parameters.
+ ///
+ ///
+ /// The path to the application host file template to use. By default this is stored in
+ /// <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate or
+ /// <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native.
+ ///
+ ///
+ /// The name of the file in the bundle that contains the entry point of the application.
+ ///
+ ///
+ /// The path to copy the PE headers and Win32 resources from. This is typically the original native executable
+ /// file that hosts the CLR, or the original AppHost file the bundle was extracted from.
+ ///
+ public BundlerParameters(string appHostTemplatePath, string appBinaryPath, string? imagePathToCopyHeadersFrom)
+ : this(
+ File.ReadAllBytes(appHostTemplatePath),
+ appBinaryPath,
+ !string.IsNullOrEmpty(imagePathToCopyHeadersFrom)
+ ? PEImage.FromFile(imagePathToCopyHeadersFrom!)
+ : null
+ )
+ {
+ }
+
+ ///
+ /// Initializes new bundler parameters.
+ ///
+ /// The application host template file to use.
+ ///
+ /// The name of the file in the bundle that contains the entry point of the application.
+ ///
+ ///
+ /// The binary to copy the PE headers and Win32 resources from. This is typically the original native executable
+ /// file that hosts the CLR, or the original AppHost file the bundle was extracted from.
+ ///
+ public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, byte[]? imageToCopyHeadersFrom)
+ : this(
+ appHostTemplate,
+ appBinaryPath,
+ imageToCopyHeadersFrom is not null
+ ? PEImage.FromBytes(imageToCopyHeadersFrom)
+ : null
+ )
+ {
+ }
+
+ ///
+ /// Initializes new bundler parameters.
+ ///
+ /// The application host template file to use.
+ ///
+ /// The name of the file in the bundle that contains the entry point of the application.
+ ///
+ ///
+ /// The binary to copy the PE headers and Win32 resources from. This is typically the original native executable
+ /// file that hosts the CLR, or the original AppHost file the bundle was extracted from.
+ ///
+ public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, IDataSource? imageToCopyHeadersFrom)
+ : this(
+ appHostTemplate,
+ appBinaryPath,
+ imageToCopyHeadersFrom is not null
+ ? PEImage.FromDataSource(imageToCopyHeadersFrom)
+ : null
+ )
+ {
+ }
+
+ ///
+ /// Initializes new bundler parameters.
+ ///
+ /// The application host template file to use.
+ ///
+ /// The name of the file in the bundle that contains the entry point of the application.
+ ///
+ ///
+ /// The PE image to copy the headers and Win32 resources from. This is typically the original native executable
+ /// file that hosts the CLR, or the original AppHost file the bundle was extracted from.
+ ///
+ public BundlerParameters(byte[] appHostTemplate, string appBinaryPath, IPEImage? imageToCopyHeadersFrom)
+ : this(
+ appHostTemplate,
+ appBinaryPath,
+ imageToCopyHeadersFrom?.SubSystem ?? SubSystem.WindowsCui,
+ imageToCopyHeadersFrom?.Resources
+ )
+ {
+ }
+
+ ///
+ /// Initializes new bundler parameters.
+ ///
+ /// The application host template file to use.
+ ///
+ /// The name of the file in the bundle that contains the entry point of the application.
+ ///
+ /// The subsystem to use in the final Windows PE binary.
+ /// The resources to copy into the final Windows PE binary.
+ public BundlerParameters(
+ byte[] appHostTemplate,
+ string appBinaryPath,
+ SubSystem subSystem,
+ IResourceDirectory? resources)
+ {
+ ApplicationHostTemplate = appHostTemplate;
+ ApplicationBinaryPath = appBinaryPath;
+ IsArm64Linux = false;
+ SubSystem = subSystem;
+ Resources = resources;
+ }
+
+ ///
+ /// Gets or sets the template application hosting binary.
+ ///
+ ///
+ /// By default, the official implementations of the application host can be found in one of the following
+ /// installation directories:
+ ///
+ /// - <DOTNET-INSTALLATION-PATH>/sdk/<version>/AppHostTemplate
+ /// - <DOTNET-INSTALLATION-PATH>/packs/Microsoft.NETCore.App.Host.<runtime-identifier>/<version>/runtimes/<runtime-identifier>/native
+ ///
+ /// It is therefore recommended to use the contents of one of these templates to ensure compatibility.
+ ///
+ public byte[] ApplicationHostTemplate
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the path to the binary within the bundle that contains the application's entry point.
+ ///
+ public string ApplicationBinaryPath
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets a value indicating whether the bundled executable targets the Linux operating system on ARM64.
+ ///
+ public bool IsArm64Linux
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the Win32 resources directory to copy into the final PE executable.
+ ///
+ ///
+ /// This field is ignored if is set to true, or
+ /// does not contain a proper PE image.
+ ///
+ public IResourceDirectory? Resources
+ {
+ get;
+ set;
+ }
+
+ ///
+ /// Gets or sets the Windows subsystem the final PE executable should target.
+ ///
+ ///
+ /// This field is ignored if is set to true, or
+ /// does not contain a proper PE image.
+ ///
+ public SubSystem SubSystem
+ {
+ get;
+ set;
+ }
+ }
+}
diff --git a/src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs b/src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs
new file mode 100644
index 000000000..79581a262
--- /dev/null
+++ b/src/AsmResolver.DotNet/Bundles/SerializedBundleFile.cs
@@ -0,0 +1,42 @@
+using AsmResolver.IO;
+
+namespace AsmResolver.DotNet.Bundles
+{
+ ///
+ /// Represents a lazily initialized implementation of that is read from an existing file.
+ ///
+ public class SerializedBundleFile : BundleFile
+ {
+ private readonly BinaryStreamReader _contentsReader;
+
+ ///
+ /// Reads a bundle file entry from the provided input stream.
+ ///
+ /// The input stream.
+ /// The file format version of the bundle.
+ public SerializedBundleFile(ref BinaryStreamReader reader, uint bundleVersionFormat)
+ : base(string.Empty)
+ {
+ ulong offset = reader.ReadUInt64();
+ ulong size = reader.ReadUInt64();
+
+ if (bundleVersionFormat >= 6)
+ {
+ ulong compressedSize = reader.ReadUInt64();
+ if (compressedSize != 0)
+ {
+ size = compressedSize;
+ IsCompressed = true;
+ }
+ }
+
+ Type = (BundleFileType) reader.ReadByte();
+ RelativePath = reader.ReadBinaryFormatterString();
+
+ _contentsReader = reader.ForkAbsolute(offset, (uint) size);
+ }
+
+ ///
+ protected override ISegment GetContents() => _contentsReader.ReadSegment(_contentsReader.Length);
+ }
+}
diff --git a/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs b/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs
new file mode 100644
index 000000000..2fce34fae
--- /dev/null
+++ b/src/AsmResolver.DotNet/Bundles/SerializedBundleManifest.cs
@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using AsmResolver.Collections;
+using AsmResolver.IO;
+
+namespace AsmResolver.DotNet.Bundles
+{
+ ///
+ /// Represents a lazily initialized implementation of that is read from an existing file.
+ ///
+ public class SerializedBundleManifest : BundleManifest
+ {
+ private readonly uint _originalMajorVersion;
+ private readonly BinaryStreamReader _fileEntriesReader;
+ private readonly int _originalFileCount;
+
+ ///
+ /// Reads a bundle manifest from the provided input stream.
+ ///
+ /// The input stream.
+ public SerializedBundleManifest(BinaryStreamReader reader)
+ {
+ MajorVersion = _originalMajorVersion = reader.ReadUInt32();
+ MinorVersion = reader.ReadUInt32();
+ _originalFileCount = reader.ReadInt32();
+ BundleID = reader.ReadBinaryFormatterString();
+
+ if (MajorVersion >= 2)
+ {
+ reader.Offset += 4 * sizeof(ulong);
+ Flags = (BundleManifestFlags) reader.ReadUInt64();
+ }
+
+ _fileEntriesReader = reader;
+ }
+
+ ///
+ protected override IList GetFiles()
+ {
+ var reader = _fileEntriesReader;
+ var result = new OwnedCollection(this);
+
+ for (int i = 0; i < _originalFileCount; i++)
+ result.Add(new SerializedBundleFile(ref reader, _originalMajorVersion));
+
+ return result;
+ }
+ }
+}
diff --git a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs
index 4fd836cfe..ea1b7d335 100644
--- a/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs
+++ b/src/AsmResolver.DotNet/Cloning/CloneContextAwareReferenceImporter.cs
@@ -17,6 +17,11 @@ public CloneContextAwareReferenceImporter(MemberCloneContext context)
_context = context;
}
+ ///
+ /// The working space for this member cloning procedure.
+ ///
+ protected MemberCloneContext Context => _context;
+
///
protected override ITypeDefOrRef ImportType(TypeDefinition type)
{
@@ -40,5 +45,15 @@ public override IMethodDefOrRef ImportMethod(IMethodDefOrRef method)
? (IMethodDefOrRef) clonedMethod
: base.ImportMethod(method);
}
+
+ ///
+ protected override ITypeDefOrRef ImportType(TypeReference type)
+ {
+ return type.Namespace == "System"
+ && type.Name == nameof(System.Object)
+ && (type.Scope?.GetAssembly()?.IsCorLib ?? false)
+ ? _context.Module.CorLibTypeFactory.Object.Type
+ : base.ImportType(type);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs
index 5f6fa56e9..78c52c8c3 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloneContext.cs
@@ -12,10 +12,18 @@ public class MemberCloneContext
/// Creates a new instance of the class.
///
/// The target module to copy the cloned members into.
- public MemberCloneContext(ModuleDefinition module)
+ public MemberCloneContext(ModuleDefinition module) : this(module, null) { }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The target module to copy the cloned members into.
+ /// The factory for creating the reference importer
+ public MemberCloneContext(ModuleDefinition module,
+ Func? importerFactory)
{
Module = module ?? throw new ArgumentNullException(nameof(module));
- Importer = new CloneContextAwareReferenceImporter(this);
+ Importer = importerFactory?.Invoke(this) ?? new CloneContextAwareReferenceImporter(this);
}
///
@@ -42,4 +50,4 @@ public ReferenceImporter Importer
get;
} = new Dictionary();
}
-}
\ No newline at end of file
+}
diff --git a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs
index eae611adb..1580d1efa 100644
--- a/src/AsmResolver.DotNet/Cloning/MemberCloner.cs
+++ b/src/AsmResolver.DotNet/Cloning/MemberCloner.cs
@@ -17,6 +17,7 @@ namespace AsmResolver.DotNet.Cloning
///
public partial class MemberCloner
{
+ private readonly Func? _importerFactory;
private readonly ModuleDefinition _targetModule;
private readonly HashSet _typesToClone = new();
@@ -29,9 +30,18 @@ public partial class MemberCloner
/// Creates a new instance of the class.
///
/// The target module to copy the members into.
- public MemberCloner(ModuleDefinition targetModule)
+ public MemberCloner(ModuleDefinition targetModule) : this(targetModule, null) { }
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The target module to copy the members into.
+ /// The factory for creating the reference importer
+ public MemberCloner(ModuleDefinition targetModule,
+ Func? importerFactory)
{
_targetModule = targetModule ?? throw new ArgumentNullException(nameof(targetModule));
+ _importerFactory = importerFactory;
}
///
@@ -220,7 +230,7 @@ public MemberCloner Include(EventDefinition @event)
/// An object representing the result of the cloning process.
public MemberCloneResult Clone()
{
- var context = new MemberCloneContext(_targetModule);
+ var context = new MemberCloneContext(_targetModule, _importerFactory);
CreateMemberStubs(context);
DeepCopyMembers(context);
diff --git a/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs b/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs
index 8287e1895..970bc7b26 100644
--- a/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs
+++ b/src/AsmResolver.DotNet/Code/Cil/CilInstructionCollection.cs
@@ -260,7 +260,7 @@ private void ExpandMacro(CilInstruction instruction)
break;
case CilCode.Ldarga_S:
- instruction.OpCode = CilOpCodes.Ldarga_S;
+ instruction.OpCode = CilOpCodes.Ldarga;
break;
case CilCode.Starg_S:
@@ -314,7 +314,7 @@ private void ExpandMacro(CilInstruction instruction)
instruction.OpCode = CilOpCodes.Brtrue;
break;
case CilCode.Bge_Un_S:
- instruction.OpCode = CilOpCodes.Bge_Un_S;
+ instruction.OpCode = CilOpCodes.Bge_Un;
break;
case CilCode.Bgt_Un_S:
instruction.OpCode = CilOpCodes.Bgt_Un;
diff --git a/src/AsmResolver.DotNet/Code/Cil/CilLabelVerifier.cs b/src/AsmResolver.DotNet/Code/Cil/CilLabelVerifier.cs
index 6e32ef202..9ad289287 100644
--- a/src/AsmResolver.DotNet/Code/Cil/CilLabelVerifier.cs
+++ b/src/AsmResolver.DotNet/Code/Cil/CilLabelVerifier.cs
@@ -130,7 +130,7 @@ private void AddDiagnostic(string message)
{
_diagnostics ??= new List();
_cachedName ??= _body.Owner.SafeToString();
- _diagnostics.Add(new InvalidCilInstructionException($"[{_cachedName}]: {message}"));
+ _diagnostics.Add(new InvalidCilInstructionException($"[In {_cachedName}]: {message}"));
}
}
diff --git a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs
index de4b9718a..180e474f8 100644
--- a/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs
+++ b/src/AsmResolver.DotNet/Code/Cil/CilMethodBody.cs
@@ -136,6 +136,7 @@ public bool VerifyLabelsOnBuild
/// The method that owns the method body.
/// The Dynamic Method/Delegate/DynamicResolver.
/// The method body.
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver")]
public static CilMethodBody FromDynamicMethod(MethodDefinition method, object dynamicMethodObj)
{
if (!(method.Module is SerializedModuleDefinition module))
@@ -147,7 +148,7 @@ public static CilMethodBody FromDynamicMethod(MethodDefinition method, object dy
//Get Runtime Fields
byte[] code = FieldReader.ReadField(dynamicMethodObj, "m_code")!;
object scope = FieldReader.ReadField
public class CilMethodBodySerializer : IMethodBodySerializer
{
+ private readonly MemoryStreamWriterPool _writerPool = new();
+
///
/// Gets or sets the value of an override switch indicating whether the max stack should always be recalculated
/// or should always be preserved.
@@ -88,7 +90,7 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont
}
// Serialize CIL stream.
- var code = BuildRawCodeStream(context, body);
+ byte[] code = BuildRawCodeStream(context, body);
// Build method body.
var rawBody = body.IsFat
@@ -98,8 +100,7 @@ public ISegmentReference SerializeMethodBody(MethodBodySerializationContext cont
return rawBody.ToReference();
}
- private static CilRawMethodBody BuildTinyMethodBody(byte[] code) =>
- new CilRawTinyMethodBody(code);
+ private static CilRawMethodBody BuildTinyMethodBody(byte[] code) => new CilRawTinyMethodBody(code);
private CilRawMethodBody BuildFatMethodBody(MethodBodySerializationContext context, CilMethodBody body, byte[] code)
{
@@ -137,35 +138,33 @@ private CilRawMethodBody BuildFatMethodBody(MethodBodySerializationContext conte
return fatBody;
}
- private static byte[] BuildRawCodeStream(MethodBodySerializationContext context, CilMethodBody body)
+ private byte[] BuildRawCodeStream(MethodBodySerializationContext context, CilMethodBody body)
{
- using var codeStream = new MemoryStream();
var bag = context.ErrorListener;
- var writer = new BinaryStreamWriter(codeStream);
+ using var rentedWriter = _writerPool.Rent();
var assembler = new CilAssembler(
- writer,
+ rentedWriter.Writer,
new CilOperandBuilder(context.TokenProvider, bag),
- body.Owner.SafeToString(),
+ body.Owner.SafeToString,
bag);
assembler.WriteInstructions(body.Instructions);
- return codeStream.ToArray();
+ return rentedWriter.GetData();
}
private byte[] SerializeExceptionHandlers(MethodBodySerializationContext context, IList exceptionHandlers, bool needsFatFormat)
{
- using var sectionStream = new MemoryStream();
- var writer = new BinaryStreamWriter(sectionStream);
+ using var rentedWriter = _writerPool.Rent();
for (int i = 0; i < exceptionHandlers.Count; i++)
{
var handler = exceptionHandlers[i];
- WriteExceptionHandler(context, writer, handler, needsFatFormat);
+ WriteExceptionHandler(context, rentedWriter.Writer, handler, needsFatFormat);
}
- return sectionStream.ToArray();
+ return rentedWriter.GetData();
}
private void WriteExceptionHandler(MethodBodySerializationContext context, IBinaryStreamWriter writer, CilExceptionHandler handler, bool useFatFormat)
diff --git a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs b/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs
index 203e7f554..d3244f8e2 100644
--- a/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs
+++ b/src/AsmResolver.DotNet/Code/Cil/DynamicCilOperandResolver.cs
@@ -16,11 +16,11 @@ namespace AsmResolver.DotNet.Code.Cil
public class DynamicCilOperandResolver : PhysicalCilOperandResolver
{
private readonly ModuleReaderContext _readerContext;
- private readonly IList
public class RuntimeConfiguration
{
- private static readonly JsonSerializerOptions JsonSerializerOptions = new()
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- WriteIndented = true,
- IgnoreNullValues = true
- };
-
///
/// Parses runtime configuration from a JSON file.
///
@@ -32,7 +25,7 @@ public class RuntimeConfiguration
/// The parsed runtime configuration.
public static RuntimeConfiguration? FromJson(string json)
{
- return JsonSerializer.Deserialize(json, JsonSerializerOptions);
+ return JsonSerializer.Deserialize(json, RuntimeConfigurationSerializerContext.Default.RuntimeConfiguration);
}
///
@@ -66,7 +59,7 @@ public RuntimeOptions RuntimeOptions
/// The JSON string.
public string ToJson()
{
- return JsonSerializer.Serialize(this, JsonSerializerOptions);
+ return JsonSerializer.Serialize(this, RuntimeConfigurationSerializerContext.Default.RuntimeConfiguration);
}
///
diff --git a/src/AsmResolver.DotNet/Config/Json/RuntimeConfigurationSerializerContext.cs b/src/AsmResolver.DotNet/Config/Json/RuntimeConfigurationSerializerContext.cs
new file mode 100644
index 000000000..7f77c1dc6
--- /dev/null
+++ b/src/AsmResolver.DotNet/Config/Json/RuntimeConfigurationSerializerContext.cs
@@ -0,0 +1,13 @@
+using System.Text.Json.Serialization;
+
+namespace AsmResolver.DotNet.Config.Json
+{
+ [JsonSourceGenerationOptions(
+ PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonSerializable(typeof(RuntimeConfiguration))]
+ internal partial class RuntimeConfigurationSerializerContext : JsonSerializerContext
+ {
+ }
+}
diff --git a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs
index 839feaa96..18e57b9e5 100644
--- a/src/AsmResolver.DotNet/DefaultMetadataResolver.cs
+++ b/src/AsmResolver.DotNet/DefaultMetadataResolver.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using AsmResolver.DotNet.Signatures;
using AsmResolver.DotNet.Signatures.Types;
@@ -11,10 +12,10 @@ namespace AsmResolver.DotNet
///
public class DefaultMetadataResolver : IMetadataResolver
{
- private readonly IDictionary _typeCache;
+ private readonly ConcurrentDictionary _typeCache;
private readonly SignatureComparer _comparer = new()
{
- AcceptNewerAssemblyVersionNumbers = true
+ IgnoreAssemblyVersionNumbers = true
};
///
@@ -24,7 +25,7 @@ public class DefaultMetadataResolver : IMetadataResolver
public DefaultMetadataResolver(IAssemblyResolver assemblyResolver)
{
AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver));
- _typeCache = new Dictionary();
+ _typeCache = new ConcurrentDictionary();
}
///
@@ -54,7 +55,7 @@ public IAssemblyResolver AssemblyResolver
// Check if type definition has changed since last lookup.
if (typeDef.IsTypeOf(type.Namespace, type.Name))
return typeDef;
- _typeCache.Remove(type);
+ _typeCache.TryRemove(type, out _);
}
return null;
diff --git a/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs b/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs
index 87aef8062..2bf0ed76d 100644
--- a/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs
+++ b/src/AsmResolver.DotNet/DotNetCoreAssemblyResolver.cs
@@ -172,10 +172,14 @@ private sealed class RuntimeNameComparer : IComparer
public static readonly RuntimeNameComparer Instance = new();
///
- public int Compare(string x, string y)
+ public int Compare(string? x, string? y)
{
if (x == y)
return 0;
+ if (x is null)
+ return -1;
+ if (y is null)
+ return 1;
if (x == KnownRuntimeNames.NetCoreApp)
return 1;
if (y == KnownRuntimeNames.NetCoreApp)
diff --git a/src/AsmResolver.DotNet/DotNetCorePathProvider.cs b/src/AsmResolver.DotNet/DotNetCorePathProvider.cs
index 63c7cd756..c01e99357 100644
--- a/src/AsmResolver.DotNet/DotNetCorePathProvider.cs
+++ b/src/AsmResolver.DotNet/DotNetCorePathProvider.cs
@@ -13,8 +13,9 @@ namespace AsmResolver.DotNet
public class DotNetCorePathProvider
{
private static readonly string[] DefaultDotNetUnixPaths = {
- "/usr/share/dotnet/shared",
- "/opt/dotnet/shared/"
+ "/usr/share/dotnet/",
+ "/usr/local/share/dotnet/",
+ "/opt/dotnet/"
};
private static readonly Regex NetCoreRuntimePattern = new(@"\.NET( Core)? \d+\.\d+\.\d+");
@@ -23,7 +24,7 @@ public class DotNetCorePathProvider
static DotNetCorePathProvider()
{
DefaultInstallationPath = FindDotNetPath();
- Default = new();
+ Default = new DotNetCorePathProvider();
}
///
@@ -153,8 +154,12 @@ public bool HasRuntimeInstalled(string runtimeName, Version runtimeVersion)
private void DetectInstalledRuntimes(string installationDirectory)
{
installationDirectory = Path.Combine(installationDirectory, "shared");
+ if (!Directory.Exists(installationDirectory))
+ return;
+
foreach (string directory in Directory.EnumerateDirectories(installationDirectory))
_installedRuntimes.Add(new DotNetInstallationInfo(directory));
+
_installedRuntimes.Sort();
}
diff --git a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs b/src/AsmResolver.DotNet/DynamicMethodDefinition.cs
index 90e5865bf..b95d8f003 100644
--- a/src/AsmResolver.DotNet/DynamicMethodDefinition.cs
+++ b/src/AsmResolver.DotNet/DynamicMethodDefinition.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Reflection;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Signatures;
@@ -18,6 +18,7 @@ public class DynamicMethodDefinition : MethodDefinition
///
/// Target Module
/// Dynamic Method / Delegate / DynamicResolver
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls ResolveDynamicResolver and FromDynamicMethod")]
public DynamicMethodDefinition(ModuleDefinition module,object dynamicMethodObj) :
base(new MetadataToken(TableIndex.Method, 0))
{
diff --git a/src/AsmResolver.DotNet/DynamicMethodHelper.cs b/src/AsmResolver.DotNet/DynamicMethodHelper.cs
index e6ad33f2b..f3cece5db 100644
--- a/src/AsmResolver.DotNet/DynamicMethodHelper.cs
+++ b/src/AsmResolver.DotNet/DynamicMethodHelper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -60,7 +60,7 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter
int tryEnd = FieldReader.ReadField(ehInfo, "m_endAddr");
int handlerStart = FieldReader.ReadField(ehInfo, "m_catchAddr")![i];
int handlerEnd = FieldReader.ReadField(ehInfo, "m_catchEndAddr")![i];
- var exceptionType = FieldReader.ReadField(ehInfo, "m_catchClass")![i];
+ var exceptionType = FieldReader.ReadField(ehInfo, "m_catchClass")![i];
var handlerType = (CilExceptionHandlerType) FieldReader.ReadField(ehInfo, "m_type")![i];
var endTryLabel = instructions.GetByOffset(tryEnd)?.CreateLabel() ?? new CilOffsetLabel(tryEnd);
@@ -74,13 +74,14 @@ private static void InterpretEHInfo(CilMethodBody methodBody, ReferenceImporter
FilterStart = null,
HandlerStart = instructions.GetLabel(handlerStart),
HandlerEnd = instructions.GetLabel(handlerEnd),
- ExceptionType = exceptionType != null ? importer.ImportType(exceptionType) : null
+ ExceptionType = exceptionType is not null ? importer.ImportType(exceptionType) : null
};
methodBody.ExceptionHandlers.Add(handler);
}
}
+ [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Calls GetTypes")]
public static object ResolveDynamicResolver(object dynamicMethodObj)
{
//Convert dynamicMethodObj to DynamicResolver
@@ -113,7 +114,7 @@ public static object ResolveDynamicResolver(object dynamicMethodObj)
dynamicMethodObj = Activator.CreateInstance(dynamicResolver, (BindingFlags) (-1), null, new[]
{
ilGenerator
- }, null);
+ }, null)!;
}
return dynamicMethodObj;
}
diff --git a/src/AsmResolver.DotNet/EventDefinition.cs b/src/AsmResolver.DotNet/EventDefinition.cs
index 649ce3734..d21d1036b 100644
--- a/src/AsmResolver.DotNet/EventDefinition.cs
+++ b/src/AsmResolver.DotNet/EventDefinition.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -136,19 +137,19 @@ public IList Semantics
}
///
- /// Gets the method definition representing the add accessor of this event definition.
+ /// Gets the method definition representing the first add accessor of this event definition.
///
public MethodDefinition? AddMethod =>
Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.AddOn)?.Method;
///
- /// Gets the method definition representing the remove accessor of this event definition.
+ /// Gets the method definition representing the first remove accessor of this event definition.
///
public MethodDefinition? RemoveMethod =>
Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.RemoveOn)?.Method;
///
- /// Gets the method definition representing the fire accessor of this event definition.
+ /// Gets the method definition representing the first fire accessor of this event definition.
///
public MethodDefinition? FireMethod =>
Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.Fire)?.Method;
@@ -164,12 +165,39 @@ public IList CustomAttributes
}
}
+ ///
+ /// Clear and apply these methods to the event definition.
+ ///
+ /// The method definition representing the add accessor of this event definition.
+ /// The method definition representing the remove accessor of this event definition.
+ /// The method definition representing the fire accessor of this event definition.
+ public void SetSemanticMethods(MethodDefinition? addMethod, MethodDefinition? removeMethod, MethodDefinition? fireMethod)
+ {
+ Semantics.Clear();
+ if (addMethod is not null)
+ Semantics.Add(new MethodSemantics(addMethod, MethodSemanticsAttributes.AddOn));
+ if (removeMethod is not null)
+ Semantics.Add(new MethodSemantics(removeMethod, MethodSemanticsAttributes.RemoveOn));
+ if (fireMethod is not null)
+ Semantics.Add(new MethodSemantics(fireMethod, MethodSemanticsAttributes.Fire));
+ }
+
///
public bool IsAccessibleFromType(TypeDefinition type) =>
Semantics.Any(s => s.Method?.IsAccessibleFromType(type) ?? false);
IMemberDefinition IMemberDescriptor.Resolve() => this;
+ ///
+ public bool IsImportedInModule(ModuleDefinition module)
+ {
+ return Module == module
+ && (EventType?.IsImportedInModule(module) ?? false);
+ }
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => throw new NotSupportedException();
+
///
/// Obtains the list of custom attributes assigned to the member.
///
@@ -181,7 +209,7 @@ public IList CustomAttributes
new OwnedCollection(this);
///
- /// Obtains the name of the property definition.
+ /// Obtains the name of the event definition.
///
/// The name.
///
@@ -190,7 +218,7 @@ public IList CustomAttributes
protected virtual Utf8String? GetName() => null;
///
- /// Obtains the event type of the property definition.
+ /// Obtains the event type of the event definition.
///
/// The event type.
///
@@ -199,7 +227,7 @@ public IList CustomAttributes
protected virtual ITypeDefOrRef? GetEventType() => null;
///
- /// Obtains the declaring type of the property definition.
+ /// Obtains the declaring type of the event definition.
///
/// The declaring type.
///
@@ -208,7 +236,7 @@ public IList CustomAttributes
protected virtual TypeDefinition? GetDeclaringType() => null;
///
- /// Obtains the methods associated to this property definition.
+ /// Obtains the methods associated to this event definition.
///
/// The method semantic objects.
///
diff --git a/src/AsmResolver.DotNet/ExportedType.cs b/src/AsmResolver.DotNet/ExportedType.cs
index 935a0a795..323bc800e 100644
--- a/src/AsmResolver.DotNet/ExportedType.cs
+++ b/src/AsmResolver.DotNet/ExportedType.cs
@@ -28,8 +28,8 @@ public class ExportedType :
protected ExportedType(MetadataToken token)
: base(token)
{
- _name = new LazyVariable(() => GetName());
- _namespace = new LazyVariable(() => GetNamespace());
+ _name = new LazyVariable(GetName);
+ _namespace = new LazyVariable(GetNamespace);
_implementation = new LazyVariable(GetImplementation);
}
@@ -145,6 +145,23 @@ public IList CustomAttributes
///
public TypeDefinition? Resolve() => Module?.MetadataResolver.ResolveType(this);
+ ///
+ public bool IsImportedInModule(ModuleDefinition module)
+ {
+ return Module == module
+ && (Implementation?.IsImportedInModule(module) ?? false);
+ }
+
+ ///
+ /// Imports the exported type using the provided importer object.
+ ///
+ /// The reference importer to use.
+ /// The imported type.
+ public ExportedType ImportWith(ReferenceImporter importer) => importer.ImportType(this);
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer);
+
IMemberDefinition? IMemberDescriptor.Resolve() => Resolve();
///
diff --git a/src/AsmResolver.DotNet/FieldDefinition.cs b/src/AsmResolver.DotNet/FieldDefinition.cs
index 1961a4db1..39b7e5c3e 100644
--- a/src/AsmResolver.DotNet/FieldDefinition.cs
+++ b/src/AsmResolver.DotNet/FieldDefinition.cs
@@ -4,6 +4,7 @@
using AsmResolver.Collections;
using AsmResolver.DotNet.Signatures;
using AsmResolver.DotNet.Signatures.Marshal;
+using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.PE.DotNet.Metadata.Tables;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
@@ -56,11 +57,6 @@ protected FieldDefinition(MetadataToken token)
/// The name of the field.
/// The attributes.
/// The signature of the field.
- ///
- /// For a valid .NET image, if of the signature referenced by
- /// is set, the bit should be unset in
- /// and vice versa.
- ///
public FieldDefinition(string? name, FieldAttributes attributes, FieldSignature? signature)
: this(new MetadataToken(TableIndex.Field, 0))
{
@@ -69,6 +65,22 @@ public FieldDefinition(string? name, FieldAttributes attributes, FieldSignature?
Signature = signature;
}
+ ///
+ /// Creates a new field definition.
+ ///
+ /// The name of the field.
+ /// The attributes.
+ /// The type of values the field contains.
+ public FieldDefinition(string? name, FieldAttributes attributes, TypeSignature? fieldType)
+ : this(new MetadataToken(TableIndex.Field, 0))
+ {
+ Name = name;
+ Attributes = attributes;
+ Signature = fieldType is not null
+ ? new FieldSignature(fieldType)
+ : null;
+ }
+
///
/// Gets or sets the name of the field.
///
@@ -374,6 +386,23 @@ public IList CustomAttributes
FieldDefinition IFieldDescriptor.Resolve() => this;
+ ///
+ public bool IsImportedInModule(ModuleDefinition module)
+ {
+ return Module == module
+ && (Signature?.IsImportedInModule(module) ?? false);
+ }
+
+ ///
+ /// Imports the field using the provided reference importer object.
+ ///
+ /// The reference importer to use.
+ /// The imported field.
+ public IFieldDescriptor ImportWith(ReferenceImporter importer) => importer.ImportField(this);
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer);
+
IMemberDefinition IMemberDescriptor.Resolve() => this;
///
diff --git a/src/AsmResolver.DotNet/FileReference.cs b/src/AsmResolver.DotNet/FileReference.cs
index e8f78cdb0..8de6ba74a 100644
--- a/src/AsmResolver.DotNet/FileReference.cs
+++ b/src/AsmResolver.DotNet/FileReference.cs
@@ -120,6 +120,20 @@ public IList CustomAttributes
}
}
+ ///
+ public bool IsImportedInModule(ModuleDefinition module) => Module == module;
+
+ ///
+ /// Imports the file using the provided reference importer object.
+ ///
+ /// The reference importer to use.
+ /// The imported file reference.
+ public FileReference ImportWith(ReferenceImporter importer) =>
+ (FileReference) importer.ImportImplementation(this);
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer);
+
///
/// Obtains the name of the referenced file.
///
diff --git a/src/AsmResolver.DotNet/FullNameGenerator.cs b/src/AsmResolver.DotNet/FullNameGenerator.cs
index e04c42aa2..fe2409c38 100644
--- a/src/AsmResolver.DotNet/FullNameGenerator.cs
+++ b/src/AsmResolver.DotNet/FullNameGenerator.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using AsmResolver.DotNet.Signatures;
using AsmResolver.DotNet.Signatures.Types;
@@ -36,6 +37,12 @@ public static string GetFieldFullName(string? name, ITypeDescriptor? declaringTy
/// The full name
public static string GetMethodFullName(string? name, ITypeDescriptor? declaringType, MethodSignature? signature)
{
+ if (signature?.GenericParameterCount > 0)
+ {
+ return GetMethodFullName(name, declaringType, signature,
+ Enumerable.Repeat("?", signature.GenericParameterCount));
+ }
+
string returnTypeString = signature?.ReturnType.FullName ?? TypeSignature.NullTypeToString;
string parameterTypesString = GetParameterTypesString(signature);
@@ -53,16 +60,23 @@ public static string GetMethodFullName(string? name, ITypeDescriptor? declaringT
/// The signature of the method.
/// The type arguments.
/// The full name
- public static string GetMethodFullName(string? name, ITypeDescriptor? declaringType, MethodSignature? signature,
- IEnumerable typeArguments)
+ public static string GetMethodFullName(
+ string? name,
+ ITypeDescriptor? declaringType,
+ MethodSignature? signature,
+ IEnumerable typeArguments)
{
string returnTypeString = signature?.ReturnType.FullName ?? TypeSignature.NullTypeToString;
string parameterTypesString = GetParameterTypesString(signature);
- string typeArgumentsString = string.Join(", ", typeArguments);
+
+ string[] argumentNames = typeArguments.ToArray();
+ string typeArgumentsString = argumentNames.Length>0
+ ? $"<{string.Join(", ", argumentNames)}>"
+ : string.Empty;
return declaringType is null
- ? $"{returnTypeString} {name}<{typeArgumentsString}>({parameterTypesString})"
- : $"{returnTypeString} {declaringType}::{name}<{typeArgumentsString}>({parameterTypesString})";
+ ? $"{returnTypeString} {name}{typeArgumentsString}({parameterTypesString})"
+ : $"{returnTypeString} {declaringType}::{name}{typeArgumentsString}({parameterTypesString})";
}
///
diff --git a/src/AsmResolver.DotNet/IImplementation.cs b/src/AsmResolver.DotNet/IImplementation.cs
index 1674e3120..7d99282d7 100644
--- a/src/AsmResolver.DotNet/IImplementation.cs
+++ b/src/AsmResolver.DotNet/IImplementation.cs
@@ -4,7 +4,7 @@ namespace AsmResolver.DotNet
/// Represents a member that is either a reference to an external file, assembly or type, and can be referenced by
/// an Implementation coded index.
///
- public interface IImplementation : IFullNameProvider, IModuleProvider, IHasCustomAttribute
+ public interface IImplementation : IFullNameProvider, IModuleProvider, IHasCustomAttribute, IImportable
{
}
}
diff --git a/src/AsmResolver.DotNet/IImportable.cs b/src/AsmResolver.DotNet/IImportable.cs
new file mode 100644
index 000000000..872e578aa
--- /dev/null
+++ b/src/AsmResolver.DotNet/IImportable.cs
@@ -0,0 +1,26 @@
+namespace AsmResolver.DotNet
+{
+ ///
+ /// Represents an entity in a .NET module that can be imported using the .
+ ///
+ public interface IImportable
+ {
+ ///
+ /// Determines whether the descriptor of the member is fully imported in the provided module.
+ ///
+ /// The module that is supposed to import the member.
+ /// true if the descriptor of the member is fully imported by the module, false otherwise.
+ ///
+ /// This method verifies all references in the descriptor of the member only. It does not verify any additional
+ /// data or contents (such as a method body) associated to the member.
+ ///
+ bool IsImportedInModule(ModuleDefinition module);
+
+ ///
+ /// Imports the member using the provided reference importer object.
+ ///
+ /// The reference importer to use for importing the object.
+ /// The imported member.
+ IImportable ImportWith(ReferenceImporter importer);
+ }
+}
diff --git a/src/AsmResolver.DotNet/IMemberDescriptor.cs b/src/AsmResolver.DotNet/IMemberDescriptor.cs
index 75cf4f91d..e837e453b 100644
--- a/src/AsmResolver.DotNet/IMemberDescriptor.cs
+++ b/src/AsmResolver.DotNet/IMemberDescriptor.cs
@@ -3,7 +3,7 @@ namespace AsmResolver.DotNet
///
/// Provides members for describing a (reference to a) member defined in a .NET assembly.
///
- public interface IMemberDescriptor : IFullNameProvider, IModuleProvider
+ public interface IMemberDescriptor : IFullNameProvider, IModuleProvider, IImportable
{
///
/// When this member is defined in a type, gets the enclosing type.
diff --git a/src/AsmResolver.DotNet/IResolutionScope.cs b/src/AsmResolver.DotNet/IResolutionScope.cs
index 22c397ff0..444a63298 100644
--- a/src/AsmResolver.DotNet/IResolutionScope.cs
+++ b/src/AsmResolver.DotNet/IResolutionScope.cs
@@ -3,7 +3,7 @@ namespace AsmResolver.DotNet
///
/// Represents a member that can be referenced by a ResolutionScope coded index.
///
- public interface IResolutionScope : IMetadataMember, INameProvider, IModuleProvider
+ public interface IResolutionScope : IMetadataMember, INameProvider, IModuleProvider, IImportable
{
///
/// Gets the underlying assembly that this scope defines.
diff --git a/src/AsmResolver.DotNet/ITypeDefOrRef.cs b/src/AsmResolver.DotNet/ITypeDefOrRef.cs
index bf80f8481..a81bc335b 100644
--- a/src/AsmResolver.DotNet/ITypeDefOrRef.cs
+++ b/src/AsmResolver.DotNet/ITypeDefOrRef.cs
@@ -28,5 +28,12 @@ public interface ITypeDefOrRef : ITypeDescriptor, IMemberRefParent, IHasCustomAt
{
get;
}
+
+ ///
+ /// Imports the type using the provided reference importer object.
+ ///
+ /// The reference importer to use for importing the type.
+ /// The imported type.
+ new ITypeDefOrRef ImportWith(ReferenceImporter importer);
}
}
diff --git a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs
index 7e63115fc..aa0dc667d 100644
--- a/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs
+++ b/src/AsmResolver.DotNet/InvalidTypeDefOrRef.cs
@@ -73,6 +73,15 @@ public static InvalidTypeDefOrRef Get(InvalidTypeSignatureError error)
return instance;
}
+ ///
+ public bool IsImportedInModule(ModuleDefinition module) => false;
+
+ ///
+ ITypeDefOrRef ITypeDefOrRef.ImportWith(ReferenceImporter importer) => throw new InvalidOperationException();
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => throw new InvalidOperationException();
+
IMemberDefinition? IMemberDescriptor.Resolve() => null;
TypeDefinition? ITypeDescriptor.Resolve() => null;
@@ -84,6 +93,5 @@ public static InvalidTypeDefOrRef Get(InvalidTypeSignatureError error)
///
public override string ToString() => ((IFullNameProvider) this).Name!;
-
}
}
diff --git a/src/AsmResolver.DotNet/KnownCorLibs.cs b/src/AsmResolver.DotNet/KnownCorLibs.cs
index 106d21c03..fbc0d3893 100644
--- a/src/AsmResolver.DotNet/KnownCorLibs.cs
+++ b/src/AsmResolver.DotNet/KnownCorLibs.cs
@@ -70,6 +70,16 @@ public static class KnownCorLibs
0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E
});
+ ///
+ /// References System.Private.CoreLib.dll, Version=7.0.0.0, PublicKeyToken=7CEC85D7BEA7798E. This is used by .NET
+ /// assemblies targeting .NET 7.0.
+ ///
+ public static readonly AssemblyReference SystemPrivateCoreLib_v7_0_0_0 = new AssemblyReference("System.Private.CoreLib",
+ new Version(7, 0, 0, 0), false, new byte[]
+ {
+ 0x7C, 0xEC, 0x85, 0xD7, 0xBE, 0xA7, 0x79, 0x8E
+ });
+
///
/// References System.Runtime.dll, Version=4.0.20.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET
/// assemblies targeting .NET standard 1.3 and 1.4.
@@ -101,7 +111,7 @@ public static class KnownCorLibs
});
///
- /// References System.Runtime.dll, Version=4.2.1.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET
+ /// References System.Runtime.dll, Version=4.2.2.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET
/// assemblies targeting .NET Core 3.1.
///
public static readonly AssemblyReference SystemRuntime_v4_2_2_0 = new AssemblyReference("System.Runtime",
@@ -130,6 +140,16 @@ public static class KnownCorLibs
0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A
});
+ ///
+ /// References System.Runtime.dll, Version=7.0.0.0, PublicKeyToken=B03F5F7F11D50A3A. This is used by .NET
+ /// assemblies targeting .NET 7.0.
+ ///
+ public static readonly AssemblyReference SystemRuntime_v7_0_0_0 = new AssemblyReference("System.Runtime",
+ new Version(7, 0, 0, 0), false, new byte[]
+ {
+ 0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A
+ });
+
///
/// References netstandard.dll, Version=2.0.0.0, PublicKeyToken=CC7B13FFCD2DDD51. This is used by .NET
/// assemblies targeting .NET standard 2.0.
@@ -164,9 +184,11 @@ static KnownCorLibs()
SystemRuntime_v4_2_2_0,
SystemRuntime_v5_0_0_0,
SystemRuntime_v6_0_0_0,
+ SystemRuntime_v7_0_0_0,
SystemPrivateCoreLib_v4_0_0_0,
SystemPrivateCoreLib_v5_0_0_0,
- SystemPrivateCoreLib_v6_0_0_0
+ SystemPrivateCoreLib_v6_0_0_0,
+ SystemPrivateCoreLib_v7_0_0_0
};
KnownCorLibNames = new HashSet(KnownCorLibReferences.Select(r => r.Name!.Value));
diff --git a/src/AsmResolver.DotNet/MemberReference.cs b/src/AsmResolver.DotNet/MemberReference.cs
index 000940b0a..69a274884 100644
--- a/src/AsmResolver.DotNet/MemberReference.cs
+++ b/src/AsmResolver.DotNet/MemberReference.cs
@@ -153,6 +153,25 @@ public IList CustomAttributes
throw new ArgumentOutOfRangeException();
}
+ ///
+ public bool IsImportedInModule(ModuleDefinition module)
+ {
+ return Module == module
+ && (Signature?.IsImportedInModule(module) ?? false);
+ }
+
+ ///
+ /// Imports the member using the provided reference importer object.
+ ///
+ /// The reference importer to use for importing the object.
+ /// The imported member.
+ public MemberReference ImportWith(ReferenceImporter importer) => IsMethod
+ ? (MemberReference) importer.ImportMethod(this)
+ : (MemberReference) importer.ImportField(this);
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer);
+
FieldDefinition? IFieldDescriptor.Resolve()
{
if (!IsField)
diff --git a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs
index d0cb8c501..2ae1e422c 100644
--- a/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs
+++ b/src/AsmResolver.DotNet/Memory/TypeMemoryLayoutDetector.cs
@@ -251,10 +251,8 @@ private TypeMemoryLayout InferExplicitLayout(TypeDefinition type, uint alignment
// All fields in an explicitly laid out structure need to have a field offset assigned.
if (!field.FieldOffset.HasValue)
{
- throw new ArgumentException(string.Format(
- "{0} ({1}) is defined in a type with explicit layout, but does not have a field offset assigned.",
- field.FullName,
- field.MetadataToken.ToString()));
+ throw new ArgumentException(
+ $"{field.FullName} ({field.MetadataToken}) is defined in a type with explicit layout, but does not have a field offset assigned.");
}
uint offset = (uint) field.FieldOffset.Value;
diff --git a/src/AsmResolver.DotNet/MethodDefinition.cs b/src/AsmResolver.DotNet/MethodDefinition.cs
index e79a423a4..4a32048be 100644
--- a/src/AsmResolver.DotNet/MethodDefinition.cs
+++ b/src/AsmResolver.DotNet/MethodDefinition.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Threading;
using AsmResolver.Collections;
using AsmResolver.DotNet.Code;
@@ -27,7 +28,7 @@ public class MethodDefinition :
IHasSecurityDeclaration,
IManagedEntrypoint
{
- private readonly LazyVariable _name;
+ private readonly LazyVariable _name;
private readonly LazyVariable _declaringType;
private readonly LazyVariable _signature;
private readonly LazyVariable _methodBody;
@@ -47,7 +48,7 @@ public class MethodDefinition :
protected MethodDefinition(MetadataToken token)
: base(token)
{
- _name =new LazyVariable(GetName);
+ _name =new LazyVariable(GetName);
_declaringType = new LazyVariable(GetDeclaringType);
_signature = new LazyVariable(GetSignature);
_methodBody = new LazyVariable(GetBody);
@@ -100,7 +101,13 @@ public MethodDefinition(string? name, MethodAttributes attributes, MethodSignatu
}
///
- public string FullName => FullNameGenerator.GetMethodFullName(Name, DeclaringType, Signature);
+ public string FullName => FullNameGenerator.GetMethodFullName(
+ Name,
+ DeclaringType,
+ Signature,
+ GenericParameters.Count > 0
+ ? GenericParameters.Select(x => x.Name?.Value ?? NullName)
+ : Enumerable.Empty());
///
/// Gets or sets the attributes associated to the method.
@@ -691,6 +698,23 @@ public IList GenericParameters
MethodDefinition IMethodDescriptor.Resolve() => this;
+ ///
+ public bool IsImportedInModule(ModuleDefinition module)
+ {
+ return Module == module
+ && (Signature?.IsImportedInModule(module) ?? false);
+ }
+
+ ///
+ /// Imports the method using the provided reference importer object.
+ ///
+ /// The reference importer to use.
+ /// The imported method.
+ public IMethodDefOrRef ImportWith(ReferenceImporter importer) => importer.ImportMethod(this);
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer);
+
IMemberDefinition IMemberDescriptor.Resolve() => this;
///
@@ -715,7 +739,7 @@ public bool IsAccessibleFromType(TypeDefinition type)
///
/// This method is called upon initialization of the property.
///
- protected virtual string? GetName() => null;
+ protected virtual Utf8String? GetName() => null;
///
/// Obtains the declaring type of the method definition.
diff --git a/src/AsmResolver.DotNet/MethodSpecification.cs b/src/AsmResolver.DotNet/MethodSpecification.cs
index 02da0eb30..31ca1226e 100644
--- a/src/AsmResolver.DotNet/MethodSpecification.cs
+++ b/src/AsmResolver.DotNet/MethodSpecification.cs
@@ -73,7 +73,7 @@ public MethodSpecification(IMethodDefOrRef? method, GenericInstanceMethodSignatu
Name,
DeclaringType,
Method?.Signature,
- Signature?.TypeArguments ?? Enumerable.Empty());
+ Signature?.TypeArguments.Select(x => x.FullName) ?? Enumerable.Empty());
///
public ModuleDefinition? Module => Method?.Module;
@@ -99,6 +99,23 @@ public IList CustomAttributes
///
public MethodDefinition? Resolve() => Method?.Resolve();
+ ///
+ public bool IsImportedInModule(ModuleDefinition module)
+ {
+ return (Method?.IsImportedInModule(module) ?? false)
+ && (Signature?.IsImportedInModule(module) ?? false);
+ }
+
+ ///
+ /// Imports the method specification using the provided reference importer.
+ ///
+ /// The reference importer to use.
+ /// The imported method specification.
+ public MethodSpecification ImportWith(ReferenceImporter importer) => importer.ImportMethod(this);
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer);
+
IMemberDefinition? IMemberDescriptor.Resolve() => Resolve();
///
diff --git a/src/AsmResolver.DotNet/ModuleDefinition.cs b/src/AsmResolver.DotNet/ModuleDefinition.cs
index 8891c859c..3e693c258 100644
--- a/src/AsmResolver.DotNet/ModuleDefinition.cs
+++ b/src/AsmResolver.DotNet/ModuleDefinition.cs
@@ -54,6 +54,7 @@ public class ModuleDefinition :
private readonly LazyVariable _runtimeVersion;
private readonly LazyVariable _nativeResources;
private IList? _debugData;
+ private ReferenceImporter? _defaultImporter;
///
/// Reads a .NET module from the provided input buffer.
@@ -303,10 +304,9 @@ public ModuleDefinition(string? name, AssemblyReference corLib)
Name = name;
var importer = new ReferenceImporter(this);
- corLib = (AssemblyReference)importer.ImportScope(corLib);
+ corLib = (AssemblyReference) importer.ImportScope(corLib);
CorLibTypeFactory = new CorLibTypeFactory(corLib);
- AssemblyReferences.Add(corLib);
OriginalTargetRuntime = DetectTargetRuntime();
MetadataResolver = new DefaultMetadataResolver(CreateAssemblyResolver(UncachedFileService.Instance));
@@ -762,6 +762,19 @@ public IMetadataResolver MetadataResolver
set => _managedEntrypoint.Value = value;
}
+ ///
+ /// Gets the default importer instance for this module.
+ ///
+ public ReferenceImporter DefaultImporter
+ {
+ get
+ {
+ if (_defaultImporter is null)
+ Interlocked.CompareExchange(ref _defaultImporter, GetDefaultImporter(), null);
+ return _defaultImporter;
+ }
+ }
+
///
/// Looks up a member by its metadata token.
///
@@ -1049,6 +1062,15 @@ public TypeDefinition GetOrCreateModuleType()
///
protected virtual IList GetDebugData() => new List();
+ ///
+ /// Obtains the default reference importer assigned to this module.
+ ///
+ /// The importer.
+ ///
+ /// This method is called upon initialization of the property.
+ ///
+ protected virtual ReferenceImporter GetDefaultImporter() => new(this);
+
///
/// Detects the runtime that this module targets.
///
@@ -1105,6 +1127,19 @@ protected IAssemblyResolver CreateAssemblyResolver(IFileService fileService)
///
public override string ToString() => Name ?? string.Empty;
+ ///
+ bool IImportable.IsImportedInModule(ModuleDefinition module) => this == module;
+
+ ///
+ /// Imports the module using the provided reference importer object.
+ ///
+ /// The reference importer to use.
+ /// The imported module.
+ public ModuleReference ImportWith(ReferenceImporter importer) => importer.ImportModule(new ModuleReference(Name));
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer);
+
///
/// Rebuilds the .NET module to a portable executable file and writes it to the file system.
///
diff --git a/src/AsmResolver.DotNet/ModuleReference.cs b/src/AsmResolver.DotNet/ModuleReference.cs
index 4feacbf12..1c4cef6d4 100644
--- a/src/AsmResolver.DotNet/ModuleReference.cs
+++ b/src/AsmResolver.DotNet/ModuleReference.cs
@@ -76,6 +76,19 @@ public IList CustomAttributes
}
}
+ ///
+ public bool IsImportedInModule(ModuleDefinition module) => Module == module;
+
+ ///
+ /// Imports the module reference using the provided reference importer object.
+ ///
+ /// The reference importer to use.
+ /// The imported module.
+ public ModuleReference ImportWith(ReferenceImporter importer) => importer.ImportModule(this);
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => ImportWith(importer);
+
///
/// Obtains the name of the module.
///
diff --git a/src/AsmResolver.DotNet/PropertyDefinition.cs b/src/AsmResolver.DotNet/PropertyDefinition.cs
index 05c94c9b2..325bacd11 100644
--- a/src/AsmResolver.DotNet/PropertyDefinition.cs
+++ b/src/AsmResolver.DotNet/PropertyDefinition.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -170,23 +171,47 @@ public IList CustomAttributes
}
///
- /// Gets the method definition representing the get accessor of this property definition.
+ /// Gets the method definition representing the first get accessor of this property definition.
///
public MethodDefinition? GetMethod =>
Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.Getter)?.Method;
///
- /// Gets the method definition representing the set accessor of this property definition.
+ /// Gets the method definition representing the first set accessor of this property definition.
///
public MethodDefinition? SetMethod =>
Semantics.FirstOrDefault(s => s.Attributes == MethodSemanticsAttributes.Setter)?.Method;
+ ///
+ /// Clear and apply these methods to the property definition.
+ ///
+ /// The method definition representing the get accessor of this property definition.
+ /// The method definition representing the set accessor of this property definition.
+ public void SetSemanticMethods(MethodDefinition? getMethod, MethodDefinition? setMethod)
+ {
+ Semantics.Clear();
+ if (getMethod is not null)
+ Semantics.Add(new MethodSemantics(getMethod, MethodSemanticsAttributes.Getter));
+ if (setMethod is not null)
+ Semantics.Add(new MethodSemantics(setMethod, MethodSemanticsAttributes.Setter));
+ }
+
///
public bool IsAccessibleFromType(TypeDefinition type) =>
Semantics.Any(s => s.Method?.IsAccessibleFromType(type) ?? false);
IMemberDefinition IMemberDescriptor.Resolve() => this;
+ ///
+ public bool IsImportedInModule(ModuleDefinition module)
+ {
+ return Module == module
+ && (Signature?.IsImportedInModule(module) ?? false);
+ }
+
+ ///
+ IImportable IImportable.ImportWith(ReferenceImporter importer) => throw new NotSupportedException();
+
///
/// Obtains the name of the property definition.
///
diff --git a/src/AsmResolver.DotNet/ReferenceImporter.cs b/src/AsmResolver.DotNet/ReferenceImporter.cs
index 5f2429407..33d904554 100644
--- a/src/AsmResolver.DotNet/ReferenceImporter.cs
+++ b/src/AsmResolver.DotNet/ReferenceImporter.cs
@@ -47,7 +47,7 @@ public IResolutionScope ImportScope(IResolutionScope? scope)
{
if (scope is null)
throw new ArgumentNullException(nameof(scope));
- if (scope.Module == TargetModule)
+ if (scope.IsImportedInModule(TargetModule))
return scope;
return scope switch
@@ -60,6 +60,27 @@ public IResolutionScope ImportScope(IResolutionScope? scope)
};
}
+ ///
+ /// Imports an implementation reference.
+ ///
+ /// The implementation reference to import.
+ /// The imported implementation reference.
+ public IImplementation ImportImplementation(IImplementation? implementation)
+ {
+ if (implementation is null)
+ throw new ArgumentNullException(nameof(implementation));
+ if (implementation.IsImportedInModule(TargetModule))
+ return implementation;
+
+ return implementation switch
+ {
+ AssemblyReference assembly => ImportAssembly(assembly),
+ ExportedType type => ImportType(type),
+ FileReference file => ImportFile(file),
+ _ => throw new ArgumentOutOfRangeException(nameof(implementation))
+ };
+ }
+
///
/// Imports a reference to an assembly.
///
@@ -69,7 +90,7 @@ protected virtual AssemblyReference ImportAssembly(AssemblyDescriptor assembly)
{
if (assembly is null)
throw new ArgumentNullException(nameof(assembly));
- if (assembly is AssemblyReference r && r.Module == TargetModule)
+ if (assembly is AssemblyReference r && assembly.IsImportedInModule(TargetModule))
return r;
var reference = TargetModule.AssemblyReferences.FirstOrDefault(a => _comparer.Equals(a, assembly));
@@ -83,6 +104,29 @@ protected virtual AssemblyReference ImportAssembly(AssemblyDescriptor assembly)
return reference;
}
+ ///
+ /// Imports a file reference.
+ ///
+ /// The file to import.
+ /// The imported file.
+ protected virtual FileReference ImportFile(FileReference file)
+ {
+ if (file is null)
+ throw new ArgumentNullException(nameof(file));
+ if (file.IsImportedInModule(TargetModule))
+ return file;
+
+ var reference = TargetModule.FileReferences.FirstOrDefault(a => a.Name == file.Name);
+
+ if (reference is null)
+ {
+ reference = new FileReference(file.Name, file.Attributes);
+ TargetModule.FileReferences.Add(reference);
+ }
+
+ return reference;
+ }
+
///
/// Imports a reference to a module.
///
@@ -92,7 +136,7 @@ public virtual ModuleReference ImportModule(ModuleReference module)
{
if (module is null)
throw new ArgumentNullException(nameof(module));
- if (module.Module == TargetModule)
+ if (module.IsImportedInModule(TargetModule))
return module;
var reference = TargetModule.ModuleReferences.FirstOrDefault(a => _comparer.Equals(a, module));
@@ -139,10 +183,14 @@ protected virtual ITypeDefOrRef ImportType(TypeDefinition type)
{
AssertTypeIsValid(type);
- if (type.Module == TargetModule)
+ if (type.IsImportedInModule(TargetModule))
return type;
- return new TypeReference(TargetModule, ImportScope(type.Module!), type.Namespace, type.Name);
+ return new TypeReference(
+ TargetModule,
+ ImportScope(((ITypeDescriptor) type).Scope),
+ type.Namespace,
+ type.Name);
}
///
@@ -154,7 +202,7 @@ protected virtual ITypeDefOrRef ImportType(TypeReference type)
{
AssertTypeIsValid(type);
- if (type.Module == TargetModule)
+ if (type.IsImportedInModule(TargetModule))
return type;
return new TypeReference(TargetModule, ImportScope(type.Scope!), type.Namespace, type.Name);
@@ -171,12 +219,35 @@ protected virtual ITypeDefOrRef ImportType(TypeSpecification type)
if (type.Signature is null)
throw new ArgumentNullException(nameof(type));
- if (type.Module == TargetModule)
+ if (type.IsImportedInModule(TargetModule))
return type;
return new TypeSpecification(ImportTypeSignature(type.Signature));
}
+ ///
+ /// Imports a forwarded type.
+ ///
+ /// The type to import.
+ /// The imported type.
+ public virtual ExportedType ImportType(ExportedType type)
+ {
+ if (type is null)
+ throw new ArgumentNullException(nameof(type));
+ if (type.IsImportedInModule(TargetModule))
+ return type;
+
+ var result = TargetModule.ExportedTypes.FirstOrDefault(a => _comparer.Equals(a, type));
+
+ if (result is null)
+ {
+ result = new ExportedType(ImportImplementation(type.Implementation), type.Namespace, type.Name);
+ TargetModule.ExportedTypes.Add(result);
+ }
+
+ return result;
+ }
+
///
/// Imports the given type signature into the target module.
///
@@ -186,7 +257,7 @@ public virtual TypeSignature ImportTypeSignature(TypeSignature type)
{
if (type is null)
throw new ArgumentNullException(nameof(type));
- if (type.Module == TargetModule)
+ if (type.IsImportedInModule(TargetModule))
return type;
return type.AcceptVisitor(this);
@@ -212,11 +283,8 @@ public virtual ITypeDefOrRef ImportType(Type type)
throw new ArgumentNullException(nameof(type));
var importedTypeSig = ImportTypeSignature(type);
- if (importedTypeSig is TypeDefOrRefSignature
- || importedTypeSig is CorLibTypeSignature)
- {
+ if (importedTypeSig is TypeDefOrRefSignature or CorLibTypeSignature)
return importedTypeSig.GetUnderlyingTypeDefOrRef()!;
- }
return new TypeSpecification(importedTypeSig);
}
@@ -266,6 +334,7 @@ private TypeSignature ImportArrayType(Type type)
var result = new ArrayTypeSignature(baseType);
for (int i = 0; i < rank; i++)
result.Dimensions.Add(new ArrayDimension());
+
return result;
}
@@ -318,7 +387,7 @@ public virtual IMethodDefOrRef ImportMethod(IMethodDefOrRef method)
if (method.Signature is null)
throw new ArgumentException("Cannot import a method that does not have a signature.");
- if (method.Module == TargetModule)
+ if (method.IsImportedInModule(TargetModule))
return method;
return new MemberReference(
@@ -406,7 +475,7 @@ public virtual MethodSpecification ImportMethod(MethodSpecification method)
if (method.DeclaringType is null)
throw new ArgumentException("Cannot import a method that is not added to a type.");
- if (method.Module == TargetModule)
+ if (method.IsImportedInModule(TargetModule))
return method;
var memberRef = ImportMethod(method.Method);
@@ -435,8 +504,8 @@ public virtual IMethodDescriptor ImportMethod(MethodBase method)
? ImportTypeSignature(info.ReturnType)
: TargetModule.CorLibTypeFactory.Void;
- var parameters = (method.DeclaringType != null && method.DeclaringType.IsConstructedGenericType)
- ? method.Module.ResolveMethod(method.MetadataToken).GetParameters()
+ var parameters = method.DeclaringType is { IsConstructedGenericType: true }
+ ? method.Module.ResolveMethod(method.MetadataToken)!.GetParameters()
: method.GetParameters();
var parameterTypes = new TypeSignature[parameters.Length];
@@ -479,7 +548,7 @@ public virtual IFieldDescriptor ImportField(IFieldDescriptor field)
if (field.Signature is null)
throw new ArgumentException("Cannot import a field that does not have a signature.");
- if (field.Module == TargetModule)
+ if (field.IsImportedInModule(TargetModule))
return field;
return new MemberReference(
@@ -512,16 +581,14 @@ public MemberReference ImportField(FieldInfo field)
if (field is null)
throw new ArgumentNullException(nameof(field));
- if (field.DeclaringType != null && field.DeclaringType.IsConstructedGenericType)
- field = field.Module.ResolveField(field.MetadataToken);
+ if (field.DeclaringType is { IsConstructedGenericType: true })
+ field = field.Module.ResolveField(field.MetadataToken)!;
var scope = field.DeclaringType != null
? ImportType(field.DeclaringType)
: TargetModule.GetModuleType();
- var signature = new FieldSignature(field.IsStatic ? 0 : CallingConventionAttributes.HasThis,
- ImportTypeSignature(field.FieldType));
-
+ var signature = new FieldSignature(ImportTypeSignature(field.FieldType));
return new MemberReference(scope, field.Name, signature);
}
diff --git a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs
index 66cb50656..082c7f483 100644
--- a/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs
+++ b/src/AsmResolver.DotNet/ReflectionAssemblyDescriptor.cs
@@ -1,3 +1,4 @@
+using System;
using System.Reflection;
using AsmResolver.PE.DotNet.Metadata.Tables;
@@ -22,7 +23,7 @@ public ReflectionAssemblyDescriptor(ModuleDefinition parentModule, AssemblyName
{
_parentModule = parentModule;
_assemblyName = assemblyName;
- Version = assemblyName.Version;
+ Version = assemblyName.Version ?? new Version();
}
///
@@ -31,6 +32,13 @@ public ReflectionAssemblyDescriptor(ModuleDefinition parentModule, AssemblyName
///
protected override Utf8String? GetCulture() => _assemblyName.CultureName;
+ ///
+ public override bool IsImportedInModule(ModuleDefinition module) => false;
+
+ ///
+ public override AssemblyReference ImportWith(ReferenceImporter importer) =>
+ (AssemblyReference) importer.ImportScope(new AssemblyReference(this));
+
///
public override bool IsCorLib => Name is not null && KnownCorLibs.KnownCorLibNames.Contains(Name);
diff --git a/src/AsmResolver.DotNet/RequiresUnreferencedCodeAttribute.cs b/src/AsmResolver.DotNet/RequiresUnreferencedCodeAttribute.cs
new file mode 100644
index 000000000..c5fd0fea6
--- /dev/null
+++ b/src/AsmResolver.DotNet/RequiresUnreferencedCodeAttribute.cs
@@ -0,0 +1,34 @@
+#if !NET6_0_OR_GREATER
+namespace System.Diagnostics.CodeAnalysis
+{
+ ///
+ /// Indicates that the specified method requires dynamic access to code that is not
+ /// referenced statically, for example, through System.Reflection.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method, Inherited = false)]
+ internal sealed class RequiresUnreferencedCodeAttribute : Attribute
+ {
+ ///
+ /// Gets a message that contains information about the usage of unreferenced code.
+ ///
+ public string Message { get; }
+
+ ///
+ /// Gets or sets an optional URL that contains more information about the method,
+ /// why it requires unreferenced code, and what options a consumer has to deal with
+ /// it.
+ ///
+ public string? Url { get; set; }
+
+ ///
+ /// Initializes a new instance of the System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute
+ /// class with the specified message.
+ ///
+ /// A message that contains information about the usage of unreferenced code.
+ public RequiresUnreferencedCodeAttribute(string message)
+ {
+ Message = message;
+ }
+ }
+}
+#endif
diff --git a/src/AsmResolver.DotNet/SafeExtensions.cs b/src/AsmResolver.DotNet/SafeExtensions.cs
index f567a20a7..47de4b13b 100644
--- a/src/AsmResolver.DotNet/SafeExtensions.cs
+++ b/src/AsmResolver.DotNet/SafeExtensions.cs
@@ -1,4 +1,3 @@
-using System;
using AsmResolver.DotNet.Builder;
namespace AsmResolver.DotNet
@@ -12,14 +11,14 @@ public static string SafeToString(this IMetadataMember? self)
try
{
- string value = self.ToString();
+ string value = self.ToString()!;
if (value.Length > 200)
value = $"{value.Remove(197)}... (truncated)";
if (self.MetadataToken.Rid != 0)
value = $"{value} (0x{self.MetadataToken.ToString()})";
return value;
}
- catch (Exception)
+ catch
{
return $"0x{self.MetadataToken.ToString()}";
}
@@ -32,9 +31,9 @@ public static string SafeToString(this object? self)
try
{
- return self.ToString();
+ return self.ToString()!;
}
- catch (Exception)
+ catch
{
return self.GetType().ToString();
}
diff --git a/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs b/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs
index 016500ecd..c21fc9e91 100644
--- a/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs
+++ b/src/AsmResolver.DotNet/Serialized/CachedSerializedMemberFactory.cs
@@ -9,6 +9,7 @@ namespace AsmResolver.DotNet.Serialized
internal class CachedSerializedMemberFactory
{
private readonly ModuleReaderContext _context;
+ private readonly TablesStream _tablesStream;
private TypeReference?[]? _typeReferences;
private TypeDefinition?[]? _typeDefinitions;
@@ -38,6 +39,7 @@ internal class CachedSerializedMemberFactory
internal CachedSerializedMemberFactory(ModuleReaderContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
+ _tablesStream = _context.Image.DotNetDirectory!.Metadata!.GetStream();
}
internal bool TryLookupMember(MetadataToken token, [NotNullWhen(true)] out IMetadataMember? member)
@@ -247,9 +249,7 @@ internal bool TryLookupMember(MetadataToken token, [NotNullWhen(true)] out IMeta
where TMember : class, IMetadataMember
{
// Obtain table.
- var table = (MetadataTable) _context.Image.DotNetDirectory!.Metadata
- !.GetStream()
- .GetTable(token.Table);
+ var table = (MetadataTable) _tablesStream.GetTable(token.Table);
// Check if within bounds.
if (token.Rid == 0 || token.Rid > table.Count)
diff --git a/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs b/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs
index 0ff094b92..07dbb19ea 100644
--- a/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs
+++ b/src/AsmResolver.DotNet/Serialized/DefaultMethodBodyReader.cs
@@ -2,7 +2,6 @@
using AsmResolver.DotNet.Code;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.PE.DotNet.Cil;
-using AsmResolver.PE.DotNet.Metadata.Tables;
using AsmResolver.PE.DotNet.Metadata.Tables.Rows;
namespace AsmResolver.DotNet.Serialized
@@ -16,27 +15,26 @@ public class DefaultMethodBodyReader : IMethodBodyReader
///
public virtual MethodBody? ReadMethodBody(ModuleReaderContext context, MethodDefinition owner, in MethodDefinitionRow row)
{
+ var bodyReference = row.Body;
+ if (bodyReference == SegmentReference.Null)
+ return null;
+
+ MethodBody? result = null;
+
try
{
- if (row.Body.CanRead)
+ if (bodyReference.CanRead && owner.IsIL)
{
- if (owner.IsIL)
- {
- var reader = row.Body.CreateReader();
- var rawBody = CilRawMethodBody.FromReader(context, ref reader);
- return rawBody is not null
- ? CilMethodBody.FromRawMethodBody(context, owner, rawBody)
- : null;
- }
- else
- {
- context.NotSupported($"Body of method {owner.MetadataToken} is native and unbounded which is not supported.");
- // TODO: handle native method bodies.
- }
+ var reader = bodyReference.CreateReader();
+ var rawBody = CilRawMethodBody.FromReader(context, ref reader);
+ if (rawBody is not null)
+ result = CilMethodBody.FromRawMethodBody(context, owner, rawBody);
}
- else if (row.Body.IsBounded && row.Body.GetSegment() is CilRawMethodBody rawMethodBody)
+
+ if (result is null && bodyReference.IsBounded)
{
- return CilMethodBody.FromRawMethodBody(context, owner, rawMethodBody);
+ if (bodyReference.GetSegment() is CilRawMethodBody rawMethodBody)
+ result = CilMethodBody.FromRawMethodBody(context, owner, rawMethodBody);
}
}
catch (Exception ex)
@@ -44,7 +42,13 @@ public class DefaultMethodBodyReader : IMethodBodyReader
context.RegisterException(new BadImageFormatException($"Failed to parse the method body of {owner.MetadataToken}.", ex));
}
- return null;
+ if (result is not null)
+ {
+ result.Address = bodyReference;
+ return result;
+ }
+
+ return new UnresolvedMethodBody(owner, bodyReference);
}
}
}
diff --git a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs
index b4310c159..c5b4e557f 100644
--- a/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs
+++ b/src/AsmResolver.DotNet/Serialized/SerializedAssemblyDefinition.cs
@@ -166,7 +166,7 @@ public override bool TryGetTargetFramework(out DotNetRuntimeInfo info)
continue;
object? element = attribute.Signature.FixedArguments[0].Element;
- if (element is string or Utf8String && DotNetRuntimeInfo.TryParse(element.ToString(), out info))
+ if (element is string or Utf8String && DotNetRuntimeInfo.TryParse(element.ToString()!, out info))
return true;
}
diff --git a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs
index 841c47adc..3d876c1d5 100644
--- a/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs
+++ b/src/AsmResolver.DotNet/Serialized/SerializedGenericParameter.cs
@@ -58,10 +58,11 @@ public SerializedGenericParameter(ModuleReaderContext context, MetadataToken tok
///
protected override IList GetConstraints()
{
- var result = new OwnedCollection(this);
-
var module = _context.ParentModule;
- foreach (uint rid in module.GetGenericParameterConstraints(MetadataToken))
+ var rids = module.GetGenericParameterConstraints(MetadataToken);
+ var result = new OwnedCollection(this, rids.Count);
+
+ foreach (uint rid in rids)
{
var constraintToken = new MetadataToken(TableIndex.GenericParamConstraint, rid);
result.Add((GenericParameterConstraint) module.LookupMember(constraintToken));
diff --git a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs
index d000183e1..c35f28b9e 100644
--- a/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs
+++ b/src/AsmResolver.DotNet/Serialized/SerializedMethodDefinition.cs
@@ -40,7 +40,7 @@ public SerializedMethodDefinition(ModuleReaderContext context, MetadataToken tok
}
///
- protected override string? GetName()
+ protected override Utf8String? GetName()
{
return _context.Metadata.TryGetStream(out var stringsStream)
? stringsStream.GetStringByIndex(_row.Name)
@@ -81,9 +81,10 @@ public SerializedMethodDefinition(ModuleReaderContext context, MetadataToken tok
///
protected override IList GetParameterDefinitions()
{
- var result = new OwnedCollection(this);
+ var parameterRange = _context.ParentModule.GetParameterRange(MetadataToken.Rid);
+ var result = new OwnedCollection(this, parameterRange.Count);
- foreach (var token in _context.ParentModule.GetParameterRange(MetadataToken.Rid))
+ foreach (var token in parameterRange)
{
if (_context.ParentModule.TryLookupMember(token, out var member) && member is ParameterDefinition parameter)
result.Add(parameter);
@@ -108,9 +109,10 @@ protected override IList GetParameterDefinitions()
///
protected override IList GetGenericParameters()
{
- var result = new OwnedCollection(this);
+ var rids = _context.ParentModule.GetGenericParameters(MetadataToken);
+ var result = new OwnedCollection(this, rids.Count);
- foreach (uint rid in _context.ParentModule.GetGenericParameters(MetadataToken))
+ foreach (uint rid in rids)
{
if (_context.ParentModule.TryLookupMember(new MetadataToken(TableIndex.GenericParam, rid), out var member)
&& member is GenericParameter genericParameter)
diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs
index f7e13d09f..e81e548aa 100644
--- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs
+++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.Metadata.cs
@@ -59,7 +59,7 @@ private void EnsureTypeDefinitionTreeInitialized()
return typeDefTree;
}
- internal IEnumerable GetNestedTypeRids(uint enclosingTypeRid)
+ internal ICollection GetNestedTypeRids(uint enclosingTypeRid)
{
EnsureTypeDefinitionTreeInitialized();
return _typeDefTree.GetValues(enclosingTypeRid);
@@ -107,8 +107,8 @@ private void InitializeMethodSemantics()
var semanticsTable = tablesStream.GetTable(TableIndex.MethodSemantics);
var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasSemantics);
- var semantics = new OneToManyRelation();
- var semanticMethods = new Dictionary();
+ var semantics = new OneToManyRelation(semanticsTable.Count);
+ var semanticMethods = new Dictionary(semanticsTable.Count);
for (int i = 0; i < semanticsTable.Count; i++)
{
var methodSemanticsRow = semanticsTable[i];
@@ -123,7 +123,7 @@ private void InitializeMethodSemantics()
Interlocked.CompareExchange(ref _semanticMethods, semanticMethods, null);
}
- internal IEnumerable GetMethodSemantics(MetadataToken owner)
+ internal ICollection GetMethodSemantics(MetadataToken owner)
{
EnsureMethodSemanticsInitialized();
return _semantics.GetValues(owner);
@@ -155,7 +155,7 @@ private void EnsureConstantsInitialized()
var constantTable = tablesStream.GetTable(TableIndex.Constant);
var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasConstant);
- var constants = new OneToOneRelation();
+ var constants = new OneToOneRelation(constantTable.Count);
for (int i = 0; i < constantTable.Count; i++)
{
var ownerToken = encoder.DecodeIndex(constantTable[i].Parent);
@@ -199,7 +199,7 @@ private void EnsureCustomAttributesInitialized()
var attributeTable = tablesStream.GetTable(TableIndex.CustomAttribute);
var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasCustomAttribute);
- var customAttributes = new OneToManyRelation();
+ var customAttributes = new OneToManyRelation(attributeTable.Count);
for (int i = 0; i < attributeTable.Count; i++)
{
var ownerToken = encoder.DecodeIndex(attributeTable[i].Parent);
@@ -228,9 +228,10 @@ internal MetadataToken GetCustomAttributeOwner(uint attributeRid)
internal IList GetCustomAttributeCollection(IHasCustomAttribute owner)
{
EnsureCustomAttributesInitialized();
- var result = new OwnedCollection(owner);
+ var rids = _customAttributes.GetValues(owner.MetadataToken);
+ var result = new OwnedCollection(owner, rids.Count);
- foreach (uint rid in _customAttributes.GetValues(owner.MetadataToken))
+ foreach (uint rid in rids)
{
var attribute = (CustomAttribute) LookupMember(new MetadataToken(TableIndex.CustomAttribute, rid));
result.Add(attribute);
@@ -252,7 +253,7 @@ private void EnsureSecurityDeclarationsInitialized()
var declarationTable = tablesStream.GetTable(TableIndex.DeclSecurity);
var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasDeclSecurity);
- var securityDeclarations = new OneToManyRelation();
+ var securityDeclarations = new OneToManyRelation(declarationTable.Count);
for (int i = 0; i < declarationTable.Count; i++)
{
var ownerToken = encoder.DecodeIndex(declarationTable[i].Parent);
@@ -272,9 +273,10 @@ internal MetadataToken GetSecurityDeclarationOwner(uint attributeRid)
internal IList GetSecurityDeclarationCollection(IHasSecurityDeclaration owner)
{
EnsureSecurityDeclarationsInitialized();
- var result = new OwnedCollection(owner);
+ var rids = _securityDeclarations.GetValues(owner.MetadataToken);
+ var result = new OwnedCollection(owner, rids.Count);
- foreach (uint rid in _securityDeclarations.GetValues(owner.MetadataToken))
+ foreach (uint rid in rids)
{
var attribute = (SecurityDeclaration) LookupMember(new MetadataToken(TableIndex.DeclSecurity, rid));
result.Add(attribute);
@@ -296,7 +298,7 @@ private void EnsureGenericParametersInitialized()
var parameterTable = tablesStream.GetTable(TableIndex.GenericParam);
var encoder = tablesStream.GetIndexEncoder(CodedIndex.TypeOrMethodDef);
- var genericParameters = new OneToManyRelation();
+ var genericParameters = new OneToManyRelation(parameterTable.Count);
for (int i = 0; i < parameterTable.Count; i++)
{
var ownerToken = encoder.DecodeIndex(parameterTable[i].Owner);
@@ -331,7 +333,7 @@ private void EnsureGenericParameterConstrainsInitialized()
var tablesStream = DotNetDirectory.Metadata!.GetStream();
var constraintTable = tablesStream.GetTable(TableIndex.GenericParamConstraint);
- var constraints = new OneToManyRelation();
+ var constraints = new OneToManyRelation(constraintTable.Count);
for (int i = 0; i < constraintTable.Count; i++)
{
var ownerToken = new MetadataToken(TableIndex.GenericParam, constraintTable[i].Owner);
@@ -366,7 +368,7 @@ private void EnsureInterfacesInitialized()
var tablesStream = DotNetDirectory.Metadata!.GetStream();
var interfaceImplTable = tablesStream.GetTable(TableIndex.InterfaceImpl);
- var interfaces = new OneToManyRelation();
+ var interfaces = new OneToManyRelation(interfaceImplTable.Count);
for (int i = 0; i < interfaceImplTable.Count; i++)
{
var ownerToken = new MetadataToken(TableIndex.TypeDef, interfaceImplTable[i].Class);
@@ -401,7 +403,7 @@ private void EnsureMethodImplementationsInitialized()
var tablesStream = DotNetDirectory.Metadata!.GetStream();
var methodImplTable = tablesStream.GetTable(TableIndex.MethodImpl);
- var methodImplementations = new OneToManyRelation();
+ var methodImplementations = new OneToManyRelation(methodImplTable.Count);
for (int i = 0; i < methodImplTable.Count; i++)
{
var ownerToken = new MetadataToken(TableIndex.TypeDef, methodImplTable[i].Class);
@@ -430,7 +432,7 @@ private void EnsureClassLayoutsInitialized()
var tablesStream = DotNetDirectory.Metadata!.GetStream();
var layoutTable = tablesStream.GetTable(TableIndex.ClassLayout);
- var layouts = new OneToOneRelation();
+ var layouts = new OneToOneRelation(layoutTable.Count);
for (int i = 0; i < layoutTable.Count; i++)
{
var ownerToken = new MetadataToken(TableIndex.TypeDef, layoutTable[i].Parent);
@@ -460,7 +462,7 @@ private void EnsureImplementationMapsInitialized()
var mapTable = tablesStream.GetTable(TableIndex.ImplMap);
var encoder = tablesStream.GetIndexEncoder(CodedIndex.TypeOrMethodDef);
- var maps = new OneToOneRelation();
+ var maps = new OneToOneRelation(mapTable.Count);
for (int i = 0; i < mapTable.Count; i++)
{
var ownerToken = encoder.DecodeIndex(mapTable[i].MemberForwarded);
@@ -495,7 +497,7 @@ private void EnsureFieldRvasInitialized()
var tablesStream = DotNetDirectory.Metadata!.GetStream();
var rvaTable = tablesStream.GetTable(TableIndex.FieldRva);
- var rvas = new OneToOneRelation();
+ var rvas = new OneToOneRelation(rvaTable.Count);
for (int i = 0; i < rvaTable.Count; i++)
{
var ownerToken = new MetadataToken(TableIndex.Field, rvaTable[i].Field);
@@ -525,7 +527,7 @@ private void EnsureFieldMarshalsInitialized()
var marshalTable = tablesStream.GetTable(TableIndex.FieldMarshal);
var encoder = tablesStream.GetIndexEncoder(CodedIndex.HasFieldMarshal);
- var marshals = new OneToOneRelation();
+ var marshals = new OneToOneRelation(marshalTable.Count);
for (int i = 0; i < marshalTable.Count; i++)
{
var ownerToken = encoder.DecodeIndex(marshalTable[i].Parent);
@@ -571,7 +573,7 @@ private void EnsureFieldLayoutsInitialized()
var tablesStream = DotNetDirectory.Metadata!.GetStream();
var layoutTable = tablesStream.GetTable(TableIndex.FieldLayout);
- var fieldLayouts = new OneToOneRelation();
+ var fieldLayouts = new OneToOneRelation(layoutTable.Count);
for (int i = 0; i < layoutTable.Count; i++)
{
var ownerToken = new MetadataToken(TableIndex.Field, layoutTable[i].Field);
diff --git a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs
index ba9e52777..5ed4f0d4a 100644
--- a/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs
+++ b/src/AsmResolver.DotNet/Serialized/SerializedModuleDefinition.cs
@@ -77,15 +77,15 @@ public SerializedModuleDefinition(IPEImage peImage, ModuleReaderParameters reade
readerParameters.PEReaderParameters.FileService));
// Prepare lazy RID lists.
- _fieldLists = new LazyRidListRelation(metadata, TableIndex.TypeDef,
+ _fieldLists = new LazyRidListRelation(metadata, TableIndex.Field, TableIndex.TypeDef,
(rid, _) => rid, tablesStream.GetFieldRange);
- _methodLists = new LazyRidListRelation(metadata, TableIndex.TypeDef,
+ _methodLists = new LazyRidListRelation(metadata, TableIndex.Method, TableIndex.TypeDef,
(rid, _) => rid, tablesStream.GetMethodRange);
- _paramLists = new LazyRidListRelation(metadata, TableIndex.Method,
+ _paramLists = new LazyRidListRelation(metadata, TableIndex.Param, TableIndex.Method,
(rid, _) => rid, tablesStream.GetParameterRange);
- _propertyLists = new LazyRidListRelation(metadata, TableIndex.PropertyMap,
+ _propertyLists = new LazyRidListRelation(metadata, TableIndex.Property, TableIndex.PropertyMap,
(_, map) => map.Parent, tablesStream.GetPropertyRange);
- _eventLists = new LazyRidListRelation(metadata, TableIndex.EventMap,
+ _eventLists = new LazyRidListRelation(metadata, TableIndex.Event, TableIndex.EventMap,
(_, map) => map.Parent, tablesStream.GetEventRange);
}
@@ -204,12 +204,18 @@ protected override IList GetTopLevelTypes()
{
EnsureTypeDefinitionTreeInitialized();
- var types = new OwnedCollection(this);
-
var typeDefTable = ReaderContext.Metadata
.GetStream()
.GetTable(TableIndex.TypeDef);
+ int nestedTypeCount = ReaderContext.Metadata
+ .GetStream()
+ .GetTable(TableIndex.NestedClass)
+ .Count;
+
+ var types = new OwnedCollection(this,
+ typeDefTable.Count - nestedTypeCount);
+
for (int i = 0; i < typeDefTable.Count; i++)
{
uint rid = (uint) i + 1;
@@ -226,12 +232,12 @@ protected override IList GetTopLevelTypes()
///
protected override IList GetAssemblyReferences()
{
- var result = new OwnedCollection(this);
-
var table = ReaderContext.Metadata
.GetStream()
.GetTable(TableIndex.AssemblyRef);
+ var result = new OwnedCollection(this, table.Count);
+
// Don't use the member factory here, this method may be called before the member factory is initialized.
for (int i = 0; i < table.Count; i++)
{
@@ -245,12 +251,12 @@ protected override IList GetAssemblyReferences()
///
protected override IList GetModuleReferences()
{
- var result = new OwnedCollection(this);
-
var table = ReaderContext.Metadata
.GetStream()
.GetTable(TableIndex.ModuleRef);
+ var result = new OwnedCollection(this, table.Count);
+
for (int i = 0; i < table.Count; i++)
{
var token = new MetadataToken(TableIndex.ModuleRef, (uint) i + 1);
@@ -264,12 +270,12 @@ protected override IList GetModuleReferences()
///
protected override IList GetFileReferences()
{
- var result = new OwnedCollection(this);
-
var table = ReaderContext.Metadata
.GetStream()
.GetTable(TableIndex.File);
+ var result = new OwnedCollection(this, table.Count);
+
for (int i = 0; i < table.Count; i++)
{
var token = new MetadataToken(TableIndex.File, (uint) i + 1);
@@ -283,12 +289,12 @@ protected override IList GetFileReferences()
///
protected override IList GetResources()
{
- var result = new OwnedCollection(this);
-
var table = ReaderContext.Metadata
.GetStream