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); } } }