Files
bd2/plugins/GameSdk/RuntimeInitialization.cs
T

56 lines
3.1 KiB
C#

using Mono.Cecil;
using Mono.Cecil.Cil;
namespace BD2.GameSdk;
internal static class RuntimeInitialization
{
private const string RuntimeAssembly = "BD2.GameNames";
private const string RuntimeType = "BD2.GameNames.RuntimeCompatibility";
private const string RuntimeMethod = "InitializePlugin";
internal static void Inject(ModuleDefinition module, string stamp)
{
TypeDefinition moduleType = module.Types.Single(t => t.Name == "<Module>");
MethodDefinition initializer = moduleType.Methods.SingleOrDefault(m => m.Name == ".cctor");
if (initializer == null)
{
initializer = new MethodDefinition(".cctor", MethodAttributes.Private | MethodAttributes.Static |
MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, module.TypeSystem.Void);
moduleType.Methods.Add(initializer);
initializer.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
}
if (initializer.HasBody && initializer.Body.Instructions.Count >= 2 && IsRuntimeCall(initializer.Body.Instructions[1]))
{
Verify(module, stamp);
return;
}
AssemblyNameReference runtime = module.AssemblyReferences.SingleOrDefault(a => a.Name == RuntimeAssembly);
if (runtime == null)
{
runtime = new AssemblyNameReference(RuntimeAssembly, new Version(1, 0, 0, 0));
module.AssemblyReferences.Add(runtime);
}
var type = new TypeReference("BD2.GameNames", "RuntimeCompatibility", module, runtime);
var method = new MethodReference(RuntimeMethod, module.TypeSystem.Void, type);
method.Parameters.Add(new ParameterDefinition(module.TypeSystem.String));
initializer.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Ldstr, stamp));
initializer.Body.Instructions.Insert(1, Instruction.Create(OpCodes.Call, method));
}
internal static void Verify(ModuleDefinition module, string stamp)
{
MethodDefinition initializer = module.Types.Single(t => t.Name == "<Module>").Methods.SingleOrDefault(m => m.Name == ".cctor");
if (initializer?.HasBody != true || initializer.Body.Instructions.Count < 3 ||
initializer.Body.Instructions[0].OpCode != OpCodes.Ldstr || initializer.Body.Instructions[0].Operand as string != stamp ||
!IsRuntimeCall(initializer.Body.Instructions[1]))
throw new InvalidDataException("Missing/mismatched SDK runtime initializer: rebuild this plugin with BD2.GameSdk");
}
private static bool IsRuntimeCall(Instruction instruction) => instruction.OpCode == OpCodes.Call &&
instruction.Operand is MethodReference method && method.Name == RuntimeMethod && !method.HasThis &&
method.DeclaringType.FullName == RuntimeType && method.DeclaringType.Scope is AssemblyNameReference assembly &&
assembly.Name == RuntimeAssembly && method.ReturnType.MetadataType == MetadataType.Void &&
method.Parameters.Count == 1 && method.Parameters[0].ParameterType.MetadataType == MetadataType.String;
}