/// <summary> /// Transpiler which replaces constructors of view models with their expanded counterparts. /// Only replaces constructors which are affected by current mod. /// </summary> /// <param name="moduleName"></param> /// <param name="input"></param> /// <returns></returns> public static IEnumerable <CodeInstruction> InstantiationCallsiteTranspiler(string moduleName, IEnumerable <CodeInstruction> input) { return(input.Select(op => { var component = UIExtender.RuntimeFor(moduleName).ViewModelComponent; if (op.opcode != OpCodes.Newobj) { return op; } var constructor = op.operand as ConstructorInfo; var type = constructor.DeclaringType; if (type == null || !type.IsSubclassOf(typeof(TaleWorlds.Library.ViewModel))) { return op; } var baseType = component.BaseTypeForPossiblyExtendedType(type); if (baseType != null && component.ExtendsViewModelType(baseType)) { op.operand = component.ExtendedViewModelTypeForType(baseType, type).GetConstructor(constructor.GetParameters().Types()); } return op; })); }
/// <summary> /// Method inserted into `WidgetFactory.LoadFrom`, which patches XmlDocument. /// A number of those are run in sequence for each extension. /// </summary> /// <param name="moduleName"></param> /// <param name="path"></param> /// <param name="document"></param> public static void ProcessMovieDocumentIfNeeded(string moduleName, string path, XmlDocument document) { // currently replicates exactly what WidgetFactory is doing var movieName = Path.GetFileNameWithoutExtension(path); // proxy call to ProcessMovieIfNeeded UIExtender.RuntimeFor(moduleName).PrefabComponent.ProcessMovieIfNeeded(movieName, document); }
/// <summary> /// Proxy method to ViewModelComponent.InitializeMixinsForViewModelInstance /// </summary> /// <param name="moduleName"></param> /// <param name="t"></param> /// <param name="instance"></param> public static void InitializeMixinsForVMInstance(string moduleName, Type t, object instance) { var runtime = UIExtender.RuntimeFor(moduleName); // Verify should have been called at this point Utils.SoftAssert(runtime.VerifyCalled, $"UIExtender.Verify was not called!"); runtime.ViewModelComponent.InitializeMixinsForVMInstance(t, instance); }
/// <summary> /// Transpiler for `LoadFrom` method that apply patches to loaded XML file /// </summary> /// <param name="moduleName"></param> /// <param name="input"></param> /// <returns></returns> public static IEnumerable <CodeInstruction> PrefabLoadTranspiler(string moduleName, IEnumerable <CodeInstruction> input) { var nopMarkName = "UIExtenderLib"; var loadXmlMethod = typeof(UIExtenderRuntimeLib).GetMethod(nameof(UIExtenderRuntimeLib.LoadXmlDocument)); var list = new List <CodeInstruction>(input); DynamicMethod CreateDynamicMethod(MethodInfo parentMethod) { var method = new DynamicMethod($"{Guid.NewGuid()}", null, new [] { typeof(string), typeof(string), typeof(XmlDocument) }); var gen = method.GetILGenerator(); if (parentMethod != null) { gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Ldarg_2); gen.EmitCall(OpCodes.Call, parentMethod, null); } var processMethod = typeof(UIExtenderRuntimeLib).GetMethod(nameof(UIExtenderRuntimeLib.ProcessMovieDocumentIfNeeded)); Debug.Assert(processMethod != null); gen.Emit(OpCodes.Ldstr, moduleName); gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Ldarg_2); gen.EmitCall(OpCodes.Call, processMethod, null); gen.Emit(OpCodes.Ret); return(method); } int FindAlreadyPatchedIndex() { for (var i = 0; i < list.Count(); i++) { if (list[i].opcode == OpCodes.Nop && (string)list[i].operand == nopMarkName) { return(i); } } return(-1); } void ApplyInitialPatch(DynamicMethod method) { var additions = new List <CodeInstruction>(); additions.Add(new CodeInstruction(OpCodes.Ldstr, moduleName)); additions.Add(new CodeInstruction(OpCodes.Ldarg_2)); additions.Add(new CodeInstruction(OpCodes.Ldloc_0)); additions.Add(new CodeInstruction(OpCodes.Call, loadXmlMethod)); // @TODO: replace NOP with Label? additions.Add(new CodeInstruction(OpCodes.Nop, nopMarkName)); additions.Add(new CodeInstruction(OpCodes.Ldstr, moduleName)); additions.Add(new CodeInstruction(OpCodes.Ldarg_2)); additions.Add(new CodeInstruction(OpCodes.Ldloc_0)); additions.Add(new CodeInstruction(OpCodes.Call, method)); // @TODO: reformat/rewrite var from = list.TakeWhile(i => !(i.opcode == OpCodes.Newobj && (i.operand as ConstructorInfo).DeclaringType == typeof(XmlReaderSettings))).Count() + 2; var to = from + list.Skip(from).TakeWhile(i => !(i.opcode == OpCodes.Newobj && (i.operand as ConstructorInfo).DeclaringType == typeof(WidgetPrefab))).Count(); var count = to - from; list.RemoveRange(from, count); list.InsertRange(from, additions); from = from + additions.Count; count = to - from; if (Utils.SoftAssert(count > 0, "Don't have enought NOP space in the IL!")) { list.InsertRange(from, Enumerable.Repeat(new CodeInstruction(OpCodes.Nop), count)); } else { UIExtender.RuntimeFor(moduleName).AddUserError($"Failed to patch {moduleName} (outdated)."); } } var index = FindAlreadyPatchedIndex(); if (index == -1) { var method = CreateDynamicMethod(null); ApplyInitialPatch(method); } else { var offset = list.Skip(index).TakeWhile(i => i.opcode != OpCodes.Call).Count(); var instruction = list[index + offset]; if (Utils.SoftAssert(instruction.opcode == OpCodes.Call, $"Invalid instruction found at marker!")) { instruction.operand = CreateDynamicMethod((MethodInfo)instruction.operand); } else { UIExtender.RuntimeFor(moduleName).AddUserError($"Failed to patch {moduleName} (outdated)."); } } return(list); }
/// <summary> /// Postfix that is used to call `OnRefresh()` on attached mixins /// </summary> /// <param name="moduleName"></param> /// <param name="__instance"></param> public static void RefreshPostfix(string moduleName, object __instance) { UIExtender.RuntimeFor(moduleName).ViewModelComponent.RefreshMixinForVMInstance(__instance); }
/// <summary> /// Proxy method to ViewModelComponent.FinalizeMixinsForInstance /// </summary> /// <param name="moduleName"></param> /// <param name="instance"></param> public static void FinalizeMixinsForVMInstance(string moduleName, object instance) { UIExtender.RuntimeFor(moduleName).ViewModelComponent.FinalizeMixinForVMInstance(instance); }
/// <summary> /// Proxy method to ViewModelComponent.MixinInstanceForObject /// </summary> /// <param name="moduleName"></param> /// <param name="mixinType"></param> /// <param name="instance"></param> /// <returns></returns> public static object MixinInstanceForVMInstance(string moduleName, Type mixinType, object instance) { return(UIExtender.RuntimeFor(moduleName).ViewModelComponent.MixinInstanceForVMInstance(mixinType, instance)); }