diff --git a/Directory.Build.props b/Directory.Build.props index cd7150f6..ccddde03 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 2.3.1.0 + 2.3.1.1 1.1.0 diff --git a/Harmony.sln b/Harmony.sln index 621e2c15..04a13f9c 100644 --- a/Harmony.sln +++ b/Harmony.sln @@ -8,8 +8,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore - azure-pipelines-job-template.yml = azure-pipelines-job-template.yml - azure-pipelines.yml = azure-pipelines.yml CNAME = CNAME CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md Directory.Build.props = Directory.Build.props diff --git a/Harmony/Documentation/articles/new.md b/Harmony/Documentation/articles/new.md index 78e85502..287326f4 100644 --- a/Harmony/Documentation/articles/new.md +++ b/Harmony/Documentation/articles/new.md @@ -4,43 +4,43 @@ Harmony 2 has come a long way since the last release 1.2.0.1. Here are all the c #### New -CI/CD on Azure, Travis and AppVeyor -Switched to `MonoMod.Common` for shared low level patching with MonoMod project -Works with more .NET versions -Inline prevention for Mono -4th patch type: `Finalizer` - for handling and manipulating exceptions -Reverse Patching (original onto one of your stub methods) -Convenience extension methods for `CodeInstruction` -Selective debug log with `[HarmonyDebug]` annotation - works even with future changes of the method -`Prepare`/`Cleanup` will be called even with exceptions during patching -Cleanup can now receive and return the current Exception during patching -Better exception reporting with `HarmonyException` -Automatic documentation generated to `https://harmony.pardeike.net` -AccessTools has methods for declared members -`FastAccess` now deals with generics -`Manipulator` transpiler helper -Get IL code from a method +CI/CD with GitHub Actions +Switched to `MonoMod.Core` for shared low level patching with MonoMod project +Works with more .NET versions +Inline prevention for Mono +4th patch type: `Finalizer` - for handling and manipulating exceptions +Reverse Patching (original onto one of your stub methods) +Convenience extension methods for `CodeInstruction` +Selective debug log with `[HarmonyDebug]` annotation - works even with future changes of the method +`Prepare`/`Cleanup` will be called even with exceptions during patching +Cleanup can now receive and return the current Exception during patching +Better exception reporting with `HarmonyException` +Automatic documentation generated to `https://harmony.pardeike.net` +AccessTools has methods for declared members +`FastAccess` now deals with generics +`Manipulator` transpiler helper +Get IL code from a method Support for IL InlineSignature (patching methods with CALLI) #### Fixed -Priority field spelling -`Traverse` can handle static members -Methods returning struct types are now patchable -Main API is now properly divided into static/instance methods -`HarmonyMethod` and other high level API throws on null input -Patch sorting -DeepCopy works with nullable types -Patch annotations API cleaned up -`FieldRef` covers more cases and is simplified -`__result` assignability checks -Handling `__state` without Prefix -Debug log writes out full type names -Documentation now uses compiled code snippets for correctness +Priority field spelling +`Traverse` can handle static members +Methods returning struct types are now patchable +Main API is now properly divided into static/instance methods +`HarmonyMethod` and other high level API throws on null input +Patch sorting +DeepCopy works with nullable types +Patch annotations API cleaned up +`FieldRef` covers more cases and is simplified +`__result` assignability checks +Handling `__state` without Prefix +Debug log writes out full type names +Documentation now uses compiled code snippets for correctness `Traverse` works with inherited fields, properties and methods #### Changes -Removed Self-patching -Renamed `Add()` extension on `IEnumerable` and `T[]` to `AddItem()` to avoid conflicts +Removed Self-patching +Renamed `Add()` extension on `IEnumerable` and `T[]` to `AddItem()` to avoid conflicts `HarmonyInstance` is now called `Harmony` and `Harmony` namespace is now called `HarmonyLib` diff --git a/Harmony/Documentation/index.md b/Harmony/Documentation/index.md index 3e07a429..d15bc316 100644 --- a/Harmony/Documentation/index.md +++ b/Harmony/Documentation/index.md @@ -10,12 +10,14 @@ It supports **Mono** and **.NET** environments on Windows, Unix and macOS except Designed to be used by multiple users (usually called Mods) that would otherwise override each others hooks, it was originally created for the game [RimWorld](https://rimworldgame.com) and its large modding community by [Andreas Pardeike](https://www.patreon.com/pardeike). -Enjoy! +Enjoy! /Andreas Pardeike # Getting Started -Installation is usually done by copying and referencing [0Harmony.dll](https://github.com/pardeike/Harmony/releases) from your project or by using the [Lib.Harmony](https://www.nuget.org/packages/Lib.Harmony) nuget package. +If you want a single file, dependency-merged assembly, you should use the [Lib.Harmony](https://www.nuget.org/packages/Lib.Harmony) nuget package. This is the **preferred** way. + +If you instead want to supply the dependencies yourself, you should use the [Lib.Harmony.Thin](https://www.nuget.org/packages/Lib.Harmony.Thin) nuget package. You get more control but you are responsible to make all references available at runtime. # Documentation @@ -27,7 +29,7 @@ If you find a factual error or if you have feedback about the documentation you - file a documentation Issue on the repo - or write about it on the official discord -**New to modding and C#?** Beside the basic language features you need at least a good overview of **Reflection** in C#. Read this short and useful [introduction](https://dotnetcademy.net/Learn/4/Pages/1). +**New to modding and C#?** Beside the basic language features you need at least a good overview of **Reflection** in C#. Read this short and useful [introduction](https://dotnetcademy.net/Learn/4/Pages/1). ## Community @@ -37,11 +39,11 @@ Help by promoting this library so other developers can find it. One way is to up # Contact -Andreas Pardeike -andreas@pardeike.net -twitter: @pardeike +Andreas Pardeike +andreas@pardeike.net +twitter: @pardeike ## Donations -Donations keep me going: +Donations keep me going: [https://www.patreon.com/pardeike](https://www.patreon.com/pardeike) diff --git a/Harmony/Internal/InlineSignatureParser.cs b/Harmony/Internal/InlineSignatureParser.cs index 9a5b2585..fa4ade73 100644 --- a/Harmony/Internal/InlineSignatureParser.cs +++ b/Harmony/Internal/InlineSignatureParser.cs @@ -9,8 +9,7 @@ namespace HarmonyLib { internal static class InlineSignatureParser { - // Based on https://github.com/MonoMod/MonoMod.Common/blob/fb7fed148af165905ee0f2db1bb4c78a0137fb89/Utils/ReflectionHelper.ParseCallSite.cs - // ... which is based on https://github.com/jbevain/cecil/blob/96026325ee1cb6627a3e4a32b924ab2905f02553/Mono.Cecil/AssemblyReader.cs#L3448 + // Based on code of MonoMod, which is based on https://github.com/jbevain/cecil/blob/96026325ee1cb6627a3e4a32b924ab2905f02553/Mono.Cecil/AssemblyReader.cs#L3448 internal static InlineSignature ImportCallSite(Module moduleFrom, byte[] data) { diff --git a/Harmony/Internal/MethodCopier.cs b/Harmony/Internal/MethodCopier.cs index 70f6b817..90e603c3 100644 --- a/Harmony/Internal/MethodCopier.cs +++ b/Harmony/Internal/MethodCopier.cs @@ -434,7 +434,7 @@ internal List FinalizeILCodes(Emitter emitter, List var cecilGenerator = generator.GetProxiedShim(); if (cecilGenerator is null) { - // Right now InlineSignatures can only be emitted using MonoMod.Common and its CecilILGenerator. + // Right now InlineSignatures can only be emitted using MonoMod and its CecilILGenerator. // That is because DynamicMethod's original ILGenerator is very restrictive about the calli opcode. throw new NotSupportedException(); } diff --git a/Harmony/Properties/AssemblyInfo.cs b/Harmony/Properties/AssemblyInfo.cs index 21a0f898..bc3f4bce 100644 --- a/Harmony/Properties/AssemblyInfo.cs +++ b/Harmony/Properties/AssemblyInfo.cs @@ -3,9 +3,9 @@ [assembly: ComVisible(false)] [assembly: InternalsVisibleTo("HarmonyTests")] -// MonoMod.Common uses IgnoresAccessChecksTo on its end, +// MonoMod.Core uses IgnoresAccessChecksTo on its end, // but older versions of the .NET runtime bundled with older versions of Windows // require Harmony to expose its internals instead. -// This is only relevant for when MonoMod.Common gets merged into Harmony. +// This is only relevant for when MonoMod.Core gets merged into Harmony. [assembly: InternalsVisibleTo("MonoMod.Utils.Cil.ILGeneratorProxy")] [assembly: Guid("69aee16a-b6e7-4642-8081-3928b32455df")] diff --git a/README.md b/README.md index 1aa6ca29..cd1c07bc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Harmony
- Version 2
+ Version 2.3
A library for patching, replacing and decorating
.NET and Mono methods during runtime.

