using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using HarmonyLib; using JetBrains.Annotations; namespace Torch.Server.ViewModels; public class DynamicViewModel(T obj) : ViewModel { private readonly T _obj = obj; public ViewModel Wrapper { get; } = CreateProxy(obj); public static implicit operator T(DynamicViewModel obj) => obj._obj; private static ViewModel CreateProxy(T obj) { if (DynamicViewModel.Proxies.TryGetValue(typeof(T), out var proxy)) return (ViewModel)Activator.CreateInstance(proxy, obj); var viewModelSetMethod = AccessTools.GetDeclaredMethods(typeof(ViewModel)) .First(b => b.Name == "SetValue" && b.GetParameters()[0].ParameterType.IsByRef); DynamicViewModel.ModuleBuilder ??= AssemblyBuilder.DefineDynamicAssembly(new("Torch.Server.ViewModels.Generated"), AssemblyBuilderAccess.Run) .DefineDynamicModule("Torch.Server.ViewModels.Generated"); var type = DynamicViewModel.ModuleBuilder.DefineType($"{typeof(T).FullName}ViewModel", TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, typeof(ViewModel)); var instanceField = type.DefineField("_instance", typeof(T), FieldAttributes.Private | FieldAttributes.InitOnly); var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, [typeof(T)]); { var il = ctor.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stfld, instanceField); il.Emit(OpCodes.Ret); } foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public)) { var prop = type.DefineProperty(field.Name, PropertyAttributes.None, field.FieldType, null); foreach (var customAttribute in field.GetCustomAttributesData()) { var hasCustomArgs = customAttribute.NamedArguments?.Any() ?? false; var customArgsField = hasCustomArgs && customAttribute.NamedArguments[0].IsField; var constructorArgs = customAttribute.ConstructorArguments.Select(b => b.Value).ToArray(); CustomAttributeBuilder attributeBuilder; if (!hasCustomArgs) attributeBuilder = new CustomAttributeBuilder(customAttribute.Constructor, constructorArgs); else if (customArgsField) attributeBuilder = new CustomAttributeBuilder(customAttribute.Constructor, constructorArgs, customAttribute.NamedArguments.Select(b => (FieldInfo)b.MemberInfo).ToArray(), customAttribute.NamedArguments.Select(b => b.TypedValue.Value).ToArray()); else attributeBuilder = new CustomAttributeBuilder(customAttribute.Constructor, constructorArgs, customAttribute.NamedArguments.Select(b => (PropertyInfo)b.MemberInfo).ToArray(), customAttribute.NamedArguments.Select(b => b.TypedValue.Value).ToArray()); prop.SetCustomAttribute(attributeBuilder); } var getMethod = type.DefineMethod($"get_{field.Name}", MethodAttributes.Public | MethodAttributes.HideBySig, field.FieldType, Type.EmptyTypes); { var il = getMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, instanceField); il.Emit(OpCodes.Ldfld, field); il.Emit(OpCodes.Ret); } prop.SetGetMethod(getMethod); if (field.IsInitOnly) continue; var setMethod = type.DefineMethod($"set_{field.Name}", MethodAttributes.Public | MethodAttributes.HideBySig, typeof(void), [field.FieldType]); { var il = setMethod.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldfld, instanceField); il.Emit(OpCodes.Ldflda, field); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldstr, field.Name); il.Emit(OpCodes.Callvirt, viewModelSetMethod.MakeGenericMethod(field.FieldType)); il.Emit(OpCodes.Ret); } prop.SetSetMethod(setMethod); } proxy = type.CreateType(); DynamicViewModel.Proxies[typeof(T)] = proxy; return (ViewModel)Activator.CreateInstance(proxy, obj); } } file static class DynamicViewModel { public static readonly Dictionary Proxies = []; [CanBeNull] public static ModuleBuilder ModuleBuilder; }