Skip to content

Commit

Permalink
Initial experimental support for running injectors from build tasks.
Browse files Browse the repository at this point in the history
  • Loading branch information
CptMoore committed Dec 23, 2024
1 parent 953347b commit bc289de
Show file tree
Hide file tree
Showing 21 changed files with 197 additions and 28 deletions.
10 changes: 8 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Release notes

All notable changes should be documented in this file.
Since v2, ModTek adheres to [Semantic Versioning](http://semver.org/).
Since v2, ModTek adheres to [Semantic Versioning](http://semver.org/) for runtime compatibility with mods.

## Known Issues

Expand All @@ -10,6 +10,12 @@ Since v2, ModTek adheres to [Semantic Versioning](http://semver.org/).
however injected assemblies are now found under `Mods/.modtek/AssembliesInjected` or loaded directly into memory after injection.
- The HarmonyX feature works well, however some mods might rely on some buggy Harmony 1.2 behaviors that HarmonyX shims don't replicate.

## Upcoming - CptMoore

For modders:
- (Experimental!) Added ability to run injectors as part of a build task outside of BT. API will most likely change.
- Some libraries were renamed, as always don't just copy-paste, clean-copy-paste!

## 4.2 - CptMoore

For users:
Expand All @@ -22,7 +28,7 @@ For modders:
- Made run.sh depend on the doorstop.ini instead of having its own inline options
- Updated HarmonyX
- New HarmonyX is based on a major rewrite of MonoMod, several bugs were encountered and fixed
- Still providing an older version of HarmonyX in-case the new HarmonyX feels unstable
- Still providing an older version of HarmonyX due to reports of instability (those went away after a restart or 2)
- Various Logging improvements and changes
- Async logging is now highly optimized
- reduction of 300+ ns to <100 ns spent on the caller thread (usually the unity main thread)
Expand Down
1 change: 1 addition & 0 deletions CommonBase.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<LangVersion>13</LangVersion>
<DebugType>embedded</DebugType>
</PropertyGroup>

<PropertyGroup>
Expand Down
1 change: 0 additions & 1 deletion CommonBattleTech.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
$(BattleTechGameDir)\BattleTech_Data\Managed
</AssemblySearchPaths>
<DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
<DebugType>embedded</DebugType>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
<PackageVersion Include="Krafs.Publicizer" Version="2.3.0" />
<PackageVersion Include="GitVersion.MsBuild" Version="5.12.0" />
<PackageVersion Include="HarmonyX" Version="2.13.0" />
Expand Down
13 changes: 7 additions & 6 deletions ModTek.Common/Globals/Paths.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
using System;
using System.IO;
using System.IO;
using System.Reflection;

namespace ModTek.Common.Globals;

internal class Paths
{
// Common paths
private const string ENV_DOORSTOP_MANAGED_FOLDER_DIR = "DOORSTOP_MANAGED_FOLDER_DIR";
internal static readonly string ManagedDirectory = Environment.GetEnvironmentVariable(ENV_DOORSTOP_MANAGED_FOLDER_DIR)
?? throw new Exception($"Can't find {ENV_DOORSTOP_MANAGED_FOLDER_DIR}");
internal static readonly string BaseDirectory = Path.GetFullPath(Path.Combine(ManagedDirectory, "..", ".."));

// BATTLETECH/Mods/ModTek/lib/ModTek.Common.dll -> BATTLETECH
internal static readonly string BaseDirectory = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "..", "..", ".."));
// Mac has Data, but we have a symlink from BattleTech_Data to Data anyway
internal static readonly string ManagedDirectory = Path.Combine(BaseDirectory, "BattleTech_Data", "Managed");

internal static readonly string ModsDirectory = Path.Combine(BaseDirectory, "Mods");
internal static readonly string ModTekDirectory = Path.Combine(ModsDirectory, "ModTek");
Expand Down
9 changes: 8 additions & 1 deletion ModTek.Common/ModTek.Common.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\CommonNetStandard.props" />

<Target Name="CopyFilesToGame" AfterTargets="CopyFilesToOutputDirectory">
<ItemGroup>
<LibFiles Include="$(OutDir)*.dll" />
</ItemGroup>
<Copy SourceFiles="@(LibFiles)" DestinationFolder="$(ModTekLibDir)" />
</Target>

<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<InternalsVisibleTo Include="ModTek" />
<InternalsVisibleTo Include="ModTek.Preloader" />
<InternalsVisibleTo Include="ModTek.InjectorRunner" />
<InternalsVisibleTo Include="ModTek.Injectors" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using ModTek.Common.Utils;
using Mono.Cecil;

namespace ModTek.InjectorRunner.Injector;
namespace ModTek.Injectors;

class AssemblyCache : IAssemblyResolver
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using ModTek.Common.Globals;
using ModTek.Common.Utils;

namespace ModTek.InjectorRunner.Injector;
namespace ModTek.Injectors;

internal class InjectionCacheManifest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using ModTek.Common.Globals;
using ModTek.Common.Logging;

namespace ModTek.InjectorRunner;
namespace ModTek.Injectors;

internal class Logger
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\CommonNetStandard.props" />

<Target Name="CopyFilesToGame" AfterTargets="CopyFilesToOutputDirectory">
<ItemGroup>
<LibFiles Include="$(OutDir)*.dll" />
</ItemGroup>
<Copy SourceFiles="@(LibFiles)" DestinationFolder="$(ModTekLibDir)" />
</Target>

<ItemGroup>
<InternalsVisibleTo Include="ModTek.Preloader" />
<InternalsVisibleTo Include="ModTek.InjectorsTask" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ModTek.Common\ModTek.Common.csproj" />
<ProjectReference Include="..\ModTek.Common\ModTek.Common.csproj" Private="false" ExcludeAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
using ModTek.Common.Logging;
using ModTek.Common.Utils;

namespace ModTek.InjectorRunner.Injector;
namespace ModTek.Injectors;

internal class InjectorsRunner : IDisposable
internal class Runner : IDisposable
{
internal static void Run()
{
using var injectorsRunner = new InjectorsRunner();
using var injectorsRunner = new Runner();
if (injectorsRunner.IsUpToDate)
{
return;
Expand All @@ -22,9 +22,14 @@ internal static void Run()
injectorsRunner.SaveToDisk();
}

internal static string[] GetInjectedPaths()
{
return Directory.GetFiles(Paths.AssembliesInjectedDirectory, "*.dll");
}

private readonly AssemblyCache _assemblyCache;
private readonly InjectionCacheManifest _injectionCacheManifest;
private InjectorsRunner()
private Runner()
{
_assemblyCache = new AssemblyCache();
_injectionCacheManifest = new InjectionCacheManifest();
Expand Down
17 changes: 17 additions & 0 deletions ModTek.InjectorsTask/ITaskItemExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Microsoft.Build.Framework;

namespace ModTek.InjectorsTask;

// from Krafs.Publicizer
internal static class TaskItemExtensions
{
internal static string FileName(this ITaskItem item)
{
return item.GetMetadata("Filename");
}

internal static string FullPath(this ITaskItem item)
{
return item.GetMetadata("Fullpath");
}
}
26 changes: 26 additions & 0 deletions ModTek.InjectorsTask/ModTek.InjectorsTask.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\CommonNetStandard.props" />

<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>

<Target Name="CopyFilesToGame" AfterTargets="CopyFilesToOutputDirectory">
<ItemGroup>
<LibFiles Include="$(OutDir)*.dll;*.props;*.targets" />
</ItemGroup>
<Copy SourceFiles="@(LibFiles)" DestinationFolder="$(ModTekLibDir)" />
</Target>

<ItemGroup>
<!-- TOOD transitive dependency to Common gets included even if Private=false -->
<ProjectReference Include="..\ModTek.Injectors\ModTek.Injectors.csproj" Private="false" ExcludeAssets="all" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core">
<PrivateAssets>All</PrivateAssets>
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
</ItemGroup>
</Project>
6 changes: 6 additions & 0 deletions ModTek.InjectorsTask/ModTek.InjectorsTask.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project>
<UsingTask
TaskName="ModTek.InjectorsTask.ModTekInjectorsRunner"
AssemblyFile="$(BattleTechGameDir)\Mods\ModTek\lib\ModTek.InjectorsTask.dll"
/>
</Project>
18 changes: 18 additions & 0 deletions ModTek.InjectorsTask/ModTek.InjectorsTask.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project>
<!-- PublicizeAssemblies is from Krafs.Publicizer -->
<Target
Name="ModTekInjectorsRunner"
DependsOnTargets="ResolveAssemblyReferences"
BeforeTargets="PublicizeAssemblies;ResolveReferences"
>
<ModTekInjectorsRunner BattleTechGameDir="$(BattleTechGameDir)" ReferencePaths="@(ReferencePath)">
<Output TaskParameter="ReferencePathsToDelete" ItemName="_ModTekReferencePathsToDelete" />
<Output TaskParameter="ReferencePathsToAdd" ItemName="_ModTekReferencePathsToAdd" />
</ModTekInjectorsRunner>

<ItemGroup>
<ReferencePath Remove="@(_ModTekReferencePathsToDelete)" />
<ReferencePath Include="@(_ModTekReferencePathsToAdd)" />
</ItemGroup>
</Target>
</Project>
68 changes: 68 additions & 0 deletions ModTek.InjectorsTask/ModTekInjectorsRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using ModTek.Injectors;

namespace ModTek.InjectorsTask;

/*
* see https://learn.microsoft.com/en-us/visualstudio/msbuild/tutorial-custom-task-code-generation?view=vs-2022#generate-a-console-app-and-use-the-custom-task
* see https://github.com/krafs/Publicizer/blob/main/src/Publicizer/Krafs.Publicizer.targets and props
* - run before Rafs.Publicizer
* - needs to modify the references? or just push them ahead so they are found?
* - see Rafs ReferencePathsToDelete and ReferencePathsToAdd and ReferencePaths etc....
*/
public class ModTekInjectorsRunner : Task
{
[Required]
public string BattleTechGameDir { get; set; } = null!;

[Required]
public ITaskItem[] ReferencePaths { get; set; } = null!;

[Output]
public ITaskItem[] ReferencePathsToDelete { get; set; } = [];

[Output]
public ITaskItem[] ReferencePathsToAdd { get; set; } = [];

public override bool Execute()
{
var executingAssembly = Assembly.GetExecutingAssembly();
var applicationDirectory = Path.GetDirectoryName(executingAssembly.Location)!;

var currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += (_, args) =>
{
var resolvingName = new AssemblyName(args.Name);
var path = Path.Combine(applicationDirectory, resolvingName.Name + ".dll");
Log.LogWarning($"Loading assembly {args.Name} from {path}");
return Assembly.LoadFrom(path);
};

Runner.Run();

var referencePathsToAdd = new List<ITaskItem>();
var referencePathsToDelete = new List<ITaskItem>();
foreach (var path in Runner.GetInjectedPaths())
{
var filename = Path.GetFileNameWithoutExtension(path);
foreach (var referencePath in ReferencePaths)
{
if (filename == referencePath.FileName())
{
var newReference = new TaskItem(path);
referencePathsToAdd.Add(newReference);
referencePathsToDelete.Add(referencePath);
break;
}
}
}
ReferencePathsToDelete = referencePathsToDelete.ToArray();
ReferencePathsToAdd = referencePathsToAdd.ToArray();
return !Log.HasLoggedErrors;
}
}
2 changes: 1 addition & 1 deletion ModTek.Preloader/Harmony12X/ShimCacheManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Linq;
using ModTek.Common.Globals;
using ModTek.Common.Utils;
using ModTek.InjectorRunner.Injector;
using ModTek.Injectors;
using Mono.Cecil;

namespace ModTek.Preloader.Harmony12X;
Expand Down
4 changes: 2 additions & 2 deletions ModTek.Preloader/InjectorsAppDomain.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using ModTek.Common.Globals;
using ModTek.InjectorRunner.Injector;
using ModTek.Injectors;

namespace ModTek.Preloader;

Expand Down Expand Up @@ -36,6 +36,6 @@ internal static void Run()

private void RunInjectors()
{
InjectorsRunner.Run();
Runner.Run();
}
}
4 changes: 2 additions & 2 deletions ModTek.Preloader/Loader/Preloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Reflection;
using ModTek.Common.Globals;
using ModTek.Common.Utils;
using ModTek.InjectorRunner.Injector;
using ModTek.Injectors;
using ModTek.Preloader.Harmony12X;

namespace ModTek.Preloader.Loader;
Expand Down Expand Up @@ -37,7 +37,7 @@ internal static void Run()
private static void PreloadAssembliesInjected()
{
Logger.Main.Log($"Preloading injected assemblies from `{FileUtils.GetRelativePath(Paths.AssembliesInjectedDirectory)}`:");
foreach (var file in Directory.GetFiles(Paths.AssembliesInjectedDirectory, "*.dll").OrderBy(p => p))
foreach (var file in Injectors.Runner.GetInjectedPaths().OrderBy(p => p))
{
Logger.Main.Log($"\t{Path.GetFileName(file)}");
Assembly.LoadFile(file);
Expand Down
8 changes: 4 additions & 4 deletions ModTek.Preloader/ModTek.Preloader.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

<Target Name="CopyFilesToGame" AfterTargets="CopyFilesToOutputDirectory">
<ItemGroup>
<Dlls Include="$(OutDir)**" />
<LibFiles Include="$(OutDir)*.dll" />
<DoorstopFiles Include="run.sh;doorstop_config.ini" />
</ItemGroup>
<Copy SourceFiles="@(Dlls)" DestinationFolder="$(ModTekLibDir)" />
<Copy SourceFiles="@(LibFiles)" DestinationFolder="$(ModTekLibDir)" />
<Copy SourceFiles="@(DoorstopFiles)" DestinationFolder="$(BattleTechGameDir)\" />
<MakeDir Directories="$(BattleTechGameDir)\Mods\ModTek\AssembliesOverride" />
</Target>
Expand Down Expand Up @@ -34,7 +34,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ModTek.Common\ModTek.Common.csproj" />
<ProjectReference Include="..\ModTek.InjectorRunner\ModTek.InjectorRunner.csproj" />
<ProjectReference Include="..\ModTek.Common\ModTek.Common.csproj" Private="false" ExcludeAssets="all" />
<ProjectReference Include="..\ModTek.Injectors\ModTek.Injectors.csproj" Private="false" ExcludeAssets="all" />
</ItemGroup>
</Project>
Loading

0 comments on commit bc289de

Please sign in to comment.