293 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using Mono.Cecil;
 | |
| using Mono.Cecil.Cil;
 | |
| using Mono.Cecil.Rocks;
 | |
| using UnrealSharpWeaver.TypeProcessors;
 | |
| using UnrealSharpWeaver.Utilities;
 | |
| 
 | |
| namespace UnrealSharpWeaver.MetaData;
 | |
| 
 | |
| public class FunctionMetaData : BaseMetaData
 | |
| { 
 | |
|     public PropertyMetaData[] Parameters { get; set; }
 | |
|     public PropertyMetaData? ReturnValue { get; set; }
 | |
|     public EFunctionFlags FunctionFlags { get; set; }
 | |
|     
 | |
|     // Non-serialized for JSON
 | |
|     public readonly MethodDefinition MethodDef;
 | |
|     public FunctionRewriteInfo RewriteInfo;
 | |
|     public FieldDefinition? FunctionPointerField;
 | |
|     public bool IsBlueprintEvent => FunctionFlags.HasAnyFlags(EFunctionFlags.BlueprintNativeEvent);
 | |
|     public bool HasParameters => Parameters.Length > 0 || HasReturnValue;
 | |
|     public bool HasReturnValue => ReturnValue != null;
 | |
|     public bool IsRpc => FunctionFlags.HasAnyFlags(Utilities.MethodUtilities.RpcFlags);
 | |
|     public bool HasOutParams => FunctionFlags.HasAnyFlags(EFunctionFlags.HasOutParms);
 | |
|     private bool _shouldBeRemoved;
 | |
|     // End non-serialized
 | |
| 
 | |
|     private const string CallInEditorName = "CallInEditor";
 | |
| 
 | |
|     public FunctionMetaData(MethodDefinition method, bool onlyCollectMetaData = false, EFunctionFlags functionFlags = EFunctionFlags.None) 
 | |
|         : base(method, Utilities.MethodUtilities.UFunctionAttribute)
 | |
|     {
 | |
|         MethodDef = method;
 | |
|         FunctionFlags = functionFlags;
 | |
|         
 | |
|         bool hasOutParams = false;
 | |
|         
 | |
|         if (!method.ReturnsVoid())
 | |
|         {
 | |
|             hasOutParams = true;
 | |
|             try
 | |
|             {
 | |
|                 ReturnValue = PropertyMetaData.FromTypeReference(method.ReturnType, "ReturnValue", ParameterType.ReturnValue);
 | |
|             }
 | |
|             catch (InvalidPropertyException)
 | |
|             {
 | |
|                 throw new InvalidUnrealFunctionException(method, $"'{method.ReturnType.FullName}' is invalid for unreal function return value.");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (BaseAttribute != null)
 | |
|         {
 | |
|             CustomAttributeArgument? callInEditor = BaseAttribute.FindAttributeField(CallInEditorName);
 | |
|             if (callInEditor.HasValue)
 | |
|             {
 | |
|                 TryAddMetaData(CallInEditorName, (bool) callInEditor.Value.Value);
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         Parameters = new PropertyMetaData[method.Parameters.Count];
 | |
|         for (int i = 0; i < method.Parameters.Count; ++i)
 | |
|         {
 | |
|             ParameterDefinition param = method.Parameters[i];
 | |
|             ParameterType modifier = ParameterType.Value;
 | |
|             TypeReference paramType = param.ParameterType;
 | |
|             
 | |
|             if (param.IsOut)
 | |
|             {
 | |
|                 hasOutParams = true;
 | |
|                 modifier = ParameterType.Out;
 | |
|             }
 | |
|             else if (paramType.IsByReference)
 | |
|             {
 | |
|                 hasOutParams = true;
 | |
|                 modifier = ParameterType.Ref;
 | |
|             }
 | |
| 
 | |
|             Parameters[i] = PropertyMetaData.FromTypeReference(paramType, param.Name, modifier, param);
 | |
| 
 | |
|             if (param.HasConstant)
 | |
|             {
 | |
|                 string? defaultValue = DefaultValueToString(param);
 | |
|                 if (defaultValue != null)
 | |
|                 {
 | |
|                     TryAddMetaData($"CPP_Default_{param.Name}", defaultValue);
 | |
|                     FunctionFlags |= EFunctionFlags.HasDefaults;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         FunctionFlags |= MethodDef.GetFunctionFlags();
 | |
|         
 | |
|         if (hasOutParams)
 | |
|         {
 | |
|             FunctionFlags |= EFunctionFlags.HasOutParms;
 | |
|         }
 | |
|         
 | |
|         if (onlyCollectMetaData)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         RewriteFunction();
 | |
|     }
 | |
|     
 | |
|     public void RewriteFunction(bool forceOverwriteBody = false)
 | |
|     {
 | |
|         TypeDefinition baseType = MethodDef.GetOriginalBaseMethod().DeclaringType;
 | |
|         if (baseType == MethodDef.DeclaringType)
 | |
|         {
 | |
|             RewriteInfo = new FunctionRewriteInfo(this);
 | |
|             FunctionProcessor.PrepareFunctionForRewrite(this, MethodDef.DeclaringType, forceOverwriteBody);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             EFunctionFlags flags = GetFunctionFlags(MethodDef.GetOriginalBaseMethod());
 | |
|             if (flags.HasAnyFlags(EFunctionFlags.BlueprintCallable) 
 | |
|                 && !flags.HasAnyFlags(EFunctionFlags.BlueprintNativeEvent))
 | |
|             {
 | |
|                 return;
 | |
|             }
 | |
|             
 | |
|             FunctionProcessor.MakeImplementationMethod(this);
 | |
|             
 | |
|             // We don't need the override anymore. It's copied into the Implementation method.
 | |
|             // But we can't remove it here because it would mess up for child classes during weaving.
 | |
|             _shouldBeRemoved = true;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public void TryRemoveMethod()
 | |
|     {
 | |
|         if (!_shouldBeRemoved)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
|         
 | |
|         MethodDef.DeclaringType.Methods.Remove(MethodDef);
 | |
|     }
 | |
|     
 | |
|     public static bool IsAsyncUFunction(MethodDefinition method)
 | |
|     {
 | |
|         if (!method.HasCustomAttributes)
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         CustomAttribute? functionAttribute = method.GetUFunction();
 | |
|         if (functionAttribute == null)
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         if (!functionAttribute.HasConstructorArguments)
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         var flags = (EFunctionFlags) (ulong) functionAttribute.ConstructorArguments[0].Value;
 | |
|         return flags == EFunctionFlags.BlueprintCallable && method.ReturnType.FullName.StartsWith("System.Threading.Tasks.Task");
 | |
|     }
 | |
| 
 | |
|     public static bool IsBlueprintEventOverride(MethodDefinition method)
 | |
|     {
 | |
|         if (!method.IsVirtual)
 | |
|         {
 | |
|             return false;
 | |
|         }
 | |
|         
 | |
|         MethodDefinition baseMethod = method.GetOriginalBaseMethod();
 | |
|         if (baseMethod != method && baseMethod.HasCustomAttributes)
 | |
|         {
 | |
|             return baseMethod.IsUFunction();
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
|     
 | |
|     public static string? DefaultValueToString(ParameterDefinition value)
 | |
|     {
 | |
|         // Can be null if the value is set to = default/null
 | |
|         if (value.Constant == null)
 | |
|         {
 | |
|             return null;
 | |
|         }
 | |
|         
 | |
|         TypeDefinition typeDefinition = value.ParameterType.Resolve();
 | |
|         if (typeDefinition.IsEnum)
 | |
|         {
 | |
|             return typeDefinition.Fields[(byte) value.Constant].Name;
 | |
|         }
 | |
|         
 | |
|         // Unreal doesn't support commas in default values
 | |
|         string defaultValue = value.Constant.ToString()!;
 | |
|         defaultValue = defaultValue.Replace(",", ".");
 | |
|         
 | |
|         return defaultValue;
 | |
|     }
 | |
| 
 | |
|     public static EFunctionFlags GetFunctionFlags(MethodDefinition method)
 | |
|     {
 | |
|         return (EFunctionFlags) GetFlags(method, "FunctionFlagsMapAttribute");
 | |
|     }
 | |
| 
 | |
|     public static bool IsInterfaceFunction(MethodDefinition method)
 | |
|     {
 | |
|         return TryGetInterfaceFunction(method) != null;
 | |
|     }
 | |
|     
 | |
|     public static MethodDefinition? TryGetInterfaceFunction(MethodDefinition method)
 | |
|     {
 | |
|         foreach (var typeInterface in method.DeclaringType.Interfaces)
 | |
|         {
 | |
|             var interfaceType = typeInterface.InterfaceType.Resolve();
 | |
|             
 | |
|             if (!interfaceType.IsUInterface())
 | |
|             {
 | |
|                 continue; 
 | |
|             }
 | |
| 
 | |
|             foreach (MethodDefinition? interfaceMethod in interfaceType.Methods)
 | |
|             {
 | |
|                 if (interfaceMethod.Name == method.Name)
 | |
|                 {
 | |
|                     return interfaceMethod;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return null;
 | |
|     }
 | |
|     
 | |
|     public static bool IsBlueprintCallable(MethodDefinition method)
 | |
|     {
 | |
|         EFunctionFlags flags = GetFunctionFlags(method);
 | |
|         return flags.HasAnyFlags(EFunctionFlags.BlueprintCallable);
 | |
|     }
 | |
|     
 | |
|     public void EmitFunctionPointers(ILProcessor processor, Instruction loadTypeField, Instruction setFunctionPointer)
 | |
|     {
 | |
|         processor.Append(loadTypeField);
 | |
|         processor.Emit(OpCodes.Ldstr, Name);
 | |
|         processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativeFunctionFromClassAndNameMethod);
 | |
|         processor.Append(setFunctionPointer);
 | |
|     }
 | |
|     
 | |
|     public void EmitFunctionParamOffsets(ILProcessor processor, Instruction loadFunctionPointer)
 | |
|     {
 | |
|         foreach (FunctionParamRewriteInfo paramRewriteInfo in RewriteInfo.FunctionParams)
 | |
|         {
 | |
|             FieldDefinition? offsetField = paramRewriteInfo.OffsetField;
 | |
|             if (offsetField == null)
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             PropertyMetaData param = paramRewriteInfo.PropertyMetaData;
 | |
|                 
 | |
|             processor.Append(loadFunctionPointer);
 | |
|             processor.Emit(OpCodes.Ldstr, param.Name);
 | |
|             processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetPropertyOffsetFromNameMethod);
 | |
|             processor.Emit(OpCodes.Stsfld, offsetField);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public void EmitFunctionParamSize(ILProcessor processor, Instruction loadFunctionPointer)
 | |
|     {
 | |
|         if (RewriteInfo.FunctionParamSizeField == null)
 | |
|         {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         processor.Append(loadFunctionPointer);
 | |
|         processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativeFunctionParamsSizeMethod);
 | |
|         processor.Emit(OpCodes.Stsfld, RewriteInfo.FunctionParamSizeField);
 | |
|     }
 | |
|     
 | |
|     public void EmitParamNativeProperty(ILProcessor processor, Instruction? loadFunctionPointer)
 | |
|     {
 | |
|         foreach (var paramRewriteInfo in RewriteInfo.FunctionParams)
 | |
|         {
 | |
|             FieldDefinition? nativePropertyField = paramRewriteInfo.NativePropertyField;
 | |
|             if (nativePropertyField == null)
 | |
|             {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             processor.Append(loadFunctionPointer);
 | |
|             processor.Emit(OpCodes.Ldstr, paramRewriteInfo.PropertyMetaData.Name);
 | |
|             processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativePropertyFromNameMethod);
 | |
|             processor.Emit(OpCodes.Stsfld, nativePropertyField);
 | |
|         }
 | |
|     }
 | |
| } |