@@ -15,15 +15,17 @@ It is also used in unit testing WPF controls and in many other areas. If you develop in C# and your code is loaded as a module/plugin into a host application, you can use Harmony to alter the functionality of all the available assemblies of that application. Where other patch libraries simply allow you to replace the original method, Harmony goes one step further and gives you: -• A way to keep the original method intact -• Execute your code before and/or after the original method -• Modify the original with IL code processors -• Multiple Harmony patches co-exist and don't conflict with each other +• A way to keep the original method intact +• Execute your code before and/or after the original method +• Modify the original with IL code processors +• Multiple Harmony patches co-exist and don't conflict with each other • Works at runtime and does not touch any files ### Installation -Installation is done by using [0Harmony.dll](https://github.com/pardeike/Harmony/releases) in your project or by using the [Lib.Harmony](https://www.nuget.org/packages/Lib.Harmony) nuget package. +If you want a single file, dependency-merged assembly, you should use the [Lib.Harmony](https://www.nuget.org/packages/Lib.Harmony) nuget package. This is the **preferred** way. + +If you instead want to supply the dependencies yourself, you should use the [Lib.Harmony.Thin](https://www.nuget.org/packages/Lib.Harmony.Thin) nuget package. You get more control but you are responsible to make all references available at runtime. ### Documentation @@ -33,9 +35,9 @@ Please check out the [documentation](https://harmony.pardeike.net) and join the I put thousands of hours into this project and its support. So every little action helps: -• Upvote this [stackoverflow answer](https://stackoverflow.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method/42043003#42043003) -• Spread the word in your developer communities • Become a [GitHub sponsor](https://github.com/sponsors/pardeike) or a [Patreon](https://www.patreon.com/pardeike) +• Upvote this [stackoverflow answer](https://stackoverflow.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method/42043003#42043003) +• Spread the word in your developer communities This project uses the great [MonoMod.Core](https://github.com/MonoMod) library by [0x0ade](https://github.com/0x0ade) and [nike4613](https://github.com/nike4613). @@ -46,20 +48,19 @@ Harmony 1 is deprecated and not under active development anymore. The latest ver
 

