-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
258 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
using System; | ||
using System.IO; | ||
using System.Xml.Serialization; | ||
using Mono.Cecil; | ||
using FieldAttributes = Mono.Cecil.FieldAttributes; | ||
|
||
namespace ModTekSimpleInjector; | ||
|
||
/* | ||
What is hard and therefore not supported: | ||
- custom attributes | ||
- e.g. [JsonIgnored] | ||
- mainly due to constructor arguments being hard [SerializableMember(SerializationTarget.SaveGame)] | ||
- custom types introduced by mods | ||
- injectors run before mods are loaded. if a modded type is added, but the mod is not even loaded.. what now? | ||
- also how does the injector know where to find the types? -> mod dlls are unknown during injector time | ||
- some kind of special assembly type with "injectable" types would be a solution, requires some ahead of type loading | ||
- modifying existing fields | ||
- visibility should not be modified during runtime since it can crash (subclassing), and compile time via publicizer is good enough | ||
- changing static, const would also crash stuff | ||
- changing type -> contra/covariance issues | ||
- custom attributes -> might be interesting but highly situational. | ||
- adding properties | ||
- this is more complicate due to compiler backed fields etc.. just use extension methods and "Unsafe.As" | ||
- adding methods | ||
- a mod can just use static methods and use the first argument with (this ok ...) and it will look like instance methods | ||
*/ | ||
internal static class Injector | ||
{ | ||
public static void Inject(IAssemblyResolver resolver) | ||
{ | ||
var baseDirectory = Path.GetDirectoryName(typeof(Injector).Assembly.Location); | ||
var files = Directory.GetFiles(baseDirectory, "ModTekSimpleInjector.*.xml"); | ||
Array.Sort(files); | ||
var serializer = new XmlSerializer(typeof(Additions)); | ||
foreach (var file in files) | ||
{ | ||
if (file.EndsWith("ModTekSimpleInjector.Example.xml")) | ||
{ | ||
continue; | ||
} | ||
Console.WriteLine($"Processing additions in file {file}"); | ||
using var reader = new StreamReader(file); | ||
var additions = (Additions)serializer.Deserialize(reader); | ||
ProcessAdditions(resolver, additions); | ||
} | ||
} | ||
|
||
private static void ProcessAdditions(IAssemblyResolver resolver, Additions additions) | ||
{ | ||
foreach (var addition in additions.AddField) | ||
{ | ||
Console.WriteLine($"Processing {addition}"); | ||
ResolveAssemblyAndType(resolver, addition, out var assemblyDefinition, out var typeDefinition); | ||
ProcessAddField(assemblyDefinition, typeDefinition, addition); | ||
} | ||
foreach (var addition in additions.AddEnumConstant) | ||
{ | ||
Console.WriteLine($"Processing {addition}"); | ||
ResolveAssemblyAndType(resolver, addition, out _, out var typeDefinition); | ||
ProcessAddEnumConstant(typeDefinition, addition); | ||
} | ||
} | ||
|
||
private static void ResolveAssemblyAndType( | ||
IAssemblyResolver resolver, | ||
Addition addition, | ||
out AssemblyDefinition assemblyDefinition, | ||
out TypeDefinition typeDefinition | ||
) { | ||
var assemblyName = new AssemblyNameReference(addition.InAssembly, null); | ||
assemblyDefinition = resolver.Resolve(assemblyName); | ||
if (assemblyDefinition == null) | ||
{ | ||
throw new ArgumentException($"Unable to resolve assembly {addition.InAssembly}"); | ||
} | ||
typeDefinition = assemblyDefinition.MainModule.GetType(addition.ToType); | ||
if (typeDefinition == null) | ||
{ | ||
throw new ArgumentException($"Unable to resolve type {addition.ToType} in assembly {addition.InAssembly}"); | ||
} | ||
} | ||
|
||
private static void ProcessAddField(AssemblyDefinition assembly, TypeDefinition type, AddField fieldAddition) | ||
{ | ||
var fieldType = ResolveType(fieldAddition.OfType); | ||
var fieldTypeReference = assembly.MainModule.ImportReference(fieldType); | ||
var field = new FieldDefinition(fieldAddition.Name, fieldAddition.Attributes, fieldTypeReference); | ||
type.Fields.Add(field); | ||
} | ||
|
||
private static void ProcessAddEnumConstant(TypeDefinition type, AddEnumConstant enumConstant) | ||
{ | ||
const FieldAttributes EnumFieldAttributes = | ||
FieldAttributes.Static | ||
| FieldAttributes.Literal | ||
| FieldAttributes.Public | ||
| FieldAttributes.HasDefault; | ||
var constantValue = enumConstant.Value; | ||
var field = new FieldDefinition(enumConstant.Name, EnumFieldAttributes, type) | ||
{ | ||
Constant = constantValue | ||
}; | ||
type.Fields.Add(field); | ||
} | ||
|
||
private static Type ResolveType(string additionType) | ||
{ | ||
var typeName = additionType; | ||
var isArray = typeName.EndsWith("[]"); | ||
if (isArray) | ||
{ | ||
typeName = typeName[..^2]; | ||
} | ||
var fieldType = typeof(int).Assembly.GetType(typeName); | ||
if (isArray) | ||
{ | ||
fieldType = fieldType.MakeArrayType(); | ||
} | ||
return fieldType; | ||
} | ||
} | ||
|
||
// XmlSerializer requires the following classes to be public | ||
|
||
[XmlRoot(ElementName = "ModTekSimpleInjector")] | ||
public class Additions | ||
{ | ||
[XmlElement(ElementName = "AddField")] | ||
public AddField[] AddField = []; | ||
[XmlElement(ElementName = "AddEnumConstant")] | ||
public AddEnumConstant[] AddEnumConstant = []; | ||
} | ||
|
||
public abstract class Addition | ||
{ | ||
[XmlAttribute("InAssembly")] | ||
public string InAssembly; | ||
[XmlAttribute("ToType")] | ||
public string ToType; | ||
|
||
public override string ToString() | ||
{ | ||
return $"{this.GetType().Name}:{InAssembly}:{ToType}"; | ||
} | ||
} | ||
|
||
[XmlType("AddField")] | ||
[XmlRoot(ElementName = "AddField")] | ||
public class AddField : Addition | ||
{ | ||
[XmlAttribute("Name")] | ||
public string Name; | ||
[XmlAttribute("OfType")] | ||
public string OfType; | ||
[XmlAttribute("Attributes")] | ||
public FieldAttributes Attributes = FieldAttributes.Private; | ||
|
||
public override string ToString() | ||
{ | ||
return $"{base.ToString()}:{Name}:{OfType}:{Attributes}"; | ||
} | ||
} | ||
|
||
[XmlType("AddEnumConstant")] | ||
[XmlRoot(ElementName = "AddEnumConstant")] | ||
public class AddEnumConstant : Addition | ||
{ | ||
[XmlAttribute("Name")] | ||
public string Name; | ||
[XmlAttribute("Value")] | ||
public int Value; | ||
|
||
public override string ToString() | ||
{ | ||
return $"{base.ToString()}:{Name}:{Value}"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!-- Introduction | ||
ModTekSimpleInjector allows you to quickly add fields to existing types without having to write your own injectors. | ||
Start by placing a modified copy of this file next to the dll. Use your mod name in the file name: | ||
> `ModTekSimpleInjector.YOURMOD.xml` | ||
Multiple `AddField` and `AddEnumConstant` elements (0..n each) can be specified. | ||
Do not rely on fields added by other mods, unless explicitly advised to do so. | ||
--> | ||
<ModTekSimpleInjector> | ||
<!-- ToType & InAssembly | ||
Nested types are referenced using `/`, seems to be Mono.Cecil specific. | ||
Only assemblies found in `Managed/` can be referenced. | ||
Assemblies already loaded and can't be modified, e.g. mscorlib, System and System.Core. | ||
--> | ||
|
||
<!-- AddField | ||
AddField adds a new instance field to an existing type. | ||
Fields can be of any types found in mscorlib. | ||
Keywords (e.g. byte) are not supported, use the types full name (e.g. System.Byte). | ||
For custom types use `System.Object` and `Unsafe.As<object, MyType>(ref addedField)` for high performance access. | ||
A `[]` suffix means that the type should be an array of the specified type. | ||
--> | ||
<AddField ToType="HBS.Logging.Logger/LogImpl" InAssembly="Assembly-CSharp" | ||
Name="nameAsBytes2" OfType="System.Byte[]" /> | ||
|
||
<!-- AddField and Attributes | ||
Added fields are private by default, using other attributes is **strongly** discouraged. | ||
Publicizers allow to efficiently access private fields anyway. | ||
--> | ||
<AddField ToType="HBS.Logging.Logger/LogImpl" InAssembly="Assembly-CSharp" | ||
Name="secondaryName" OfType="System.String" Attributes="Public" /> | ||
|
||
<!-- AddEnumConstant | ||
AddEnumConstant adds an enum constant to an existing enum type. | ||
Multiple enum constants with the same value can be added, they should behave like aliases. This is usually unwanted. | ||
Make sure to understand `[Flags]` in case it applies. | ||
Enums are just ints and one can convert to and from enums using ints, other mods might already be using an int value you are targeting. | ||
--> | ||
<AddEnumConstant ToType="HBS.Logging.LogLevel" InAssembly="Assembly-CSharp" | ||
Name="Trace" Value="200" /> | ||
</ModTekSimpleInjector> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
<Import Project="..\CommonNetStandard.props" /> | ||
|
||
<Target Name="CopyFilesToGame" AfterTargets="CopyFilesToOutputDirectory"> | ||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(BattleTechGameDir)\Mods\ModTek\Injectors\" /> | ||
<Copy SourceFiles="ModTekSimpleInjector.Example.xml" DestinationFolder="$(BattleTechGameDir)\Mods\ModTek\Injectors\" /> | ||
</Target> | ||
|
||
<ItemGroup> | ||
<!-- we only need Mono.Cecil, but HarmonyX pins the version we need --> | ||
<PackageReference Include="HarmonyX"> | ||
<PrivateAssets>All</PrivateAssets> | ||
<ExcludeAssets>runtime</ExcludeAssets> | ||
</PackageReference> | ||
<Reference Include="Mono.Cecil"> | ||
<Private>False</Private> | ||
</Reference> | ||
</ItemGroup> | ||
</Project> |