BasketWeaver is a Weaver/Injector for HBS's BattleTech. It provides users the capability to inject C# code into BattleTech without requiring manual specification of CIL.
When ModTek is installed and BattleTech starts, it uses UnityDoorstop to load early and avoid loading .NET assemblies. During this phase, ModTek can inject code into .NET assemblies through Injectors, inclusive itself. BasketWeaver enables the user to write C# code instead of CIL through helper libraries that wrap user specified fields and methods to override existing classes. These injected methods can then be called by BattleTech and optionally Harmony patches later in the loading process.
- In
CHANGEME.Directory.Build.Props
, setBattleTechGameDir
to the location of theBATTLETECH
install folder:<BattleTechGameDir>CHANGEME_TO_FULL_PATH_TO_BTG_DIR</BattleTechGameDir>
- Open IDE of choice and compile.
Build/*
will contain a mirror of of what is copied to yourBATTLETECH/Mods/
folder if built succeeds.
By default, BasketWeaver uses three components:
- BasketWeaver:
/Mods/BasketWeaver
- BasketWeaverInjector:
/Mods/ModTek/Injectors
(mirrored in/Mods/BasketWeaver/Injectors
) - Helper Libraries:
/Mods/BasketWeaver/Helpers
BasketWeaver installs by default into BattleTech/Mods/BasketWeaver
. This parent mod has minimal functionality and is used to provide a container for Helper Libraries that BasketWeaverInjector will load during the ModTek's injection phase.
BasketWeaver installs by default into BattleTech/Mods/ModTek/Injectors/BasketWeaverInjector.dll
. It provides the following capabilities:
- Injects helpers from
/Mods/BasketWeaver/Helpers/
into the game - Basic heuristics for automated inlining of specified
Modules
/.DLLs
- Conflict detection to avoid injecting or inlining methods that may bypass Harmony patches. This could occur due to inlining relocating instructions to caller methods and bypassing the patched methods.
A configuration file in BattleTech/Mods/ModTek/Injectors/BasketWeaver.json
is provided to toggle Helper Injection
and Automated Inlining
routines. It can also specific a desired Helper Namespace
, set to BasketWeaver
to start.
Automated inlining attempts to speed up Unity Engine and game performance by detecting methods that can be inlined. It naively uses method instructions and method attributes to identify wrapper-like functions that can be safely inlined at minimal cost. Due to the nature of possible Harmony bypasses, this should be specified carefully and may break runtime behavior.
Helper libraries are .NET Class Libraries that provide methods to replace existing code at injection time. This library can reference BattleTech's injectable .DLLs
and insert user specified code to replace original methods. The reference IL can be extracted through tools like ILSpy or dnSpy, modified, and compiled against BattleTech's .DLLs
and automatically injected by BasketWeaverInjector
This helper library is provided by default and loaded by BasketWeaverInjector. It attempts to improve performance of the UnityEngine.CoreModule
. The following optimizations are considered:
- Remove
return new Constructor();
calls in core mathematical results. - Reuse of Left-Hand-Side (LHS) valuetypes instead of allocation where applicable when result assignments do not cross axis boundaries or require temporary variables.
- Improvement of
ToString
throughStringBuilder.Append
for formatting - Precalculating divisions in vectorized divides to avoid floating point overhead. May or may not be optimized by the JIT.
- Removing wrapped
UnityEngine.Mathf
calls and passing equivalently castedSystem.Math
calls directly to reduce call overhead. - Attaching the
[AggressiveInlining]
attribute to math and helper functions similar toUnity.Mathematics
to further reduce call overhead.
Many of these optimizations were found optimizing logging overhead for the purposes of speeding up BattleTech. Some of them can be found in future versions of Unity.Mathematics and further discussions on these techniques can be founds in the below references:
- Unity.Mathematics - Uses many of the above techniques but not included as default math by the Unity 2018.4.2f1 build as used by BattleTech.
- Frame Rate Booster - A plugin that attempts to apply some of these optimizations to existing Unity builds.
- FRB Discussion - Discussion of the frame rate implications and JIT/IL2CPP behavior of above optimizations
Following along the PerfLibHelper
, these steps provide a reference on replacing a method:
In this example, we replace the +
operator to optimize addition. First return new Color();
is removed, and Color a
reused as a variable to pass the result. As structures are passed by value, this results in no side effects in the calling method and simultaneously avoids construction overhead.
From ILSpy inspection of UnityEngine.CoreModule.dll
:
namespace UnityEngine
{
public struct Color
public static Color operator +(Color a, Color b)
{
return new Color(a.r + b.r, a.g + b.g, a.b + b.b, a.a + b.a);
}
}
See the PerfLibHelpers directory for examples on project setup. Otherwise, in IDE of choice, create a new Class Library targeting .NET Framework 4.7.2
and reference the UnityEngine.CoreModule.dll
. Referencing .DLLs
may be required for Mono.Cecil
to resolve references during injection. Configure PostBuild actions as necessary.
The following snippet replaces the +
operator with an optimized method. Member variables called by replaced methods shall include all preceding variables in the order they were defined. This may ease resolving references where field indexes are provided in CIL operands. The BasketWeaver
namespace will be removed during injector and method operands resolved against the BattleTech loaded .DLL
. The wrapper namespace can be configured using the HelperNamespace
option in theBasketWeaverInjector.json
settings file.
NOTE: .NET
can ambiguously resolve the namespaces against the helper namespace. In these instances, manually inserting the references may be required. Types are generally resolved by full-name in the injector.
Color.cs
snippet from PerfLibHelpers:
using System;
using System.Runtime.CompilerServices;
using System.Text;
using UnityEngine;
// Helper Namespace, referenced and removed by BasketWeaver during Injetion
namespace BasketWeaver
{
namespace UnityEngine
{
public struct Color : IEquatable<Color>
{
public float r;
public float g;
public float b;
public float a;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color operator +(Color a, Color b)
{
a.r = a.r + b.r;
a.g = a.g + b.g;
a.b = a.b + b.b;
a.a = a.a + b.a;
return a;
}
}
}
}
After build, copy the output .DLL
helper library into the BasketWeaver/Helpers/*
directory. Upon runtime, all .DLLs
in this folder will be searched for the specified wrapper namespace and injected.
NOTE: The copy may be done automatically depending on post-build options. See PerfLibHelpers for examples. PerfLibHelpers provides post-build options to copy the helpers to both the chosen BattleTechGameDir
and a local Build
directory that can be used for distribution.