- - - + + +

- - + +

- - - + +

- - + +

diff --git a/azure-pipelines-job-template.yml b/azure-pipelines-job-template.yml deleted file mode 100644 index 93c324db..00000000 --- a/azure-pipelines-job-template.yml +++ /dev/null @@ -1,178 +0,0 @@ -parameters: - -- name: dependsOn - type: string - default: '' - -- name: os - type: string - values: [windows, ubuntu, macOS] - -- name: runtimeType - type: string - values: [dotnet, mono] - default: dotnet - -- name: architecture - type: string - values: [x86, x64] - -- name: frameworks - type: object - -- name: buildConfiguration - type: string - default: ReleaseFat - -- name: testConfiguration - type: string - default: Release - -- name: publishBuild - type: boolean - default: false - -jobs: -- job: ${{parameters.os}}_${{parameters.runtimeType}}_${{parameters.architecture}} - dependsOn: ${{parameters.DependsOn}} - displayName: ${{replace(format('{0} {1} {2}', parameters.os, replace(parameters.runtimeType, 'dotnet', ''), parameters.architecture), ' ', ' ')}} - - pool: - vmImage: ${{parameters.os}}-latest - - # "variables" is misleading here - these are actually constants. - # These must be declared here, since templates don't allow combining a root jobs (or steps) section and a root variables section. - variables: - # Note: Azure Pipelines currently has an issue where if the test name is in the fully qualified name format "namespace.classname.methodname(args)", - # it only shows "methodname" rather than the fully qualified name (something do with more than 2 periods in the test name). - # Workaround is to use the format: "namespace.classname:methodname(args)" (note the colon) - # Also, Azure Pipelines command line argument parser tends to treat single quotes within argument strings literally, so using double quotes instead. - runSettingsArgs: "NUnit.DefaultTestNamePattern=\"{C}:{m}{a}\" RunConfiguration.TargetPlatform=${{parameters.architecture}}" - # Blame mode helps isolate tests that cause crashes (https://github.com/Microsoft/vstest-docs/blob/master/docs/extensions/blame-datacollector.md), - # although it somewhat ironically also makes such unstable tests crash less often, and makes the test suite run slightly slower. - blameArg: --blame - - steps: - - checkout: self - submodules: recursive - - # For each .NET Core (and .NET 5+) version, ensure corresponding `dotnet` is installed. - # Relies on all .NET Core (and .NET 5+) framework having a dot in their TFM. - # Assumes that .NET Standard is never used. - - ${{ each framework in parameters.frameworks }}: - # .NET Core (and .NET 5+) detection is done by taking advantage of TFMs for such frameworks always having a period, which .NET Framework TFMs lack. - - ${{ if contains(framework, '.') }}: - # Assume x64 agents lack installed x86 .NET Core runtimes (which is the case for MS-hosted agents), so install them as needed. - # However, such x86 runtimes can't be installed via the UseDotNet task, so we need to use the official dotnet-install.ps1 script. - - ${{ if and(eq(parameters.architecture, 'x86'), eq(parameters.os, 'windows')) }}: - - pwsh: | - Invoke-WebRequest 'https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1' -OutFile dotnet-install.ps1 - .\dotnet-install.ps1 -Channel ${{replace(replace(framework, 'coreapp', ''), 'net', '')}} -Architecture ${{parameters.architecture}} -Runtime dotnet -InstallDir "${Env:ProgramFiles(x86)}\dotnet" -NoPath - displayName: "Install latest .NET ${{replace(replace(framework, 'coreapp', 'Core '), 'net', '')}} ${{parameters.architecture}} Runtime" - - - ${{ if or(ne(parameters.architecture, 'x86'), ne(parameters.os, 'windows')) }}: # currently no `else` construct in Azure Pipelines - - task: UseDotNet@2 - displayName: "Install latest .NET ${{replace(replace(framework, 'coreapp', 'Core '), 'net', '')}} Runtime" - inputs: - version: ${{replace(replace(framework, 'coreapp', ''), 'net', '')}}.x - packageType: sdk - - - bash: "dotnet clean --configuration ${{parameters.buildConfiguration}} && dotnet nuget locals all --clear" - displayName: 'Clean' - - - task: DotNetCoreCLI@2 - displayName: 'Build' - inputs: - command: build # `dotnet build` implicitly runs `dotnet restore` as well - projects: Harmony.sln - arguments: "--configuration ${{parameters.buildConfiguration}}" - - # For some reason, if mono is installed, above solution restore/build fails when restoring nugets (doesn't fail when only building a project). - # Since mono is only needed for running tests in 'mono' runtimeType mode, only install mono after the restore/build. - - ${{ if eq(parameters.runtimeType, 'mono') }}: - - bash: "choco install mono --yes --no-progress --${{parameters.architecture}} --ignore-checksums" - displayName: 'Install Mono' - - # 'dotnet' runtimeType means using `dotnet test`. - - ${{ each framework in parameters.frameworks }}: - - ${{ if eq(parameters.runtimeType, 'dotnet') }}: - # dotnet usage notes: - # - `dotnet test` does work for .NET Framework targets, but they still implicitly require mono on non-Windows. - # `dotnet test` for .NET Core targets do not require mono (.NET Core is cross-platform). - # - `dotnet test HarmonyTests`, even with --framework net35, won't run .NET Framework 3.5 tests unless IsTestPlatform is explicitly set - # (since Microsoft.NET.Test.Sdk only sets IsTestProject property for net40+). HarmonyTests.csproj works around this by setting IsTestPlatform for net35. - # - `dotnet test path/to//HarmonyTests.dll --framework ` does work, but there's currently a bug where inline runSettings don't work - # (this is apparently fixed in .NET 5.0+), requiring a runSettings file (which would need to be generated beforehand). So not doing this. - # - Even with the above workaround for running .NET Framework 3.5 tests, there is no CLR 2.0 runtime (or mono equivalent) available to properly test it. - # Both `dotnet test path/to/net35/HarmonyTests.dll --framework net35` and `mono path/to/nunit3-console.exe path/to/net35/HarmonyTests.dll` - # run the tests in 'CLR 4.0 "compatibility mode"' (or the mono equivalent of this). - - task: DotNetCoreCLI@2 - displayName: "Test ${{replace(framework, 'net35', 'net35 in CLR 4.0 \"compatibility mode\"')}}" - condition: succeededOrFailed() - inputs: - command: test - projects: HarmonyTests - arguments: "--no-build --configuration ${{parameters.testConfiguration}} --framework ${{framework}} --logger \"console;verbosity=normal\" $(blameArg) -- $(runSettingsArgs)" - publishTestResults: true - # .NET Framework on non-Windows implicitly always uses mono. - ${{ if and(ne(parameters.os, 'windows'), not(contains(framework, '.'))) }}: - testRunTitle: "${{framework}} (${{parameters.os}} mono ${{parameters.architecture}})" - ${{ if or(eq(parameters.os, 'windows'), contains(framework, '.')) }}: # currently no `else` construct in Azure Pipelines - testRunTitle: "${{framework}} (${{parameters.os}} ${{parameters.architecture}})" - - # 'mono' runtimeType is for running .NET Framework tests on Windows (non-Windows will always run .NET Framework tests on mono even via `dotnet test`). - - ${{ if eq(parameters.runtimeType, 'mono') }}: - # Following attempts to replicate the behavior of DotNetCoreCLI test with publishTestResults=true, the main difference being: - # - the usage of mono + vstest.console (since mono + dotnet test doesn't work) - # - the actual test publishing delegated to a followup PublishTestResults task - - bash: | - shopt -s failglob globstar - set -x - # Remove any existing test result files. - for testResultFile in "$(Agent.TempDirectory)"/**/*.trx; do - rm "$testResultFile" - # If there are test attachments, they are in a directory with same name as trx file, excluding the .trx extension. - testResultDir="${testResultFile%.trx}" - if [ -e "$testResultDir" ]; then - rm -r "$testResultDir" - fi - done - # Execute the tests via mono + vstest.console. - mono="$(eval $programFilesCmd)/Mono/bin/mono.exe" - vstest="$(vswhere.exe -latest -property installationPath)/Common7/IDE/CommonExtensions/Microsoft/TestWindow/vstest.console.exe" - "$mono" "$vstest" "HarmonyTests/bin/${{parameters.testConfiguration}}/${{framework}}/HarmonyTests.dll" --Framework:${{framework}} --logger:trx --ResultsDirectory:"$(Agent.TempDirectory)" $(blameArg) -- $(runSettingsArgs) - displayName: "Test ${{replace(framework, 'net35', 'net35 in CLR 4.0 \"compatibility mode\"')}}" - condition: succeededOrFailed() - env: - ${{ if eq(parameters.architecture, 'x86') }}: - programFilesCmd: "cmd //c 'echo %ProgramFiles(x86)%'" # no direct way to access ProgramFiles(x86) env var from within bash - ${{ if ne(parameters.architecture, 'x86') }}: # currently no `else` construct in Azure Pipelines - programFilesCmd: "echo $PROGRAMFILES" - - - ${{ if eq(parameters.runtimeType, 'mono') }}: - - task: PublishTestResults@2 - displayName: "Publish ${{framework}} test results" - condition: succeededOrFailed() - inputs: - buildPlatform: ${{parameters.architecture}} - buildConfiguration: ${{parameters.buildConfiguration}} - testResultsFormat: VSTest - testResultsFiles: '**/*.trx' - searchFolder: $(Agent.TempDirectory) - testRunTitle: "${{framework}} (${{parameters.os}} mono ${{parameters.architecture}})" - - - ${{ if parameters.publishBuild }}: - # publish task doesn't support globbing, so have to find the file and output it as a variable. Yes, this is clunky. - - bash: | - shopt -s failglob - # Expecting only one zip file, but this allows script to error if no such zip file is found. - for buildZipFile in Harmony/bin/Harmony*.zip; do - echo "##vso[task.setvariable variable=buildZipFile]$buildZipFile" - done - name: FindBuildArtifact - displayName: "Find Harmony zip for build artifact publishing" - condition: succeededOrFailed() - - - publish: $(buildZipFile) - displayName: "Publish Harmony zip as build artifact" - artifact: ${{parameters.buildConfiguration}} diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 50793693..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,68 +0,0 @@ -# .NET Desktop -# Build and run tests for .NET Desktop or Windows classic desktop solutions. -# Add steps that publish symbols, save build artifacts, and more: -# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net - -trigger: -- master -pr: -- master - -jobs: -- template: azure-pipelines-job-template.yml - parameters: - os: windows - architecture: x64 - frameworks: [net35, net452, net472, net48, netcoreapp3.0, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0] - publishBuild: true - -- template: azure-pipelines-job-template.yml - parameters: - # DependsOn: windows_dotnet_x64 - os: windows - architecture: x86 - frameworks: [net35, net452, net472, net48, netcoreapp3.0, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0] - -- template: azure-pipelines-job-template.yml - parameters: - # DependsOn: windows_dotnet_x86 - os: windows - architecture: x64 - runtimeType: mono - frameworks: [net35, net452, net472, net48] # mono for .NET Core just delegates to actual .NET Core, so no need for mono .NET Core tests - -- template: azure-pipelines-job-template.yml - parameters: - # DependsOn: windows_mono_x64 - os: windows - architecture: x86 - runtimeType: mono - frameworks: [net35, net452, net472, net48] # mono for .NET Core just delegates to actual .NET Core, so no need for mono .NET Core tests - -- template: azure-pipelines-job-template.yml - parameters: - # DependsOn: windows_mono_x86 - os: ubuntu - architecture: x64 - frameworks: [net35, net452, net472, net48, netcoreapp3.0, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0] - -# Without an x86 ubuntu agent and with mono's packaging not multi-arch-safe (can't install x86 mono on x64 ubuntu), we can't test on x86 ubuntu. -#- template: azure-pipelines-job-template.yml -# parameters: -# os: ubuntu -# architecture: x86 -# frameworks: [net35, net452, net472, net48] # .NET Core x86 is unavailable for non-Windows - -- template: azure-pipelines-job-template.yml - parameters: - # DependsOn: ubuntu_dotnet_x64 - os: macOS - architecture: x64 - frameworks: [net35, net452, net472, net48, netcoreapp3.0, netcoreapp3.1, net5.0, net6.0, net7.0, net8.0] - -# x86 ubuntu does apply to x86 macOS as well. -#- template: azure-pipelines-job-template.yml -# parameters: -# os: macOS -# architecture: x86 -# frameworks: [net35, net452, net472, net48] # .NET Core x86 is unavailable for non-Windows