Skip to content

Commit

Permalink
Make MonoMod.RuntimeDetour optional
Browse files Browse the repository at this point in the history
  • Loading branch information
Kir-Antipov committed Jan 9, 2025
1 parent 011d2b9 commit 20b2907
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 40 deletions.
6 changes: 4 additions & 2 deletions src/HotAvalonia/HotAvalonia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
<PackageReference Condition="$(TargetFramework) == 'netstandard2.0'" Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="$(AvaloniaVersion)" PrivateAssets="All" />

<!-- Technically, we do not need an explicit reference for `MonoMod.Backports`. -->
<!-- However, for some ungodly reason, it includes its own polyfills for the `System.Memory` types, -->
<!-- which clash with... the `System.Memory` types. So, for the project to build successfully, -->
<!-- we **must** explicitly alias those. Sigh... -->
<PackageReference Include="MonoMod.Backports" Version="1.1.2" Aliases="MMB" />
<PackageReference Include="MonoMod.RuntimeDetour" Version="25.2.1" />
<PackageReference Include="MonoMod.Backports" Version="1.1.2" Aliases="MMB" PrivateAssets="All" />
<PackageReference Include="MonoMod.RuntimeDetour" Version="25.2.1" PrivateAssets="All" />
</ItemGroup>

</Project>
121 changes: 83 additions & 38 deletions src/HotAvalonia/Reflection/Inject/MethodInjector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal static class MethodInjector
/// <exception cref="InvalidOperationException"/>
public static IInjection Inject(MethodBase source, MethodInfo replacement) => InjectionType switch
{
InjectionType.Native => new NativeInjection(source, replacement),
InjectionType.Native => NativeInjection.Create(source, replacement),
_ => ThrowNotSupportedException(),
};

Expand Down Expand Up @@ -72,60 +72,105 @@ private static InjectionType DetectSupportedInjectionType()
if (IsDisabled())
return InjectionType.None;

try
{
// Enable dynamic code generation, which is required for MonoMod to function.
using IDisposable context = AssemblyHelper.ForceAllowDynamicCode();

// `PlatformTriple.Current` may throw exceptions such as:
// - NotImplementedException
// - PlatformNotSupportedException
// - etc.
// This happens if the current environment is not (yet) supported.
if (PlatformTriple.Current is not null)
return InjectionType.Native;
}
catch { }

return InjectionType.None;
return NativeInjection.IsSupported ? InjectionType.Native : InjectionType.None;
}
}

/// <summary>
/// Provides functionality to inject a replacement method using native code hooks.
/// </summary>
file sealed class NativeInjection : IInjection
file static class NativeInjection
{
/// <summary>
/// The hook used for the method injection.
/// Injects a replacement method implementation for the specified source method.
/// </summary>
private readonly Hook _hook;
/// <param name="source">The method to be replaced.</param>
/// <param name="replacement">The replacement method implementation.</param>
/// <returns>An <see cref="IInjection"/> instance representing the method injection.</returns>
public static IInjection Create(MethodBase source, MethodInfo replacement)
=> new MonoModInjection(source, replacement);

/// <summary>
/// Initializes a new instance of the <see cref="NativeInjection"/> class.
/// Indicates whether native method injections are supported in the current runtime environment.
/// </summary>
/// <param name="source">The method to be replaced.</param>
/// <param name="replacement">The replacement method implementation.</param>
public NativeInjection(MethodBase source, MethodInfo replacement)
public static bool IsSupported
{
// Enable dynamic code generation, which is required for MonoMod to function.
// Note that we cannot enable it forcefully just once and call it a day,
// because this only affects the current thread.
_ = AssemblyHelper.ForceAllowDynamicCode();

_hook = new(source, replacement, applyByDefault: true);
get
{
try
{
// If `MonoMod.RuntimeDetour` is not present,
// this will result in `TypeLoadException`,
// that's why we need this wrapper.
return MonoModInjection.IsSupported;
}
catch
{
return false;
}
}
}

/// <summary>
/// Applies the method injection.
/// Represents a MonoMod-based injection.
/// </summary>
public void Apply() => _hook.Apply();
private sealed class MonoModInjection : IInjection
{
/// <summary>
/// The hook used for the method injection.
/// </summary>
private readonly Hook _hook;

/// <summary>
/// Initializes a new instance of the <see cref="MonoModInjection"/> class.
/// </summary>
/// <param name="source">The method to be replaced.</param>
/// <param name="replacement">The replacement method implementation.</param>
public MonoModInjection(MethodBase source, MethodInfo replacement)
{
// Enable dynamic code generation, which is required for MonoMod to function.
// Note that we cannot enable it forcefully just once and call it a day,
// because this only affects the current thread.
_ = AssemblyHelper.ForceAllowDynamicCode();

/// <summary>
/// Reverts all the effects caused by the method injection.
/// </summary>
public void Undo() => _hook.Undo();
_hook = new(source, replacement, applyByDefault: true);
}

/// <inheritdoc/>
public void Dispose() => _hook.Dispose();
/// <summary>
/// Indicates whether MonoMod is supported in the current environment.
/// </summary>
/// <exception cref="PlatformNotSupportedException"/>
/// <exception cref="InvalidOperationException"/>
/// <exception cref="TypeLoadException"/>
/// <exception cref="FileNotFoundException"/>
/// <exception cref="NotImplementedException"/>
public static bool IsSupported
{
get
{
// Enable dynamic code generation, which is required for MonoMod to function.
using IDisposable context = AssemblyHelper.ForceAllowDynamicCode();

// `PlatformTriple.Current` may throw exceptions such as:
// - NotImplementedException
// - PlatformNotSupportedException
// - etc.
// This happens if the current environment is not (yet) supported.
return PlatformTriple.Current is not null;
}
}

/// <summary>
/// Applies the method injection.
/// </summary>
public void Apply() => _hook.Apply();

/// <summary>
/// Reverts all the effects caused by the method injection.
/// </summary>
public void Undo() => _hook.Undo();

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

0 comments on commit 20b2907

Please sign in to comment.