| @ -0,0 +1,52 @@ | ||||
| using System.Collections.ObjectModel; | ||||
|  | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class BuildSolution : BuildToolAction | ||||
| { | ||||
|     private readonly BuildConfig _buildConfig; | ||||
|     private readonly string _folder; | ||||
|     private readonly Collection<string>? _extraArguments; | ||||
|  | ||||
|     public BuildSolution(string folder, Collection<string>? extraArguments = null, BuildConfig buildConfig = BuildConfig.Debug) | ||||
|     { | ||||
|         _folder = Program.FixPath(folder); | ||||
|         _buildConfig = buildConfig; | ||||
|         _extraArguments = extraArguments; | ||||
|     } | ||||
|  | ||||
|     public override bool RunAction() | ||||
|     { | ||||
|         if (!Directory.Exists(_folder)) | ||||
|         { | ||||
|             throw new Exception($"Couldn't find the solution file at \"{_folder}\""); | ||||
|         } | ||||
|  | ||||
|         using BuildToolProcess buildSolutionProcess = new BuildToolProcess(); | ||||
|  | ||||
|         if (_buildConfig == BuildConfig.Publish) | ||||
|         { | ||||
|             buildSolutionProcess.StartInfo.ArgumentList.Add("publish"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             buildSolutionProcess.StartInfo.ArgumentList.Add("build"); | ||||
|         } | ||||
|  | ||||
|         buildSolutionProcess.StartInfo.ArgumentList.Add($"{_folder}"); | ||||
|  | ||||
|         buildSolutionProcess.StartInfo.ArgumentList.Add("--configuration"); | ||||
|         buildSolutionProcess.StartInfo.ArgumentList.Add(Program.GetBuildConfiguration(_buildConfig)); | ||||
|         buildSolutionProcess.StartInfo.WorkingDirectory = _folder; | ||||
|          | ||||
|         if (_extraArguments != null) | ||||
|         { | ||||
|             foreach (var argument in _extraArguments) | ||||
|             { | ||||
|                 buildSolutionProcess.StartInfo.ArgumentList.Add(argument); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return buildSolutionProcess.StartBuildToolProcess(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public abstract class BuildToolAction | ||||
| { | ||||
|     public static bool InitializeAction() | ||||
|     { | ||||
|         BuildToolAction buildToolAction = Program.BuildToolOptions.Action switch | ||||
|         { | ||||
|             BuildAction.Build => new BuildUserSolution(), | ||||
|             BuildAction.Clean => new CleanSolution(), | ||||
|             BuildAction.GenerateProject => new GenerateProject(), | ||||
|             BuildAction.UpdateProjectDependencies => new UpdateProjectDependencies(), | ||||
|             BuildAction.Rebuild => new RebuildSolution(), | ||||
|             BuildAction.Weave => new WeaveProject(), | ||||
|             BuildAction.PackageProject => new PackageProject(), | ||||
|             BuildAction.GenerateSolution => new GenerateSolution(), | ||||
|             BuildAction.BuildWeave => new BuildWeave(), | ||||
|             _ => throw new Exception($"Can't find build action with name \"{Program.BuildToolOptions.Action}\"") | ||||
|         }; | ||||
|  | ||||
|         return buildToolAction.RunAction(); | ||||
|     } | ||||
|  | ||||
|     public abstract bool RunAction(); | ||||
| } | ||||
| @ -0,0 +1,12 @@ | ||||
| using System.Collections.ObjectModel; | ||||
|  | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class BuildUserSolution : BuildSolution | ||||
| { | ||||
|     public BuildUserSolution(Collection<string>? extraArguments = null, BuildConfig buildConfig = BuildConfig.Debug)  | ||||
|         : base(Program.GetScriptFolder(), extraArguments, buildConfig) | ||||
|     { | ||||
|          | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class BuildWeave : BuildToolAction | ||||
| { | ||||
|     public override bool RunAction() | ||||
|     { | ||||
|         BuildSolution buildSolution = new BuildUserSolution(); | ||||
|         WeaveProject weaveProject = new WeaveProject(); | ||||
|         return buildSolution.RunAction() && weaveProject.RunAction() && AddLaunchSettings(); | ||||
|     } | ||||
|      | ||||
|     bool AddLaunchSettings() | ||||
|     { | ||||
|         List<FileInfo> allProjectFiles = Program.GetAllProjectFiles(new DirectoryInfo(Program.GetProjectDirectory())); | ||||
|  | ||||
|         foreach (FileInfo projectFile in allProjectFiles) | ||||
|         { | ||||
|             if (projectFile.Directory!.Name.EndsWith(".Glue")) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             string csProjectPath = Path.Combine(Program.GetScriptFolder(), projectFile.Directory.Name); | ||||
|             string propertiesDirectoryPath = Path.Combine(csProjectPath, "Properties"); | ||||
|             string launchSettingsPath = Path.Combine(propertiesDirectoryPath, "launchSettings.json"); | ||||
|             if (!Directory.Exists(propertiesDirectoryPath)) | ||||
|             { | ||||
|                 Directory.CreateDirectory(propertiesDirectoryPath); | ||||
|             } | ||||
|             if (File.Exists(launchSettingsPath)) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|             Program.CreateOrUpdateLaunchSettings(launchSettingsPath); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class CleanSolution : BuildToolAction | ||||
| { | ||||
|     public override bool RunAction() | ||||
|     { | ||||
|         using BuildToolProcess cleanProcess = new BuildToolProcess(); | ||||
|  | ||||
|         string unrealSharpBinaries = Program.GetOutputPath(); | ||||
|  | ||||
|         if (Directory.Exists(unrealSharpBinaries)) | ||||
|         { | ||||
|             Directory.Delete(unrealSharpBinaries, true); | ||||
|         } | ||||
|  | ||||
|         return cleanProcess.StartBuildToolProcess(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,342 @@ | ||||
| using System.Xml; | ||||
| using Newtonsoft.Json; | ||||
|  | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class GenerateProject : BuildToolAction | ||||
| { | ||||
|     private string _projectPath = string.Empty; | ||||
|     private string _projectFolder = string.Empty; | ||||
|     private string _projectRoot = string.Empty; | ||||
|      | ||||
|     bool ContainsUPluginOrUProjectFile(string folder) | ||||
|     { | ||||
|         string[] files = Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories); | ||||
|          | ||||
|         foreach (string file in files) | ||||
|         { | ||||
|             if (file.EndsWith(".uplugin", StringComparison.OrdinalIgnoreCase) || file.EndsWith(".uproject", StringComparison.OrdinalIgnoreCase)) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     public override bool RunAction() | ||||
|     { | ||||
|         string folder = Program.TryGetArgument("NewProjectFolder"); | ||||
|         _projectRoot = Program.TryGetArgument("ProjectRoot"); | ||||
|          | ||||
|         if (!ContainsUPluginOrUProjectFile(_projectRoot)) | ||||
|         { | ||||
|             throw new InvalidOperationException("Project folder must contain a .uplugin or .uproject file."); | ||||
|         } | ||||
|          | ||||
|         if (folder == _projectRoot) | ||||
|         { | ||||
|             folder = Path.Combine(folder, "Script"); | ||||
|         } | ||||
|  | ||||
|         string projectName = Program.TryGetArgument("NewProjectName"); | ||||
|         string csProjFileName = $"{projectName}.csproj"; | ||||
|  | ||||
|         if (!Directory.Exists(folder)) | ||||
|         { | ||||
|             Directory.CreateDirectory(folder); | ||||
|         } | ||||
|  | ||||
|         _projectFolder = Path.Combine(folder, projectName); | ||||
|         _projectPath = Path.Combine(_projectFolder, csProjFileName); | ||||
|  | ||||
|         string version = Program.GetVersion(); | ||||
|         using BuildToolProcess generateProjectProcess = new BuildToolProcess(); | ||||
|  | ||||
|         // Create a class library. | ||||
|         generateProjectProcess.StartInfo.ArgumentList.Add("new"); | ||||
|         generateProjectProcess.StartInfo.ArgumentList.Add("classlib"); | ||||
|  | ||||
|         // Assign project name to the class library. | ||||
|         generateProjectProcess.StartInfo.ArgumentList.Add("-n"); | ||||
|         generateProjectProcess.StartInfo.ArgumentList.Add(projectName); | ||||
|  | ||||
|         // Set the target framework to the current version. | ||||
|         generateProjectProcess.StartInfo.ArgumentList.Add("-f"); | ||||
|         generateProjectProcess.StartInfo.ArgumentList.Add(version); | ||||
|  | ||||
|         generateProjectProcess.StartInfo.WorkingDirectory = folder; | ||||
|  | ||||
|         if (!generateProjectProcess.StartBuildToolProcess()) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // dotnet new class lib generates a file named Class1, remove it. | ||||
|         string myClassFile = Path.Combine(_projectFolder, "Class1.cs"); | ||||
|         if (File.Exists(myClassFile)) | ||||
|         { | ||||
|             File.Delete(myClassFile); | ||||
|         } | ||||
|  | ||||
|         string slnPath = Program.GetSolutionFile(); | ||||
|         if (!File.Exists(slnPath)) | ||||
|         { | ||||
|             GenerateSolution generateSolution = new GenerateSolution(); | ||||
|             generateSolution.RunAction(); | ||||
|         } | ||||
|  | ||||
|         if (Program.HasArgument("SkipUSharpProjSetup")) | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         AddLaunchSettings(); | ||||
|         ModifyCSProjFile(); | ||||
|  | ||||
|         string relativePath = Path.GetRelativePath(Program.GetScriptFolder(), _projectPath); | ||||
|         AddProjectToSln(relativePath); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     public static void AddProjectToSln(string relativePath) | ||||
|     { | ||||
|         AddProjectToSln([relativePath]); | ||||
|     } | ||||
|  | ||||
|     public static void AddProjectToSln(List<string> relativePaths) | ||||
|     { | ||||
|         foreach (IGrouping<string, string> projects in GroupPathsBySolutionFolder(relativePaths)) | ||||
|         { | ||||
|             using BuildToolProcess addProjectToSln = new BuildToolProcess(); | ||||
|             addProjectToSln.StartInfo.ArgumentList.Add("sln"); | ||||
|             addProjectToSln.StartInfo.ArgumentList.Add("add"); | ||||
|  | ||||
|             foreach (string relativePath in projects) | ||||
|             { | ||||
|                 addProjectToSln.StartInfo.ArgumentList.Add(relativePath); | ||||
|             } | ||||
|  | ||||
|             addProjectToSln.StartInfo.ArgumentList.Add("-s"); | ||||
|             addProjectToSln.StartInfo.ArgumentList.Add(projects.Key); | ||||
|  | ||||
|             addProjectToSln.StartInfo.WorkingDirectory = Program.GetScriptFolder(); | ||||
|             addProjectToSln.StartBuildToolProcess(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static IEnumerable<IGrouping<string, string>> GroupPathsBySolutionFolder(List<string> relativePaths) | ||||
|     { | ||||
|         return relativePaths.GroupBy(GetPathRelativeToProject)!; | ||||
|     } | ||||
|  | ||||
|     private static string GetPathRelativeToProject(string path) | ||||
|     { | ||||
|         var fullPath = Path.GetFullPath(path, Program.GetScriptFolder()); | ||||
|         var relativePath = Path.GetRelativePath(Program.GetProjectDirectory(), fullPath); | ||||
|         var projectDirName = Path.GetDirectoryName(relativePath)!; | ||||
|  | ||||
|         // If we're in the script folder we want these to be in the Script solution folder, otherwise we want these to | ||||
|         // be in the directory for the plugin itself. | ||||
|         var containingDirName = Path.GetDirectoryName(projectDirName)!; | ||||
|         return containingDirName == "Script" ? containingDirName : Path.GetDirectoryName(containingDirName)!; | ||||
|     } | ||||
|  | ||||
|     private void ModifyCSProjFile() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             XmlDocument csprojDocument = new XmlDocument(); | ||||
|             csprojDocument.Load(_projectPath); | ||||
|  | ||||
|             if (csprojDocument.SelectSingleNode("//ItemGroup") is not XmlElement newItemGroup) | ||||
|             { | ||||
|                 newItemGroup = csprojDocument.CreateElement("ItemGroup"); | ||||
|                 csprojDocument.DocumentElement!.AppendChild(newItemGroup); | ||||
|             } | ||||
|  | ||||
|             AppendProperties(csprojDocument); | ||||
|             AppendConstantDefines(csprojDocument); | ||||
|  | ||||
|             AppendReference(csprojDocument, newItemGroup, "UnrealSharp", GetPathToBinaries()); | ||||
|             AppendReference(csprojDocument, newItemGroup, "UnrealSharp.Core", GetPathToBinaries()); | ||||
|  | ||||
|             AppendSourceGeneratorReference(csprojDocument, newItemGroup); | ||||
|  | ||||
|             if (!Program.HasArgument("SkipIncludeProjectGlue")) | ||||
|             { | ||||
|                 AppendGeneratedCode(csprojDocument, newItemGroup); | ||||
|             } | ||||
|  | ||||
|             foreach (string dependency in Program.GetArguments("Dependency")) | ||||
|             { | ||||
|                 AddDependency(csprojDocument, newItemGroup, dependency); | ||||
|             } | ||||
|  | ||||
|             csprojDocument.Save(_projectPath); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             throw new InvalidOperationException($"An error occurred while updating the .csproj file: {ex.Message}", ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void AddProperty(string name, string value, XmlDocument doc, XmlNode propertyGroup) | ||||
|     { | ||||
|         XmlNode? newProperty = propertyGroup.SelectSingleNode(name); | ||||
|  | ||||
|         if (newProperty == null) | ||||
|         { | ||||
|             newProperty = doc.CreateElement(name); | ||||
|             propertyGroup.AppendChild(newProperty); | ||||
|         } | ||||
|  | ||||
|         newProperty.InnerText = value; | ||||
|     } | ||||
|  | ||||
|     private void AppendProperties(XmlDocument doc) | ||||
|     { | ||||
|         XmlNode? propertyGroup = doc.SelectSingleNode("//PropertyGroup"); | ||||
|  | ||||
|         if (propertyGroup == null) | ||||
|         { | ||||
|             propertyGroup = doc.CreateElement("PropertyGroup"); | ||||
|         } | ||||
|  | ||||
|         AddProperty("CopyLocalLockFileAssembliesName", "true", doc, propertyGroup); | ||||
|         AddProperty("AllowUnsafeBlocks", "true", doc, propertyGroup); | ||||
|         AddProperty("EnableDynamicLoading", "true", doc, propertyGroup); | ||||
|     } | ||||
|      | ||||
|     private void AddConstDefine(string value, XmlDocument doc, XmlNode propertyGroup, string? condition = null) | ||||
|     { | ||||
|         var newProperty = doc.CreateElement("DefineConstants"); | ||||
|         propertyGroup.AppendChild(newProperty); | ||||
|         newProperty.InnerText = value; | ||||
|         if (condition is not null) | ||||
|         { | ||||
|             newProperty.SetAttribute("Condition", condition); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private void AppendConstantDefines(XmlDocument doc) | ||||
|     { | ||||
|         var propertyGroup = doc.CreateElement("PropertyGroup"); | ||||
|  | ||||
|         AddConstDefine("WITH_EDITOR", doc, propertyGroup); | ||||
|         AddConstDefine("$(DefineConstants.Replace('WITH_EDITOR;', '').Replace('WITH_EDITOR', ''))", doc, propertyGroup,  | ||||
|             "'$(DisableWithEditor)' == 'true'"); | ||||
|         AddConstDefine("$(DefineConstants);$(DefineAdditionalConstants)", doc, propertyGroup,  | ||||
|             "'$(DefineAdditionalConstants)' != ''"); | ||||
|     } | ||||
|  | ||||
|     private string GetPathToBinaries() | ||||
|     { | ||||
|         string directoryPath = Path.GetDirectoryName(_projectPath)!; | ||||
|         string unrealSharpPath = GetRelativePathToUnrealSharp(directoryPath); | ||||
|         return Path.Combine(unrealSharpPath, "Binaries", "Managed"); | ||||
|     } | ||||
|  | ||||
|     private void AppendReference(XmlDocument doc, XmlElement itemGroup, string referenceName, string binPath) | ||||
|     { | ||||
|         XmlElement referenceElement = doc.CreateElement("Reference"); | ||||
|         referenceElement.SetAttribute("Include", referenceName); | ||||
|  | ||||
|         XmlElement hintPath = doc.CreateElement("HintPath"); | ||||
|         hintPath.InnerText = Path.Combine(binPath, Program.GetVersion(), referenceName + ".dll"); | ||||
|         referenceElement.AppendChild(hintPath); | ||||
|         itemGroup.AppendChild(referenceElement); | ||||
|     } | ||||
|  | ||||
|     private void AppendSourceGeneratorReference(XmlDocument doc, XmlElement itemGroup) | ||||
|     { | ||||
|         string sourceGeneratorPath = Path.Combine(GetPathToBinaries(), "UnrealSharp.SourceGenerators.dll"); | ||||
|         XmlElement sourceGeneratorReference = doc.CreateElement("Analyzer"); | ||||
|         sourceGeneratorReference.SetAttribute("Include", sourceGeneratorPath); | ||||
|         itemGroup.AppendChild(sourceGeneratorReference); | ||||
|     } | ||||
|  | ||||
|     private void AppendPackageReference(XmlDocument doc, XmlElement itemGroup, string packageName, string packageVersion) | ||||
|     { | ||||
|         XmlElement packageReference = doc.CreateElement("PackageReference"); | ||||
|         packageReference.SetAttribute("Include", packageName); | ||||
|         packageReference.SetAttribute("Version", packageVersion); | ||||
|         itemGroup.AppendChild(packageReference); | ||||
|     } | ||||
|  | ||||
|     private void AppendGeneratedCode(XmlDocument doc, XmlElement itemGroup) | ||||
|     { | ||||
|         string providedGlueName = Program.TryGetArgument("GlueProjectName"); | ||||
|         string scriptFolder = string.IsNullOrEmpty(_projectRoot) ? Program.GetScriptFolder() : Path.Combine(_projectRoot, "Script"); | ||||
|         string generatedGluePath = Path.Combine(scriptFolder, providedGlueName, $"{providedGlueName}.csproj"); | ||||
|         AddDependency(doc, itemGroup, generatedGluePath); | ||||
|     } | ||||
|  | ||||
|     private void AddDependency(XmlDocument doc, XmlElement itemGroup, string dependency) | ||||
|     { | ||||
|         string relativePath = GetRelativePath(_projectFolder, dependency); | ||||
|  | ||||
|         XmlElement generatedCode = doc.CreateElement("ProjectReference"); | ||||
|         generatedCode.SetAttribute("Include", relativePath); | ||||
|         itemGroup.AppendChild(generatedCode); | ||||
|     } | ||||
|  | ||||
|     private string GetRelativePathToUnrealSharp(string basePath) | ||||
|     { | ||||
|         string targetPath = Path.Combine(basePath, Program.BuildToolOptions.PluginDirectory); | ||||
|         return GetRelativePath(basePath, targetPath); | ||||
|     } | ||||
|  | ||||
|     public static string GetRelativePath(string basePath, string targetPath) | ||||
|     { | ||||
|         Uri baseUri = new Uri(basePath.EndsWith(Path.DirectorySeparatorChar.ToString()) | ||||
|                 ? basePath | ||||
|                 : basePath + Path.DirectorySeparatorChar); | ||||
|         Uri targetUri = new Uri(targetPath); | ||||
|         Uri relativeUri = baseUri.MakeRelativeUri(targetUri); | ||||
|         return OperatingSystem.IsWindows() ? Uri.UnescapeDataString(relativeUri.ToString()).Replace('/', '\\') : Uri.UnescapeDataString(relativeUri.ToString()); | ||||
|     } | ||||
|  | ||||
|     void AddLaunchSettings() | ||||
|     { | ||||
|         string csProjectPath = Path.Combine(Program.GetScriptFolder(), _projectFolder); | ||||
|         string propertiesDirectoryPath = Path.Combine(csProjectPath, "Properties"); | ||||
|         string launchSettingsPath = Path.Combine(propertiesDirectoryPath, "launchSettings.json"); | ||||
|  | ||||
|         if (!Directory.Exists(propertiesDirectoryPath)) | ||||
|         { | ||||
|             Directory.CreateDirectory(propertiesDirectoryPath); | ||||
|         } | ||||
|  | ||||
|         if (File.Exists(launchSettingsPath)) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         Program.CreateOrUpdateLaunchSettings(launchSettingsPath); | ||||
|     } | ||||
| } | ||||
|  | ||||
| public class Root | ||||
| { | ||||
|     [JsonProperty("profiles")] | ||||
|     public Profiles Profiles { get; set; } = new Profiles(); | ||||
| } | ||||
| public class Profiles | ||||
| { | ||||
|     [JsonProperty("UnrealSharp")] | ||||
|     public Profile ProfileName { get; set; } = new Profile(); | ||||
| } | ||||
|  | ||||
| public class Profile | ||||
| { | ||||
|     [JsonProperty("commandName")] | ||||
|     public string CommandName { get; set; } = string.Empty; | ||||
|  | ||||
|     [JsonProperty("executablePath")] | ||||
|     public string ExecutablePath { get; set; } = string.Empty; | ||||
|  | ||||
|     [JsonProperty("commandLineArgs")] | ||||
|     public string CommandLineArgs { get; set; } = string.Empty; | ||||
| } | ||||
| @ -0,0 +1,47 @@ | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class GenerateSolution : BuildToolAction | ||||
| { | ||||
|     public override bool RunAction() | ||||
|     { | ||||
|         using BuildToolProcess generateSln = new BuildToolProcess(); | ||||
|  | ||||
|         // Create a solution. | ||||
|         generateSln.StartInfo.ArgumentList.Add("new"); | ||||
|         generateSln.StartInfo.ArgumentList.Add("sln"); | ||||
|  | ||||
|         // Assign project name to the solution. | ||||
|         generateSln.StartInfo.ArgumentList.Add("-n"); | ||||
|         generateSln.StartInfo.ArgumentList.Add(Program.GetProjectNameAsManaged()); | ||||
|         generateSln.StartInfo.WorkingDirectory = Program.GetScriptFolder(); | ||||
|  | ||||
|         // Force the creation of the solution. | ||||
|         generateSln.StartInfo.ArgumentList.Add("--force"); | ||||
|         generateSln.StartBuildToolProcess(); | ||||
|  | ||||
|         List<string> existingProjectsList = GetExistingProjects() | ||||
|                 .Select(x => Path.GetRelativePath(Program.GetScriptFolder(), x)) | ||||
|                 .ToList(); | ||||
|  | ||||
|         GenerateProject.AddProjectToSln(existingProjectsList); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     private static IEnumerable<string> GetExistingProjects() | ||||
|     { | ||||
|         var scriptsDirectory = new DirectoryInfo(Program.GetScriptFolder()); | ||||
|         var pluginsDirectory = new DirectoryInfo(Program.GetPluginsFolder()); | ||||
|         return FindCSharpProjects(scriptsDirectory) | ||||
|                 .Concat(pluginsDirectory.EnumerateFiles("*.uplugin", SearchOption.AllDirectories) | ||||
|                         .Select(x => x.Directory) | ||||
|                         .SelectMany(x => x!.EnumerateDirectories("Script")) | ||||
|                         .SelectMany(FindCSharpProjects)) | ||||
|                 .Select(x => x.FullName); | ||||
|     } | ||||
|  | ||||
|     private static IEnumerable<FileInfo> FindCSharpProjects(DirectoryInfo directoryInfo) | ||||
|     { | ||||
|         IEnumerable<FileInfo> files = directoryInfo.EnumerateFiles("*.csproj", SearchOption.AllDirectories); | ||||
|         return files; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,42 @@ | ||||
| using System.Collections.ObjectModel; | ||||
|  | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class PackageProject : BuildToolAction | ||||
| { | ||||
|     public override bool RunAction() | ||||
|     { | ||||
|         string archiveDirectoryPath = Program.TryGetArgument("ArchiveDirectory"); | ||||
|          | ||||
|         if (string.IsNullOrEmpty(archiveDirectoryPath)) | ||||
|         { | ||||
|             throw new Exception("ArchiveDirectory argument is required for the Publish action."); | ||||
|         } | ||||
|  | ||||
|         string rootProjectPath = Path.Combine(archiveDirectoryPath, Program.BuildToolOptions.ProjectName); | ||||
|         string binariesPath = Program.GetOutputPath(rootProjectPath); | ||||
|         string bindingsPath = Path.Combine(Program.BuildToolOptions.PluginDirectory, "Managed", "UnrealSharp"); | ||||
|         string bindingsOutputPath = Path.Combine(Program.BuildToolOptions.PluginDirectory, "Intermediate", "Build", "Managed"); | ||||
|          | ||||
|         Collection<string> extraArguments = | ||||
|         [ | ||||
|             "--self-contained", | ||||
|             "--runtime", | ||||
|             "win-x64", | ||||
| 			"-p:DisableWithEditor=true", | ||||
|             $"-p:PublishDir=\"{binariesPath}\"", | ||||
|             $"-p:OutputPath=\"{bindingsOutputPath}\"", | ||||
|         ]; | ||||
|  | ||||
|         BuildSolution buildBindings = new BuildSolution(bindingsPath, extraArguments, BuildConfig.Publish); | ||||
|         buildBindings.RunAction(); | ||||
|          | ||||
|         BuildUserSolution buildUserSolution = new BuildUserSolution(null, BuildConfig.Publish); | ||||
|         buildUserSolution.RunAction(); | ||||
|          | ||||
|         WeaveProject weaveProject = new WeaveProject(binariesPath); | ||||
|         weaveProject.RunAction(); | ||||
|          | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,25 @@ | ||||
| using System.Collections.ObjectModel; | ||||
|  | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class RebuildSolution : BuildToolAction | ||||
| { | ||||
|     public override bool RunAction() | ||||
|     { | ||||
|         CleanSolution cleanSolutionProcess = new CleanSolution(); | ||||
|          | ||||
|         if (!cleanSolutionProcess.RunAction()) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         BuildSolution buildSolution = new BuildUserSolution(); | ||||
|          | ||||
|         if (!buildSolution.RunAction()) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,77 @@ | ||||
| using System.Collections.Immutable; | ||||
| using System.Xml; | ||||
|  | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class UpdateProjectDependencies : BuildToolAction | ||||
| { | ||||
|     private string _projectPath = string.Empty; | ||||
|     private string _projectFolder = string.Empty; | ||||
|     private ImmutableList<string> _dependencies = ImmutableList<string>.Empty; | ||||
|     private HashSet<string> _existingDependencies = new HashSet<string>(); | ||||
|  | ||||
|     public override bool RunAction() | ||||
|     { | ||||
|         _projectPath = Program.TryGetArgument("ProjectPath"); | ||||
|         _projectFolder = Directory.GetParent(_projectPath)!.FullName; | ||||
|         _dependencies = Program.GetArguments("Dependency").ToImmutableList(); | ||||
|  | ||||
|         Console.WriteLine($"Project Path: {_projectPath}"); | ||||
|         Console.WriteLine($"Project Folder: {_projectFolder}"); | ||||
|  | ||||
|         UpdateProject(); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     private void UpdateProject() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             XmlDocument csprojDocument = new XmlDocument(); | ||||
|             csprojDocument.Load(_projectPath); | ||||
|  | ||||
|             XmlNodeList itemGroups = csprojDocument.SelectNodes("//ItemGroup")!; | ||||
|  | ||||
|             _existingDependencies = itemGroups | ||||
|                     .OfType<XmlElement>() | ||||
|                     .Where(x => x.Name == "ItemGroup") | ||||
|                     .SelectMany(x => x.ChildNodes.OfType<XmlElement>()) | ||||
|                     .Where(x => x.Name == "ProjectReference") | ||||
|                     .Select(x => x.GetAttribute("Include")) | ||||
|                     .ToHashSet(); | ||||
|  | ||||
|             XmlElement? newItemGroup = itemGroups.OfType<XmlElement>().FirstOrDefault(); | ||||
|              | ||||
|             if (newItemGroup is null) | ||||
|             { | ||||
|                 newItemGroup = csprojDocument.CreateElement("ItemGroup"); | ||||
|                 csprojDocument.DocumentElement!.AppendChild(newItemGroup); | ||||
|             } | ||||
|  | ||||
|             foreach (string dependency in _dependencies) | ||||
|             { | ||||
|                 AddDependency(csprojDocument, newItemGroup, dependency); | ||||
|             } | ||||
|  | ||||
|             csprojDocument.Save(_projectPath); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             throw new InvalidOperationException($"An error occurred while updating the .csproj file: {ex.Message}", ex); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void AddDependency(XmlDocument doc, XmlElement itemGroup, string dependency) | ||||
|     { | ||||
|         string relativePath = GenerateProject.GetRelativePath(_projectFolder, dependency); | ||||
|          | ||||
|         if (_existingDependencies.Contains(relativePath)) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         XmlElement generatedCode = doc.CreateElement("ProjectReference"); | ||||
|         generatedCode.SetAttribute("Include", relativePath); | ||||
|         itemGroup.AppendChild(generatedCode); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,64 @@ | ||||
| namespace UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| public class WeaveProject : BuildToolAction | ||||
| { | ||||
|     readonly string _outputDirectory; | ||||
|  | ||||
|     public WeaveProject(string outputDirectory = "") | ||||
|     { | ||||
|         _outputDirectory = string.IsNullOrEmpty(outputDirectory) ? Program.GetOutputPath() : outputDirectory; | ||||
|     } | ||||
|  | ||||
|     public override bool RunAction() | ||||
|     { | ||||
|         string weaverPath = Program.GetWeaver(); | ||||
|  | ||||
|         if (!File.Exists(weaverPath)) | ||||
|         { | ||||
|             throw new Exception("Couldn't find the weaver"); | ||||
|         } | ||||
|  | ||||
|         DirectoryInfo scriptRootDirInfo = new DirectoryInfo(Program.GetProjectDirectory()); | ||||
|         return Weave(scriptRootDirInfo, weaverPath); | ||||
|     } | ||||
|  | ||||
|     private bool Weave(DirectoryInfo scriptFolder, string weaverPath) | ||||
|     { | ||||
|         Dictionary<string, List<FileInfo>> projectFiles = Program.GetProjectFilesByDirectory(scriptFolder); | ||||
|         List<FileInfo> allProjectFiles = projectFiles.Values.SelectMany(x => x).ToList(); | ||||
|          | ||||
|         if (allProjectFiles.Count == 0) | ||||
|         { | ||||
|             Console.WriteLine("No project files found. Skipping weaving..."); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         using BuildToolProcess weaveProcess = new BuildToolProcess(); | ||||
|         weaveProcess.StartInfo.ArgumentList.Add(weaverPath); | ||||
|         weaveProcess.StartInfo.WorkingDirectory = Directory.GetCurrentDirectory(); | ||||
|  | ||||
|         bool foundValidProject = false; | ||||
|         foreach (FileInfo projectFile in allProjectFiles) | ||||
|         { | ||||
|             weaveProcess.StartInfo.ArgumentList.Add("-p"); | ||||
|             string csProjName = Path.GetFileNameWithoutExtension(projectFile.Name); | ||||
|             string assemblyPath = Path.Combine(projectFile.DirectoryName!, "bin", | ||||
|                 Program.GetBuildConfiguration(), Program.GetVersion(), csProjName + ".dll"); | ||||
|  | ||||
|             weaveProcess.StartInfo.ArgumentList.Add(assemblyPath); | ||||
|             foundValidProject = true; | ||||
|         } | ||||
|  | ||||
|         if (!foundValidProject) | ||||
|         { | ||||
|             Console.WriteLine("No valid project found to weave. Skipping weaving..."); | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // Add path to the output folder for the weaver. | ||||
|         weaveProcess.StartInfo.ArgumentList.Add("-o"); | ||||
|         weaveProcess.StartInfo.ArgumentList.Add($"{Program.FixPath(_outputDirectory)}"); | ||||
|  | ||||
|         return weaveProcess.StartBuildToolProcess(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,83 @@ | ||||
| using System.Reflection; | ||||
| using CommandLine; | ||||
| using CommandLine.Text; | ||||
|  | ||||
| namespace UnrealSharpBuildTool; | ||||
|  | ||||
| public enum BuildAction : int | ||||
| { | ||||
|     Build, | ||||
|     Clean, | ||||
|     GenerateProject, | ||||
|     UpdateProjectDependencies, | ||||
|     Rebuild, | ||||
|     Weave, | ||||
|     PackageProject, | ||||
|     GenerateSolution, | ||||
|     BuildWeave, | ||||
| } | ||||
|  | ||||
| public enum BuildConfig : int | ||||
| { | ||||
|     Debug, | ||||
|     Release, | ||||
|     Publish, | ||||
| } | ||||
|  | ||||
| public class BuildToolOptions | ||||
| { | ||||
|     [Option("Action", Required = true, HelpText = "The action the build tool should process. Possible values: Build, Clean, GenerateProject, Rebuild, Weave, PackageProject, GenerateSolution, BuildWeave.")] | ||||
|     public BuildAction Action { get; set; } | ||||
|  | ||||
|     [Option("DotNetPath", Required = false, HelpText = "The path to the dotnet.exe")] | ||||
|     public string DotNetPath { get; set; } = string.Empty; | ||||
|  | ||||
|     [Option("ProjectDirectory", Required = true, HelpText = "The directory where the .uproject file resides.")] | ||||
|     public string ProjectDirectory { get; set; } = string.Empty; | ||||
|  | ||||
|     [Option("PluginDirectory", Required = false, HelpText = "The UnrealSharp plugin directory.")] | ||||
|     public string PluginDirectory { get; set; } = string.Empty; | ||||
|  | ||||
|     [Option("EngineDirectory", Required = false, HelpText = "The Unreal Engine directory.")] | ||||
|     public string EngineDirectory { get; set; } = string.Empty; | ||||
|  | ||||
|     [Option("ProjectName", Required = true, HelpText = "The name of the Unreal Engine project.")] | ||||
|     public string ProjectName { get; set; } = string.Empty; | ||||
|  | ||||
|     [Option("AdditionalArgs", Required = false, HelpText = "Additional key-value arguments for the build tool.")] | ||||
|     public IEnumerable<string> AdditionalArgs { get; set; } = new List<string>(); | ||||
|  | ||||
|     public string TryGetArgument(string argument) | ||||
|     { | ||||
|         return GetArguments(argument).FirstOrDefault() ?? string.Empty; | ||||
|     } | ||||
|  | ||||
|     public IEnumerable<string> GetArguments(string argument) | ||||
|     { | ||||
|         return AdditionalArgs.Where(arg => arg.StartsWith(argument)) | ||||
|                 .Select(arg => arg[(argument.Length + 1)..]); | ||||
|     } | ||||
|  | ||||
|     public bool HasArgument(string argument) | ||||
|     { | ||||
|         return AdditionalArgs.Any(arg => arg.StartsWith(argument)); | ||||
|     } | ||||
|  | ||||
|     public static void PrintHelp(ParserResult<BuildToolOptions> result) | ||||
|     { | ||||
|         string name = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly()!.Location); | ||||
|         Console.Error.WriteLine($"Usage: {name} [options]"); | ||||
|         Console.Error.WriteLine("Options:"); | ||||
|  | ||||
|         var helpText = HelpText.AutoBuild(result, h => h, e => e); | ||||
|         Console.WriteLine(helpText); | ||||
|     } | ||||
|  | ||||
|     public void NormalizePaths() | ||||
|     { | ||||
|         ProjectDirectory = Path.GetFullPath(ProjectDirectory); | ||||
|         PluginDirectory = Path.GetFullPath(PluginDirectory); | ||||
|         EngineDirectory = Path.GetFullPath(EngineDirectory); | ||||
|         DotNetPath = Path.GetFullPath(DotNetPath); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,74 @@ | ||||
| using System.Collections; | ||||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
|  | ||||
| namespace UnrealSharpBuildTool; | ||||
|  | ||||
| public class BuildToolProcess : Process | ||||
| { | ||||
|     public BuildToolProcess(string? fileName = null) | ||||
|     { | ||||
|         if (fileName == null) | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(Program.BuildToolOptions.DotNetPath)) | ||||
|             { | ||||
|                 fileName = "dotnet"; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 fileName = Program.BuildToolOptions.DotNetPath; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         StartInfo.FileName = fileName; | ||||
|         StartInfo.CreateNoWindow = true; | ||||
|         StartInfo.ErrorDialog = false; | ||||
|         StartInfo.UseShellExecute = false; | ||||
|         StartInfo.RedirectStandardError = true; | ||||
|         StartInfo.RedirectStandardInput = true; | ||||
|         StartInfo.RedirectStandardOutput = true; | ||||
|         EnableRaisingEvents = true; | ||||
|     } | ||||
|      | ||||
|     public bool StartBuildToolProcess() | ||||
|     { | ||||
|         StringBuilder output = new StringBuilder(); | ||||
|         OutputDataReceived += (sender, e) => | ||||
|         { | ||||
|             if (e.Data != null) | ||||
|             { | ||||
|                 output.AppendLine(e.Data); | ||||
|             } | ||||
|         }; | ||||
|              | ||||
|         ErrorDataReceived += (sender, e) => | ||||
|         { | ||||
|             if (e.Data != null) | ||||
|             { | ||||
|                 output.AppendLine(e.Data); | ||||
|             } | ||||
|         }; | ||||
|              | ||||
|         if (!Start()) | ||||
|         { | ||||
|             throw new Exception("Failed to start process"); | ||||
|         } | ||||
|              | ||||
|         BeginErrorReadLine(); | ||||
|         BeginOutputReadLine(); | ||||
|         WaitForExit(); | ||||
|  | ||||
|         if (ExitCode != 0) | ||||
|         { | ||||
|             string errorMessage = output.ToString(); | ||||
|             if (string.IsNullOrEmpty(errorMessage)) | ||||
|             { | ||||
|                 errorMessage = "BuildTool process exited with non-zero exit code, but no output was captured."; | ||||
|             } | ||||
|              | ||||
|             throw new Exception($"BuildTool process failed with exit code {ExitCode}:\n{errorMessage}"); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,258 @@ | ||||
| using System.Xml.Linq; | ||||
| using CommandLine; | ||||
| using Newtonsoft.Json; | ||||
| using UnrealSharpBuildTool.Actions; | ||||
|  | ||||
| namespace UnrealSharpBuildTool; | ||||
|  | ||||
| public static class Program | ||||
| { | ||||
|     public static BuildToolOptions BuildToolOptions = null!; | ||||
|  | ||||
|     public static int Main(string[] args) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             Console.WriteLine(">>> UnrealSharpBuildTool"); | ||||
|             Parser parser = new Parser(with => with.HelpWriter = null); | ||||
|             ParserResult<BuildToolOptions> result = parser.ParseArguments<BuildToolOptions>(args); | ||||
|  | ||||
|             if (result.Tag == ParserResultType.NotParsed) | ||||
|             { | ||||
|                 BuildToolOptions.PrintHelp(result); | ||||
|  | ||||
|                 string errors = string.Empty; | ||||
|                 foreach (Error error in result.Errors) | ||||
|                 { | ||||
|                     if (error is TokenError tokenError) | ||||
|                     { | ||||
|                         errors += $"{tokenError.Tag}: {tokenError.Token} \n"; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 throw new Exception($"Invalid arguments. Errors: {errors}"); | ||||
|             } | ||||
|  | ||||
|             BuildToolOptions = result.Value; | ||||
|              | ||||
|             if (!BuildToolAction.InitializeAction()) | ||||
|             { | ||||
|                 return 1; | ||||
|             } | ||||
|  | ||||
|             Console.WriteLine($"UnrealSharpBuildTool executed {BuildToolOptions.Action.ToString()} action successfully."); | ||||
|         } | ||||
|         catch (Exception exception) | ||||
|         { | ||||
|             Console.WriteLine("An error occurred: " + exception.Message + Environment.NewLine + exception.StackTrace); | ||||
|             return 1; | ||||
|         } | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     public static string TryGetArgument(string argument) | ||||
|     { | ||||
|         return BuildToolOptions.TryGetArgument(argument); | ||||
|     } | ||||
|  | ||||
|     public static IEnumerable<string> GetArguments(string argument) | ||||
|     { | ||||
|         return BuildToolOptions.GetArguments(argument); | ||||
|     } | ||||
|  | ||||
|     public static bool HasArgument(string argument) | ||||
|     { | ||||
|         return BuildToolOptions.HasArgument(argument); | ||||
|     } | ||||
|  | ||||
|     public static string GetSolutionFile() | ||||
|     { | ||||
|         return Path.Combine(GetScriptFolder(), BuildToolOptions.ProjectName + ".sln"); | ||||
|     } | ||||
|  | ||||
|     public static string GetUProjectFilePath() | ||||
|     { | ||||
|         return Path.Combine(BuildToolOptions.ProjectDirectory, BuildToolOptions.ProjectName + ".uproject"); | ||||
|     } | ||||
|  | ||||
|     public static string GetBuildConfiguration() | ||||
|     { | ||||
|         string buildConfig = TryGetArgument("BuildConfig"); | ||||
|         if (string.IsNullOrEmpty(buildConfig)) | ||||
|         { | ||||
|             buildConfig = "Debug"; | ||||
|         } | ||||
|         return buildConfig; | ||||
|     } | ||||
|  | ||||
|     public static BuildConfig GetBuildConfig() | ||||
|     { | ||||
|         string buildConfig = GetBuildConfiguration(); | ||||
|         Enum.TryParse(buildConfig, out BuildConfig config); | ||||
|         return config; | ||||
|     } | ||||
|  | ||||
|     public static string GetBuildConfiguration(BuildConfig buildConfig) | ||||
|     { | ||||
|         return buildConfig switch | ||||
|         { | ||||
|             BuildConfig.Debug => "Debug", | ||||
|             BuildConfig.Release => "Release", | ||||
|             BuildConfig.Publish => "Release", | ||||
|             _ => "Release" | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public static string GetScriptFolder() | ||||
|     { | ||||
|         return Path.Combine(BuildToolOptions.ProjectDirectory, "Script"); | ||||
|     } | ||||
|  | ||||
|     public static string GetPluginsFolder() | ||||
|     { | ||||
|         return Path.Combine(BuildToolOptions.ProjectDirectory, "Plugins"); | ||||
|     } | ||||
|  | ||||
|     public static string GetProjectDirectory() | ||||
|     { | ||||
|         return BuildToolOptions.ProjectDirectory; | ||||
|     } | ||||
|  | ||||
|     public static string FixPath(string path) | ||||
|     { | ||||
|         if (OperatingSystem.IsWindows()) | ||||
|         { | ||||
|             return path.Replace('/', '\\'); | ||||
|         } | ||||
|  | ||||
|         return path; | ||||
|     } | ||||
|  | ||||
|     public static string GetProjectNameAsManaged() | ||||
|     { | ||||
|         return "Managed" + BuildToolOptions.ProjectName; | ||||
|     } | ||||
|  | ||||
|     public static string GetOutputPath(string rootDir = "") | ||||
|     { | ||||
|         if (string.IsNullOrEmpty(rootDir)) | ||||
|         { | ||||
|             rootDir = BuildToolOptions.ProjectDirectory; | ||||
|         } | ||||
|  | ||||
|         return Path.Combine(rootDir, "Binaries", "Managed"); | ||||
|     } | ||||
|  | ||||
|     public static string GetWeaver() | ||||
|     { | ||||
|         return Path.Combine(GetManagedBinariesDirectory(), "UnrealSharpWeaver.dll"); | ||||
|     } | ||||
|  | ||||
|     public static string GetManagedBinariesDirectory() | ||||
|     { | ||||
|         return Path.Combine(BuildToolOptions.PluginDirectory, "Binaries", "Managed"); | ||||
|     } | ||||
|  | ||||
|     public static string GetVersion() | ||||
|     { | ||||
|         Version currentVersion = Environment.Version; | ||||
|         string currentVersionStr = $"{currentVersion.Major}.{currentVersion.Minor}"; | ||||
|         return "net" + currentVersionStr; | ||||
|     } | ||||
|  | ||||
|     public static void CreateOrUpdateLaunchSettings(string launchSettingsPath) | ||||
|     { | ||||
|         Root root = new Root(); | ||||
|  | ||||
|         string executablePath = string.Empty; | ||||
|         if (OperatingSystem.IsWindows()) | ||||
|         { | ||||
|             executablePath = Path.Combine(BuildToolOptions.EngineDirectory, "Binaries", "Win64", "UnrealEditor.exe"); | ||||
|         } | ||||
|         else if (OperatingSystem.IsMacOS()) | ||||
|         { | ||||
|             executablePath = Path.Combine(BuildToolOptions.EngineDirectory, "Binaries", "Mac", "UnrealEditor"); | ||||
|         } | ||||
|         string commandLineArgs = FixPath(GetUProjectFilePath()); | ||||
|  | ||||
|         // Create a new profile if it doesn't exist | ||||
|         if (root.Profiles == null) | ||||
|         { | ||||
|             root.Profiles = new Profiles(); | ||||
|         } | ||||
|  | ||||
|         root.Profiles.ProfileName = new Profile | ||||
|         { | ||||
|             CommandName = "Executable", | ||||
|             ExecutablePath = executablePath, | ||||
|             CommandLineArgs = $"\"{commandLineArgs}\"", | ||||
|         }; | ||||
|  | ||||
|         string newJsonString = JsonConvert.SerializeObject(root, Formatting.Indented); | ||||
|         StreamWriter writer = File.CreateText(launchSettingsPath); | ||||
|         writer.Write(newJsonString); | ||||
|         writer.Close(); | ||||
|     } | ||||
|  | ||||
|     public static List<FileInfo> GetAllProjectFiles(DirectoryInfo folder) | ||||
|     { | ||||
|         return folder.GetDirectories("Script") | ||||
|                 .SelectMany(GetProjectsInDirectory) | ||||
|                 .Concat(folder.GetDirectories("Plugins") | ||||
|                         .SelectMany(x => x.EnumerateFiles("*.uplugin", SearchOption.AllDirectories)) | ||||
|                         .Select(x => x.Directory) | ||||
|                         .Select(x => x!.GetDirectories("Script").FirstOrDefault()) | ||||
|                         .Where(x => x is not null) | ||||
|                         .SelectMany(GetProjectsInDirectory!)) | ||||
|                 .ToList(); | ||||
|     } | ||||
|  | ||||
|     public static Dictionary<string, List<FileInfo>> GetProjectFilesByDirectory(DirectoryInfo folder) | ||||
|     { | ||||
|         Dictionary<string, List<FileInfo>> result = new Dictionary<string, List<FileInfo>>(); | ||||
|         DirectoryInfo? scriptsFolder = folder.GetDirectories("Script").FirstOrDefault(); | ||||
|          | ||||
|         if (scriptsFolder is not null) | ||||
|         { | ||||
|             result.Add(GetOutputPathForDirectory(scriptsFolder), GetProjectsInDirectory(scriptsFolder).ToList()); | ||||
|         } | ||||
|  | ||||
|         foreach (DirectoryInfo? pluginFolder in folder.GetDirectories("Plugins") | ||||
|                          .SelectMany(x => x.EnumerateFiles("*.uplugin", SearchOption.AllDirectories)) | ||||
|                          .Select(x => x.Directory) | ||||
|                          .Select(x => x!.GetDirectories("Script").FirstOrDefault()) | ||||
|                          .Where(x => x is not null)) | ||||
|         { | ||||
|             result.Add(GetOutputPathForDirectory(pluginFolder!), GetProjectsInDirectory(pluginFolder!).ToList()); | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private static string GetOutputPathForDirectory(DirectoryInfo directory) | ||||
|     { | ||||
|         return Path.Combine(directory.Parent!.FullName, "Binaries", "Managed"); | ||||
|     } | ||||
|  | ||||
|     private static IEnumerable<FileInfo> GetProjectsInDirectory(DirectoryInfo folder) | ||||
|     { | ||||
|         var csprojFiles = folder.EnumerateFiles("*.csproj", SearchOption.AllDirectories); | ||||
|         var fsprojFiles = folder.EnumerateFiles("*.fsproj", SearchOption.AllDirectories); | ||||
|         return csprojFiles.Concat(fsprojFiles).Where(IsWeavableProject); | ||||
|     } | ||||
|  | ||||
|     private static bool IsWeavableProject(FileInfo projectFile) | ||||
|     { | ||||
|         // We need to be able to filter out certain non-production projects. | ||||
|         // The main target of this is source generators and analyzers which users | ||||
|         // may want to leverage as part of their solution and can't be weaved because | ||||
|         // they have to use netstandard2.0. | ||||
|         XDocument doc = XDocument.Load(projectFile.FullName); | ||||
|         return !doc.Descendants() | ||||
|             .Where(element => element.Name.LocalName == "PropertyGroup") | ||||
|             .SelectMany(element => element.Elements()) | ||||
|             .Any(element => element.Name.LocalName == "ExcludeFromWeaver" && | ||||
|                             element.Value.Equals("true", StringComparison.OrdinalIgnoreCase)); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,15 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|     <PropertyGroup> | ||||
|         <OutputType>Exe</OutputType> | ||||
|         <TargetFramework>net9.0</TargetFramework> | ||||
|         <ImplicitUsings>enable</ImplicitUsings> | ||||
|         <Nullable>enable</Nullable> | ||||
|     </PropertyGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|         <PackageReference Include="CommandLineParser" /> | ||||
|         <PackageReference Include="Newtonsoft.Json" /> | ||||
|     </ItemGroup> | ||||
|      | ||||
| </Project> | ||||
| @ -0,0 +1,39 @@ | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| [Flags] | ||||
| public enum ClassFlags : ulong | ||||
| { | ||||
|     None				  = 0x00000000u, | ||||
|     Abstract            = 0x00000001u, | ||||
|     DefaultConfig		  = 0x00000002u, | ||||
|     Config			  = 0x00000004u, | ||||
|     Transient			  = 0x00000008u, | ||||
|     Optional            = 0x00000010u, | ||||
|     MatchedSerializers  = 0x00000020u, | ||||
|     ProjectUserConfig	  = 0x00000040u, | ||||
|     Native			  = 0x00000080u, | ||||
|     NoExport = 0x00000100u, | ||||
|     NotPlaceable        = 0x00000200u, | ||||
|     PerObjectConfig     = 0x00000400u, | ||||
|     ReplicationDataIsSetUp = 0x00000800u, | ||||
|     EditInlineNew		  = 0x00001000u, | ||||
|     CollapseCategories  = 0x00002000u, | ||||
|     Interface           = 0x00004000u, | ||||
|     CustomConstructor = 0x00008000u, | ||||
|     Const			      = 0x00010000u, | ||||
|     NeedsDeferredDependencyLoading = 0x00020000u, | ||||
|     CompiledFromBlueprint  = 0x00040000u, | ||||
|     MinimalAPI	      = 0x00080000u, | ||||
|     RequiredAPI	      = 0x00100000u, | ||||
|     DefaultToInstanced  = 0x00200000u, | ||||
|     TokenStreamAssembled  = 0x00400000u, | ||||
|     HasInstancedReference= 0x00800000u, | ||||
|     Hidden			  = 0x01000000u, | ||||
|     Deprecated		  = 0x02000000u, | ||||
|     HideDropDown		  = 0x04000000u, | ||||
|     GlobalUserConfig	  = 0x08000000u, | ||||
|     Intrinsic			  = 0x10000000u, | ||||
|     Constructed		  = 0x20000000u, | ||||
|     ConfigDoNotCheckDefaults = 0x40000000u, | ||||
|     NewerVersionExists  = 0x80000000u, | ||||
| } | ||||
| @ -0,0 +1,131 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
|  | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| [Serializable] | ||||
| class WeaverProcessError : Exception | ||||
| { | ||||
|     public string File { get; private set; } = string.Empty; | ||||
|     public int Line { get; private set; } | ||||
|  | ||||
|     public WeaverProcessError(string message) : base(message)  | ||||
|     { | ||||
|         Line = -1; | ||||
|     } | ||||
|  | ||||
|     public WeaverProcessError(string message, string file, int line) : base(message)  | ||||
|     { | ||||
|         File = file; | ||||
|         Line = line; | ||||
|     } | ||||
|  | ||||
|     public WeaverProcessError (string message, SequencePoint? point) : base(message) | ||||
|     { | ||||
|         if (point != null) | ||||
|         { | ||||
|             File = point.Document.Url; | ||||
|             Line = point.StartLine; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             Line = -1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public WeaverProcessError(string message, Exception? innerException) : base(message,innerException)  | ||||
|     { | ||||
|         Line = -1; | ||||
|     } | ||||
|  | ||||
|     public WeaverProcessError(string message, Exception? innerException, SequencePoint? point) : base(message, innerException) | ||||
|     { | ||||
|         if (point != null) | ||||
|         { | ||||
|             File = point.Document.Url; | ||||
|             Line = point.StartLine; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             Line = -1; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| static class ErrorEmitter | ||||
| { | ||||
|     public static void Error (WeaverProcessError error) | ||||
|     { | ||||
|         Error(error.GetType().Name, error.File, error.Line, error.Message); | ||||
|     } | ||||
|  | ||||
|     public static void Error(string code, string file, int line, string message) | ||||
|     { | ||||
|         if (!string.IsNullOrEmpty(file)) | ||||
|         { | ||||
|             Console.Error.Write(file); | ||||
|             if (line != -1) | ||||
|             { | ||||
|                 Console.Error.Write("({0})",line); | ||||
|             } | ||||
|  | ||||
|             Console.Error.Write(" : "); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             Console.Error.Write("UnrealSharpWeaver: "); | ||||
|         } | ||||
|  | ||||
|         Console.Error.WriteLine("error {0}: {1}",code,message); | ||||
|     } | ||||
|  | ||||
|     private static SequencePoint? ExtractFirstSequencePoint (MethodDefinition method) | ||||
|     { | ||||
|         return method?.DebugInformation?.SequencePoints.FirstOrDefault (); | ||||
|     } | ||||
|  | ||||
|     public static SequencePoint? GetSequencePointFromMemberDefinition(IMemberDefinition member) | ||||
|     { | ||||
|         if (member is PropertyDefinition propertyDefinition) | ||||
|         { | ||||
|             SequencePoint? point = ExtractFirstSequencePoint(propertyDefinition.GetMethod); | ||||
|             if (point != null) | ||||
|             { | ||||
|                 return point; | ||||
|             } | ||||
|              | ||||
|             point = ExtractFirstSequencePoint(propertyDefinition.SetMethod); | ||||
|             if (point != null) | ||||
|             { | ||||
|                 return point; | ||||
|             } | ||||
|              | ||||
|             return GetSequencePointFromMemberDefinition(member.DeclaringType); | ||||
|         } | ||||
|  | ||||
|         if (member is MethodDefinition definition) | ||||
|         { | ||||
|             SequencePoint? point = ExtractFirstSequencePoint(definition); | ||||
|             if (point != null) | ||||
|             { | ||||
|                 return point; | ||||
|             } | ||||
|              | ||||
|             return GetSequencePointFromMemberDefinition(definition.DeclaringType); | ||||
|         } | ||||
|          | ||||
|         if (member is TypeDefinition type) | ||||
|         { | ||||
|             foreach(MethodDefinition method in type.Methods) | ||||
|             { | ||||
|                 SequencePoint? point = ExtractFirstSequencePoint(method); | ||||
|                 if (point != null) | ||||
|                 { | ||||
|                     return point; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,69 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
|  | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| [Serializable] | ||||
| class InvalidConstructorException(MethodDefinition constructor, string message) : WeaverProcessError(message, ErrorEmitter.GetSequencePointFromMemberDefinition(constructor)); | ||||
|  | ||||
| [Serializable] | ||||
| class ConstructorNotFoundException(TypeDefinition type, string message) : WeaverProcessError(message, ErrorEmitter.GetSequencePointFromMemberDefinition(type)); | ||||
|  | ||||
| [Serializable] | ||||
| class InvalidUnrealClassException(string propertyName, SequencePoint? sequencePoint, string message) : WeaverProcessError($"Class '{propertyName}' is invalid as a unreal class: {message}", | ||||
|         sequencePoint) | ||||
| { | ||||
|     public InvalidUnrealClassException(TypeDefinition klass, string message) | ||||
|         : this(klass.FullName, ErrorEmitter.GetSequencePointFromMemberDefinition(klass), message) | ||||
|     { | ||||
|     } | ||||
| } | ||||
|  | ||||
| [Serializable] | ||||
| class InvalidUnrealStructException(TypeDefinition structType, string message)  | ||||
|     : WeaverProcessError($"Struct '{structType.FullName}' is invalid as Unreal struct: {message}", ErrorEmitter.GetSequencePointFromMemberDefinition(structType)); | ||||
|  | ||||
| [Serializable] | ||||
| class InvalidUnrealEnumException(TypeDefinition enumType, string message) : WeaverProcessError( | ||||
|     $"Enum '{enumType.FullName}' is invalid as Unreal enum: {message}", | ||||
|     ErrorEmitter.GetSequencePointFromMemberDefinition(enumType)); | ||||
|  | ||||
| [Serializable] | ||||
| class InvalidPropertyException(string propertyName, SequencePoint? sequencePoint, string message) | ||||
|     : WeaverProcessError($"Property '{propertyName}' is invalid for unreal property: {message}", | ||||
|         sequencePoint) | ||||
| { | ||||
|     public InvalidPropertyException(IMemberDefinition property, string message) | ||||
|         : this(property.FullName, ErrorEmitter.GetSequencePointFromMemberDefinition(property), message) | ||||
|     { | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| [Serializable] | ||||
| class InvalidUnrealFunctionException(MethodDefinition method, string message) | ||||
|     : WeaverProcessError($"Method '{method.Name}' is invalid for unreal function: {message}", null, | ||||
|         ErrorEmitter.GetSequencePointFromMemberDefinition(method)); | ||||
|  | ||||
| [Serializable] | ||||
| class NotDerivableClassException(TypeDefinition klass, TypeDefinition superKlass) : WeaverProcessError( | ||||
|     $"Class '{klass.FullName}' is invalid because '{superKlass.FullName}' may not be derived from in managed code.", | ||||
|     ErrorEmitter.GetSequencePointFromMemberDefinition(klass)); | ||||
|  | ||||
| [Serializable] | ||||
| class UnableToFixPropertyBackingReferenceException : WeaverProcessError | ||||
| { | ||||
|     public UnableToFixPropertyBackingReferenceException(MethodDefinition constructor, PropertyDefinition property, OpCode opCode) | ||||
|         : base($"The type {constructor.DeclaringType.FullName}'s constructor references the property {property.Name} using an unsupported IL pattern", ErrorEmitter.GetSequencePointFromMemberDefinition(constructor)) | ||||
|     { | ||||
|     } | ||||
| } | ||||
|  | ||||
| [Serializable] | ||||
| class UnsupportedPropertyInitializerException(PropertyDefinition property) : WeaverProcessError($"Property initializer for UProperty {property.Name} is not a supported constant type", ErrorEmitter.GetSequencePointFromMemberDefinition(property)); | ||||
|  | ||||
| [Serializable] | ||||
| class RewriteException(TypeDefinition type, string message) : WeaverProcessError($"{type.FullName}: {message}", ErrorEmitter.GetSequencePointFromMemberDefinition(type)); | ||||
|  | ||||
| [Serializable] | ||||
| class InvalidAttributeException(TypeDefinition attributeType, SequencePoint? point, string message) : WeaverProcessError($"Invalid attribute class {attributeType.Name}: {message}", point); | ||||
| @ -0,0 +1,36 @@ | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| [Flags] | ||||
| public enum EFunctionFlags : ulong | ||||
| { | ||||
|     None = 0x00000000, | ||||
|     Final = 0x00000001, | ||||
|     RequiredAPI = 0x00000002, | ||||
|     BlueprintAuthorityOnly = 0x00000004, | ||||
|     BlueprintCosmetic = 0x00000008, | ||||
|     Net = 0x00000040, | ||||
|     NetReliable = 0x00000080, | ||||
|     NetRequest = 0x00000100, | ||||
|     Exec = 0x00000200, | ||||
|     Native = 0x00000400, | ||||
|     Event = 0x00000800, | ||||
|     NetResponse = 0x00001000, | ||||
|     Static = 0x00002000, | ||||
|     NetMulticast = 0x00004000, | ||||
|     MulticastDelegate = 0x00010000, | ||||
|     Public = 0x00020000, | ||||
|     Private = 0x00040000, | ||||
|     Protected = 0x00080000, | ||||
|     Delegate = 0x00100000, | ||||
|     NetServer = 0x00200000, | ||||
|     HasOutParms = 0x00400000, | ||||
|     HasDefaults = 0x00800000, | ||||
|     NetClient = 0x01000000, | ||||
|     DLLImport = 0x02000000, | ||||
|     BlueprintCallable = 0x04000000, | ||||
|     BlueprintNativeEvent = 0x08000000, | ||||
|     BlueprintPure = 0x10000000, | ||||
|     EditorOnly = 0x20000000, | ||||
|     Const = 0x40000000, | ||||
|     NetValidate = 0x80000000, | ||||
| }; | ||||
| @ -0,0 +1,21 @@ | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| public enum LifetimeCondition | ||||
| { | ||||
| 	None = 0, | ||||
| 	InitialOnly = 1, | ||||
| 	OwnerOnly = 2,	 | ||||
| 	SkipOwner = 3,	 | ||||
| 	SimulatedOnly = 4,	 | ||||
| 	AutonomousOnly = 5, | ||||
| 	SimulatedOrPhysics = 6, | ||||
| 	InitialOrOwner = 7, | ||||
| 	Custom = 8,		 | ||||
| 	ReplayOrOwner = 9, | ||||
| 	ReplayOnly = 10,		 | ||||
| 	SimulatedOnlyNoReplay = 11,	 | ||||
| 	SimulatedOrPhysicsNoReplay = 12, | ||||
| 	SkipReplay = 13, | ||||
| 	Dynamic = 14,				 | ||||
| 	Never = 15, | ||||
| }; | ||||
| @ -0,0 +1,22 @@ | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public class ApiMetaData | ||||
| { | ||||
|     public ApiMetaData(string assemblyName) | ||||
|     { | ||||
|         AssemblyName = assemblyName; | ||||
|         ClassMetaData = new List<ClassMetaData>(); | ||||
|         StructMetaData = new List<StructMetaData>(); | ||||
|         EnumMetaData = new List<EnumMetaData>(); | ||||
|         InterfacesMetaData = new List<InterfaceMetaData>(); | ||||
|         DelegateMetaData = new List<DelegateMetaData>(); | ||||
|     } | ||||
|  | ||||
|     public List<ClassMetaData> ClassMetaData { get; set; }   | ||||
|     public List<StructMetaData> StructMetaData { get; set; } | ||||
|     public List<EnumMetaData> EnumMetaData { get; set; } | ||||
|     public List<InterfaceMetaData> InterfacesMetaData { get; set; } | ||||
|     public List<DelegateMetaData> DelegateMetaData { get; set; } | ||||
|      | ||||
|     public string AssemblyName { get; set; } | ||||
| } | ||||
| @ -0,0 +1,282 @@ | ||||
| using System.Globalization; | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Collections.Generic; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public class BaseMetaData | ||||
| { | ||||
|     public string Name { get; set; } | ||||
|     public Dictionary<string, string> MetaData { get; set; } | ||||
|  | ||||
|     // Non-serialized for JSON | ||||
|     public readonly string AttributeName; | ||||
|     public readonly IMemberDefinition MemberDefinition; | ||||
|     public readonly CustomAttribute? BaseAttribute; | ||||
|     public readonly string SourceName; | ||||
|     // End non-serialized | ||||
|  | ||||
|     public BaseMetaData(MemberReference member, string attributeName) | ||||
|     { | ||||
|         MemberDefinition = member.Resolve(); | ||||
|         SourceName = MemberDefinition.Name; | ||||
|         Name = MemberDefinition.GetEngineName(); | ||||
|  | ||||
|         AttributeName = attributeName; | ||||
|         MetaData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); | ||||
|         BaseAttribute = MemberDefinition.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, AttributeName)!; | ||||
|  | ||||
|         AddMetaData();          // Add any [UMetaData("key", "value")] attributes (general metadata attribute to allow support of any engine tag) | ||||
|         AddMetaTagsNamespace(); // Add all named attributes in the UnrealSharp.Attributes.MetaTags namespace | ||||
|         AddBaseAttributes();    // Add fields from base attribute e.g. [UClass | UFunction | UEnum | UProperty | UStruct] | ||||
|         AddDefaultCategory();   // Add Category="Default" if no category yet added | ||||
|         AddBlueprintAccess();   // Add default Blueprint access if not already added | ||||
|     } | ||||
|  | ||||
|     public void TryAddMetaData(string key, string value = "") | ||||
|     { | ||||
|         if (MetaData.TryAdd(key, value)) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         MetaData[key] = value; | ||||
|     } | ||||
|  | ||||
|     public void TryAddMetaData(string key, bool value) | ||||
|     { | ||||
|         TryAddMetaData(key, value ? "true" : "false"); | ||||
|     } | ||||
|  | ||||
|     public void TryAddMetaData(string key, int value) | ||||
|     { | ||||
|         TryAddMetaData(key, value.ToString()); | ||||
|     } | ||||
|  | ||||
|     public void TryAddMetaData(string key, ulong value) | ||||
|     { | ||||
|         TryAddMetaData(key, value.ToString()); | ||||
|     } | ||||
|  | ||||
|     public void TryAddMetaData(string key, float value) | ||||
|     { | ||||
|         TryAddMetaData(key, value.ToString()); | ||||
|     } | ||||
|  | ||||
|     public void TryAddMetaData(string key, double value) | ||||
|     { | ||||
|         TryAddMetaData(key, value.ToString()); | ||||
|     } | ||||
|  | ||||
|     public void TryAddMetaData(string key, object value) | ||||
|     { | ||||
|         TryAddMetaData(key, value?.ToString() ?? ""); | ||||
|     } | ||||
|  | ||||
|     public void AddDefaultCategory() | ||||
|     { | ||||
|         if (!MetaData.ContainsKey("Category")) | ||||
|         { | ||||
|             TryAddMetaData("Category", "Default"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public void AddBlueprintAccess() | ||||
|     { | ||||
|         if (MetaData.ContainsKey("NotBlueprintType")) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         TryAddMetaData("BlueprintType", "true"); | ||||
|         TryAddMetaData("IsBlueprintBase", "true"); | ||||
|     } | ||||
|  | ||||
|     public static ulong GetFlags(IEnumerable<CustomAttribute> customAttributes, string flagsAttributeName) | ||||
|     { | ||||
|         CustomAttribute? flagsAttribute = customAttributes.FindAttributeByType(WeaverImporter.UnrealSharpNamespace + ".Attributes", flagsAttributeName); | ||||
|         return flagsAttribute == null ? 0 : GetFlags(flagsAttribute); | ||||
|     } | ||||
|  | ||||
|     public static ulong GetFlags(CustomAttribute flagsAttribute) | ||||
|     { | ||||
|         return (ulong) flagsAttribute.ConstructorArguments[0].Value; | ||||
|     } | ||||
|  | ||||
|     public static ulong ExtractBoolAsFlags(TypeDefinition attributeType, CustomAttributeNamedArgument namedArg, string flagsAttributeName) | ||||
|     { | ||||
|         var arg = namedArg.Argument; | ||||
|  | ||||
|         if (!(bool)arg.Value) | ||||
|         { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         // Find the property definition for this argument to resolve the true value to the desired flags map. | ||||
|         var properties = (from prop in attributeType.Properties where prop.Name == namedArg.Name select prop).ToArray(); | ||||
|         TypeProcessors.ConstructorBuilder.VerifySingleResult(properties, attributeType, "attribute property " + namedArg.Name); | ||||
|         return GetFlags(properties[0].CustomAttributes, flagsAttributeName); | ||||
|     } | ||||
|  | ||||
|     public static ulong ExtractStringAsFlags(TypeDefinition attributeType, CustomAttributeNamedArgument namedArg, string flagsAttributeName) | ||||
|     { | ||||
|         var arg = namedArg.Argument; | ||||
|         var argValue = (string) arg.Value; | ||||
|  | ||||
|         if (argValue is not { Length: > 0 }) | ||||
|         { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         PropertyDefinition? foundProperty = attributeType.FindPropertyByName(namedArg.Name); | ||||
|  | ||||
|         if (foundProperty == null) | ||||
|         { | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         TypeProcessors.ConstructorBuilder.VerifySingleResult([foundProperty], attributeType, "attribute property " + namedArg.Name); | ||||
|         return GetFlags(foundProperty.CustomAttributes, flagsAttributeName); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static ulong GetFlags(IMemberDefinition member, string flagsAttributeName) | ||||
|     { | ||||
|         SequencePoint? sequencePoint = ErrorEmitter.GetSequencePointFromMemberDefinition(member); | ||||
|         Collection<CustomAttribute>? customAttributes = member.CustomAttributes; | ||||
|          | ||||
|         ulong flags = 0; | ||||
|  | ||||
|         foreach (CustomAttribute attribute in customAttributes) | ||||
|         { | ||||
|             TypeDefinition? attributeClass = attribute.AttributeType.Resolve(); | ||||
|  | ||||
|             if (attributeClass == null) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             CustomAttribute? flagsMap = attributeClass.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, flagsAttributeName); | ||||
|  | ||||
|             if (flagsMap == null) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             flags |= GetFlags(flagsMap); | ||||
|  | ||||
|             if (attribute.HasConstructorArguments) | ||||
|             { | ||||
|                 foreach (CustomAttributeArgument arg in attribute.ConstructorArguments) | ||||
|                 { | ||||
|                     flags |= Convert.ToUInt64(arg.Value); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (!attribute.HasProperties) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             foreach (CustomAttributeNamedArgument arg in attribute.Properties) | ||||
|             { | ||||
|                 TypeDefinition argType = arg.Argument.Type.Resolve(); | ||||
|  | ||||
|                 if (argType.IsValueType && argType.Namespace == "System" && argType.Name == "Boolean") | ||||
|                 { | ||||
|                     flags |= ExtractBoolAsFlags(attributeClass, arg, flagsAttributeName); | ||||
|                 } | ||||
|                 else if (argType.Namespace == "System" && argType.Name == "String") | ||||
|                 { | ||||
|                     flags |= ExtractStringAsFlags(attributeClass, arg, flagsAttributeName); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     throw new InvalidAttributeException(attributeClass, sequencePoint, $"{argType.FullName} is not supported as an attribute property type."); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return flags; | ||||
|     } | ||||
|  | ||||
|     private void AddMetaData() | ||||
|     { | ||||
|         AddMetaData(MemberDefinition); | ||||
|     } | ||||
|  | ||||
|     protected void AddMetaData(ICustomAttributeProvider provider) | ||||
|     { | ||||
|         //[UMetaData("key","value")] | ||||
|         List<CustomAttribute> metaDataAttributes = provider.CustomAttributes.FindMetaDataAttributes(); | ||||
|         foreach (var attrib in metaDataAttributes) | ||||
|         { | ||||
|             switch (attrib.ConstructorArguments.Count) | ||||
|             { | ||||
|                 case < 1: | ||||
|                     continue; | ||||
|                 case 1:  | ||||
|                     TryAddMetaData((string)attrib.ConstructorArguments[0].Value); | ||||
|                     break; | ||||
|                 default:  | ||||
|                     TryAddMetaData((string)attrib.ConstructorArguments[0].Value, (string)attrib.ConstructorArguments[1].Value); | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void AddMetaTagsNamespace()  | ||||
|     { | ||||
|         AddMetaTagsNamespace(MemberDefinition); | ||||
|     } | ||||
|      | ||||
|     protected void AddMetaTagsNamespace(ICustomAttributeProvider provider)  | ||||
|     { | ||||
|         //Specific MetaData Tags - all attributes in the UnrealSharp.Attributes.MetaTags Namespace | ||||
|         List<CustomAttribute> metaDataAttributes = provider.CustomAttributes.FindMetaDataAttributesByNamespace(); | ||||
|         foreach (var attrib in metaDataAttributes) | ||||
|         { | ||||
|             var key = attrib.AttributeType.Name.Replace("Attribute", ""); | ||||
|             if (attrib.HasConstructorArguments) | ||||
|             { | ||||
|                 TryAddMetaData(key, attrib.ConstructorArguments[0].Value); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 TryAddMetaData(key, "true"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void AddBaseAttributes() | ||||
|     { | ||||
|         if (BaseAttribute == null) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         CustomAttributeArgument? displayNameArgument = BaseAttribute.FindAttributeField("DisplayName"); | ||||
|         if (displayNameArgument.HasValue) | ||||
|         { | ||||
|             TryAddMetaData("DisplayName", (string) displayNameArgument.Value.Value); | ||||
|         } | ||||
|  | ||||
|         CustomAttributeArgument? categoryArgument = BaseAttribute.FindAttributeField("Category"); | ||||
|         if (categoryArgument.HasValue) | ||||
|         { | ||||
|             TryAddMetaData("Category", (string) categoryArgument.Value.Value); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     protected bool GetBoolMetadata(string key) | ||||
|     { | ||||
|         if (!MetaData.TryGetValue(key, out var val)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         return 0 == StringComparer.OrdinalIgnoreCase.Compare(val, "true"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,200 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.TypeProcessors; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public class ClassMetaData : TypeReferenceMetadata | ||||
| { | ||||
|     public TypeReferenceMetadata ParentClass { get; set; } | ||||
|     public List<PropertyMetaData> Properties { get; set; } | ||||
|     public List<FunctionMetaData> Functions { get; set; } | ||||
|     public List<FunctionMetaData> VirtualFunctions { get; set; } | ||||
|     public List<TypeReferenceMetadata> Interfaces { get; set; } | ||||
|     public string ConfigCategory { get; set; }  | ||||
|     public ClassFlags ClassFlags { get; set; } | ||||
|      | ||||
|     // Non-serialized for JSON | ||||
|     public bool HasProperties => Properties.Count > 0; | ||||
|     private readonly TypeDefinition _classDefinition; | ||||
|     // End non-serialized | ||||
|      | ||||
|     public ClassMetaData(TypeDefinition type) : base(type, TypeDefinitionUtilities.UClassAttribute) | ||||
|     { | ||||
|         _classDefinition = type; | ||||
|          | ||||
|         Properties = []; | ||||
|         Functions = []; | ||||
|         VirtualFunctions = []; | ||||
|          | ||||
|         ConfigCategory = string.Empty; | ||||
|         Interfaces = []; | ||||
|          | ||||
|         PopulateInterfaces(); | ||||
|         PopulateProperties(); | ||||
|         PopulateFunctions(); | ||||
|          | ||||
|         AddConfigCategory(); | ||||
|          | ||||
|         ParentClass = new TypeReferenceMetadata(type.BaseType.Resolve()); | ||||
|         ClassFlags |= GetClassFlags(type, AttributeName) | ClassFlags.CompiledFromBlueprint; | ||||
|          | ||||
|         // Force DefaultConfig if Config is set and no other config flag is set | ||||
|         if (ClassFlags.HasFlag(ClassFlags.Config) && | ||||
|             !ClassFlags.HasFlag(ClassFlags.GlobalUserConfig | ClassFlags.DefaultConfig | ClassFlags.ProjectUserConfig)) | ||||
|         { | ||||
|             ClassFlags |= ClassFlags.DefaultConfig; | ||||
|         } | ||||
|  | ||||
|         if (type.IsChildOf(WeaverImporter.Instance.UActorComponentDefinition)) | ||||
|         { | ||||
|             TryAddMetaData("BlueprintSpawnableComponent", true); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void AddConfigCategory() | ||||
|     { | ||||
|         CustomAttribute uClassAttribute = _classDefinition.GetUClass()!; | ||||
|         CustomAttributeArgument? configCategoryProperty = uClassAttribute.FindAttributeField(nameof(ConfigCategory)); | ||||
|         if (configCategoryProperty != null) | ||||
|         { | ||||
|             ConfigCategory = (string) configCategoryProperty.Value.Value; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void PopulateProperties() | ||||
|     { | ||||
|         if (_classDefinition.Properties.Count == 0) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         Properties = []; | ||||
|          | ||||
|         foreach (PropertyDefinition property in _classDefinition.Properties) | ||||
|         { | ||||
|             CustomAttribute? uPropertyAttribute = property.GetUProperty(); | ||||
|  | ||||
|             if (uPropertyAttribute == null) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             PropertyMetaData propertyMetaData = new PropertyMetaData(property); | ||||
|             Properties.Add(propertyMetaData); | ||||
|                  | ||||
|             if (propertyMetaData.IsInstancedReference) | ||||
|             { | ||||
|                 ClassFlags |= ClassFlags.HasInstancedReference; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void PopulateFunctions() | ||||
|     { | ||||
|         if (_classDefinition.Methods.Count == 0) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         Functions = []; | ||||
|         VirtualFunctions = []; | ||||
|          | ||||
|         for (var i = _classDefinition.Methods.Count - 1; i >= 0; i--) | ||||
|         { | ||||
|             MethodDefinition method = _classDefinition.Methods[i]; | ||||
|  | ||||
|             if (method.HasParameters) | ||||
|             { | ||||
|                 var paramNameSet = new HashSet<string>(); | ||||
|                 var uniqueNum = 0; | ||||
|                 foreach (var param in method.Parameters) | ||||
|                 { | ||||
|                     if (!paramNameSet.Add(param.Name)) | ||||
|                     { | ||||
|                         param.Name = $"{param.Name}_{uniqueNum++}"; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (FunctionMetaData.IsAsyncUFunction(method)) | ||||
|             { | ||||
|                 method.CustomAttributes.Clear(); | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             bool isBlueprintOverride = FunctionMetaData.IsBlueprintEventOverride(method); | ||||
|             bool isInterfaceFunction = FunctionMetaData.IsInterfaceFunction(method); | ||||
|              | ||||
|             if (method.IsUFunction() && !isInterfaceFunction) | ||||
|             { | ||||
|                 if (isBlueprintOverride) | ||||
|                 { | ||||
|                     throw new Exception($"{method.FullName} is a Blueprint override and cannot be marked as a UFunction again."); | ||||
|                 } | ||||
|                  | ||||
|                 FunctionMetaData functionMetaData = new FunctionMetaData(method); | ||||
|                  | ||||
|                 if (isInterfaceFunction && functionMetaData.FunctionFlags.HasFlag(EFunctionFlags.BlueprintNativeEvent)) | ||||
|                 { | ||||
|                     throw new Exception("Interface functions cannot be marked as BlueprintEvent. Mark base declaration as BlueprintEvent instead."); | ||||
|                 } | ||||
|                  | ||||
|                 Functions.Add(functionMetaData); | ||||
|             } | ||||
|              | ||||
|             if (isBlueprintOverride || isInterfaceFunction && method.GetBaseMethod().DeclaringType == _classDefinition) | ||||
|             { | ||||
|                 EFunctionFlags functionFlags = EFunctionFlags.None; | ||||
|                 if (isInterfaceFunction) | ||||
|                 { | ||||
|                     MethodDefinition interfaceFunction = FunctionMetaData.TryGetInterfaceFunction(method)!; | ||||
|                     functionFlags = interfaceFunction.GetFunctionFlags(); | ||||
|                 } | ||||
|                  | ||||
|                 VirtualFunctions.Add(new FunctionMetaData(method, false, functionFlags)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static ClassFlags GetClassFlags(TypeReference classReference, string flagsAttributeName) | ||||
|     { | ||||
|         return (ClassFlags) GetFlags(classReference.Resolve().CustomAttributes, flagsAttributeName); | ||||
|     } | ||||
|      | ||||
|     void PopulateInterfaces() | ||||
|     { | ||||
|         if (_classDefinition.Interfaces.Count == 0) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         Interfaces = []; | ||||
|          | ||||
|         foreach (InterfaceImplementation? typeInterface in _classDefinition.Interfaces) | ||||
|         { | ||||
|             TypeDefinition interfaceType = typeInterface.InterfaceType.Resolve(); | ||||
|  | ||||
|             if (interfaceType == WeaverImporter.Instance.IInterfaceType || !interfaceType.IsUInterface()) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             Interfaces.Add(new TypeReferenceMetadata(interfaceType)); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public void PostWeaveCleanup() | ||||
|     { | ||||
|         foreach (FunctionMetaData function in Functions) | ||||
|         { | ||||
|             function.TryRemoveMethod(); | ||||
|         } | ||||
|          | ||||
|         foreach (FunctionMetaData virtualFunction in VirtualFunctions) | ||||
|         { | ||||
|             virtualFunction.TryRemoveMethod(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public class DelegateMetaData : TypeReferenceMetadata | ||||
| { | ||||
|     public FunctionMetaData Signature { get; set; } | ||||
|      | ||||
|     public DelegateMetaData(FunctionMetaData signature, TypeReference member, string attributeName = "", EFunctionFlags functionFlags = EFunctionFlags.None) : base(member, attributeName) | ||||
|     { | ||||
|         Name = DelegateUtilities.GetUnrealDelegateName(member); | ||||
|          | ||||
|         Signature = signature; | ||||
|         Signature.FunctionFlags |= functionFlags; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,24 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public class EnumMetaData : TypeReferenceMetadata | ||||
| { | ||||
|     public List<string> Items { get; set; } | ||||
|  | ||||
|     public EnumMetaData(TypeDefinition enumType) : base(enumType, TypeDefinitionUtilities.UEnumAttribute) | ||||
|     { | ||||
|         Items = new List<string>(); | ||||
|          | ||||
|         foreach (var field in enumType.Fields) | ||||
|         { | ||||
|             if (!field.IsStatic && field.Name == "value__") | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             Items.Add(field.Name); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,293 @@ | ||||
| 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,49 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public class InterfaceMetaData : TypeReferenceMetadata | ||||
| {  | ||||
|     public TypeReferenceMetadata ParentInterface { get; set; } | ||||
|     public List<FunctionMetaData> Functions { get; set; } | ||||
|      | ||||
|     // Non-serialized for JSON | ||||
|     const string CannotImplementInterfaceInBlueprint = "CannotImplementInterfaceInBlueprint"; | ||||
|     // End non-serialized | ||||
|      | ||||
|     public InterfaceMetaData(TypeDefinition typeDefinition) : base(typeDefinition, TypeDefinitionUtilities.UInterfaceAttribute) | ||||
|     { | ||||
|         Functions = []; | ||||
|          | ||||
|         if (typeDefinition.HasInterfaces) | ||||
|         { | ||||
|             foreach (InterfaceImplementation? interfaceType in typeDefinition.Interfaces) | ||||
|             { | ||||
|                 TypeDefinition interfaceDef = interfaceType.InterfaceType.Resolve(); | ||||
|                 if (!interfaceDef.IsUInterface()) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                  | ||||
|                 ParentInterface = new TypeReferenceMetadata(interfaceDef); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|          | ||||
|         foreach (var method in typeDefinition.Methods) | ||||
|         { | ||||
|             if (method.IsAbstract && method.IsUFunction()) | ||||
|             { | ||||
|                 Functions.Add(new FunctionMetaData(method, onlyCollectMetaData: true)); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         CustomAttributeArgument? nonBpInterface = BaseAttribute!.FindAttributeField(CannotImplementInterfaceInBlueprint); | ||||
|         if (nonBpInterface != null) | ||||
|         { | ||||
|             TryAddMetaData(CannotImplementInterfaceInBlueprint, (bool) nonBpInterface.Value.Value); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,291 @@ | ||||
| using System.Text.Json.Serialization; | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using UnrealSharpWeaver.NativeTypes; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public class PropertyMetaData : BaseMetaData | ||||
| { | ||||
|     public PropertyFlags PropertyFlags { get; set; } = PropertyFlags.None; | ||||
|     public NativeDataType PropertyDataType { get; set; } = null!; | ||||
|     public string RepNotifyFunctionName { get; set; } = string.Empty; | ||||
|     public LifetimeCondition LifetimeCondition { get; set; } = LifetimeCondition.None; | ||||
|     public string BlueprintSetter { get; set; } = string.Empty; | ||||
|     public string BlueprintGetter { get; set; } = string.Empty; | ||||
|     public bool HasCustomAccessors { get; set; } = false; | ||||
|     [JsonIgnore] | ||||
|     public PropertyDefinition? GeneratedAccessorProperty { get; set; } = null; | ||||
|  | ||||
|     // Non-serialized for JSON | ||||
|     public FieldDefinition? PropertyOffsetField; | ||||
|     public FieldDefinition? NativePropertyField; | ||||
|     public readonly MemberReference? MemberRef; | ||||
|     public bool IsOutParameter => (PropertyFlags & PropertyFlags.OutParm) == PropertyFlags.OutParm; | ||||
|     public bool IsReferenceParameter => (PropertyFlags & PropertyFlags.ReferenceParm) == PropertyFlags.ReferenceParm; | ||||
|     public bool IsReturnParameter => (PropertyFlags & PropertyFlags.ReturnParm) == PropertyFlags.ReturnParm; | ||||
|     public bool IsInstancedReference => (PropertyFlags & PropertyFlags.InstancedReference) == PropertyFlags.InstancedReference; | ||||
|     // End non-serialized | ||||
|      | ||||
|     private PropertyMetaData(MemberReference memberRef) : base(memberRef, PropertyUtilities.UPropertyAttribute) | ||||
|     { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private PropertyMetaData(TypeReference typeRef, string paramName, ParameterType modifier) : this(typeRef) | ||||
|     { | ||||
|         MemberRef = typeRef; | ||||
|         Name = paramName; | ||||
|         PropertyDataType = typeRef.GetDataType(paramName, null); | ||||
|          | ||||
|         PropertyFlags flags = PropertyFlags.None; | ||||
|          | ||||
|         if (modifier != ParameterType.None) | ||||
|         { | ||||
|             flags |= PropertyFlags.Parm; | ||||
|         } | ||||
|          | ||||
|         switch (modifier) | ||||
|         { | ||||
|             case ParameterType.Out: | ||||
|                 flags |= PropertyFlags.OutParm; | ||||
|                 break; | ||||
|             case ParameterType.Ref: | ||||
|                 flags |= PropertyFlags.OutParm | PropertyFlags.ReferenceParm; | ||||
|                 break; | ||||
|             case ParameterType.ReturnValue: | ||||
|                 flags |= PropertyFlags.ReturnParm | PropertyFlags.OutParm; | ||||
|                 break; | ||||
|         } | ||||
|  | ||||
|         PropertyFlags = flags; | ||||
|     } | ||||
|  | ||||
|     public PropertyMetaData(PropertyDefinition property) : this((MemberReference) property) | ||||
|     { | ||||
|         MemberRef = property; | ||||
|          | ||||
|         MethodDefinition getter = property.GetMethod; | ||||
|         MethodDefinition setter = property.SetMethod; | ||||
|  | ||||
|         if (getter == null) | ||||
|         { | ||||
|             throw new InvalidPropertyException(property, "Unreal properties must have a default get method"); | ||||
|         } | ||||
|          | ||||
|         // Check if we have custom accessors | ||||
|         bool hasCustomGetter = !getter.MethodIsCompilerGenerated(); | ||||
|         bool hasCustomSetter = setter != null && !setter.MethodIsCompilerGenerated(); | ||||
|  | ||||
|         HasCustomAccessors = hasCustomGetter || hasCustomSetter; | ||||
|  | ||||
|         // Allow custom getter/setter implementations | ||||
|         if (!HasCustomAccessors) | ||||
|         { | ||||
|             // Only throw exception if not a custom accessor | ||||
|             if (!getter.MethodIsCompilerGenerated()) | ||||
|             { | ||||
|                 throw new InvalidPropertyException(property, "Getter can not have a body for Unreal properties unless it's a custom accessor"); | ||||
|             } | ||||
|  | ||||
|             if (setter != null && !setter.MethodIsCompilerGenerated()) | ||||
|             { | ||||
|                 throw new InvalidPropertyException(property, "Setter can not have a body for Unreal properties unless it's a custom accessor"); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Register custom accessors as UFunctions if they have BlueprintGetter/Setter specified | ||||
|             RegisterPropertyAccessorAsUFunction(property.GetMethod, true); | ||||
|             RegisterPropertyAccessorAsUFunction(property.SetMethod, false); | ||||
|         } | ||||
|          | ||||
|         if (getter.IsPrivate && PropertyFlags.HasFlag(PropertyFlags.BlueprintVisible)) | ||||
|         { | ||||
|             if(!GetBoolMetadata("AllowPrivateAccess")) | ||||
|             { | ||||
|                 throw new InvalidPropertyException(property, "Blueprint visible properties can not be set to private."); | ||||
|             }  | ||||
|         } | ||||
|          | ||||
|         Initialize(property, property.PropertyType); | ||||
|     } | ||||
|  | ||||
|     public PropertyMetaData(FieldDefinition property) : this((MemberReference) property) | ||||
|     { | ||||
|         MemberRef = property; | ||||
|         Initialize(property, property.FieldType); | ||||
|     } | ||||
|      | ||||
|     private void Initialize(IMemberDefinition property, TypeReference propertyType) | ||||
|     { | ||||
|         Name = property.Name; | ||||
|         PropertyDataType = propertyType.GetDataType(property.FullName, property.CustomAttributes); | ||||
|         PropertyFlags flags = (PropertyFlags) GetFlags(property, "PropertyFlagsMapAttribute"); | ||||
|          | ||||
|         CustomAttribute? upropertyAttribute = property.GetUProperty(); | ||||
|         if (upropertyAttribute == null) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         CustomAttributeArgument? blueprintSetterArgument = upropertyAttribute.FindAttributeField("BlueprintSetter"); | ||||
|         if (blueprintSetterArgument.HasValue) | ||||
|         { | ||||
|             BlueprintSetter = (string) blueprintSetterArgument.Value.Value; | ||||
|         } | ||||
|          | ||||
|         CustomAttributeArgument? blueprintGetterArgument = upropertyAttribute.FindAttributeField("BlueprintGetter"); | ||||
|         if (blueprintGetterArgument.HasValue) | ||||
|         { | ||||
|             BlueprintGetter = (string) blueprintGetterArgument.Value.Value; | ||||
|         } | ||||
|          | ||||
|         CustomAttributeArgument? lifetimeConditionField = upropertyAttribute.FindAttributeField("LifetimeCondition"); | ||||
|         if (lifetimeConditionField.HasValue) | ||||
|         { | ||||
|             LifetimeCondition = (LifetimeCondition) lifetimeConditionField.Value.Value; | ||||
|         } | ||||
|          | ||||
|         CustomAttributeArgument? notifyMethodArgument = upropertyAttribute.FindAttributeField("ReplicatedUsing"); | ||||
|         if (notifyMethodArgument.HasValue) | ||||
|         { | ||||
|             string notifyMethodName = (string) notifyMethodArgument.Value.Value; | ||||
|             MethodReference? notifyMethod = property.DeclaringType.FindMethod(notifyMethodName); | ||||
|              | ||||
|             if (notifyMethod == null) | ||||
|             { | ||||
|                 throw new InvalidPropertyException(property, $"RepNotify method '{notifyMethodName}' not found on {property.DeclaringType.Name}"); | ||||
|             } | ||||
|              | ||||
|             if (!notifyMethod.Resolve().IsUFunction()) | ||||
|             { | ||||
|                 throw new InvalidPropertyException(property, $"RepNotify method '{notifyMethodName}' needs to be declared as a UFunction."); | ||||
|             } | ||||
|  | ||||
|             if (!notifyMethod.ReturnsVoid()) | ||||
|             { | ||||
|                 throw new InvalidPropertyException(property, $"RepNotify method '{notifyMethodName}' must return void"); | ||||
|             } | ||||
|  | ||||
|             if (notifyMethod.Parameters.Count > 0) | ||||
|             { | ||||
|                 if (notifyMethod.Parameters[0].ParameterType != propertyType) | ||||
|                 { | ||||
|                     throw new InvalidPropertyException(property, $"RepNotify can only have matching parameters to the property it is notifying. '{notifyMethodName}' takes a '{notifyMethod.Parameters[0].ParameterType.FullName}' but the property is a '{propertyType.FullName}'"); | ||||
|                 } | ||||
|                  | ||||
|                 if (notifyMethod.Parameters.Count > 1) | ||||
|                 { | ||||
|                     throw new InvalidPropertyException(property, $"RepNotify method '{notifyMethodName}' must take a single argument"); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // Just a quality of life, if the property is set to ReplicatedUsing, it should be replicating | ||||
|             flags |= PropertyFlags.Net | PropertyFlags.RepNotify; | ||||
|             RepNotifyFunctionName = notifyMethodName; | ||||
|         } | ||||
|          | ||||
|         if (flags.HasFlag(PropertyFlags.Net) && !PropertyDataType.IsNetworkSupported) | ||||
|         { | ||||
|             throw new InvalidPropertyException(property, $"{Name} is marked as replicated but the {PropertyDataType.CSharpType} is not supported for replication"); | ||||
|         } | ||||
|          | ||||
|         bool isDefaultComponent = NativeDataDefaultComponent.IsDefaultComponent(property.CustomAttributes); | ||||
|         bool isPersistentInstance = (flags & PropertyFlags.PersistentInstance) != 0; | ||||
|  | ||||
|         const PropertyFlags instancedFlags = PropertyFlags.InstancedReference | PropertyFlags.ExportObject; | ||||
|          | ||||
|         if ((flags & PropertyFlags.InstancedReference) != 0 || isPersistentInstance) | ||||
|         { | ||||
|             flags |= instancedFlags; | ||||
|         } | ||||
|          | ||||
|         if (isDefaultComponent) | ||||
|         { | ||||
|             flags = PropertyFlags.BlueprintVisible | PropertyFlags.NonTransactional | PropertyFlags.InstancedReference; | ||||
|         } | ||||
|  | ||||
|         if (isPersistentInstance || isDefaultComponent) | ||||
|         { | ||||
|             TryAddMetaData("EditInline", "true"); | ||||
|         } | ||||
|          | ||||
|         PropertyFlags = flags; | ||||
|     } | ||||
|      | ||||
|     public void InitializePropertyPointers(ILProcessor processor, Instruction loadNativeType, Instruction setPropertyPointer) | ||||
|     { | ||||
|         processor.Append(loadNativeType); | ||||
|         processor.Emit(OpCodes.Ldstr, Name); | ||||
|         processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativePropertyFromNameMethod); | ||||
|         processor.Append(setPropertyPointer); | ||||
|     } | ||||
|      | ||||
|     public void InitializePropertyOffsets(ILProcessor processor, Instruction loadNativeType) | ||||
|     { | ||||
|         processor.Append(loadNativeType); | ||||
|         processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetPropertyOffset); | ||||
|         processor.Emit(OpCodes.Stsfld, PropertyOffsetField); | ||||
|     } | ||||
|      | ||||
|     public static PropertyMetaData FromTypeReference(TypeReference typeRef, string paramName, ParameterType modifier = ParameterType.None, ParameterDefinition? parameterDefinition = null) | ||||
|     { | ||||
|         var metadata = new PropertyMetaData(typeRef, paramName, modifier); | ||||
|         if (parameterDefinition is null) return metadata; | ||||
|         metadata.AddMetaData(parameterDefinition); | ||||
|         metadata.AddMetaTagsNamespace(parameterDefinition); | ||||
|         return metadata; | ||||
|     } | ||||
|      | ||||
|     private void RegisterPropertyAccessorAsUFunction(MethodDefinition accessorMethod, bool isGetter) | ||||
|     { | ||||
|         if (accessorMethod == null) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // Set the appropriate blueprint accessor name based on getter or setter | ||||
|         if (isGetter) | ||||
|         { | ||||
|             BlueprintGetter = accessorMethod.Name; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             BlueprintSetter = accessorMethod.Name; | ||||
|         } | ||||
|  | ||||
|         // Add UFunction attribute if not already present | ||||
|         if (!accessorMethod.IsUFunction()) | ||||
|         { | ||||
|             var ufunctionCtor = WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference( | ||||
|                 WeaverImporter.Instance.UFunctionAttributeConstructor); | ||||
|  | ||||
|             // Create constructor arguments array | ||||
|             var ctorArgs = new[] | ||||
|             { | ||||
|                 // First argument - FunctionFlags (combine BlueprintCallable with BlueprintPure for getters) | ||||
|                 new CustomAttributeArgument( | ||||
|                     WeaverImporter.Instance.UInt64TypeRef, | ||||
|                     (ulong)(isGetter  | ||||
|                         ? EFunctionFlags.BlueprintCallable | EFunctionFlags.BlueprintPure  | ||||
|                         : EFunctionFlags.BlueprintCallable)) | ||||
|             }; | ||||
|  | ||||
|             var ufunctionAttribute = new CustomAttribute(ufunctionCtor) | ||||
|             { | ||||
|                 ConstructorArguments = { ctorArgs[0] } | ||||
|             }; | ||||
|  | ||||
|             accessorMethod.CustomAttributes.Add(ufunctionAttribute); | ||||
|              | ||||
|             var blueprintInternalUseOnlyCtor = WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference( | ||||
|                 WeaverImporter.Instance.BlueprintInternalUseAttributeConstructor); | ||||
|             accessorMethod.CustomAttributes.Add(new CustomAttribute(blueprintInternalUseOnlyCtor)); | ||||
|         } | ||||
|  | ||||
|         // Make the method public to be accessible from Blueprint | ||||
|         accessorMethod.Attributes = (accessorMethod.Attributes & ~MethodAttributes.MemberAccessMask) | MethodAttributes.Public; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,92 @@ | ||||
| using System.Text.RegularExpressions; | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
| using PropertyUtilities = UnrealSharpWeaver.Utilities.PropertyUtilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public partial class StructMetaData : TypeReferenceMetadata | ||||
| { | ||||
|     public List<PropertyMetaData> Fields { get; set; } | ||||
|     public StructFlags StructFlags { get; set; } | ||||
|      | ||||
|     // Non-serialized for JSON | ||||
|     public readonly bool IsBlittableStruct; | ||||
|     // End non-serialized | ||||
|      | ||||
|     public StructMetaData(TypeDefinition structDefinition) : base(structDefinition, TypeDefinitionUtilities.UStructAttribute) | ||||
|     { | ||||
|         Fields = new List<PropertyMetaData>(); | ||||
|         IsBlittableStruct = true; | ||||
|          | ||||
|  | ||||
|         var backingFieldRegex = BackingFieldRegex(); | ||||
|         foreach (var field in structDefinition.Fields) | ||||
|         { | ||||
|             if (field.IsStatic) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             if (!field.IsUProperty()) | ||||
|             { | ||||
|                 // Struct is not blittable if it has non-UProperty fields | ||||
|                 IsBlittableStruct = false; | ||||
|             } | ||||
|              | ||||
|             PropertyMetaData property = new PropertyMetaData(field); | ||||
|              | ||||
|             // If we match against a backing property field use the property name instead. | ||||
|             var backingFieldMatch = backingFieldRegex.Match(field.Name); | ||||
|             if (backingFieldMatch.Success) | ||||
|             { | ||||
|                 string propertyName = backingFieldMatch.Groups[1].Value; | ||||
|                 property.Name = propertyName; | ||||
|                  | ||||
|             } | ||||
|  | ||||
|             if (property.IsInstancedReference) | ||||
|             { | ||||
|                 StructFlags |= StructFlags.HasInstancedReference; | ||||
|             } | ||||
|              | ||||
|             Fields.Add(property); | ||||
|         } | ||||
|          | ||||
|         bool isPlainOldData = true; | ||||
|         foreach (var prop in Fields) | ||||
|         { | ||||
|             if (!prop.PropertyDataType.IsBlittable) | ||||
|             { | ||||
|                 IsBlittableStruct = false; | ||||
|             } | ||||
|              | ||||
|             if (!prop.PropertyDataType.IsPlainOldData) | ||||
|             { | ||||
|                 isPlainOldData = false; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         StructFlags |= (StructFlags) GetFlags(structDefinition, "StructFlagsMapAttribute"); | ||||
|  | ||||
|         if (isPlainOldData) | ||||
|         { | ||||
|             StructFlags |= StructFlags.IsPlainOldData; | ||||
|             StructFlags |= StructFlags.NoDestructor; | ||||
|             StructFlags |= StructFlags.ZeroConstructor; | ||||
|         } | ||||
|  | ||||
|         if (!IsBlittableStruct) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         CustomAttribute structFlagsAttribute = new CustomAttribute(WeaverImporter.Instance.BlittableTypeConstructor); | ||||
|         structDefinition.CustomAttributes.Add(structFlagsAttribute); | ||||
|          | ||||
|         TryAddMetaData("BlueprintType", true); | ||||
|     } | ||||
|  | ||||
|     [GeneratedRegex("<([a-zA-Z$_][a-zA-Z0-9$_]*)>k__BackingField")] | ||||
|     private static partial Regex BackingFieldRegex(); | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public class TypeReferenceMetadata : BaseMetaData | ||||
| { | ||||
|     public string AssemblyName { get; set; } | ||||
|     public string Namespace { get; set; } | ||||
|      | ||||
|     // Non-serialized for JSON | ||||
|     public readonly TypeReference TypeRef; | ||||
|     // End non-serialized | ||||
|      | ||||
|     public TypeReferenceMetadata(TypeReference member, string attributeName = "") : base(member, attributeName) | ||||
|     { | ||||
|         AssemblyName = member.Module.Assembly.Name.Name; | ||||
|         Namespace = member.Namespace; | ||||
|         TypeRef = member; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| namespace UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| public class UnrealSharpMetadata | ||||
| { | ||||
|     public ICollection<string> AssemblyLoadingOrder { get; set; } = []; | ||||
| } | ||||
| @ -0,0 +1,16 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| public static class MethodUtilities | ||||
| { | ||||
|     public static bool ReturnsVoid(this MethodDefinition method) | ||||
|     { | ||||
|         return method.ReturnType == method.Module.TypeSystem.Void; | ||||
|     } | ||||
|      | ||||
|     public static bool ReturnsVoid(this MethodReference method) | ||||
|     { | ||||
|         return ReturnsVoid(method.Resolve()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataArrayType(TypeReference typeRef, int containerDim, TypeReference innerType)  | ||||
|     : NativeDataContainerType(typeRef, containerDim, PropertyType.Array, innerType) | ||||
| { | ||||
|     public override string GetContainerMarshallerName() | ||||
|     { | ||||
|         return "ArrayMarshaller`1"; | ||||
|     } | ||||
|  | ||||
|     public override string GetCopyContainerMarshallerName() | ||||
|     { | ||||
|         return "ArrayCopyMarshaller`1"; | ||||
|     } | ||||
|  | ||||
|     public override string GetContainerWrapperType() | ||||
|     { | ||||
|         return "System.Collections.Generic.IList`1"; | ||||
|     } | ||||
|  | ||||
|     public override void WriteSetter(TypeDefinition type, MethodDefinition setter, Instruction[] loadBufferPtr, | ||||
|                                      FieldDefinition? fieldDefinition) | ||||
|     { | ||||
|         base.WriteSetter(type, setter, loadBufferPtr, fieldDefinition); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,65 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| public abstract class NativeDataBaseDelegateType : NativeDataSimpleType | ||||
| { | ||||
|     public TypeReferenceMetadata UnrealDelegateType { get; set; } | ||||
|      | ||||
|     public MethodDefinition Signature; | ||||
|     public TypeReference fieldType; | ||||
|     public TypeReference delegateType; | ||||
|     public TypeReference wrapperType; | ||||
|  | ||||
|     protected override TypeReference[] GetTypeParams() | ||||
|     { | ||||
|         return [delegateType.ImportType()]; | ||||
|     } | ||||
|  | ||||
|     public NativeDataBaseDelegateType(TypeReference typeRef, string marshallerName, PropertyType propertyType)  | ||||
|         : base(typeRef, marshallerName, 1, propertyType) | ||||
|     { | ||||
|         fieldType = typeRef; | ||||
|         delegateType = GetDelegateType(typeRef); | ||||
|         wrapperType = GetWrapperType(delegateType); | ||||
|  | ||||
|         UnrealDelegateType = new TypeReferenceMetadata(wrapperType); | ||||
|         UnrealDelegateType.Name = DelegateUtilities.GetUnrealDelegateName(wrapperType); | ||||
|          | ||||
|         TypeDefinition delegateTypeDefinition = delegateType.Resolve(); | ||||
|         foreach (MethodDefinition method in delegateTypeDefinition.Methods) | ||||
|         { | ||||
|             if (method.Name != "Invoke") | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             if (!method.ReturnsVoid()) | ||||
|             { | ||||
|                 throw new Exception($"{delegateType.FullName} is exposed to Unreal Engine, and must have a void return type."); | ||||
|             } | ||||
|  | ||||
|             Signature = method; | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         if (Signature == null) | ||||
|         { | ||||
|             throw new Exception("Could not find Invoke method in delegate type"); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     protected TypeReference GetDelegateType(TypeReference typeRef) | ||||
|     { | ||||
|         return ((GenericInstanceType) typeRef).GenericArguments[0]; | ||||
|     } | ||||
|      | ||||
|     protected TypeReference GetWrapperType(TypeReference delegateType) | ||||
|     { | ||||
|         TypeDefinition delegateTypeDefinition = delegateType.Resolve(); | ||||
|         return delegateTypeDefinition.Module.Assembly.FindType($"U{delegateType.Name}", delegateType.Namespace)!; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataBlittableStructType(TypeReference structType, int arrayDim) : NativeDataBlittableStructTypeBase(structType, arrayDim); | ||||
| @ -0,0 +1,9 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataBlittableStructTypeBase(TypeReference structType, int arrayDim, PropertyType propertyType = PropertyType.Struct) | ||||
|     : NativeDataStructType(structType, "BlittableMarshaller`1", arrayDim, propertyType) | ||||
| { | ||||
|     public override bool IsBlittable => true; | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataBooleanType(TypeReference typeRef, int arrayDim) : NativeDataSimpleType(typeRef, "BoolMarshaller", arrayDim, PropertyType.Bool) | ||||
| { | ||||
|     public override bool IsPlainOldData => false; | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataBuiltinType(TypeReference typeRef, int arrayDim, PropertyType propertyType) : NativeDataSimpleType(typeRef, "BlittableMarshaller`1", arrayDim, propertyType) | ||||
| {  | ||||
|     public override bool IsBlittable => true; | ||||
| } | ||||
| @ -0,0 +1,13 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataClassBaseType(TypeReference typeRef, TypeReference innerTypeReference, int arrayDim, string marshallerClass, PropertyType propertyType) | ||||
|     : NativeDataGenericObjectType(typeRef, innerTypeReference, marshallerClass, arrayDim, propertyType) | ||||
| { | ||||
|     protected override TypeReference[] GetTypeParams() | ||||
|     { | ||||
|         return [InnerType.TypeRef.ImportType()]; | ||||
|     } | ||||
| }; | ||||
| @ -0,0 +1,6 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataClassType(TypeReference typeRef, TypeReference innerTypeReference, int arrayDim) | ||||
|     : NativeDataClassBaseType(typeRef, innerTypeReference, arrayDim, "SubclassOfMarshaller`1", PropertyType.Class); | ||||
| @ -0,0 +1,309 @@ | ||||
| using System.Collections.Immutable; | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.TypeProcessors; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| public class NativeDataContainerType : NativeDataType | ||||
| { | ||||
|     public PropertyMetaData InnerProperty { get; set; } | ||||
|  | ||||
|     private TypeReference? _containerMarshallerType; | ||||
|     private MethodReference? _containerMashallerCtor; | ||||
|     private FieldDefinition? _containerMarshallerField; | ||||
|  | ||||
|     private MethodReference? _fromNative; | ||||
|     private MethodReference? _toNative; | ||||
|  | ||||
|     private FieldDefinition? _nativePropertyField; | ||||
|  | ||||
|     private MethodReference? _copyDestructInstance; | ||||
|      | ||||
|     protected virtual AssemblyDefinition MarshallerAssembly => WeaverImporter.Instance.UnrealSharpAssembly; | ||||
|     protected virtual string MarshallerNamespace => WeaverImporter.UnrealSharpNamespace; | ||||
|  | ||||
|     protected virtual bool AllowsSetter => false; | ||||
|      | ||||
|     protected TypeReference[] ContainerMarshallerTypeParameters { get; set; } = []; | ||||
|      | ||||
|     public NativeDataContainerType(TypeReference typeRef, int containerDim, PropertyType propertyType, TypeReference value) : base(typeRef, containerDim, propertyType) | ||||
|     { | ||||
|         InnerProperty = PropertyMetaData.FromTypeReference(value, "Inner"); | ||||
|         NeedsNativePropertyField = true; | ||||
|     } | ||||
|  | ||||
|     public virtual string GetContainerMarshallerName() | ||||
|     { | ||||
|         throw new NotImplementedException(); | ||||
|     } | ||||
|      | ||||
|     public virtual string GetCopyContainerMarshallerName() | ||||
|     { | ||||
|         throw new NotImplementedException(); | ||||
|     } | ||||
|      | ||||
|     public virtual void InitializeMarshallerParameters() | ||||
|     { | ||||
|         ContainerMarshallerTypeParameters = [InnerProperty.PropertyDataType.CSharpType.ImportType()]; | ||||
|     } | ||||
|      | ||||
|     public virtual string GetContainerWrapperType() | ||||
|     { | ||||
|         throw new NotImplementedException(); | ||||
|     } | ||||
|      | ||||
|     public override void EmitFixedArrayMarshallerDelegates(ILProcessor processor, TypeDefinition type) | ||||
|     { | ||||
|         throw new NotImplementedException(); | ||||
|     } | ||||
|      | ||||
|     public override void EmitDynamicArrayMarshallerDelegates(ILProcessor processor, TypeDefinition type) | ||||
|     { | ||||
|         InnerProperty.PropertyDataType.EmitDynamicArrayMarshallerDelegates(processor, type); | ||||
|     } | ||||
|  | ||||
|     public override void PrepareForRewrite(TypeDefinition typeDefinition, PropertyMetaData propertyMetadata, object outer) | ||||
|     { | ||||
|         base.PrepareForRewrite(typeDefinition, propertyMetadata, outer); | ||||
|         InnerProperty.PropertyDataType.PrepareForRewrite(typeDefinition, propertyMetadata, ""); | ||||
|  | ||||
|         // Ensure that IList<T> itself is imported. | ||||
|         CSharpType.ImportType(); | ||||
|  | ||||
|         InitializeMarshallerParameters(); | ||||
|  | ||||
|         // Instantiate generics for the direct access and copying marshallers. | ||||
|         string prefix = propertyMetadata.Name + "_"; | ||||
|         bool shouldUseCopyMarshaller; | ||||
|  | ||||
|         if (outer is MethodDefinition method) | ||||
|         { | ||||
|             // The property is a parameter to a method, use copy marshaller | ||||
|             prefix = method.Name + "_" + prefix; | ||||
|             shouldUseCopyMarshaller = true; | ||||
|         } | ||||
|         else if (typeDefinition.IsValueType) | ||||
|         { | ||||
|             // The property belongs to a struct, use copy marshaller | ||||
|             shouldUseCopyMarshaller = true; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // The property belongs to a class, use regular marshaller | ||||
|             shouldUseCopyMarshaller = false; | ||||
|         } | ||||
|  | ||||
|         FieldAttributes fieldAttributes = FieldAttributes.Private; | ||||
|         if (shouldUseCopyMarshaller) | ||||
|         { | ||||
|             TypeReference genericCopyMarshallerTypeRef = MarshallerAssembly.FindType(GetCopyContainerMarshallerName(), MarshallerNamespace)!; | ||||
|              | ||||
|             _containerMarshallerType = genericCopyMarshallerTypeRef.Resolve().MakeGenericInstanceType(ContainerMarshallerTypeParameters).ImportType(); | ||||
|              | ||||
|             _copyDestructInstance = _containerMarshallerType.Resolve().FindMethod("DestructInstance")!; | ||||
|             _copyDestructInstance = FunctionProcessor.MakeMethodDeclaringTypeGeneric(_copyDestructInstance, ContainerMarshallerTypeParameters); | ||||
|              | ||||
|             fieldAttributes |= FieldAttributes.Static; | ||||
|         } | ||||
|         else if (outer is FieldDefinition) | ||||
|         { | ||||
|             TypeReference genericCopyMarshallerTypeRef = MarshallerAssembly.FindType(GetCopyContainerMarshallerName(), MarshallerNamespace)!; | ||||
|             _containerMarshallerType = genericCopyMarshallerTypeRef.Resolve().MakeGenericInstanceType(ContainerMarshallerTypeParameters).ImportType(); | ||||
|              | ||||
|             fieldAttributes |= FieldAttributes.Static; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             TypeReference genericMarshallerTypeRef = MarshallerAssembly.FindType(GetContainerMarshallerName(), MarshallerNamespace)!; | ||||
|             _containerMarshallerType = genericMarshallerTypeRef.Resolve().MakeGenericInstanceType(ContainerMarshallerTypeParameters).ImportType(); | ||||
|  | ||||
|             if (propertyMetadata.MemberRef is PropertyDefinition propertyDefinition && !AllowsSetter) | ||||
|             { | ||||
|                 typeDefinition.Methods.Remove(propertyDefinition.SetMethod); | ||||
|                 propertyDefinition.SetMethod = null; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         TypeDefinition arrTypeDef = _containerMarshallerType.Resolve(); | ||||
|          | ||||
|         _containerMashallerCtor = arrTypeDef.GetConstructors().Single(); | ||||
|         _containerMashallerCtor = _containerMashallerCtor.ImportMethod(); | ||||
|         _containerMashallerCtor = FunctionProcessor.MakeMethodDeclaringTypeGeneric(_containerMashallerCtor, ContainerMarshallerTypeParameters); | ||||
|          | ||||
|         _fromNative = arrTypeDef.FindMethod("FromNative")!; | ||||
|         _fromNative = FunctionProcessor.MakeMethodDeclaringTypeGeneric(_fromNative, ContainerMarshallerTypeParameters); | ||||
|  | ||||
|         _toNative = arrTypeDef.FindMethod("ToNative")!; | ||||
|         _toNative = FunctionProcessor.MakeMethodDeclaringTypeGeneric(_toNative, ContainerMarshallerTypeParameters); | ||||
|          | ||||
|         _containerMarshallerField = typeDefinition.AddField(prefix + "Marshaller", _containerMarshallerType, fieldAttributes); | ||||
|         _nativePropertyField = propertyMetadata.NativePropertyField!; | ||||
|     } | ||||
|  | ||||
|     public override void WriteGetter(TypeDefinition type, MethodDefinition getter, Instruction[] loadBufferPtr, FieldDefinition? fieldDefinition) | ||||
|     { | ||||
|         ILProcessor processor = InitPropertyAccessor(getter); | ||||
|  | ||||
|         processor.Emit(OpCodes.Ldarg_0); | ||||
|         processor.Emit(OpCodes.Ldfld, _containerMarshallerField); | ||||
|  | ||||
|         // Save the position of the branch instruction for later, when we have a reference to its target. | ||||
|         processor.Emit(OpCodes.Ldarg_0); | ||||
|         Instruction branchPosition = processor.Body.Instructions[^1]; | ||||
|          | ||||
|         processor.Emit(OpCodes.Ldsfld, fieldDefinition); | ||||
|         EmitDynamicArrayMarshallerDelegates(processor, type); | ||||
|  | ||||
|         if (_containerMarshallerType == null) | ||||
|         { | ||||
|             throw new InvalidOperationException("Container marshaller type is null"); | ||||
|         } | ||||
|  | ||||
|         MethodDefinition? constructor = _containerMarshallerType.Resolve().GetConstructors().Single(); | ||||
|         processor.Emit(OpCodes.Newobj, FunctionProcessor.MakeMethodDeclaringTypeGeneric(WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference(constructor), ContainerMarshallerTypeParameters)); | ||||
|         processor.Emit(OpCodes.Stfld, _containerMarshallerField); | ||||
|  | ||||
|         // Store the branch destination | ||||
|         processor.Emit(OpCodes.Ldarg_0); | ||||
|         Instruction branchTarget = processor.Body.Instructions[^1]; | ||||
|         processor.Emit(OpCodes.Ldfld, _containerMarshallerField); | ||||
|          | ||||
|         foreach (Instruction inst in loadBufferPtr) | ||||
|         { | ||||
|             processor.Append(inst); | ||||
|         } | ||||
|          | ||||
|         processor.Emit(OpCodes.Ldc_I4_0); | ||||
|         processor.Emit(OpCodes.Call, _fromNative); | ||||
|          | ||||
|         //Now insert the branch | ||||
|         Instruction branchInstruction = processor.Create(OpCodes.Brtrue_S, branchTarget); | ||||
|         processor.InsertBefore(branchPosition, branchInstruction); | ||||
|  | ||||
|         getter.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public override void WriteSetter(TypeDefinition type, MethodDefinition setter, Instruction[] loadBufferPtr, | ||||
|         FieldDefinition? fieldDefinition) | ||||
|     { | ||||
|         ILProcessor processor = BeginSimpleSetter(setter); | ||||
|         Instruction loadValue = processor.Create(OpCodes.Ldarg_1); | ||||
|         WriteMarshalToNative(processor, type, loadBufferPtr, processor.Create(OpCodes.Ldc_I4_0), loadValue); | ||||
|         setter.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public override void WriteLoad(ILProcessor processor, TypeDefinition type, Instruction loadBufferInstruction, | ||||
|         FieldDefinition offsetField, VariableDefinition localVar) | ||||
|     { | ||||
|         WriteMarshalFromNative(processor, type, GetArgumentBufferInstructions(loadBufferInstruction, offsetField), processor.Create(OpCodes.Ldc_I4_0)); | ||||
|         processor.Emit(OpCodes.Stloc, localVar); | ||||
|     } | ||||
|      | ||||
|     public override void WriteLoad(ILProcessor processor, TypeDefinition type, Instruction loadBufferInstruction, | ||||
|         FieldDefinition offsetField, FieldDefinition destField) | ||||
|     { | ||||
|         processor.Emit(OpCodes.Ldarg_0); | ||||
|         WriteMarshalFromNative(processor, type, GetArgumentBufferInstructions(loadBufferInstruction, offsetField), processor.Create(OpCodes.Ldc_I4_0)); | ||||
|         processor.Emit(OpCodes.Stfld, destField); | ||||
|     } | ||||
|  | ||||
|     public override IList<Instruction>? WriteStore(ILProcessor processor, TypeDefinition type, | ||||
|         Instruction loadBufferInstruction, FieldDefinition offsetField, int argIndex, | ||||
|         ParameterDefinition paramDefinition) | ||||
|     { | ||||
|         Instruction[] loadSource = [processor.Create(OpCodes.Ldarg, argIndex)]; | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBufferInstruction, offsetField); | ||||
|         return WriteMarshalToNativeWithCleanup(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0), loadSource); | ||||
|     } | ||||
|      | ||||
|     public override IList<Instruction>? WriteStore(ILProcessor processor, TypeDefinition type, | ||||
|         Instruction loadBufferInstruction, FieldDefinition offsetField, FieldDefinition srcField) | ||||
|     { | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBufferInstruction, offsetField); | ||||
|         Instruction[] loadSource = [processor.Create(OpCodes.Ldarg_0), processor.Create(OpCodes.Ldfld, srcField)]; | ||||
|         return WriteMarshalToNativeWithCleanup(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0), loadSource); | ||||
|     } | ||||
|  | ||||
|     public override void WriteMarshalFromNative(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, Instruction loadContainerIndex) | ||||
|     { | ||||
|         WriteInitMarshaller(processor, type); | ||||
|         AppendLoadMarshallerInstructions(processor); | ||||
|          | ||||
|         foreach (Instruction inst in loadBufferPtr) | ||||
|         { | ||||
|             processor.Append(inst); | ||||
|         } | ||||
|          | ||||
|         processor.Emit(OpCodes.Ldc_I4_0); | ||||
|         processor.Emit(OpCodes.Call, _fromNative); | ||||
|     } | ||||
|  | ||||
|     public override void WriteMarshalToNative(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, | ||||
|         Instruction loadContainerIndex, Instruction[] loadSource) | ||||
|     { | ||||
|         WriteInitMarshaller(processor, type); | ||||
|         AppendLoadMarshallerInstructions(processor); | ||||
|          | ||||
|         foreach (var i in loadBufferPtr) | ||||
|         { | ||||
|             processor.Append(i); | ||||
|         } | ||||
|          | ||||
|         processor.Append(loadContainerIndex); | ||||
|          | ||||
|         foreach( var i in loadSource) | ||||
|         { | ||||
|             processor.Append(i); | ||||
|         } | ||||
|          | ||||
|         processor.Emit(OpCodes.Call, _toNative); | ||||
|     } | ||||
|  | ||||
|     public override IList<Instruction>? WriteMarshalToNativeWithCleanup(ILProcessor processor, TypeDefinition type, | ||||
|         Instruction[] loadBufferPtr, Instruction loadContainerIndex, Instruction[] loadSource) | ||||
|     { | ||||
|         WriteMarshalToNative(processor, type, loadBufferPtr, loadContainerIndex, loadSource); | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     private void WriteInitMarshaller(ILProcessor processor, TypeDefinition type) | ||||
|     { | ||||
|         AppendLoadMarshallerInstructions(processor); | ||||
|          | ||||
|         Instruction branchTarget = processor.Create(OpCodes.Nop); | ||||
|         processor.Emit(OpCodes.Brtrue_S, branchTarget); | ||||
|          | ||||
|         processor.Emit(OpCodes.Ldarg_0); | ||||
|         processor.Emit(OpCodes.Ldsfld, _nativePropertyField); | ||||
|          | ||||
|         EmitDynamicArrayMarshallerDelegates(processor, type); | ||||
|          | ||||
|         processor.Emit(OpCodes.Newobj, _containerMashallerCtor); | ||||
|         processor.Emit(OpCodes.Stfld, _containerMarshallerField); | ||||
|          | ||||
|         processor.Append(branchTarget); | ||||
|     } | ||||
|      | ||||
|     private void AppendLoadMarshallerInstructions(ILProcessor processor) | ||||
|     { | ||||
|         if (_containerMarshallerField == null) | ||||
|         { | ||||
|             throw new InvalidOperationException("Container marshaller field is null"); | ||||
|         } | ||||
|          | ||||
|         if (_containerMarshallerField.IsStatic) | ||||
|         {   | ||||
|             processor.Emit(OpCodes.Ldsfld, _containerMarshallerField); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             processor.Emit(OpCodes.Ldarg_0); | ||||
|             processor.Emit(OpCodes.Ldfld, _containerMarshallerField); | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,23 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
| class NativeDataCoreStructType : NativeDataBlittableStructTypeBase | ||||
| {  | ||||
|     public NativeDataCoreStructType(TypeReference structType, int arrayDim) : base(structType, arrayDim) | ||||
|     { | ||||
|         var innerPropertyName = structType.Name switch | ||||
|         { | ||||
|             "Quaternion" => "Quat", | ||||
|             "Vector2" => "Vector2D", | ||||
|             "Vector3" => "Vector", | ||||
|             "Matrix4x4" => "Matrix", | ||||
|             _ => structType.Name | ||||
|         }; | ||||
|  | ||||
|         InnerType = new TypeReferenceMetadata(structType.Resolve()) | ||||
|         { | ||||
|             Name = innerPropertyName | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.TypeProcessors; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| public class NativeDataDelegateType : NativeDataBaseDelegateType | ||||
| { | ||||
|     public NativeDataDelegateType(TypeReference type) : base(type, "SingleDelegateMarshaller`1", PropertyType.Delegate) | ||||
|     { | ||||
|  | ||||
|     } | ||||
|      | ||||
|     public override void WritePostInitialization(ILProcessor processor, PropertyMetaData propertyMetadata, Instruction loadNativePointer, Instruction setNativePointer) | ||||
|     { | ||||
|         if (!Signature.HasParameters) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         if (propertyMetadata.MemberRef is not PropertyDefinition) | ||||
|         { | ||||
|             VariableDefinition propertyPointer = processor.Body.Method.AddLocalVariable(WeaverImporter.Instance.IntPtrType); | ||||
|             processor.Append(loadNativePointer); | ||||
|             processor.Emit(OpCodes.Ldstr, propertyMetadata.Name); | ||||
|             processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetNativePropertyFromNameMethod); | ||||
|             processor.Emit(OpCodes.Stloc, propertyPointer); | ||||
|             processor.Emit(OpCodes.Ldloc, propertyPointer); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             processor.Append(loadNativePointer); | ||||
|         } | ||||
|          | ||||
|         MethodReference initialize = UnrealDelegateProcessor.FindOrCreateInitializeDelegate(wrapperType.Resolve()); | ||||
|         processor.Emit(OpCodes.Call, initialize); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataEnumType(TypeReference typeRef, int arrayDim) : NativeDataSimpleType(typeRef, "EnumMarshaller`1", arrayDim, PropertyType.Enum) | ||||
| { | ||||
|     public TypeReferenceMetadata InnerProperty { get; set; } = new(typeRef.Resolve()); | ||||
|  | ||||
|     public override void PrepareForRewrite(TypeDefinition typeDefinition, | ||||
|         PropertyMetaData propertyMetadata, object outer) | ||||
|     { | ||||
|         base.PrepareForRewrite(typeDefinition, propertyMetadata, outer); | ||||
|          | ||||
|         if (!InnerProperty.TypeRef.Resolve().IsUEnum()) | ||||
|         { | ||||
|             throw new Exception($"{propertyMetadata.MemberRef!.FullName} needs to be a UEnum if exposed through UProperty!"); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| @ -0,0 +1,22 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| abstract class NativeDataGenericObjectType(TypeReference typeRef, TypeReference innerTypeReference, string marshallerClass, int arrayDim, PropertyType propertyType) | ||||
|     : NativeDataSimpleType(typeRef, marshallerClass, arrayDim, propertyType) | ||||
| { | ||||
|     public TypeReferenceMetadata InnerType { get; set; } = new(innerTypeReference.Resolve()); | ||||
|  | ||||
|     public override void PrepareForRewrite(TypeDefinition typeDefinition, PropertyMetaData propertyMetadata, | ||||
|         object outer) | ||||
|     { | ||||
|         base.PrepareForRewrite(typeDefinition, propertyMetadata, outer); | ||||
|          | ||||
|         if (!InnerType.TypeRef.Resolve().IsUObject()) | ||||
|         { | ||||
|             throw new Exception($"{propertyMetadata.MemberRef!.FullName} needs to be a UClass if exposed through UProperty!"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| public class NativeDataInterfaceType : NativeDataSimpleType | ||||
| { | ||||
|     public NativeDataInterfaceType(TypeReference typeRef, string marshallerName) : base(typeRef, marshallerName, 0, PropertyType.ScriptInterface) | ||||
|     { | ||||
|         InnerType = new TypeReferenceMetadata(typeRef.Resolve()); | ||||
|     } | ||||
|  | ||||
|     public override void PrepareForRewrite(TypeDefinition typeDefinition, PropertyMetaData propertyMetadata, object outer) | ||||
|     { | ||||
|         TypeDefinition interfaceTypeDef = InnerType.TypeRef.Resolve(); | ||||
|         if (!interfaceTypeDef.IsUInterface()) | ||||
|         { | ||||
|             throw new Exception($"{interfaceTypeDef.FullName} needs to be a UInterface if exposed to Unreal Engine's reflection system!"); | ||||
|         } | ||||
|          | ||||
|         base.PrepareForRewrite(typeDefinition, propertyMetadata, outer); | ||||
|     } | ||||
|  | ||||
|     public TypeReferenceMetadata InnerType { get; set; } | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| public class NativeDataManagedObjectType(TypeReference managedType, int arrayDim) : NativeDataSimpleType(managedType, "ManagedObjectMarshaller`1", arrayDim, PropertyType.Struct) | ||||
| { | ||||
|     public TypeReferenceMetadata InnerType { get; set; } =  new(WeaverImporter.Instance.ManagedObjectHandle.Resolve()); | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| public class NativeDataMapType : NativeDataContainerType | ||||
| { | ||||
|     public PropertyMetaData ValueProperty { get; set; } | ||||
|      | ||||
|     public NativeDataMapType(TypeReference typeRef, int arrayDim, TypeReference key, TypeReference value) : base(typeRef, arrayDim, PropertyType.Map, key) | ||||
|     { | ||||
|         ValueProperty = PropertyMetaData.FromTypeReference(value, "Value"); | ||||
|         IsNetworkSupported = false; | ||||
|     } | ||||
|  | ||||
|     public override string GetContainerMarshallerName() | ||||
|     { | ||||
|         return "MapMarshaller`2"; | ||||
|     } | ||||
|  | ||||
|     public override string GetCopyContainerMarshallerName() | ||||
|     { | ||||
|         return "MapCopyMarshaller`2"; | ||||
|     } | ||||
|  | ||||
|     public override void EmitDynamicArrayMarshallerDelegates(ILProcessor processor, TypeDefinition type) | ||||
|     { | ||||
|         base.EmitDynamicArrayMarshallerDelegates(processor, type); | ||||
|         ValueProperty.PropertyDataType.EmitDynamicArrayMarshallerDelegates(processor, type); | ||||
|     } | ||||
|  | ||||
|     public override void PrepareForRewrite(TypeDefinition typeDefinition, PropertyMetaData propertyMetadata, | ||||
|         object outer) | ||||
|     { | ||||
|         base.PrepareForRewrite(typeDefinition, propertyMetadata, outer); | ||||
|         ValueProperty.PropertyDataType.PrepareForRewrite(typeDefinition, propertyMetadata, ""); | ||||
|     } | ||||
|  | ||||
|     public override void InitializeMarshallerParameters() | ||||
|     { | ||||
|         ContainerMarshallerTypeParameters = | ||||
|         [ | ||||
|             InnerProperty.PropertyDataType.CSharpType.ImportType(), | ||||
|             ValueProperty.PropertyDataType.CSharpType.ImportType() | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     public override string GetContainerWrapperType() | ||||
|     { | ||||
|         return "System.Collections.Generic.IDictionary`2"; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataNameType(TypeReference structType, int arrayDim) : NativeDataBlittableStructTypeBase(structType, arrayDim, PropertyType.Name); | ||||
| @ -0,0 +1,28 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataNativeArrayType(TypeReference typeRef, int containerDim, TypeReference innerType) | ||||
|     : NativeDataContainerType(typeRef, containerDim, PropertyType.Array, innerType) | ||||
| { | ||||
|     public override string GetContainerMarshallerName() | ||||
|     { | ||||
|         return "NativeArrayMarshaller`1"; | ||||
|     } | ||||
|  | ||||
|     public override string GetCopyContainerMarshallerName() | ||||
|     { | ||||
|         return "NativeArrayCopyMarshaller`1"; | ||||
|     } | ||||
|  | ||||
|     public override string GetContainerWrapperType() | ||||
|     { | ||||
|         return "System.ReadOnlySpan`1"; | ||||
|     } | ||||
|  | ||||
|     public override void EmitDynamicArrayMarshallerDelegates(ILProcessor processor, TypeDefinition type) { } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataObjectType(TypeReference propertyTypeRef, TypeReference innerTypeReference, int arrayDim)  | ||||
|     : NativeDataGenericObjectType(propertyTypeRef, innerTypeReference, "ObjectMarshaller`1", arrayDim, PropertyType.Object); | ||||
| @ -0,0 +1,25 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| internal class NativeDataOptionalType(TypeReference propertyTypeRef, TypeReference innerTypeReference, int arrayDim) | ||||
|     : NativeDataContainerType(propertyTypeRef, arrayDim, PropertyType.Optional, innerTypeReference) | ||||
|     { | ||||
|          | ||||
|         protected override AssemblyDefinition MarshallerAssembly => WeaverImporter.Instance.UnrealSharpCoreAssembly; | ||||
|         protected override string MarshallerNamespace => WeaverImporter.UnrealSharpCoreMarshallers; | ||||
|          | ||||
|         protected override bool AllowsSetter => true; | ||||
|          | ||||
|     public override string GetContainerMarshallerName() | ||||
|     { | ||||
|         return "OptionalMarshaller`1"; | ||||
|     } | ||||
|  | ||||
|     public override string GetCopyContainerMarshallerName() | ||||
|     { | ||||
|         return "OptionalMarshaller`1"; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| public class NativeDataSetType : NativeDataContainerType | ||||
| { | ||||
|     public NativeDataSetType(TypeReference typeRef, int containerDim, TypeReference value)  | ||||
|         : base(typeRef, containerDim, PropertyType.Set, value) | ||||
|     { | ||||
|     } | ||||
|      | ||||
|     public override string GetContainerMarshallerName() | ||||
|     { | ||||
|         return "SetMarshaller`1"; | ||||
|     } | ||||
|  | ||||
|     public override string GetCopyContainerMarshallerName() | ||||
|     { | ||||
|         return "SetCopyMarshaller`1"; | ||||
|     } | ||||
|  | ||||
|     public override string GetContainerWrapperType() | ||||
|     { | ||||
|         return "System.Collections.Generic.ISet`1"; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,220 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.TypeProcessors; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
| using OpCodes = Mono.Cecil.Cil.OpCodes; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| public abstract class NativeDataSimpleType(TypeReference typeRef, string marshallerName, int arrayDim, PropertyType propertyType)  | ||||
|     : NativeDataType(typeRef, arrayDim, propertyType) | ||||
| { | ||||
|     protected TypeReference? MarshallerClass; | ||||
|     protected MethodReference? ToNative; | ||||
|     protected MethodReference? FromNative; | ||||
|      | ||||
|     private bool _isReference; | ||||
|     private AssemblyDefinition? _assembly; | ||||
|      | ||||
|     public override bool IsPlainOldData => true; | ||||
|  | ||||
|     protected virtual TypeReference[] GetTypeParams() | ||||
|     { | ||||
|         return [CSharpType.ImportType()]; | ||||
|     } | ||||
|  | ||||
|     public override void PrepareForRewrite(TypeDefinition typeDefinition, PropertyMetaData propertyMetadata, | ||||
|         object outer) | ||||
|     { | ||||
|         base.PrepareForRewrite(typeDefinition, propertyMetadata, outer); | ||||
|         _isReference = propertyMetadata.IsOutParameter; | ||||
|         var isGenericMarshaller = marshallerName.Contains('`'); | ||||
|  | ||||
|         TypeReference[] typeParams = GetTypeParams(); | ||||
|  | ||||
|         bool FindMarshaller(AssemblyDefinition assembly) | ||||
|         { | ||||
|             TypeReference? foundMarshaller = isGenericMarshaller | ||||
|                 ? assembly.FindGenericType("", marshallerName, typeParams, false)  | ||||
|                 : GetTypeInAssembly(assembly); | ||||
|  | ||||
|             if (foundMarshaller is null) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|              | ||||
|             MarshallerClass = foundMarshaller; | ||||
|             _assembly = assembly; | ||||
|             return false; | ||||
|  | ||||
|         } | ||||
|  | ||||
|         FindMarshaller(CSharpType.Module.Assembly); | ||||
|  | ||||
|         if (MarshallerClass is null || _assembly is null) | ||||
|         { | ||||
|             AssemblyUtilities.ForEachAssembly(FindMarshaller); | ||||
|         } | ||||
|          | ||||
|         if (MarshallerClass is null) | ||||
|         { | ||||
|             throw new Exception($"Could not find marshaller class {marshallerName} for type {CSharpType.FullName}"); | ||||
|         } | ||||
|          | ||||
|         var marshallerTypeDefinition = GetMarshallerTypeDefinition(); | ||||
|         ToNative = marshallerTypeDefinition.FindMethod("ToNative")!; | ||||
|         FromNative = marshallerTypeDefinition.FindMethod("FromNative")!; | ||||
|          | ||||
|         if (isGenericMarshaller) | ||||
|         { | ||||
|             ToNative = FunctionProcessor.MakeMethodDeclaringTypeGeneric(ToNative, typeParams); | ||||
|             FromNative = FunctionProcessor.MakeMethodDeclaringTypeGeneric(FromNative, typeParams); | ||||
|         } | ||||
|          | ||||
|         ToNative = ToNative.ImportMethod(); | ||||
|         FromNative = FromNative.ImportMethod(); | ||||
|     } | ||||
|  | ||||
|     public override void WriteGetter(TypeDefinition type, MethodDefinition getter, Instruction[] loadBufferPtr, FieldDefinition? fieldDefinition) | ||||
|     { | ||||
|         ILProcessor processor = BeginSimpleGetter(getter); | ||||
|         WriteMarshalFromNative(processor, type, loadBufferPtr, processor.Create(OpCodes.Ldc_I4_0)); | ||||
|         getter.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public override void WriteSetter(TypeDefinition type, MethodDefinition setter, Instruction[] loadBufferPtr, FieldDefinition? fieldDefinition) | ||||
|     { | ||||
|         ILProcessor processor = BeginSimpleSetter(setter); | ||||
|         Instruction loadValue = processor.Create(_isReference ? OpCodes.Ldarga : OpCodes.Ldarg, 1); | ||||
|         WriteMarshalToNative(processor, type, loadBufferPtr, processor.Create(OpCodes.Ldc_I4_0), loadValue); | ||||
|         setter.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public override void WriteLoad(ILProcessor processor, TypeDefinition type, Instruction loadBuffer, FieldDefinition offsetField, VariableDefinition localVar) | ||||
|     { | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBuffer, offsetField); | ||||
|         WriteMarshalFromNative(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0)); | ||||
|         processor.Emit(OpCodes.Stloc, localVar); | ||||
|     } | ||||
|  | ||||
|     public override void WriteLoad(ILProcessor processor, TypeDefinition type, Instruction loadBuffer, FieldDefinition offsetField, FieldDefinition destField) | ||||
|     { | ||||
|         processor.Emit(OpCodes.Ldarg_0); | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBuffer, offsetField); | ||||
|         WriteMarshalFromNative(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0)); | ||||
|         processor.Emit(OpCodes.Stfld, destField); | ||||
|     } | ||||
|  | ||||
|     public override IList<Instruction>? WriteStore(ILProcessor processor, TypeDefinition type, Instruction loadBuffer, FieldDefinition offsetField, int argIndex, ParameterDefinition paramDefinition) | ||||
|     { | ||||
|         // Load parameter index onto the stack. First argument is always 1, because 0 is the instance. | ||||
|         List<Instruction> source = [processor.Create(OpCodes.Ldarg, argIndex)]; | ||||
|          | ||||
|         if (_isReference) | ||||
|         { | ||||
|             Instruction loadInstructionOutParam = paramDefinition.CreateLoadInstructionOutParam(PropertyType); | ||||
|             source.Add(loadInstructionOutParam); | ||||
|         } | ||||
|          | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBuffer, offsetField); | ||||
|          | ||||
|         return WriteMarshalToNativeWithCleanup(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0), source.ToArray()); | ||||
|     } | ||||
|  | ||||
|     public override IList<Instruction>? WriteStore(ILProcessor processor, TypeDefinition type, Instruction loadBuffer, FieldDefinition offsetField, FieldDefinition srcField) | ||||
|     { | ||||
|         Instruction[] loadField = | ||||
|         [ | ||||
|             processor.Create(OpCodes.Ldarg_0), | ||||
|             processor.Create(_isReference ? OpCodes.Ldflda : OpCodes.Ldfld, srcField) | ||||
|         ]; | ||||
|          | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBuffer, offsetField); | ||||
|         return WriteMarshalToNativeWithCleanup(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0), loadField); | ||||
|     } | ||||
|      | ||||
|     public override void WriteMarshalToNative(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, Instruction loadArrayIndex, Instruction[] loadSource) | ||||
|     { | ||||
|         foreach (var i in loadBufferPtr) | ||||
|         { | ||||
|             processor.Append(i); | ||||
|         } | ||||
|          | ||||
|         processor.Append(loadArrayIndex); | ||||
|          | ||||
|         foreach (Instruction i in loadSource) | ||||
|         { | ||||
|             processor.Append(i); | ||||
|         } | ||||
|          | ||||
|         processor.Emit(OpCodes.Call, ToNative); | ||||
|     } | ||||
|  | ||||
|     public override void WriteMarshalFromNative(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, | ||||
|         Instruction loadArrayIndex) | ||||
|     { | ||||
|         foreach (var i in loadBufferPtr) | ||||
|         { | ||||
|             processor.Append(i); | ||||
|         } | ||||
|          | ||||
|         processor.Append(loadArrayIndex); | ||||
|         processor.Emit(OpCodes.Call, FromNative); | ||||
|     } | ||||
|  | ||||
|     public override void EmitFixedArrayMarshallerDelegates(ILProcessor processor, TypeDefinition type) | ||||
|     { | ||||
|         TypeReference[] typeParams = []; | ||||
|          | ||||
|         if (marshallerName.EndsWith("`1")) | ||||
|         { | ||||
|             if (!CSharpType.IsGenericInstance) | ||||
|             { | ||||
|                 typeParams = [CSharpType]; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 GenericInstanceType generic = (GenericInstanceType)CSharpType; | ||||
|                 typeParams = [WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference(generic.GenericArguments[0].Resolve())]; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         EmitSimpleMarshallerDelegates(processor, marshallerName, typeParams); | ||||
|     } | ||||
|  | ||||
|     private TypeReference? GetTypeInAssembly(AssemblyDefinition assemblyDefinition) | ||||
|     { | ||||
|         // Try to find the marshaller in the bindings again, but with the namespace of the property type. | ||||
|         TypeDefinition? propType = CSharpType.Resolve(); | ||||
|         TypeReference? type = assemblyDefinition.FindType(marshallerName, propType.Namespace, false); | ||||
|         if (type is not null) | ||||
|         { | ||||
|             return type; | ||||
|         } | ||||
|          | ||||
|         // Try to find the marshaller in the bindings assembly. These are unique so we don't need to check the namespace. | ||||
|         TypeReference? typeInBindingAssembly = assemblyDefinition.FindType(marshallerName, "", false); | ||||
|         if (typeInBindingAssembly is not null) | ||||
|         { | ||||
|             return typeInBindingAssembly; | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     private TypeDefinition GetMarshallerTypeDefinition() | ||||
|     { | ||||
|         if (MarshallerClass is null) | ||||
|         { | ||||
|             throw new Exception($"Marshaller class is null for type {CSharpType.FullName}"); | ||||
|         } | ||||
|          | ||||
|         if (_assembly is null) | ||||
|         { | ||||
|             throw new Exception($"Could not find assembly for marshaller {MarshallerClass.Name}"); | ||||
|         } | ||||
|          | ||||
|         return GetMarshallerTypeDefinition(_assembly, MarshallerClass); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataSoftClassType(TypeReference typeRef, TypeReference innerTypeReference, int arrayDim)  | ||||
|     : NativeDataClassBaseType(typeRef, innerTypeReference, arrayDim, "SoftClassMarshaller`1", PropertyType.SoftClass); | ||||
| @ -0,0 +1,6 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataSoftObjectType(TypeReference typeRef, TypeReference innerTypeReference, int arrayDim) | ||||
|     : NativeDataClassBaseType(typeRef, innerTypeReference, arrayDim, "SoftObjectMarshaller`1", PropertyType.SoftObject); | ||||
| @ -0,0 +1,142 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataStringType(TypeReference typeRef, int arrayDim) : NativeDataType(typeRef, arrayDim, PropertyType.String) | ||||
| { | ||||
|     private static MethodReference? _toNative; | ||||
|     private static MethodReference? _fromNative; | ||||
|     private static MethodReference? _destructInstance; | ||||
|     private static AssemblyDefinition? _userAssembly; | ||||
|  | ||||
|     public override void PrepareForRewrite(TypeDefinition typeDefinition, PropertyMetaData propertyMetadata, | ||||
|         object outer) | ||||
|     { | ||||
|         base.PrepareForRewrite(typeDefinition, propertyMetadata, ""); | ||||
|          | ||||
|         if (IsInitialized()) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         TypeDefinition marshallerType = WeaverImporter.Instance.UnrealSharpCoreAssembly.FindType("StringMarshaller", WeaverImporter.UnrealSharpCoreMarshallers)!.Resolve(); | ||||
|         _toNative = marshallerType.FindMethod("ToNative")!; | ||||
|         _fromNative = marshallerType.FindMethod("FromNative")!; | ||||
|         _destructInstance = marshallerType.FindMethod("DestructInstance")!; | ||||
|     } | ||||
|  | ||||
|     public override void EmitFixedArrayMarshallerDelegates(ILProcessor processor, TypeDefinition type) | ||||
|     { | ||||
|         EmitSimpleMarshallerDelegates(processor, "StringMarshaller", null); | ||||
|     } | ||||
|  | ||||
|     public override void WriteGetter(TypeDefinition type, MethodDefinition getter, Instruction[] loadBufferPtr, | ||||
|         FieldDefinition? fieldDefinition) | ||||
|     { | ||||
|         ILProcessor processor = BeginSimpleGetter(getter); | ||||
|         WriteMarshalFromNative(processor, type, loadBufferPtr, processor.Create(OpCodes.Ldc_I4_0)); | ||||
|         getter.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public override void WriteSetter(TypeDefinition type, MethodDefinition setter, Instruction[] loadBufferPtr, | ||||
|         FieldDefinition? fieldDefinition) | ||||
|     { | ||||
|         ILProcessor processor = BeginSimpleSetter(setter); | ||||
|         Instruction loadValue = processor.Create(OpCodes.Ldarg_1); | ||||
|         WriteMarshalToNative(processor, type, loadBufferPtr, processor.Create(OpCodes.Ldc_I4_0), loadValue); | ||||
|         setter.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public override void WriteLoad(ILProcessor processor, TypeDefinition type, Instruction loadBuffer, FieldDefinition offsetField, VariableDefinition localVar) | ||||
|     { | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBuffer, offsetField); | ||||
|         WriteMarshalFromNative(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0)); | ||||
|         processor.Emit(OpCodes.Stloc, localVar); | ||||
|     } | ||||
|      | ||||
|     public override void WriteLoad(ILProcessor processor, TypeDefinition type, Instruction loadBuffer, FieldDefinition offsetField, FieldDefinition destField) | ||||
|     { | ||||
|         processor.Emit(OpCodes.Ldarg_0); | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBuffer, offsetField); | ||||
|         WriteMarshalFromNative(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0)); | ||||
|         processor.Emit(OpCodes.Stfld, destField); | ||||
|     } | ||||
|  | ||||
|     public override void WriteMarshalToNative(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, Instruction loadArrayIndex, Instruction[] loadSourceInstructions) | ||||
|     { | ||||
|         foreach (var i in loadBufferPtr) | ||||
|         { | ||||
|             processor.Append(i); | ||||
|         } | ||||
|         processor.Append(loadArrayIndex); | ||||
|         foreach (Instruction i in loadSourceInstructions) | ||||
|         { | ||||
|             processor.Append(i); | ||||
|         } | ||||
|         processor.Emit(OpCodes.Call, _toNative); | ||||
|     } | ||||
|  | ||||
|     public override void WriteMarshalFromNative(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, Instruction loadArrayIndex) | ||||
|     { | ||||
|         foreach (var i in loadBufferPtr) | ||||
|         { | ||||
|             processor.Append(i); | ||||
|         } | ||||
|         processor.Append(loadArrayIndex); | ||||
|         processor.Emit(OpCodes.Call, _fromNative); | ||||
|     } | ||||
|  | ||||
|     public override IList<Instruction>? WriteMarshalToNativeWithCleanup(ILProcessor processor, TypeDefinition type, | ||||
|         Instruction[] loadBufferPtr, Instruction loadArrayIndex, | ||||
|         Instruction[] loadSourceInstructions) | ||||
|     { | ||||
|         WriteMarshalToNative(processor, type, loadBufferPtr, loadArrayIndex, loadSourceInstructions); | ||||
|  | ||||
|         Instruction offsteField = loadBufferPtr[1]; | ||||
|         IList<Instruction> cleanupInstructions = new List<Instruction>(); ; | ||||
|         cleanupInstructions.Add(Instruction.Create(OpCodes.Ldloc_1)); | ||||
|         cleanupInstructions.Add(offsteField); | ||||
|         cleanupInstructions.Add(Instruction.Create(OpCodes.Call, WeaverImporter.Instance.IntPtrAdd)); | ||||
|         cleanupInstructions.Add(loadArrayIndex); | ||||
|         cleanupInstructions.Add(processor.Create(OpCodes.Call, _destructInstance)); | ||||
|          | ||||
|         return cleanupInstructions; | ||||
|     } | ||||
|  | ||||
|     public override IList<Instruction>? WriteStore(ILProcessor processor, TypeDefinition type, Instruction loadBuffer, | ||||
|         FieldDefinition offsetField, int argIndex, ParameterDefinition paramDefinition) | ||||
|     { | ||||
|         Instruction[] loadSource = argIndex switch | ||||
|         { | ||||
|             0 => [processor.Create(OpCodes.Ldarg_0)], | ||||
|             1 => [processor.Create(OpCodes.Ldarg_1)], | ||||
|             2 => [processor.Create(OpCodes.Ldarg_2)], | ||||
|             3 => [processor.Create(OpCodes.Ldarg_3)], | ||||
|             _ => [processor.Create(OpCodes.Ldarg_S, (byte)argIndex)], | ||||
|         }; | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBuffer, offsetField); | ||||
|         return WriteMarshalToNativeWithCleanup(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0), loadSource); | ||||
|     } | ||||
|     public override IList<Instruction>? WriteStore(ILProcessor processor, TypeDefinition type, Instruction loadBuffer, FieldDefinition offsetField, FieldDefinition srcField) | ||||
|     { | ||||
|         Instruction[] loadSource =  | ||||
|         { | ||||
|             processor.Create(OpCodes.Ldarg_0), | ||||
|             processor.Create(OpCodes.Ldfld, srcField), | ||||
|         }; | ||||
|          | ||||
|         Instruction[] loadBufferInstructions = GetArgumentBufferInstructions(loadBuffer, offsetField); | ||||
|         return WriteMarshalToNativeWithCleanup(processor, type, loadBufferInstructions, processor.Create(OpCodes.Ldc_I4_0), loadSource); | ||||
|     } | ||||
|  | ||||
|     private static bool IsInitialized() | ||||
|     { | ||||
|         if (ReferenceEquals(_userAssembly, WeaverImporter.Instance.CurrentWeavingAssembly)) return true; | ||||
|          | ||||
|         _userAssembly = WeaverImporter.Instance.CurrentWeavingAssembly; | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataStructType(TypeReference structType, string marshallerName, int arrayDim, PropertyType propertyType = PropertyType.Struct)  | ||||
|     : NativeDataSimpleType(structType, marshallerName, arrayDim, propertyType) | ||||
| { | ||||
|     public TypeReferenceMetadata InnerType { get; set; } = new(structType.Resolve()); | ||||
|  | ||||
|     public override void PrepareForRewrite(TypeDefinition typeDefinition, | ||||
|         PropertyMetaData propertyMetadata, object outer) | ||||
|     { | ||||
|         base.PrepareForRewrite(typeDefinition, propertyMetadata, outer); | ||||
|  | ||||
|         if (!InnerType.TypeRef.Resolve().IsUStruct()) | ||||
|         { | ||||
|             throw new Exception($"{propertyMetadata.MemberRef!.FullName} needs to be a UStruct if exposed through UProperty!"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataTextType(TypeReference textType) : NativeDataSimpleType(textType, "TextMarshaller", 1, PropertyType.Text); | ||||
| @ -0,0 +1,277 @@ | ||||
| using System.Text.Json.Serialization; | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.TypeProcessors; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
| using OpCodes = Mono.Cecil.Cil.OpCodes; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| [JsonDerivedType(typeof(NativeDataEnumType))] | ||||
| [JsonDerivedType(typeof(NativeDataNameType))] | ||||
| [JsonDerivedType(typeof(NativeDataTextType))] | ||||
| [JsonDerivedType(typeof(NativeDataArrayType))] | ||||
| [JsonDerivedType(typeof(NativeDataNativeArrayType))] | ||||
| [JsonDerivedType(typeof(NativeDataClassBaseType))] | ||||
| [JsonDerivedType(typeof(NativeDataObjectType))] | ||||
| [JsonDerivedType(typeof(NativeDataStringType))] | ||||
| [JsonDerivedType(typeof(NativeDataStructType))] | ||||
| [JsonDerivedType(typeof(NativeDataBooleanType))] | ||||
| [JsonDerivedType(typeof(NativeDataBuiltinType))] | ||||
| [JsonDerivedType(typeof(NativeDataCoreStructType))] | ||||
| [JsonDerivedType(typeof(NativeDataWeakObjectType))] | ||||
| [JsonDerivedType(typeof(NativeDataMulticastDelegate))] | ||||
| [JsonDerivedType(typeof(NativeDataBlittableStructType))] | ||||
| [JsonDerivedType(typeof(NativeDataDefaultComponent))] | ||||
| [JsonDerivedType(typeof(NativeDataSoftObjectType))] | ||||
| [JsonDerivedType(typeof(NativeDataSoftClassType))] | ||||
| [JsonDerivedType(typeof(NativeDataDelegateType))] | ||||
| [JsonDerivedType(typeof(NativeDataMapType))] | ||||
| [JsonDerivedType(typeof(NativeDataSetType))] | ||||
| [JsonDerivedType(typeof(NativeDataClassType))] | ||||
| [JsonDerivedType(typeof(NativeDataInterfaceType))] | ||||
| [JsonDerivedType(typeof(NativeDataOptionalType))] | ||||
| [JsonDerivedType(typeof(NativeDataManagedObjectType))] | ||||
| [JsonDerivedType(typeof(NativeDataUnmanagedType))] | ||||
| public abstract class NativeDataType | ||||
| { | ||||
|     internal TypeReference CSharpType { get; set; } | ||||
|     public int ArrayDim { get; set; } | ||||
|     public PropertyType PropertyType { get; set; } | ||||
|     public virtual bool IsBlittable => false; | ||||
|     public virtual bool IsPlainOldData => false; | ||||
|     public bool IsNetworkSupported = true; | ||||
|      | ||||
|     protected FieldDefinition? BackingField; | ||||
|      | ||||
|     public bool NeedsNativePropertyField { get; set; } | ||||
|      | ||||
|     private TypeReference? ToNativeDelegateType; | ||||
|     private TypeReference? FromNativeDelegateType; | ||||
|     // End non-json properties | ||||
|      | ||||
|     public NativeDataType(TypeReference typeRef, int arrayDim, PropertyType propertyType = PropertyType.Unknown) | ||||
|     { | ||||
|         if (typeRef.IsByReference) | ||||
|         { | ||||
|             typeRef = typeRef.GetElementType(); | ||||
|         } | ||||
|          | ||||
|         CSharpType = typeRef.ImportType(); | ||||
|         ArrayDim = arrayDim; | ||||
|         PropertyType = propertyType; | ||||
|     } | ||||
|  | ||||
|     protected static ILProcessor InitPropertyAccessor(MethodDefinition method) | ||||
|     { | ||||
|         method.Body = new MethodBody(method); | ||||
|         method.CustomAttributes.Clear(); | ||||
|         ILProcessor processor = method.Body.GetILProcessor(); | ||||
|         method.Body.Instructions.Clear(); | ||||
|         return processor; | ||||
|     } | ||||
|      | ||||
|     protected void AddBackingField(TypeDefinition type, PropertyMetaData propertyMetaData) | ||||
|     { | ||||
|         if (BackingField != null) | ||||
|         { | ||||
|             throw new Exception($"Backing field already exists for {propertyMetaData.Name} in {type.FullName}"); | ||||
|         } | ||||
|          | ||||
|         BackingField = type.AddField($"{propertyMetaData.Name}_BackingField", CSharpType, FieldAttributes.Private); | ||||
|     } | ||||
|      | ||||
|     public static Instruction[] GetArgumentBufferInstructions(Instruction? loadBufferInstruction, FieldDefinition offsetField) | ||||
|     { | ||||
|         List<Instruction> instructionBuffer = []; | ||||
|          | ||||
|         if (loadBufferInstruction != null) | ||||
|         { | ||||
|             instructionBuffer.Add(loadBufferInstruction); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             instructionBuffer.Add(Instruction.Create(OpCodes.Ldarg_0)); | ||||
|             instructionBuffer.Add(Instruction.Create(OpCodes.Call, WeaverImporter.Instance.NativeObjectGetter)); | ||||
|         } | ||||
|  | ||||
|         instructionBuffer.Add(Instruction.Create(OpCodes.Ldsfld, offsetField));   | ||||
|         instructionBuffer.Add(Instruction.Create(OpCodes.Call, WeaverImporter.Instance.IntPtrAdd)); | ||||
|  | ||||
|         return instructionBuffer.ToArray(); | ||||
|     } | ||||
|  | ||||
|     protected static ILProcessor BeginSimpleGetter(MethodDefinition getter) | ||||
|     { | ||||
|         ILProcessor processor = InitPropertyAccessor(getter); | ||||
|         /* | ||||
|         .method public hidebysig specialname instance int32 | ||||
|                 get_TestReadableInt32() cil managed | ||||
|         { | ||||
|           // Code size       25 (0x19) | ||||
|           .maxstack  2 | ||||
|           .locals init ([0] int32 ToReturn) | ||||
|           IL_0000:  ldarg.0 | ||||
|           IL_0001:  call       instance native int [UnrealEngine.Runtime]UnrealEngine.Runtime.UnrealObject::get_NativeObject() | ||||
|           IL_0006:  ldsfld     int32 UnrealEngine.MonoRuntime.MonoTestsObject::TestReadableInt32_Offset | ||||
|           IL_000b:  call       native int [mscorlib]System.IntPtr::Add(native int, | ||||
|                                                                        int32) | ||||
|           IL_0010:  call       void* [mscorlib]System.IntPtr::op_Explicit(native int) | ||||
|           IL_0015:  ldind.i4 | ||||
|           IL_0018:  ret | ||||
|         } // end of method MonoTestsObject::get_TestReadableInt32 | ||||
|          */ | ||||
|         return processor; | ||||
|     } | ||||
|      | ||||
|     protected static ILProcessor BeginSimpleSetter(MethodDefinition setter) | ||||
|     { | ||||
|         ILProcessor processor = InitPropertyAccessor(setter); | ||||
|         /* | ||||
|          .method public hidebysig specialname instance void | ||||
|                 set_TestReadWriteFloat(float32 'value') cil managed | ||||
|         { | ||||
|           // Code size       24 (0x18) | ||||
|           .maxstack  8 | ||||
|           IL_0000:  ldarg.0 | ||||
|           IL_0001:  call       instance native int [UnrealEngine.Runtime]UnrealEngine.Runtime.UnrealObject::get_NativeObject() | ||||
|           IL_0006:  ldsfld     int32 UnrealEngine.MonoRuntime.MonoTestsObject::TestReadWriteFloat_Offset | ||||
|           IL_000b:  call       native int [mscorlib]System.IntPtr::Add(native int, | ||||
|                                                                        int32) | ||||
|           IL_0010:  call       void* [mscorlib]System.IntPtr::op_Explicit(native int) | ||||
|           IL_0015:  ldarg.1 | ||||
|           IL_0016:  stind.r4 | ||||
|           IL_0017:  ret | ||||
|         } // end of method MonoTestsObject::set_TestReadWriteFloat | ||||
|          */ | ||||
|         return processor; | ||||
|     } | ||||
|  | ||||
|     public virtual void WritePostInitialization(ILProcessor processor, PropertyMetaData propertyMetadata, Instruction loadNativePointer, Instruction setNativePointer) | ||||
|     { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     // Subclasses may override to do additional prep, such as adding additional backing fields. | ||||
|     public virtual void PrepareForRewrite(TypeDefinition typeDefinition, PropertyMetaData propertyMetadata, | ||||
|         object outer) | ||||
|     { | ||||
|         TypeReference? marshallingDelegates = WeaverImporter.Instance.UnrealSharpCoreAssembly.FindGenericType(WeaverImporter.UnrealSharpCoreMarshallers, "MarshallingDelegates`1", [CSharpType]); | ||||
|          | ||||
|         if (marshallingDelegates == null) | ||||
|         { | ||||
|             throw new Exception($"Could not find marshalling delegates for {CSharpType.FullName}"); | ||||
|         } | ||||
|          | ||||
|         TypeDefinition marshallingDelegatesDef = marshallingDelegates.Resolve(); | ||||
|          | ||||
|         ToNativeDelegateType = marshallingDelegatesDef.FindNestedType("ToNative"); | ||||
|         FromNativeDelegateType = marshallingDelegatesDef.FindNestedType("FromNative"); | ||||
|     } | ||||
|  | ||||
|     protected void EmitDelegate(ILProcessor processor, TypeReference delegateType, MethodReference method) | ||||
|     { | ||||
|         processor.Emit(OpCodes.Ldnull); | ||||
|         method = WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference(method); | ||||
|         processor.Emit(OpCodes.Ldftn, method); | ||||
|         MethodReference ctor = (from constructor in delegateType.Resolve().GetConstructors() where constructor.Parameters.Count == 2 select constructor).First().Resolve(); | ||||
|         ctor = FunctionProcessor.MakeMethodDeclaringTypeGeneric(ctor, CSharpType); | ||||
|         ctor = WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference(ctor); | ||||
|         processor.Emit(OpCodes.Newobj, ctor); | ||||
|     } | ||||
|  | ||||
|     // Emits IL for a default constructible and possibly generic fixed array marshalling helper object. | ||||
|     // If typeParams is null, a non-generic type is assumed. | ||||
|     protected void EmitSimpleMarshallerDelegates(ILProcessor processor, string marshallerTypeName, TypeReference[]? typeParams) | ||||
|     { | ||||
|         TypeReference? marshallerType = null; | ||||
|         AssemblyDefinition? marshallerAssembly = null; | ||||
|         AssemblyUtilities.ForEachAssembly(action: assembly => | ||||
|         { | ||||
|             if (typeParams is { Length: > 0 }) | ||||
|             { | ||||
|                 marshallerType = assembly.FindGenericType(string.Empty, marshallerTypeName, typeParams, false); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 marshallerType = assembly.FindType(marshallerTypeName, string.Empty, false); | ||||
|             } | ||||
|              | ||||
|             if (marshallerType != null) | ||||
|             { | ||||
|                 marshallerAssembly = assembly; | ||||
|             } | ||||
|              | ||||
|             return marshallerType == null; | ||||
|         }); | ||||
|          | ||||
|         if (marshallerType == null || marshallerAssembly == null) | ||||
|         { | ||||
|             throw new Exception($"Could not find marshaller type {marshallerTypeName} in any assembly."); | ||||
|         } | ||||
|  | ||||
|         TypeDefinition marshallerTypeDef = GetMarshallerTypeDefinition(marshallerAssembly, marshallerType); | ||||
|         MethodReference fromNative = marshallerTypeDef.FindMethod("FromNative")!; | ||||
|         MethodReference toNative = marshallerTypeDef.FindMethod("ToNative")!; | ||||
|  | ||||
|         if (typeParams != null) | ||||
|         { | ||||
|             fromNative = FunctionProcessor.MakeMethodDeclaringTypeGeneric(fromNative, typeParams); | ||||
|             toNative = FunctionProcessor.MakeMethodDeclaringTypeGeneric(toNative, typeParams); | ||||
|         } | ||||
|          | ||||
|         if (ToNativeDelegateType == null || FromNativeDelegateType == null) | ||||
|         { | ||||
|             throw new Exception($"Could not find marshaller delegates for {marshallerTypeName}"); | ||||
|         } | ||||
|  | ||||
|         EmitDelegate(processor, ToNativeDelegateType, toNative); | ||||
|         EmitDelegate(processor, FromNativeDelegateType, fromNative); | ||||
|     } | ||||
|  | ||||
|     public abstract void EmitFixedArrayMarshallerDelegates(ILProcessor processor, TypeDefinition type); | ||||
|     public virtual void EmitDynamicArrayMarshallerDelegates(ILProcessor processor, TypeDefinition type) | ||||
|     { | ||||
|         EmitFixedArrayMarshallerDelegates(processor, type); | ||||
|     } | ||||
|  | ||||
|     public abstract void WriteGetter(TypeDefinition type, MethodDefinition getter, Instruction[] loadBufferPtr, | ||||
|         FieldDefinition? fieldDefinition); | ||||
|     public abstract void WriteSetter(TypeDefinition type, MethodDefinition setter, Instruction[] loadBufferPtr, | ||||
|         FieldDefinition? fieldDefinition); | ||||
|  | ||||
|     // Subclasses must implement to handle loading of values from a native buffer. | ||||
|     // Returns the local variable containing the loaded value. | ||||
|     public abstract void WriteLoad(ILProcessor processor, TypeDefinition type, Instruction loadBufferInstruction, FieldDefinition offsetField, VariableDefinition localVar); | ||||
|     public abstract void WriteLoad(ILProcessor processor, TypeDefinition type, Instruction loadBufferInstruction, FieldDefinition offsetField, FieldDefinition destField); | ||||
|  | ||||
|     // Subclasses must implement to handle storing of a value into a native buffer. | ||||
|     // Return value is a list of instructions that must be executed to clean up the value in the buffer, or null if no cleanup is required. | ||||
|     public abstract IList<Instruction>? WriteStore(ILProcessor processor, TypeDefinition type, | ||||
|         Instruction loadBufferInstruction, FieldDefinition offsetField, int argIndex, | ||||
|         ParameterDefinition paramDefinition); | ||||
|     public abstract IList<Instruction>? WriteStore(ILProcessor processor, TypeDefinition type, Instruction loadBufferInstruction, FieldDefinition offsetField, FieldDefinition srcField); | ||||
|  | ||||
|     public abstract void WriteMarshalFromNative(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, Instruction loadArrayIndex); | ||||
|     public abstract void WriteMarshalToNative(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, Instruction loadArrayIndex, Instruction[] loadSource); | ||||
|          | ||||
|     public void WriteMarshalToNative(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, Instruction loadArrayIndex, Instruction loadSource) | ||||
|     { | ||||
|         WriteMarshalToNative(processor, type, loadBufferPtr, loadArrayIndex, [loadSource]); | ||||
|     } | ||||
|  | ||||
|     public virtual IList<Instruction>? WriteMarshalToNativeWithCleanup(ILProcessor processor, TypeDefinition type, Instruction[] loadBufferPtr, Instruction loadArrayIndex, Instruction[] loadSource) | ||||
|     { | ||||
|         WriteMarshalToNative(processor, type, loadBufferPtr, loadArrayIndex, loadSource); | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     protected static TypeDefinition GetMarshallerTypeDefinition(AssemblyDefinition assembly, TypeReference marshallerTypeReference) | ||||
|     { | ||||
|         return assembly.Modules.SelectMany(x => x.GetTypes()).First(x => x.Name == marshallerTypeReference.Name); | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,9 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| public class NativeDataUnmanagedType(TypeReference unmanagedType, int arrayDim) : NativeDataSimpleType(unmanagedType, "UnmanagedTypeMarshaller`1", arrayDim, PropertyType.Struct) | ||||
| { | ||||
|     public TypeReferenceMetadata InnerType { get; set; } =  new(WeaverImporter.Instance.UnmanagedDataStore.Resolve()); | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
| class NativeDataWeakObjectType(TypeReference typeRef, TypeReference innerTypeRef, int arrayDim)  | ||||
|     : NativeDataGenericObjectType(typeRef, innerTypeRef, "BlittableMarshaller`1", arrayDim, PropertyType.WeakObject); | ||||
| @ -0,0 +1,92 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Collections.Generic; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataDefaultComponent : NativeDataSimpleType | ||||
| { | ||||
|     public bool IsRootComponent { get; set; } | ||||
|     public string AttachmentComponent { get; set; } = string.Empty; | ||||
|     public string AttachmentSocket { get; set; } = string.Empty; | ||||
|     public TypeReferenceMetadata InnerType { get; set; } | ||||
|      | ||||
|     public NativeDataDefaultComponent(Collection<CustomAttribute> customAttributes, TypeReference typeRef, int arrayDim)  | ||||
|         : base(typeRef, "DefaultComponentMarshaller`1", arrayDim, PropertyType.DefaultComponent) | ||||
|     { | ||||
|         TypeDefinition? defaultComponentType = typeRef.Resolve(); | ||||
|          | ||||
|         if (!defaultComponentType.IsUObject()) | ||||
|         { | ||||
|             throw new Exception($"{defaultComponentType.FullName} needs to be a UClass if exposed through UProperty!"); | ||||
|         } | ||||
|          | ||||
|         InnerType = new TypeReferenceMetadata(defaultComponentType); | ||||
|          | ||||
|         CustomAttribute upropertyAttribute = PropertyUtilities.GetUProperty(customAttributes)!; | ||||
|          | ||||
|         CustomAttributeArgument? isRootComponentValue = upropertyAttribute.FindAttributeField("RootComponent"); | ||||
|         if (isRootComponentValue != null) | ||||
|         { | ||||
|             IsRootComponent = (bool) isRootComponentValue.Value.Value; | ||||
|         } | ||||
|  | ||||
|         CustomAttributeArgument? attachmentComponentValue = upropertyAttribute.FindAttributeField("AttachmentComponent"); | ||||
|         if (attachmentComponentValue != null) | ||||
|         { | ||||
|             AttachmentComponent = (string) attachmentComponentValue.Value.Value; | ||||
|         } | ||||
|          | ||||
|         CustomAttributeArgument? attachmentSocketValue = upropertyAttribute.FindAttributeField("AttachmentSocket"); | ||||
|         if (attachmentSocketValue != null) | ||||
|         { | ||||
|             AttachmentSocket = (string) attachmentSocketValue.Value.Value; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public override void WriteGetter(TypeDefinition type, MethodDefinition getter, Instruction[] loadBufferPtr, | ||||
|         FieldDefinition? fieldDefinition) | ||||
|     { | ||||
|         ILProcessor processor = BeginSimpleGetter(getter); | ||||
|         string propertyName = getter.Name.Substring(4); | ||||
|          | ||||
|         List<Instruction> loadBuffer = new List<Instruction>(); | ||||
|         loadBuffer.Add(processor.Create(OpCodes.Ldarg_0)); | ||||
|         loadBuffer.Add(processor.Create(OpCodes.Ldstr, propertyName)); | ||||
|          | ||||
|         foreach (Instruction instruction in loadBufferPtr) | ||||
|         { | ||||
|             loadBuffer.Add(instruction); | ||||
|         } | ||||
|          | ||||
|         WriteMarshalFromNative(processor, type, loadBuffer.ToArray(), processor.Create(OpCodes.Ldc_I4_0)); | ||||
|         getter.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public static bool IsDefaultComponent(Collection<CustomAttribute>? customAttributes) | ||||
|     { | ||||
|         if (customAttributes == null) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         var upropertyAttribute = PropertyUtilities.GetUProperty(customAttributes); | ||||
|  | ||||
|         if (upropertyAttribute == null) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         CustomAttributeArgument? isDefaultComponent = upropertyAttribute.FindAttributeField("DefaultComponent"); | ||||
|  | ||||
|         if (isDefaultComponent == null) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         bool isDefaultComponentValue = (bool)isDefaultComponent.Value.Value; | ||||
|         return isDefaultComponentValue; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,55 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.TypeProcessors; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| class NativeDataMulticastDelegate : NativeDataBaseDelegateType | ||||
| { | ||||
|     public NativeDataMulticastDelegate(TypeReference delegateType)  | ||||
|         : base(delegateType, "MulticastDelegateMarshaller`1", PropertyType.MulticastInlineDelegate) | ||||
|     { | ||||
|         NeedsNativePropertyField = true; | ||||
|     } | ||||
|  | ||||
|     public override void PrepareForRewrite(TypeDefinition typeDefinition, PropertyMetaData propertyMetadata, | ||||
|         object outer) | ||||
|     { | ||||
|         AddBackingField(typeDefinition, propertyMetadata); | ||||
|         base.PrepareForRewrite(typeDefinition, propertyMetadata, outer); | ||||
|     } | ||||
|      | ||||
|     public override void WritePostInitialization(ILProcessor processor, PropertyMetaData propertyMetadata, | ||||
|         Instruction loadNativePointer, Instruction setNativePointer) | ||||
|     { | ||||
|         if (!Signature.HasParameters) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         TypeReference foundType = GetWrapperType(delegateType); | ||||
|         processor.Append(loadNativePointer); | ||||
|          | ||||
|         MethodReference initializeDelegateMethod = UnrealDelegateProcessor.FindOrCreateInitializeDelegate(foundType.Resolve()); | ||||
|         processor.Emit(OpCodes.Call, initializeDelegateMethod); | ||||
|     } | ||||
|  | ||||
|     public override void WriteGetter(TypeDefinition type, MethodDefinition getter, Instruction[] loadBufferPtr, | ||||
|         FieldDefinition? fieldDefinition) | ||||
|     { | ||||
|         ILProcessor processor = BeginSimpleGetter(getter); | ||||
|          | ||||
|         foreach (Instruction instruction in loadBufferPtr) | ||||
|         { | ||||
|             processor.Append(instruction); | ||||
|         } | ||||
|          | ||||
|         processor.Emit(OpCodes.Ldsfld, fieldDefinition); | ||||
|         processor.Emit(OpCodes.Ldc_I4_0); | ||||
|  | ||||
|         processor.Emit(OpCodes.Call, FromNative); | ||||
|         getter.FinalizeMethod(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| public enum ParameterType | ||||
| { | ||||
|     None, | ||||
|     Value, | ||||
|     Ref, | ||||
|     Out, | ||||
|     ReturnValue | ||||
| } | ||||
| @ -0,0 +1,468 @@ | ||||
| using System.Text.Json; | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Pdb; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.TypeProcessors; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| public static class Program | ||||
| { | ||||
|     public static WeaverOptions WeaverOptions { get; private set; } = null!; | ||||
|  | ||||
|     public static void Weave(WeaverOptions weaverOptions) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             WeaverOptions = weaverOptions; | ||||
|             LoadBindingsAssembly(); | ||||
|             ProcessUserAssemblies(); | ||||
|         } | ||||
|         finally | ||||
|         { | ||||
|             WeaverImporter.Shutdown(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static int Main(string[] args) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             WeaverOptions = WeaverOptions.ParseArguments(args); | ||||
|             LoadBindingsAssembly(); | ||||
|             ProcessUserAssemblies(); | ||||
|             return 0; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Console.Error.WriteLine(ex); | ||||
|             return 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void LoadBindingsAssembly() | ||||
|     { | ||||
|         DefaultAssemblyResolver resolver = new DefaultAssemblyResolver(); | ||||
|  | ||||
|         List<string> searchPaths = new(); | ||||
|         foreach (string assemblyPath in WeaverOptions.AssemblyPaths) | ||||
|         { | ||||
|             string? directory = Path.GetDirectoryName(StripQuotes(assemblyPath)); | ||||
|  | ||||
|             if (string.IsNullOrEmpty(directory) || searchPaths.Contains(directory)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (!Directory.Exists(directory)) | ||||
|             { | ||||
|                 throw new InvalidOperationException("Could not determine directory for assembly path."); | ||||
|             } | ||||
|  | ||||
|             resolver.AddSearchDirectory(directory); | ||||
|             searchPaths.Add(directory); | ||||
|         } | ||||
|          | ||||
|         WeaverImporter.Instance.AssemblyResolver = resolver; | ||||
|     } | ||||
|  | ||||
|     private static void ProcessUserAssemblies() | ||||
|     { | ||||
|         DirectoryInfo outputDirInfo = new DirectoryInfo(StripQuotes(WeaverOptions.OutputDirectory)); | ||||
|  | ||||
|         if (!outputDirInfo.Exists) | ||||
|         { | ||||
|             outputDirInfo.Create(); | ||||
|         } | ||||
|  | ||||
|         DefaultAssemblyResolver resolver = GetAssemblyResolver(); | ||||
|         List<AssemblyDefinition> assembliesToProcess = LoadInputAssemblies(resolver); | ||||
|         ICollection<AssemblyDefinition> orderedUserAssemblies = OrderInputAssembliesByReferences(assembliesToProcess); | ||||
|         WeaverImporter.Instance.AllProjectAssemblies = assembliesToProcess; | ||||
|  | ||||
|         WriteUnrealSharpMetadataFile(orderedUserAssemblies, outputDirInfo); | ||||
|         ProcessOrderedAssemblies(orderedUserAssemblies, outputDirInfo); | ||||
|     } | ||||
|  | ||||
|     private static void WriteUnrealSharpMetadataFile(ICollection<AssemblyDefinition> orderedAssemblies, DirectoryInfo outputDirectory) | ||||
|     { | ||||
|         UnrealSharpMetadata unrealSharpMetadata = new UnrealSharpMetadata | ||||
|         { | ||||
|             AssemblyLoadingOrder = orderedAssemblies | ||||
|                 .Select(x => Path.GetFileNameWithoutExtension(x.MainModule.FileName)).ToList(), | ||||
|         }; | ||||
|  | ||||
|         string metaDataContent = JsonSerializer.Serialize(unrealSharpMetadata, new JsonSerializerOptions | ||||
|         { | ||||
|             WriteIndented = false, | ||||
|         }); | ||||
|  | ||||
|         string fileName = Path.Combine(outputDirectory.FullName, "UnrealSharp.assemblyloadorder.json"); | ||||
|         File.WriteAllText(fileName, metaDataContent); | ||||
|     } | ||||
|  | ||||
|     private static void ProcessOrderedAssemblies(ICollection<AssemblyDefinition> assemblies, DirectoryInfo outputDirectory) | ||||
|     { | ||||
|         Exception? exception = null; | ||||
|  | ||||
|         foreach (AssemblyDefinition assembly in assemblies) | ||||
|         { | ||||
|             if (assembly.Name.Name.EndsWith("Glue")) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 string outputPath = Path.Combine(outputDirectory.FullName, Path.GetFileName(assembly.MainModule.FileName)); | ||||
|                 StartWeavingAssembly(assembly, outputPath); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 exception = ex; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         foreach (AssemblyDefinition assembly in assemblies) | ||||
|         { | ||||
|             assembly.Dispose(); | ||||
|         } | ||||
|  | ||||
|         if (exception != null) | ||||
|         { | ||||
|             throw new AggregateException("Assembly processing failed", exception); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static ICollection<AssemblyDefinition> OrderInputAssembliesByReferences(ICollection<AssemblyDefinition> assemblies) | ||||
|     { | ||||
|         HashSet<string> assemblyNames = new HashSet<string>(); | ||||
|          | ||||
|         foreach (AssemblyDefinition assembly in assemblies) | ||||
|         { | ||||
|             assemblyNames.Add(assembly.FullName); | ||||
|         } | ||||
|  | ||||
|         List<AssemblyDefinition> result = new List<AssemblyDefinition>(assemblies.Count); | ||||
|         HashSet<AssemblyDefinition> remaining = new HashSet<AssemblyDefinition>(assemblies); | ||||
|  | ||||
|         // Add assemblies with no references first between the user assemblies. | ||||
|         foreach (AssemblyDefinition assembly in assemblies) | ||||
|         { | ||||
|             bool hasReferenceToUserAssembly = false; | ||||
|             foreach (AssemblyNameReference? reference in assembly.MainModule.AssemblyReferences) | ||||
|             { | ||||
|                 if (!assemblyNames.Contains(reference.FullName)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                  | ||||
|                 hasReferenceToUserAssembly = true; | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             if (hasReferenceToUserAssembly) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             result.Add(assembly); | ||||
|             remaining.Remove(assembly); | ||||
|         } | ||||
|          | ||||
|         do | ||||
|         { | ||||
|             bool added = false; | ||||
|  | ||||
|             foreach (AssemblyDefinition assembly in assemblies) | ||||
|             { | ||||
|                 if (!remaining.Contains(assembly)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                  | ||||
|                 bool allResolved = true; | ||||
|                 foreach (AssemblyNameReference? reference in assembly.MainModule.AssemblyReferences) | ||||
|                 { | ||||
|                     if (assemblyNames.Contains(reference.FullName)) | ||||
|                     { | ||||
|                         bool found = false; | ||||
|                         foreach (AssemblyDefinition addedAssembly in result) | ||||
|                         { | ||||
|                             if (addedAssembly.FullName != reference.FullName) | ||||
|                             { | ||||
|                                 continue; | ||||
|                             } | ||||
|                              | ||||
|                             found = true; | ||||
|                             break; | ||||
|                         } | ||||
|  | ||||
|                         if (found) | ||||
|                         { | ||||
|                             continue; | ||||
|                         } | ||||
|                          | ||||
|                         allResolved = false; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (!allResolved) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                  | ||||
|                 result.Add(assembly); | ||||
|                 remaining.Remove(assembly); | ||||
|                 added = true; | ||||
|             } | ||||
|              | ||||
|             if (added || remaining.Count <= 0) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             foreach (AssemblyDefinition asm in remaining) | ||||
|             { | ||||
|                 result.Add(asm); | ||||
|             } | ||||
|              | ||||
|             break; | ||||
|  | ||||
|         } while (remaining.Count > 0); | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private static DefaultAssemblyResolver GetAssemblyResolver() | ||||
|     { | ||||
|         return WeaverImporter.Instance.AssemblyResolver; | ||||
|     } | ||||
|  | ||||
|     private static List<AssemblyDefinition> LoadInputAssemblies(IAssemblyResolver resolver) | ||||
|     { | ||||
|         ReaderParameters readerParams = new ReaderParameters | ||||
|         { | ||||
|             AssemblyResolver = resolver, | ||||
|             ReadSymbols = true, | ||||
|             SymbolReaderProvider = new PdbReaderProvider(), | ||||
|         }; | ||||
|  | ||||
|         List<AssemblyDefinition> result = new List<AssemblyDefinition>(); | ||||
|  | ||||
|         foreach (var assemblyPath in WeaverOptions.AssemblyPaths.Select(StripQuotes)) | ||||
|         { | ||||
|             if (!File.Exists(assemblyPath)) | ||||
|             { | ||||
|                 throw new FileNotFoundException($"Could not find assembly at: {assemblyPath}"); | ||||
|             } | ||||
|  | ||||
|             AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, readerParams); | ||||
|             result.Add(assembly); | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private static string StripQuotes(string value) | ||||
|     { | ||||
|         if (value.StartsWith('\"') && value.EndsWith('\"')) | ||||
|         { | ||||
|             return value.Substring(1, value.Length - 2); | ||||
|         } | ||||
|  | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     static void StartWeavingAssembly(AssemblyDefinition assembly, string assemblyOutputPath) | ||||
|     { | ||||
|         void CleanOldFilesAndMoveExistingFiles() | ||||
|         { | ||||
|             var pdbOutputFile = new FileInfo(Path.ChangeExtension(assemblyOutputPath, ".pdb")); | ||||
|  | ||||
|             if (!pdbOutputFile.Exists) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             var tmpDirectory = Path.Join(Path.GetTempPath(), assembly.Name.Name); | ||||
|             if (Path.GetPathRoot(tmpDirectory) != Path.GetPathRoot(pdbOutputFile.FullName)) //if the temp directory is on a different drive, move will not work as desired if file is locked since it does a copy for drive boundaries | ||||
|             { | ||||
|                 tmpDirectory = Path.Join(Path.GetDirectoryName(assemblyOutputPath), "..", "_Temporary", assembly.Name.Name); | ||||
|             } | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 if (Directory.Exists(tmpDirectory)) | ||||
|                 { | ||||
|                     foreach (var file in Directory.GetFiles(tmpDirectory)) | ||||
|                     { | ||||
|                         File.Delete(file); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     Directory.CreateDirectory(tmpDirectory); | ||||
|                 } | ||||
|             } | ||||
|             catch | ||||
|             { | ||||
|                 //no action needed | ||||
|             } | ||||
|  | ||||
|             //move the file to an temp folder to prevent write locks in case a debugger is attached to UE which locks the pdb for writes (common strategy). | ||||
|             var tmpDestFileName = Path.Join(tmpDirectory, Path.GetFileName(Path.ChangeExtension(Path.GetTempFileName(), ".pdb"))); | ||||
|             File.Move(pdbOutputFile.FullName, tmpDestFileName); | ||||
|         } | ||||
|  | ||||
|         Task cleanupTask = Task.Run(CleanOldFilesAndMoveExistingFiles); | ||||
|         WeaverImporter.Instance.ImportCommonTypes(assembly); | ||||
|  | ||||
|         ApiMetaData assemblyMetaData = new ApiMetaData(assembly.Name.Name); | ||||
|         StartProcessingAssembly(assembly, assemblyMetaData); | ||||
|  | ||||
|         string sourcePath = Path.GetDirectoryName(assembly.MainModule.FileName)!; | ||||
|         CopyAssemblyDependencies(assemblyOutputPath, sourcePath); | ||||
|  | ||||
|         Task.WaitAll(cleanupTask); | ||||
|         assembly.Write(assemblyOutputPath, new WriterParameters | ||||
|         { | ||||
|             SymbolWriterProvider = new PdbWriterProvider(), | ||||
|         }); | ||||
|  | ||||
|         WriteAssemblyMetaDataFile(assemblyMetaData, assemblyOutputPath); | ||||
|     } | ||||
|  | ||||
|     private static void WriteAssemblyMetaDataFile(ApiMetaData metadata, string outputPath) | ||||
|     { | ||||
|         string metaDataContent = JsonSerializer.Serialize(metadata, new JsonSerializerOptions | ||||
|         { | ||||
|             WriteIndented = false, | ||||
|         }); | ||||
|  | ||||
|         string metadataFilePath = Path.ChangeExtension(outputPath, "metadata.json"); | ||||
|         File.WriteAllText(metadataFilePath, metaDataContent); | ||||
|     } | ||||
|  | ||||
|     private static void StartProcessingAssembly(AssemblyDefinition userAssembly, ApiMetaData metadata) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             List<TypeDefinition> classes = []; | ||||
|             List<TypeDefinition> structs = []; | ||||
|             List<TypeDefinition> enums = []; | ||||
|             List<TypeDefinition> interfaces = []; | ||||
|             List<TypeDefinition> multicastDelegates = []; | ||||
|             List<TypeDefinition> delegates = []; | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 void RegisterType(List<TypeDefinition> typeDefinitions, TypeDefinition typeDefinition) | ||||
|                 { | ||||
|                     typeDefinitions.Add(typeDefinition); | ||||
|                     typeDefinition.AddGeneratedTypeAttribute(); | ||||
|                 } | ||||
|  | ||||
|                 foreach (ModuleDefinition? module in userAssembly.Modules) | ||||
|                 { | ||||
|                     foreach (TypeDefinition? type in module.Types) | ||||
|                     { | ||||
|                         if (type.IsUClass()) | ||||
|                         { | ||||
|                             RegisterType(classes, type); | ||||
|                         } | ||||
|                         else if (type.IsUEnum()) | ||||
|                         { | ||||
|                             RegisterType(enums, type); | ||||
|                         } | ||||
|                         else if (type.IsUStruct()) | ||||
|                         { | ||||
|                             RegisterType(structs, type); | ||||
|                         } | ||||
|                         else if (type.IsUInterface()) | ||||
|                         { | ||||
|                             RegisterType(interfaces, type); | ||||
|                         } | ||||
|                         else if (type.BaseType != null && type.BaseType.FullName.Contains("UnrealSharp.MulticastDelegate")) | ||||
|                         { | ||||
|                             RegisterType(multicastDelegates, type); | ||||
|                         } | ||||
|                         else if (type.BaseType != null && type.BaseType.FullName.Contains("UnrealSharp.Delegate")) | ||||
|                         { | ||||
|                             RegisterType(delegates, type); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 Console.Error.WriteLine($"Error enumerating types: {ex.Message}"); | ||||
|                 throw; | ||||
|             } | ||||
|  | ||||
|             UnrealEnumProcessor.ProcessEnums(enums, metadata); | ||||
|             UnrealInterfaceProcessor.ProcessInterfaces(interfaces, metadata); | ||||
|             UnrealStructProcessor.ProcessStructs(structs, metadata, userAssembly); | ||||
|             UnrealClassProcessor.ProcessClasses(classes, metadata); | ||||
|             UnrealDelegateProcessor.ProcessDelegates(delegates, multicastDelegates, userAssembly, metadata.DelegateMetaData); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Console.Error.WriteLine($"Error during assembly processing: {ex.Message}"); | ||||
|             throw; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void RecursiveFileCopy(DirectoryInfo sourceDirectory, DirectoryInfo destinationDirectory) | ||||
|     { | ||||
|         // Early out of our search if the last updated timestamps match | ||||
|         if (sourceDirectory.LastWriteTimeUtc == destinationDirectory.LastWriteTimeUtc) return; | ||||
|  | ||||
|         if (!destinationDirectory.Exists) | ||||
|         { | ||||
|             destinationDirectory.Create(); | ||||
|         } | ||||
|  | ||||
|         foreach (FileInfo sourceFile in sourceDirectory.GetFiles()) | ||||
|         { | ||||
|             string destinationFilePath = Path.Combine(destinationDirectory.FullName, sourceFile.Name); | ||||
|             FileInfo destinationFile = new FileInfo(destinationFilePath); | ||||
|  | ||||
|             if (!destinationFile.Exists || sourceFile.LastWriteTimeUtc > destinationFile.LastWriteTimeUtc) | ||||
|             { | ||||
|                 sourceFile.CopyTo(destinationFilePath, true); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Update our write time to match source for faster copying | ||||
|         destinationDirectory.LastWriteTimeUtc = sourceDirectory.LastWriteTimeUtc; | ||||
|  | ||||
|         foreach (DirectoryInfo subSourceDirectory in sourceDirectory.GetDirectories()) | ||||
|         { | ||||
|             string subDestinationDirectoryPath = Path.Combine(destinationDirectory.FullName, subSourceDirectory.Name); | ||||
|             DirectoryInfo subDestinationDirectory = new DirectoryInfo(subDestinationDirectoryPath); | ||||
|  | ||||
|             RecursiveFileCopy(subSourceDirectory, subDestinationDirectory); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void CopyAssemblyDependencies(string destinationPath, string sourcePath) | ||||
|     { | ||||
|         var directoryName = Path.GetDirectoryName(destinationPath) ?? throw new InvalidOperationException("Assembly path does not have a valid directory."); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             var destinationDirectory = new DirectoryInfo(directoryName); | ||||
|             var sourceDirectory = new DirectoryInfo(sourcePath); | ||||
|  | ||||
|             RecursiveFileCopy(sourceDirectory, destinationDirectory); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             ErrorEmitter.Error("WeaverError", sourcePath, 0, "Failed to copy dependencies: " + ex.Message); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,82 @@ | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| [Flags] | ||||
| public enum PropertyFlags : ulong | ||||
| { | ||||
|     None = 0, | ||||
|     Edit = 0x0000000000000001, | ||||
|     ConstParm = 0x0000000000000002, | ||||
|     BlueprintVisible = 0x0000000000000004, | ||||
|     ExportObject = 0x0000000000000008, | ||||
|     BlueprintReadOnly = 0x0000000000000010, | ||||
|     Net = 0x0000000000000020, | ||||
|     EditFixedSize = 0x0000000000000040, | ||||
|     Parm = 0x0000000000000080, | ||||
|     OutParm = 0x0000000000000100, | ||||
|     ZeroConstructor = 0x0000000000000200, | ||||
|     ReturnParm = 0x0000000000000400, | ||||
|     DisableEditOnTemplate = 0x0000000000000800, | ||||
|     Transient = 0x0000000000002000, | ||||
|     Config = 0x0000000000004000, | ||||
|     DisableEditOnInstance = 0x0000000000010000, | ||||
|     EditConst = 0x0000000000020000, | ||||
|     GlobalConfig = 0x0000000000040000, | ||||
|     InstancedReference = 0x0000000000080000, | ||||
|     DuplicateTransient = 0x0000000000200000, | ||||
|     SubobjectReference = 0x0000000000400000, | ||||
|     SaveGame = 0x0000000001000000, | ||||
|     NoClear = 0x0000000002000000, | ||||
|     ReferenceParm = 0x0000000008000000, | ||||
|     BlueprintAssignable = 0x0000000010000000, | ||||
|     Deprecated = 0x0000000020000000, | ||||
|     IsPlainOldData = 0x0000000040000000, | ||||
|     RepSkip = 0x0000000080000000, | ||||
|     RepNotify = 0x0000000100000000, | ||||
|     Interp = 0x0000000200000000, | ||||
|     NonTransactional = 0x0000000400000000, | ||||
|     EditorOnly = 0x0000000800000000, | ||||
|     NoDestructor = 0x0000001000000000, | ||||
|     AutoWeak = 0x0000004000000000, | ||||
|     ContainsInstancedReference = 0x0000008000000000, | ||||
|     AssetRegistrySearchable = 0x0000010000000000, | ||||
|     SimpleDisplay = 0x0000020000000000, | ||||
|     AdvancedDisplay = 0x0000040000000000, | ||||
|     Protected = 0x0000080000000000, | ||||
|     BlueprintCallable = 0x0000100000000000, | ||||
|     BlueprintAuthorityOnly = 0x0000200000000000, | ||||
|     TextExportTransient = 0x0000400000000000, | ||||
|     NonPIEDuplicateTransient = 0x0000800000000000, | ||||
|     ExposeOnSpawn = 0x0001000000000000, | ||||
|     PersistentInstance = 0x0002000000000000, | ||||
|     UObjectWrapper = 0x0004000000000000, | ||||
|     HasGetValueTypeHash = 0x0008000000000000, | ||||
|     NativeAccessSpecifierPublic = 0x0010000000000000, | ||||
|     NativeAccessSpecifierProtected = 0x0020000000000000, | ||||
|     NativeAccessSpecifierPrivate = 0x0040000000000000, | ||||
|     SkipSerialization = 0x0080000000000000, | ||||
|  | ||||
|     /* Combination flags */ | ||||
|  | ||||
|     NativeAccessSpecifiers = NativeAccessSpecifierPublic | NativeAccessSpecifierProtected | NativeAccessSpecifierPrivate, | ||||
|  | ||||
|     ParmFlags = Parm | OutParm | ReturnParm | ReferenceParm | ConstParm, | ||||
|     PropagateToArrayInner = ExportObject | PersistentInstance | InstancedReference | ContainsInstancedReference | Config | EditConst | Deprecated | EditorOnly | AutoWeak | UObjectWrapper, | ||||
|     PropagateToMapValue = ExportObject | PersistentInstance | InstancedReference | ContainsInstancedReference | Config | EditConst | Deprecated | EditorOnly | AutoWeak | UObjectWrapper | Edit, | ||||
|     PropagateToMapKey = ExportObject | PersistentInstance | InstancedReference | ContainsInstancedReference | Config | EditConst | Deprecated | EditorOnly | AutoWeak | UObjectWrapper | Edit, | ||||
|     PropagateToSetElement = ExportObject | PersistentInstance | InstancedReference | ContainsInstancedReference | Config | EditConst | Deprecated | EditorOnly | AutoWeak | UObjectWrapper | Edit, | ||||
|  | ||||
|     /** the flags that should never be set on interface properties */ | ||||
|     InterfaceClearMask = ExportObject | InstancedReference | ContainsInstancedReference, | ||||
|  | ||||
|     /** all the properties that can be stripped for final release console builds */ | ||||
|     DevelopmentAssets = EditorOnly, | ||||
|  | ||||
|     /** all the properties that should never be loaded or saved */ | ||||
|     ComputedFlags = IsPlainOldData | NoDestructor | ZeroConstructor | HasGetValueTypeHash, | ||||
|  | ||||
|     EditDefaultsOnly = Edit | BlueprintVisible | DisableEditOnInstance, | ||||
|     EditInstanceOnly = Edit | BlueprintVisible, | ||||
|     EditAnywhere = Edit | BlueprintVisible | BlueprintReadOnly, | ||||
|  | ||||
|     AllFlags = 0xFFFFFFFFFFFFFFFF | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| public enum PropertyType | ||||
| { | ||||
|     Unknown, | ||||
|  | ||||
|     Bool, | ||||
|  | ||||
|     Int8, | ||||
|     Int16, | ||||
|     Int, | ||||
|     Int64, | ||||
|  | ||||
|     Byte, | ||||
|     UInt16, | ||||
|     UInt32, | ||||
|     UInt64, | ||||
|  | ||||
|     Double, | ||||
|     Float, | ||||
|  | ||||
|     Enum, | ||||
|  | ||||
|     Interface, | ||||
|     Struct, | ||||
|     Class, | ||||
|  | ||||
|     Object, | ||||
|     ObjectPtr, | ||||
|     DefaultComponent, | ||||
|     LazyObject, | ||||
|     WeakObject, | ||||
|      | ||||
|     ScriptInterface, | ||||
|  | ||||
|     SoftClass, | ||||
|     SoftObject, | ||||
|  | ||||
|     Delegate, | ||||
|     MulticastInlineDelegate, | ||||
|     MulticastSparseDelegate, | ||||
|  | ||||
|     Array, | ||||
|     Map, | ||||
|     Set, | ||||
|     Optional, | ||||
|          | ||||
|     String, | ||||
|     Name, | ||||
|     Text, | ||||
|  | ||||
|     InternalNativeFixedSizeArray, | ||||
|     InternalManagedFixedSizeArray | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| [Flags] | ||||
| public enum StructFlags : ulong | ||||
| { | ||||
|     NoFlags				= 0x00000000,	 | ||||
|     Native				= 0x00000001, | ||||
|     IdenticalNative		= 0x00000002, | ||||
|     HasInstancedReference= 0x00000004, | ||||
|     NoExport				= 0x00000008, | ||||
|     Atomic				= 0x00000010, | ||||
|     Immutable			= 0x00000020, | ||||
|     AddStructReferencedObjects = 0x00000040, | ||||
|     RequiredAPI			= 0x00000200,	 | ||||
|     NetSerializeNative	= 0x00000400,	 | ||||
|     SerializeNative		= 0x00000800,	 | ||||
|     CopyNative			= 0x00001000,	 | ||||
|     IsPlainOldData		= 0x00002000,	 | ||||
|     NoDestructor			= 0x00004000,	 | ||||
|     ZeroConstructor		= 0x00008000,	 | ||||
|     ExportTextItemNative	= 0x00010000,	 | ||||
|     ImportTextItemNative	= 0x00020000,	 | ||||
|     PostSerializeNative  = 0x00040000, | ||||
|     SerializeFromMismatchedTag = 0x00080000, | ||||
|     NetDeltaSerializeNative = 0x00100000, | ||||
|     PostScriptConstruct     = 0x00200000, | ||||
|     NetSharedSerialization = 0x00400000, | ||||
|     Trashed = 0x00800000, | ||||
|     NewerVersionExists = 0x01000000, | ||||
|     CanEditChange = 0x02000000, | ||||
| }; | ||||
| @ -0,0 +1,116 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| public static class ConstructorBuilder | ||||
| { | ||||
|     public static MethodDefinition MakeStaticConstructor(TypeDefinition type) | ||||
|     { | ||||
|         return CreateStaticConstructor(type, MethodAttributes.Static); | ||||
|     } | ||||
|  | ||||
|     public static MethodDefinition CreateStaticConstructor(TypeDefinition type, MethodAttributes attributes, params TypeReference[] parameterTypes) | ||||
|     { | ||||
|         MethodDefinition staticConstructor = type.GetStaticConstructor(); | ||||
|  | ||||
|         if (staticConstructor == null) | ||||
|         { | ||||
|             staticConstructor = type.AddMethod(".cctor",  | ||||
|                 WeaverImporter.Instance.VoidTypeRef, | ||||
|                 attributes | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.HideBySig, | ||||
|                 parameterTypes); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Remove the return instruction from existing static constructor | ||||
|             staticConstructor.RemoveReturnInstruction(); | ||||
|         } | ||||
|          | ||||
|         return staticConstructor; | ||||
|     } | ||||
|      | ||||
|     public static MethodDefinition CreateConstructor(TypeDefinition typeDefinition, MethodAttributes attributes, params TypeReference[] parameterTypes) | ||||
|     { | ||||
|         MethodDefinition? constructor = typeDefinition.GetConstructors() | ||||
|             .FirstOrDefault(ctor => ctor.Parameters.Count == parameterTypes.Length && ctor.Parameters[0].ParameterType == parameterTypes[0]); | ||||
|          | ||||
|         if (constructor == null) | ||||
|         { | ||||
|             constructor = typeDefinition.AddMethod(".ctor",  | ||||
|                 WeaverImporter.Instance.VoidTypeRef, | ||||
|                 attributes | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, | ||||
|                 parameterTypes); | ||||
|         } | ||||
|          | ||||
|         return constructor; | ||||
|     } | ||||
|      | ||||
|     public static void CreateTypeInitializer(TypeDefinition typeDefinition, Instruction field, Instruction[] initializeInstructions, string engineName = "", bool finalizeMethod = false) | ||||
|     { | ||||
|         MethodDefinition staticConstructorMethod = MakeStaticConstructor(typeDefinition); | ||||
|         ILProcessor processor = staticConstructorMethod.Body.GetILProcessor(); | ||||
|          | ||||
|         processor.Emit(OpCodes.Ldstr, typeDefinition.Module.Assembly.Name.Name); | ||||
|         processor.Emit(OpCodes.Ldstr, typeDefinition.Namespace); | ||||
|          | ||||
|         engineName = string.IsNullOrEmpty(engineName) ? typeDefinition.GetEngineName() : engineName; | ||||
|         processor.Emit(OpCodes.Ldstr, engineName); | ||||
|          | ||||
|         foreach (Instruction instruction in initializeInstructions) | ||||
|         { | ||||
|             processor.Append(instruction); | ||||
|         } | ||||
|          | ||||
|         processor.Append(field); | ||||
|          | ||||
|         if (finalizeMethod) | ||||
|         { | ||||
|             staticConstructorMethod.FinalizeMethod(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public static void VerifySingleResult<T>(T[] results, TypeDefinition type, string endMessage) | ||||
|     { | ||||
|         switch (results.Length) | ||||
|         { | ||||
|             case 0: | ||||
|                 throw new RewriteException(type, $"Could not find {endMessage}"); | ||||
|             case > 1: | ||||
|                 throw new RewriteException(type, $"Found more than one {endMessage}"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void InitializeFields(MethodDefinition staticConstructor, List<PropertyMetaData> fields, Instruction loadNativeClassField) | ||||
|     { | ||||
|         ILProcessor processor = staticConstructor.Body.GetILProcessor(); | ||||
|         foreach (var property in fields) | ||||
|         { | ||||
|             if (property.HasCustomAccessors)  | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             Instruction loadNativeProperty; | ||||
|             Instruction setNativeProperty; | ||||
|             if (property.NativePropertyField == null) | ||||
|             { | ||||
|                 VariableDefinition nativePropertyVar = processor.Body.Method.AddLocalVariable(WeaverImporter.Instance.IntPtrType); | ||||
|                 loadNativeProperty = Instruction.Create(OpCodes.Ldloc, nativePropertyVar); | ||||
|                 setNativeProperty = Instruction.Create(OpCodes.Stloc, nativePropertyVar); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 loadNativeProperty = Instruction.Create(OpCodes.Ldsfld, property.NativePropertyField); | ||||
|                 setNativeProperty = Instruction.Create(OpCodes.Stsfld, property.NativePropertyField); | ||||
|             } | ||||
|              | ||||
|             property.InitializePropertyPointers(processor, loadNativeClassField, setNativeProperty); | ||||
|             property.InitializePropertyOffsets(processor, loadNativeProperty); | ||||
|             property.PropertyDataType.WritePostInitialization(processor, property, loadNativeProperty, setNativeProperty); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,537 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.NativeTypes; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| public static class FunctionProcessor | ||||
| { | ||||
|     public static void PrepareFunctionForRewrite(FunctionMetaData function, TypeDefinition classDefinition, bool forceOverwriteBody = false) | ||||
|     { | ||||
|         FieldDefinition? paramsSizeField = null; | ||||
|  | ||||
|         if (function.HasParameters) | ||||
|         { | ||||
|             for (int i = 0; i < function.Parameters.Length; i++) | ||||
|             { | ||||
|                 PropertyMetaData param = function.Parameters[i]; | ||||
|                 AddOffsetField(classDefinition, param, function, i, function.RewriteInfo.FunctionParams); | ||||
|                 AddNativePropertyField(classDefinition, param, function, i, function.RewriteInfo.FunctionParams); | ||||
|             } | ||||
|  | ||||
|             paramsSizeField = classDefinition.AddField($"{function.Name}_ParamsSize", WeaverImporter.Instance.Int32TypeRef); | ||||
|             function.RewriteInfo.FunctionParamSizeField = paramsSizeField; | ||||
|         } | ||||
|  | ||||
|         if (function.ReturnValue != null) | ||||
|         { | ||||
|             int index = function.Parameters.Length > 0 ? function.Parameters.Length : 0; | ||||
|             AddOffsetField(classDefinition, function.ReturnValue, function, index, function.RewriteInfo.FunctionParams); | ||||
|             AddNativePropertyField(classDefinition, function.ReturnValue, function, index, function.RewriteInfo.FunctionParams); | ||||
|         } | ||||
|          | ||||
|         if (forceOverwriteBody || function.IsBlueprintEvent || function.IsRpc) | ||||
|         { | ||||
|             function.FunctionPointerField = classDefinition.AddField($"{function.Name}_NativeFunction", WeaverImporter.Instance.IntPtrType, FieldAttributes.Private); | ||||
|             RewriteMethodAsUFunctionInvoke(classDefinition, function, paramsSizeField, function.RewriteInfo.FunctionParams, forceOverwriteBody); | ||||
|         } | ||||
|         else if (function.FunctionFlags.HasAnyFlags(EFunctionFlags.BlueprintCallable)) | ||||
|         { | ||||
|             foreach (var virtualFunction in classDefinition.Methods) | ||||
|             { | ||||
|                 if (virtualFunction.Name != function.Name) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if (virtualFunction.IsVirtual && virtualFunction.GetBaseMethod() != virtualFunction) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 MakeManagedMethodInvoker(classDefinition, function, virtualFunction, function.RewriteInfo.FunctionParams); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             MakeManagedMethodInvoker(classDefinition, function, function.MethodDef, function.RewriteInfo.FunctionParams); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public static void LoadNativeFunctionField(ILProcessor processor, FunctionMetaData functionMetaData) | ||||
|     { | ||||
|         if (functionMetaData.FunctionPointerField == null) | ||||
|         { | ||||
|             throw new InvalidOperationException("Function pointer field is null."); | ||||
|         } | ||||
|          | ||||
|         if (functionMetaData.FunctionPointerField.IsStatic) | ||||
|         { | ||||
|             processor.Emit(OpCodes.Ldsfld, functionMetaData.FunctionPointerField); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             processor.Emit(OpCodes.Ldarg_0); | ||||
|             processor.Emit(OpCodes.Ldfld, functionMetaData.FunctionPointerField); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public static bool HasSameSignature(MethodReference a, MethodReference b) | ||||
|     { | ||||
|         if (a.Parameters.Count != b.Parameters.Count) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         for (int i = 0; i < a.Parameters.Count; i++) | ||||
|         { | ||||
|             if (a.Parameters[i].ParameterType.FullName != b.Parameters[i].ParameterType.FullName) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|      | ||||
|     public static MethodDefinition MakeImplementationMethod(FunctionMetaData func) | ||||
|     { | ||||
|         MethodDefinition copiedMethod = Utilities.MethodUtilities.CopyMethod(func.MethodDef.Name + "_Implementation", func.MethodDef); | ||||
|         if (copiedMethod.IsVirtual) | ||||
|         { | ||||
|             // Find the call to the original function and replace it with a call to the implementation. | ||||
|             foreach (var instruction in copiedMethod.Body.Instructions) | ||||
|             { | ||||
|                 if (instruction.OpCode != OpCodes.Call) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                  | ||||
|                 MethodReference calledMethod = (MethodReference) instruction.Operand; | ||||
|                  | ||||
|                 if (func.SourceName != calledMethod.Name) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if (!copiedMethod.DeclaringType.Resolve().IsChildOf(calledMethod.DeclaringType.Resolve())  | ||||
|                     || !HasSameSignature(copiedMethod, calledMethod)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 MethodReference implementationMethod = copiedMethod.DeclaringType.BaseType.Resolve().FindMethod(copiedMethod.Name)!; | ||||
|                 instruction.Operand = implementationMethod.ImportMethod(); | ||||
|             } | ||||
|         } | ||||
|         return copiedMethod; | ||||
|     } | ||||
|  | ||||
|     public static MethodReference MakeMethodDeclaringTypeGeneric(MethodReference method, params TypeReference[] args) | ||||
|     { | ||||
|         if (args.Length == 0) | ||||
|         { | ||||
|             return method; | ||||
|         } | ||||
|  | ||||
|         if (method.DeclaringType.GenericParameters.Count != args.Length) | ||||
|         { | ||||
|             throw new ArgumentException("Invalid number of generic type arguments supplied"); | ||||
|         } | ||||
|          | ||||
|         var genericTypeRef = method.DeclaringType.MakeGenericInstanceType(args); | ||||
|  | ||||
|         var newMethodRef = new MethodReference(method.Name, method.ReturnType, genericTypeRef) | ||||
|         { | ||||
|             HasThis = method.HasThis, | ||||
|             ExplicitThis = method.ExplicitThis, | ||||
|             CallingConvention = method.CallingConvention | ||||
|         }; | ||||
|  | ||||
|         foreach (var parameter in method.Parameters) | ||||
|         { | ||||
|             newMethodRef.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); | ||||
|         } | ||||
|  | ||||
|         foreach (var genericParam in method.GenericParameters) | ||||
|         { | ||||
|             newMethodRef.GenericParameters.Add(new GenericParameter(genericParam.Name, newMethodRef)); | ||||
|         } | ||||
|  | ||||
|         return newMethodRef; | ||||
|     } | ||||
|  | ||||
|     private static void MakeManagedMethodInvoker(TypeDefinition type, FunctionMetaData func, MethodDefinition methodToCall, FunctionParamRewriteInfo[] paramRewriteInfos) | ||||
|     { | ||||
|         MethodDefinition invokerFunction = type.AddMethod("Invoke_" + func.Name,  | ||||
|             WeaverImporter.Instance.VoidTypeRef,  | ||||
|             MethodAttributes.Private,  | ||||
|             [WeaverImporter.Instance.IntPtrType, WeaverImporter.Instance.IntPtrType]); | ||||
|  | ||||
|         ILProcessor processor = invokerFunction.Body.GetILProcessor(); | ||||
|         Instruction loadBuffer = processor.Create(OpCodes.Ldarg_1); | ||||
|          | ||||
|         VariableDefinition[] paramVariables = new VariableDefinition[func.Parameters.Length]; | ||||
|          | ||||
|         for (int i = 0; i < func.Parameters.Length; ++i) | ||||
|         { | ||||
|             PropertyMetaData param = func.Parameters[i]; | ||||
|             TypeReference paramType = param.PropertyDataType.CSharpType.ImportType(); | ||||
|             paramVariables[i] = invokerFunction.AddLocalVariable(paramType); | ||||
|              | ||||
|             param.PropertyDataType.PrepareForRewrite(type, param, methodToCall); | ||||
|  | ||||
|             if (param.PropertyFlags.HasFlag(PropertyFlags.OutParm) && !param.PropertyFlags.HasFlag(PropertyFlags.ReferenceParm)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             param.PropertyDataType.WriteLoad(processor, type, loadBuffer, paramRewriteInfos[i].OffsetField!, paramVariables[i]); | ||||
|         } | ||||
|  | ||||
|         OpCode callOp = OpCodes.Call; | ||||
|          | ||||
|         if (!methodToCall.IsStatic) | ||||
|         { | ||||
|             processor.Emit(OpCodes.Ldarg_0); | ||||
|             if (methodToCall.IsVirtual) | ||||
|             { | ||||
|                 callOp = OpCodes.Callvirt; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for (var i = 0; i < paramVariables.Length; ++i) | ||||
|         { | ||||
|             VariableDefinition local = paramVariables[i]; | ||||
|             PropertyMetaData param = func.Parameters[i]; | ||||
|             OpCode loadCode = param.IsOutParameter ? OpCodes.Ldloca : OpCodes.Ldloc; | ||||
|             processor.Emit(loadCode, local); | ||||
|         } | ||||
|  | ||||
|         var returnIndex = 0; | ||||
|  | ||||
|         if (func.ReturnValue != null) | ||||
|         { | ||||
|             TypeReference returnType = func.ReturnValue.PropertyDataType.CSharpType.ImportType(); | ||||
|             invokerFunction.AddLocalVariable(returnType); | ||||
|             returnIndex = invokerFunction.Body.Variables.Count - 1; | ||||
|         } | ||||
|  | ||||
|         processor.Emit(callOp, methodToCall.ImportMethod()); | ||||
|  | ||||
|         // Marshal out params back to the native parameter buffer. | ||||
|         for (int i = 0; i < paramVariables.Length; ++i) | ||||
|         { | ||||
|             PropertyMetaData param = func.Parameters[i]; | ||||
|              | ||||
|             if (!param.IsOutParameter) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             VariableDefinition localVariable = paramVariables[i]; | ||||
|             FieldDefinition offsetField = paramRewriteInfos[i].OffsetField!; | ||||
|             NativeDataType nativeDataParamType = param.PropertyDataType; | ||||
|  | ||||
|             Instruction loadLocalVariable = processor.Create(OpCodes.Ldloc, localVariable); | ||||
|             nativeDataParamType.PrepareForRewrite(type, param, invokerFunction); | ||||
|              | ||||
|             Instruction[] loadBufferPtr = NativeDataType.GetArgumentBufferInstructions(loadBuffer, offsetField); | ||||
|              | ||||
|             nativeDataParamType.WriteMarshalToNative(processor,  | ||||
|                 type,  | ||||
|                 loadBufferPtr,  | ||||
|                 processor.Create(OpCodes.Ldc_I4_0),  | ||||
|                 loadLocalVariable); | ||||
|         } | ||||
|  | ||||
|         if (func.ReturnValue != null) | ||||
|         { | ||||
|             NativeDataType nativeReturnType = func.ReturnValue.PropertyDataType; | ||||
|             processor.Emit(OpCodes.Stloc, returnIndex); | ||||
|  | ||||
|             Instruction loadReturnProperty = processor.Create(OpCodes.Ldloc, returnIndex); | ||||
|  | ||||
|             nativeReturnType.PrepareForRewrite(type, func.ReturnValue, invokerFunction); | ||||
|              | ||||
|             nativeReturnType.WriteMarshalToNative(processor, type, [processor.Create(OpCodes.Ldarg_2)], | ||||
|                 processor.Create(OpCodes.Ldc_I4_0), loadReturnProperty); | ||||
|         } | ||||
|          | ||||
|         invokerFunction.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     private static void PrepareForRawEventInvoker(TypeDefinition type, FunctionMetaData func) | ||||
|     { | ||||
|          | ||||
|         foreach (PropertyMetaData param in func.Parameters) | ||||
|         { | ||||
|             param.PropertyDataType.PrepareForRewrite(type, param, func); | ||||
|         } | ||||
|  | ||||
|         if (func.ReturnValue == null) return; | ||||
|         NativeDataType nativeReturnType = func.ReturnValue.PropertyDataType; | ||||
|         nativeReturnType.PrepareForRewrite(type, func.ReturnValue, func); | ||||
|     } | ||||
|      | ||||
|     public static void RewriteMethodAsUFunctionInvoke(TypeDefinition type, FunctionMetaData func, FieldDefinition? paramsSizeField, FunctionParamRewriteInfo[] paramRewriteInfos, bool forceOverwriteBody = false) | ||||
|     { | ||||
|         if (!forceOverwriteBody && func.MethodDef.Body != null) | ||||
|         { | ||||
|             MakeManagedMethodInvoker(type, func, MakeImplementationMethod(func), paramRewriteInfos); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             PrepareForRawEventInvoker(type, func); | ||||
|         } | ||||
|          | ||||
|         RewriteOriginalFunctionToInvokeNative(type, func, func.MethodDef, paramsSizeField, paramRewriteInfos); | ||||
|     } | ||||
|  | ||||
|     public static void RewriteOriginalFunctionToInvokeNative(TypeDefinition type,  | ||||
|         FunctionMetaData metadata, | ||||
|         MethodDefinition methodDef,  | ||||
|         FieldDefinition? paramsSizeField, | ||||
|         FunctionParamRewriteInfo[] paramRewriteInfos) | ||||
|     { | ||||
|         // Remove the original method body. We'll replace it with a call to the native function. | ||||
|         methodDef.Body = new MethodBody(methodDef); | ||||
|          | ||||
|         if (metadata.FunctionPointerField == null) | ||||
|         { | ||||
|             throw new InvalidOperationException("Function pointer field is null."); | ||||
|         } | ||||
|  | ||||
|         bool staticNativeFunction = metadata.FunctionPointerField.IsStatic; | ||||
|         bool hasReturnValue = !methodDef.ReturnsVoid(); | ||||
|         bool hasParams = methodDef.Parameters.Count > 0 || hasReturnValue; | ||||
|  | ||||
|         ILProcessor processor = methodDef.Body.GetILProcessor(); | ||||
|         VariableDefinition? argumentsBufferPtr = null; | ||||
|         List<Instruction> allCleanupInstructions = []; | ||||
|          | ||||
|         Instruction loadObjectInstance = Instruction.Create(OpCodes.Ldarg_0); | ||||
|         Instruction? loadArgumentBuffer = null; | ||||
|          | ||||
|         if (hasParams) | ||||
|         { | ||||
|             WriteParametersToNative(processor, methodDef, metadata, paramsSizeField, paramRewriteInfos, out argumentsBufferPtr, out loadArgumentBuffer, allCleanupInstructions); | ||||
|         } | ||||
|          | ||||
|         processor.Emit(OpCodes.Ldarg_0); | ||||
|         processor.Emit(OpCodes.Call, WeaverImporter.Instance.NativeObjectGetter); | ||||
|  | ||||
|         if (staticNativeFunction) | ||||
|         { | ||||
|             processor.Emit(OpCodes.Ldsfld, metadata.FunctionPointerField); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             processor.Append(loadObjectInstance); | ||||
|             processor.Emit(OpCodes.Ldfld, metadata.FunctionPointerField); | ||||
|         } | ||||
|  | ||||
|         if (hasParams) | ||||
|         { | ||||
|             processor.Emit(OpCodes.Ldloc, argumentsBufferPtr); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             processor.Emit(OpCodes.Ldsfld, WeaverImporter.Instance.IntPtrZero); | ||||
|         } | ||||
|  | ||||
|         if (hasReturnValue) | ||||
|         { | ||||
|             processor.Emit(OpCodes.Ldloc, argumentsBufferPtr); | ||||
|             processor.Emit(OpCodes.Ldsfld, metadata.ReturnValue!.PropertyOffsetField); | ||||
|             processor.Emit(OpCodes.Call, WeaverImporter.Instance.IntPtrAdd); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             processor.Emit(OpCodes.Ldsfld, WeaverImporter.Instance.IntPtrZero); | ||||
|         } | ||||
|          | ||||
|         processor.Emit(OpCodes.Call, DetermineInvokeFunction(metadata)); | ||||
|  | ||||
|         foreach (Instruction instruction in allCleanupInstructions) | ||||
|         { | ||||
|             processor.Append(instruction); | ||||
|         } | ||||
|          | ||||
|         // Marshal out params back from the native parameter buffer. | ||||
|         if (metadata.FunctionFlags.HasFlag(EFunctionFlags.HasOutParms)) | ||||
|         { | ||||
|             for (var i = 0; i < metadata.Parameters.Length; ++i) | ||||
|             { | ||||
|                 PropertyMetaData param = metadata.Parameters[i]; | ||||
|  | ||||
|                 if (!param.PropertyFlags.HasFlag(PropertyFlags.OutParm)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 processor.Emit(OpCodes.Ldarg, i + 1); | ||||
|  | ||||
|                 Instruction[] load = NativeDataType.GetArgumentBufferInstructions(loadArgumentBuffer, paramRewriteInfos[i].OffsetField!); | ||||
|                 param.PropertyDataType.WriteMarshalFromNative(processor, type, load, processor.Create(OpCodes.Ldc_I4_0)); | ||||
|              | ||||
|                 Instruction setInstructionOutParam = methodDef.Parameters[i].CreateSetInstructionOutParam(param.PropertyDataType.PropertyType); | ||||
|                 processor.Append(setInstructionOutParam); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Marshal return value back from the native parameter buffer. | ||||
|         if (metadata.ReturnValue != null) | ||||
|         { | ||||
|             // Return value is always the last parameter. | ||||
|             Instruction[] load = NativeDataType.GetArgumentBufferInstructions(loadArgumentBuffer, paramRewriteInfos[^1].OffsetField!); | ||||
|             metadata.ReturnValue.PropertyDataType.WriteMarshalFromNative(processor, type, load, Instruction.Create(OpCodes.Ldc_I4_0)); | ||||
|         } | ||||
|  | ||||
|         processor.Emit(OpCodes.Ret); | ||||
|          | ||||
|         if (staticNativeFunction) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         Instruction branchTarget = processor.Body.Instructions[0]; | ||||
|         processor.InsertBefore(branchTarget, processor.Create(OpCodes.Ldarg_0)); | ||||
|         processor.InsertBefore(branchTarget, processor.Create(OpCodes.Ldfld, metadata.FunctionPointerField)); | ||||
|         processor.InsertBefore(branchTarget, processor.Create(OpCodes.Ldsfld, WeaverImporter.Instance.IntPtrZero)); | ||||
|          | ||||
|         processor.InsertBefore(branchTarget, processor.Create(OpCodes.Call, WeaverImporter.Instance.IntPtrEqualsOperator)); | ||||
|  | ||||
|         Instruction branchPosition = processor.Create(OpCodes.Ldarg_0); | ||||
|  | ||||
|         processor.InsertBefore(branchTarget, branchPosition); | ||||
|         processor.InsertBefore(branchTarget, processor.Create(OpCodes.Ldarg_0)); | ||||
|         processor.InsertBefore(branchTarget, processor.Create(OpCodes.Call, WeaverImporter.Instance.NativeObjectGetter)); | ||||
|         processor.InsertBefore(branchTarget, processor.Create(OpCodes.Ldstr, methodDef.Name)); | ||||
|         processor.InsertBefore(branchTarget, processor.Create(OpCodes.Call, WeaverImporter.Instance.GetNativeFunctionFromInstanceAndNameMethod)); | ||||
|         processor.InsertBefore(branchTarget, processor.Create(OpCodes.Stfld, metadata.FunctionPointerField)); | ||||
|         processor.InsertBefore(branchPosition, processor.Create(OpCodes.Brfalse, branchTarget)); | ||||
|          | ||||
|         methodDef.OptimizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public static MethodDefinition CreateMethod(TypeDefinition declaringType, string name, MethodAttributes attributes, TypeReference? returnType = null, TypeReference[]? parameters = null) | ||||
|     { | ||||
|         MethodDefinition def = new MethodDefinition(name, attributes, returnType ?? WeaverImporter.Instance.VoidTypeRef); | ||||
|  | ||||
|         if (parameters != null) | ||||
|         { | ||||
|             foreach (var type in parameters) | ||||
|             { | ||||
|                 if (type == null) | ||||
|                 { | ||||
|                     throw new ArgumentException("Parameter type cannot be null.", nameof(parameters)); | ||||
|                 } | ||||
|                  | ||||
|                 def.Parameters.Add(new ParameterDefinition(type)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         declaringType.Methods.Add(def); | ||||
|         return def; | ||||
|     } | ||||
|      | ||||
|     public static void AddOffsetField(TypeDefinition classDefinition, PropertyMetaData propertyMetaData, FunctionMetaData func, int index, FunctionParamRewriteInfo[] paramRewriteInfos) | ||||
|     { | ||||
|         FieldDefinition newField = classDefinition.AddField(func.Name + "_" + propertyMetaData.Name + "_Offset", WeaverImporter.Instance.Int32TypeRef); | ||||
|         paramRewriteInfos[index].OffsetField = newField; | ||||
|         propertyMetaData.PropertyOffsetField = newField; | ||||
|     } | ||||
|  | ||||
|     public static void AddNativePropertyField(TypeDefinition classDefinition, PropertyMetaData propertyMetaData, FunctionMetaData func, int index, FunctionParamRewriteInfo[] paramRewriteInfos) | ||||
|     { | ||||
|         if (!propertyMetaData.PropertyDataType.NeedsNativePropertyField) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         var newField = classDefinition.AddField(func.Name + "_" + propertyMetaData.Name + "_NativeProperty", WeaverImporter.Instance.IntPtrType, | ||||
|             FieldAttributes.InitOnly | FieldAttributes.Static | FieldAttributes.Private); | ||||
|         paramRewriteInfos[index].NativePropertyField = newField; | ||||
|         propertyMetaData.NativePropertyField = newField; | ||||
|     } | ||||
|  | ||||
|     public static void WriteParametersToNative(ILProcessor processor,  | ||||
|         MethodDefinition methodDef, | ||||
|         FunctionMetaData metadata, | ||||
|         FieldDefinition? paramsSizeField, | ||||
|         FunctionParamRewriteInfo[] paramRewriteInfos,  | ||||
|         out VariableDefinition argumentsBufferPtr,  | ||||
|         out Instruction loadArgumentBuffer,  | ||||
|         List<Instruction> allCleanupInstructions) | ||||
|     { | ||||
|         VariableDefinition argumentsBuffer = methodDef.AddLocalVariable(new PointerType(WeaverImporter.Instance.ByteTypeRef)); | ||||
|         methodDef.Body.Variables.Add(argumentsBuffer); | ||||
|          | ||||
|         processor.Emit(OpCodes.Ldsfld, paramsSizeField); | ||||
|         processor.Emit(OpCodes.Conv_I4); | ||||
|         processor.Emit(OpCodes.Localloc); | ||||
|         processor.Emit(OpCodes.Stloc, argumentsBuffer); | ||||
|  | ||||
|         // nint num = (nint) ptr; | ||||
|         //IL_0037: ldloc 0 | ||||
|         //IL_003b: conv.i | ||||
|         //IL_003c: stloc 1 | ||||
|         processor.Emit(OpCodes.Ldloc, argumentsBuffer); | ||||
|         processor.Emit(OpCodes.Conv_I); | ||||
|         argumentsBufferPtr = methodDef.AddLocalVariable(WeaverImporter.Instance.IntPtrType); | ||||
|         processor.Emit(OpCodes.Stloc, argumentsBufferPtr); | ||||
|          | ||||
|         // Initialize values | ||||
|         LoadNativeFunctionField(processor, metadata); | ||||
|         processor.Emit(OpCodes.Ldloc, argumentsBufferPtr); | ||||
|         processor.Emit(OpCodes.Call, WeaverImporter.Instance.InitializeStructMethod); | ||||
|          | ||||
|         loadArgumentBuffer = processor.Create(OpCodes.Ldloc, argumentsBufferPtr); | ||||
|  | ||||
|         for (byte i = 0; i < paramRewriteInfos.Length; ++i) | ||||
|         { | ||||
|             PropertyMetaData paramType = paramRewriteInfos[i].PropertyMetaData; | ||||
|              | ||||
|             if (paramType.IsReturnParameter) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (paramType is { IsOutParameter: true, IsReferenceParameter: false }) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|          | ||||
|             FieldDefinition offsetField = paramRewriteInfos[i].OffsetField!; | ||||
|             NativeDataType nativeDataType = paramType.PropertyDataType; | ||||
|  | ||||
|             IList<Instruction>? cleanupInstructions = nativeDataType.WriteStore(processor, methodDef.DeclaringType, loadArgumentBuffer, offsetField, i + 1, methodDef.Parameters[i]); | ||||
|  | ||||
|             if (cleanupInstructions != null) | ||||
|             { | ||||
|                 allCleanupInstructions.AddRange(cleanupInstructions); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     static MethodReference DetermineInvokeFunction(FunctionMetaData functionMetaData) | ||||
|     { | ||||
|         if (functionMetaData.IsRpc) | ||||
|         { | ||||
|             return WeaverImporter.Instance.InvokeNativeNetFunction; | ||||
|         } | ||||
|  | ||||
|         if (functionMetaData.HasOutParams) | ||||
|         { | ||||
|             return WeaverImporter.Instance.InvokeNativeFunctionOutParms; | ||||
|         } | ||||
|  | ||||
|         return WeaverImporter.Instance.InvokeNativeFunctionMethod; | ||||
|     } | ||||
|      | ||||
| } | ||||
| @ -0,0 +1,39 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| namespace UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| public struct FunctionParamRewriteInfo(PropertyMetaData propertyMetadata) | ||||
| { | ||||
|     public readonly PropertyMetaData PropertyMetaData = propertyMetadata; | ||||
|     public FieldDefinition? OffsetField; | ||||
|     public FieldDefinition? NativePropertyField; | ||||
| } | ||||
|  | ||||
| public struct FunctionRewriteInfo | ||||
| { | ||||
|     public FunctionRewriteInfo(FunctionMetaData functionMetadata) | ||||
|     { | ||||
|         int paramSize = functionMetadata.Parameters.Length; | ||||
|  | ||||
|         if (functionMetadata.ReturnValue != null) | ||||
|         { | ||||
|             paramSize++; | ||||
|         } | ||||
|          | ||||
|         FunctionParams = new FunctionParamRewriteInfo[paramSize]; | ||||
|  | ||||
|         for (int i = 0; i < functionMetadata.Parameters.Length; i++) | ||||
|         { | ||||
|             FunctionParams[i] = new(functionMetadata.Parameters[i]); | ||||
|         } | ||||
|  | ||||
|         if (functionMetadata.ReturnValue != null) | ||||
|         { | ||||
|             FunctionParams[^1] = new(functionMetadata.ReturnValue); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public FieldDefinition? FunctionParamSizeField; | ||||
|     public readonly FunctionParamRewriteInfo[] FunctionParams; | ||||
| } | ||||
| @ -0,0 +1,265 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.NativeTypes; | ||||
|  | ||||
| namespace UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| public static class PropertyProcessor | ||||
| { | ||||
|     public static void ProcessClassMembers( | ||||
|         ref List<Tuple<FieldDefinition, PropertyMetaData>> propertyOffsetsToInitialize, | ||||
|         ref List<Tuple<FieldDefinition, PropertyMetaData>> propertyPointersToInitialize, | ||||
|         TypeDefinition type, | ||||
|         IEnumerable<PropertyMetaData> properties) | ||||
|     { | ||||
|         var removedBackingFields = new Dictionary<string, (PropertyMetaData, PropertyDefinition, FieldDefinition, FieldDefinition?)>(); | ||||
|  | ||||
|         foreach (PropertyMetaData prop in properties) | ||||
|         { | ||||
|             if (prop.HasCustomAccessors) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             FieldDefinition offsetField = AddOffsetField(type, prop, WeaverImporter.Instance.Int32TypeRef); | ||||
|             FieldDefinition? nativePropertyField = AddNativePropertyField(type, prop, WeaverImporter.Instance.IntPtrType); | ||||
|              | ||||
|             propertyOffsetsToInitialize.Add(Tuple.Create(offsetField, prop)); | ||||
|              | ||||
|             if (nativePropertyField != null) | ||||
|             { | ||||
|                 prop.NativePropertyField = nativePropertyField; | ||||
|                 propertyPointersToInitialize.Add(Tuple.Create(nativePropertyField, prop)); | ||||
|             } | ||||
|  | ||||
|             if (prop.MemberRef == null) | ||||
|             { | ||||
|                 throw new InvalidDataException($"Property '{prop.Name}' does not have a member reference"); | ||||
|             } | ||||
|              | ||||
|             prop.PropertyDataType.PrepareForRewrite(type, prop, prop.MemberRef); | ||||
|  | ||||
|             Instruction[] loadBuffer = NativeDataType.GetArgumentBufferInstructions(null, offsetField); | ||||
|              | ||||
|             if (prop.MemberRef.Resolve() is PropertyDefinition propertyRef) | ||||
|             { | ||||
|                 // Standard property handling | ||||
|                 prop.PropertyDataType.WriteGetter(type, propertyRef.GetMethod, loadBuffer, nativePropertyField); | ||||
|                 if (propertyRef.SetMethod is not null) { | ||||
|                   prop.PropertyDataType.WriteSetter(type, propertyRef.SetMethod, loadBuffer, nativePropertyField); | ||||
|                 } | ||||
|                  | ||||
|                 string backingFieldName = RemovePropertyBackingField(type, prop); | ||||
|                 removedBackingFields.Add(backingFieldName, (prop, propertyRef, offsetField, nativePropertyField)); | ||||
|             } | ||||
|              | ||||
|             prop.PropertyOffsetField = offsetField; | ||||
|         } | ||||
|  | ||||
|         RemoveBackingFieldReferences(type, removedBackingFields); | ||||
|     } | ||||
|      | ||||
|     private static void RemoveBackingFieldReferences(TypeDefinition type, Dictionary<string, (PropertyMetaData, PropertyDefinition, FieldDefinition, FieldDefinition?)> strippedFields) | ||||
|     { | ||||
|         foreach (MethodDefinition? method in type.GetConstructors().ToArray()) | ||||
|         { | ||||
|             if (!method.HasBody) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             bool baseCallFound = false; | ||||
|             var alteredInstructions = new List<Instruction>(); | ||||
|             var deferredInstructions = new List<Instruction>(); | ||||
|  | ||||
|             foreach (Instruction? instr in method.Body.Instructions) | ||||
|             { | ||||
|                 alteredInstructions.Add(instr); | ||||
|  | ||||
|                 if (instr.Operand is MethodReference baseCtor && baseCtor.Name == ".ctor") | ||||
|                 { | ||||
|                     baseCallFound = true; | ||||
|                     alteredInstructions.AddRange(deferredInstructions); | ||||
|                 } | ||||
|  | ||||
|                 if (instr.Operand is not FieldReference field) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if (!strippedFields.TryGetValue(field.Name, out (PropertyMetaData meta, PropertyDefinition def, FieldDefinition offsetField, | ||||
|                         FieldDefinition? nativePropertyField) prop)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                  | ||||
|                 if (instr.OpCode != OpCodes.Stfld) | ||||
|                 { | ||||
|                     throw new UnableToFixPropertyBackingReferenceException(method, prop.def, instr.OpCode); | ||||
|                 } | ||||
|  | ||||
|                 MethodDefinition? setMethod = prop.def.SetMethod; | ||||
|  | ||||
|                 //if the property did not have a setter, add a private one for the ctor to use | ||||
|                 if (setMethod == null) | ||||
|                 { | ||||
|                     var voidRef = type.Module.ImportReference(typeof(void)); | ||||
|                     prop.def.SetMethod = setMethod = new MethodDefinition($"set_{prop.def.Name}", | ||||
|                         MethodAttributes.SpecialName | MethodAttributes.Private | MethodAttributes.HideBySig, | ||||
|                         voidRef); | ||||
|                     setMethod.Parameters.Add(new ParameterDefinition(prop.def.PropertyType)); | ||||
|                     type.Methods.Add(setMethod); | ||||
|                      | ||||
|                     // If this is a property with custom accessors, we need to use the generated property | ||||
|                     if (prop.meta.HasCustomAccessors && prop.meta.GeneratedAccessorProperty != null) | ||||
|                     { | ||||
|                         // Use the generated accessor's set method | ||||
|                         setMethod = prop.meta.GeneratedAccessorProperty.SetMethod; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         // Standard property handling | ||||
|                         Instruction[] loadBuffer = NativeDataType.GetArgumentBufferInstructions(null, prop.offsetField); | ||||
|                         prop.meta.PropertyDataType.WriteSetter(type, prop.def.SetMethod, loadBuffer, prop.nativePropertyField); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 // Determine which setter to call based on whether we have custom accessors | ||||
|                 var methodToCall = (prop.meta.HasCustomAccessors && prop.meta.GeneratedAccessorProperty != null)  | ||||
|                     ? prop.meta.GeneratedAccessorProperty.SetMethod  | ||||
|                     : setMethod; | ||||
|  | ||||
|                 var newInstr = Instruction.Create((methodToCall.IsReuseSlot && methodToCall.IsVirtual) ? OpCodes.Callvirt : OpCodes.Call, methodToCall); | ||||
|                 newInstr.Offset = instr.Offset; | ||||
|                 alteredInstructions[alteredInstructions.Count - 1] = newInstr; | ||||
|  | ||||
|                 // now the hairy bit. initializers happen _before_ the base ctor call, so the NativeObject is not yet set, and they fail | ||||
|                 //we need to relocate these to after the base ctor call | ||||
|                 if (baseCallFound) | ||||
|                 { | ||||
|                     // if they're after the base ctor call it's fine | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 //handle the simple pattern `ldarg0; ldconst*; call set_*` | ||||
|                 if (alteredInstructions[^3].OpCode != OpCodes.Ldarg_0) | ||||
|                 { | ||||
|                     throw new UnsupportedPropertyInitializerException(prop.def);  | ||||
|                 } | ||||
|  | ||||
|                 var ldconst = alteredInstructions[^2]; | ||||
|  | ||||
|                 if (!IsLdconst(ldconst)) | ||||
|                 { | ||||
|                     throw new UnsupportedPropertyInitializerException(prop.def); | ||||
|                 } | ||||
|  | ||||
|                 CopyLastElements(alteredInstructions, deferredInstructions, 3); | ||||
|  | ||||
|                 // we should skip the initialization if the value is null, as it will be set to null by default, | ||||
|                 // and we don't want to call the setter because in this case it will marshal a null value to the native side | ||||
|                 // which will cause issues for types like TArray, TMap, etc. | ||||
|                 if (ldconst.OpCode == OpCodes.Ldnull) | ||||
|                 { | ||||
|                     deferredInstructions.RemoveRange(deferredInstructions.Count - 3, 3); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             //add back the instructions and fix up their offsets | ||||
|             method.Body.Instructions.Clear(); | ||||
|             int offset = 0; | ||||
|             foreach (var instr in alteredInstructions) | ||||
|             { | ||||
|                 int oldOffset = instr.Offset; | ||||
|                 instr.Offset = offset; | ||||
|                 method.Body.Instructions.Add(instr); | ||||
|  | ||||
|                 //fix up the sequence point offsets too | ||||
|                 if (method.DebugInformation == null || oldOffset == offset) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 //this only uses the offset so doesn't matter that we replaced the instruction | ||||
|                 var seqPoint = method.DebugInformation?.GetSequencePoint(instr); | ||||
|                 if (seqPoint == null) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 if (method.DebugInformation == null) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 method.DebugInformation.SequencePoints.Remove(seqPoint); | ||||
|                 method.DebugInformation.SequencePoints.Add( | ||||
|                     new SequencePoint(instr, seqPoint.Document) | ||||
|                     { | ||||
|                         StartLine = seqPoint.StartLine, | ||||
|                         StartColumn = seqPoint.StartColumn, | ||||
|                         EndLine = seqPoint.EndLine, | ||||
|                         EndColumn = seqPoint.EndColumn | ||||
|                     }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static string RemovePropertyBackingField(TypeDefinition type, PropertyMetaData prop) | ||||
|     { | ||||
|         string backingFieldName = $"<{prop.Name}>k__BackingField"; | ||||
|  | ||||
|         for (var i = 0; i < type.Fields.Count; i++) | ||||
|         { | ||||
|             if (type.Fields[i].Name != backingFieldName) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             type.Fields.RemoveAt(i); | ||||
|             return backingFieldName; | ||||
|         } | ||||
|          | ||||
|         throw new InvalidDataException($"Property '{prop.Name}' does not have a backing field"); | ||||
|     } | ||||
|  | ||||
|     public static FieldDefinition AddOffsetField(TypeDefinition type, PropertyMetaData prop, TypeReference int32TypeRef) | ||||
|     { | ||||
|         var field = new FieldDefinition(prop.Name + "_Offset", | ||||
|             FieldAttributes.Static | FieldAttributes.Private, int32TypeRef); | ||||
|         type.Fields.Add(field); | ||||
|         return field; | ||||
|     } | ||||
|  | ||||
|     public static FieldDefinition? AddNativePropertyField(TypeDefinition type, PropertyMetaData prop, TypeReference intPtrTypeRef) | ||||
|     { | ||||
|         if (!prop.PropertyDataType.NeedsNativePropertyField) | ||||
|         { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         FieldDefinition field = new FieldDefinition(prop.Name + "_NativeProperty", FieldAttributes.InitOnly | FieldAttributes.Static | FieldAttributes.Private, intPtrTypeRef); | ||||
|         type.Fields.Add(field); | ||||
|         return field; | ||||
|     } | ||||
|  | ||||
|     public static bool IsLdconst(Instruction ldconst) | ||||
|     { | ||||
|         return ldconst.OpCode.Op1 == 0xff && ldconst.OpCode.Op2 >= 0x14 && ldconst.OpCode.Op2 <= 0x23; | ||||
|     } | ||||
|  | ||||
|     public static void CopyLastElements(List<Instruction> from, List<Instruction> to, int count) | ||||
|     { | ||||
|         int startIdx = from.Count - count; | ||||
|         for (int i = startIdx; i < startIdx + count; i++) | ||||
|         { | ||||
|             to.Add(from[i]); | ||||
|         } | ||||
|         for (int i = startIdx + count -1; i >= startIdx; i--) | ||||
|         { | ||||
|             from.RemoveAt(i); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,132 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| public static class UnrealClassProcessor | ||||
| {  | ||||
|     public static void ProcessClasses(IList<TypeDefinition> classes, ApiMetaData assemblyMetadata) | ||||
|     { | ||||
|         assemblyMetadata.ClassMetaData.Capacity = classes.Count; | ||||
|  | ||||
|         var rewrittenClasses = new HashSet<TypeDefinition>(); | ||||
|         foreach (var classDef in classes) | ||||
|         { | ||||
|             ProcessParentClass(classDef, classes, rewrittenClasses, assemblyMetadata); | ||||
|         } | ||||
|          | ||||
|         foreach (ClassMetaData classMetaData in assemblyMetadata.ClassMetaData) | ||||
|         { | ||||
|             classMetaData.PostWeaveCleanup(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private static void ProcessParentClass(TypeDefinition type, IList<TypeDefinition> classes, HashSet<TypeDefinition> rewrittenClasses, ApiMetaData assemblyMetadata) | ||||
|     { | ||||
|         TypeDefinition baseType = type.BaseType.Resolve(); | ||||
|          | ||||
|         if (!baseType.IsUObject()) | ||||
|         { | ||||
|             throw new Exception($"{type.FullName} is marked with UClass but doesn't inherit from CoreUObject.Object."); | ||||
|         } | ||||
|          | ||||
|         if (baseType != null && baseType.Module == type.Module && classes.Contains(baseType) && !rewrittenClasses.Contains(baseType)) | ||||
|         { | ||||
|             ProcessParentClass(baseType, classes, rewrittenClasses, assemblyMetadata); | ||||
|         } | ||||
|  | ||||
|         if (rewrittenClasses.Contains(type)) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         ClassMetaData classMetaData = new ClassMetaData(type); | ||||
|         assemblyMetadata.ClassMetaData.Add(classMetaData); | ||||
|          | ||||
|         ProcessClass(type, classMetaData); | ||||
|         rewrittenClasses.Add(type); | ||||
|     } | ||||
|      | ||||
|     private static void ProcessClass(TypeDefinition classTypeDefinition, ClassMetaData metadata) | ||||
|     { | ||||
|         // Rewrite all the properties of the class to make getters/setters that call Native code. | ||||
|         if (metadata.Properties != null) | ||||
|         { | ||||
|             var offsetsToInitialize = new List<Tuple<FieldDefinition, PropertyMetaData>>(); | ||||
|             var pointersToInitialize = new List<Tuple<FieldDefinition, PropertyMetaData>>(); | ||||
|             PropertyProcessor.ProcessClassMembers(ref offsetsToInitialize, ref pointersToInitialize, classTypeDefinition, metadata.Properties); | ||||
|         } | ||||
|          | ||||
|         // Add a field to cache the native UClass pointer. | ||||
|         // Example: private static readonly nint NativeClassPtr = UCoreUObjectExporter.CallGetNativeClassFromName("MyActorClass"); | ||||
|         FieldDefinition nativeClassField = classTypeDefinition.AddField("NativeClass", WeaverImporter.Instance.IntPtrType); | ||||
|          | ||||
|         ConstructorBuilder.CreateTypeInitializer(classTypeDefinition, Instruction.Create(OpCodes.Stsfld, nativeClassField),  | ||||
|             [Instruction.Create(OpCodes.Call, WeaverImporter.Instance.GetNativeClassFromNameMethod)]); | ||||
|  | ||||
|         foreach (var field in classTypeDefinition.Fields) | ||||
|         { | ||||
|             if (field.IsUProperty()) | ||||
|             { | ||||
|                 throw new InvalidPropertyException(field, "Fields cannot be UProperty"); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         MethodDefinition staticConstructor = ConstructorBuilder.MakeStaticConstructor(classTypeDefinition); | ||||
|         ILProcessor processor = staticConstructor.Body.GetILProcessor(); | ||||
|         Instruction loadNativeClassField = Instruction.Create(OpCodes.Ldsfld, nativeClassField); | ||||
|          | ||||
|         if (metadata.Properties != null) | ||||
|         { | ||||
|             ConstructorBuilder.InitializeFields(staticConstructor, metadata.Properties, loadNativeClassField); | ||||
|         } | ||||
|          | ||||
|         foreach (FunctionMetaData function in metadata.Functions) | ||||
|         { | ||||
|             EmitFunctionGlueToStaticCtor(function, processor, loadNativeClassField, staticConstructor); | ||||
|         } | ||||
|  | ||||
|         foreach (FunctionMetaData virtualFunction in metadata.VirtualFunctions) | ||||
|         { | ||||
|             if (!FunctionMetaData.IsInterfaceFunction(virtualFunction.MethodDef)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             EmitFunctionGlueToStaticCtor(virtualFunction, processor, loadNativeClassField, staticConstructor); | ||||
|         } | ||||
|          | ||||
|         staticConstructor.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public static void EmitFunctionGlueToStaticCtor(FunctionMetaData function, ILProcessor processor, Instruction loadNativeClassField, MethodDefinition staticConstructor) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (!function.HasParameters) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|              | ||||
|             VariableDefinition variableDefinition = staticConstructor.AddLocalVariable(WeaverImporter.Instance.IntPtrType); | ||||
|             Instruction loadNativePointer = Instruction.Create(OpCodes.Ldloc, variableDefinition); | ||||
|             Instruction storeNativePointer = Instruction.Create(OpCodes.Stloc, variableDefinition); | ||||
|              | ||||
|             function.EmitFunctionPointers(processor, loadNativeClassField, Instruction.Create(OpCodes.Stloc, variableDefinition)); | ||||
|             function.EmitFunctionParamOffsets(processor, loadNativePointer); | ||||
|             function.EmitFunctionParamSize(processor, loadNativePointer); | ||||
|             function.EmitParamNativeProperty(processor, loadNativePointer); | ||||
|              | ||||
|             foreach (var param in function.Parameters) | ||||
|             { | ||||
|                 param.PropertyDataType.WritePostInitialization(processor, param, loadNativePointer, storeNativePointer); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception e) | ||||
|         { | ||||
|             throw new Exception($"Failed to emit function glue for {function.Name}", e); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,205 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.NativeTypes; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| public static class UnrealDelegateProcessor | ||||
| { | ||||
|     public static readonly string InitializeUnrealDelegate = "InitializeUnrealDelegate"; | ||||
|      | ||||
|     public static void ProcessDelegates(List<TypeDefinition> delegates, List<TypeDefinition> multicastDelegates, AssemblyDefinition assembly, List<DelegateMetaData> delegateMetaData) | ||||
|     { | ||||
|         int totalDelegateCount = multicastDelegates.Count + delegates.Count; | ||||
|         if (totalDelegateCount <= 0) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|              | ||||
|         delegateMetaData.Capacity = totalDelegateCount; | ||||
|          | ||||
|         ProcessMulticastDelegates(multicastDelegates, delegateMetaData); | ||||
|         ProcessSingleDelegates(delegates, assembly, delegateMetaData); | ||||
|     } | ||||
|      | ||||
|     private static void ProcessMulticastDelegates(List<TypeDefinition> delegateExtensions, List<DelegateMetaData> delegateMetaData) | ||||
|     { | ||||
|         foreach (TypeDefinition type in delegateExtensions) | ||||
|         { | ||||
|             MethodReference? invokerMethod = type.FindMethod("Invoker", throwIfNotFound: false); | ||||
|              | ||||
|             if (invokerMethod == null) | ||||
|             { | ||||
|                 throw new Exception("Could not find Invoker method in delegate extension type"); | ||||
|             } | ||||
|              | ||||
|             FunctionMetaData functionMetaData = new FunctionMetaData(invokerMethod.Resolve()); | ||||
|             DelegateMetaData newDelegate = new DelegateMetaData(functionMetaData,  | ||||
|                 type,  | ||||
|                 "UMulticastDelegate",  | ||||
|                 EFunctionFlags.MulticastDelegate); | ||||
|              | ||||
|             delegateMetaData.Add(newDelegate); | ||||
|              | ||||
|             if (invokerMethod.Parameters.Count == 0) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             WriteInvokerMethod(type, invokerMethod, functionMetaData); | ||||
|             ProcessInitialize(type, functionMetaData); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private static void ProcessSingleDelegates(List<TypeDefinition> delegateExtensions, AssemblyDefinition assembly, List<DelegateMetaData> delegateMetaData) | ||||
|     { | ||||
|         if (delegateExtensions.Count == 0) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         TypeReference delegateDataStruct = WeaverImporter.Instance.UnrealSharpAssembly.FindType("DelegateData", WeaverImporter.UnrealSharpNamespace)!; | ||||
|         TypeReference blittableMarshaller = WeaverImporter.Instance.UnrealSharpCoreAssembly.FindGenericType(WeaverImporter.UnrealSharpCoreMarshallers, "BlittableMarshaller`1", [delegateDataStruct])!; | ||||
|          | ||||
|         MethodReference blittabletoNativeMethod = blittableMarshaller.Resolve().FindMethod("ToNative")!; | ||||
|         MethodReference blittablefromNativeMethod = blittableMarshaller.Resolve().FindMethod("FromNative")!; | ||||
|         blittabletoNativeMethod = FunctionProcessor.MakeMethodDeclaringTypeGeneric(blittabletoNativeMethod, [delegateDataStruct]); | ||||
|         blittablefromNativeMethod = FunctionProcessor.MakeMethodDeclaringTypeGeneric(blittablefromNativeMethod, [delegateDataStruct]); | ||||
|          | ||||
|         foreach (TypeDefinition type in delegateExtensions) | ||||
|         { | ||||
|             TypeDefinition marshaller = assembly.CreateNewClass(type.Namespace, type.Name + "Marshaller", TypeAttributes.Class | TypeAttributes.Public); | ||||
|              | ||||
|             // Create a delegate from the marshaller | ||||
|             MethodDefinition fromNativeMethod = marshaller.AddFromNativeMethod(type); | ||||
|             MethodDefinition toNativeMethod = marshaller.AddToNativeMethod(type); | ||||
|             ILProcessor processor = fromNativeMethod.Body.GetILProcessor(); | ||||
|              | ||||
|             MethodReference constructor = type.FindMethod(".ctor", true, delegateDataStruct)!; | ||||
|             constructor.DeclaringType = type; | ||||
|  | ||||
|             VariableDefinition delegateDataVar = fromNativeMethod.AddLocalVariable(delegateDataStruct); | ||||
|              | ||||
|             // Load native buffer | ||||
|             processor.Emit(OpCodes.Ldarg_0); | ||||
|              | ||||
|             // Load array offset of 0 | ||||
|             processor.Emit(OpCodes.Ldc_I4_0); | ||||
|              | ||||
|             processor.Emit(OpCodes.Call, blittablefromNativeMethod); | ||||
|             processor.Emit(OpCodes.Stloc, delegateDataVar); | ||||
|              | ||||
|             processor.Emit(OpCodes.Ldloc, delegateDataVar); | ||||
|              | ||||
|             MethodReference? constructorDelegate = type.FindMethod(".ctor", true, [delegateDataStruct]); | ||||
|             processor.Emit(OpCodes.Newobj, constructorDelegate); | ||||
|             processor.Emit(OpCodes.Ret); | ||||
|              | ||||
|             MethodReference? invokerMethod = type.FindMethod("Invoker"); | ||||
|              | ||||
|             if (invokerMethod == null) | ||||
|             { | ||||
|                 throw new Exception("Could not find Invoker method in delegate type"); | ||||
|             } | ||||
|              | ||||
|             FunctionMetaData functionMetaData = new FunctionMetaData(invokerMethod.Resolve()); | ||||
|             DelegateMetaData newDelegate = new DelegateMetaData(functionMetaData,  | ||||
|                 type,  | ||||
|                 "USingleDelegate"); | ||||
|             delegateMetaData.Add(newDelegate); | ||||
|              | ||||
|             if (invokerMethod.Parameters.Count == 0) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             WriteInvokerMethod(type, invokerMethod, functionMetaData); | ||||
|             ProcessInitialize(type, functionMetaData); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void WriteInvokerMethod(TypeDefinition delegateType, MethodReference invokerMethod, FunctionMetaData functionMetaData) | ||||
|     { | ||||
|         GenericInstanceType baseGenericDelegateType = (GenericInstanceType)delegateType.BaseType; | ||||
|         TypeReference processDelegateType = baseGenericDelegateType.GenericArguments[0]; | ||||
|          | ||||
|         MethodReference processDelegateBase = delegateType.FindMethod("ProcessDelegate")!; | ||||
|         MethodReference declaredType = FunctionProcessor.MakeMethodDeclaringTypeGeneric(processDelegateBase.Resolve().GetBaseMethod(), | ||||
|                 processDelegateType.ImportType()).ImportMethod(); | ||||
|          | ||||
|         MethodDefinition invokerMethodDefinition = invokerMethod.Resolve(); | ||||
|         ILProcessor invokerMethodProcessor = invokerMethodDefinition.Body.GetILProcessor(); | ||||
|         invokerMethodProcessor.Body.Instructions.Clear(); | ||||
|  | ||||
|         if (functionMetaData.Parameters.Length > 0) | ||||
|         { | ||||
|             functionMetaData.FunctionPointerField = delegateType.AddField("SignatureFunction", WeaverImporter.Instance.IntPtrType, FieldAttributes.Public | FieldAttributes.Static); | ||||
|             List<Instruction> allCleanupInstructions = []; | ||||
|  | ||||
|             for (int i = 0; i < functionMetaData.Parameters.Length; ++i) | ||||
|             { | ||||
|                 PropertyMetaData param = functionMetaData.Parameters[i]; | ||||
|                 NativeDataType nativeDataType = param.PropertyDataType; | ||||
|                  | ||||
|                 if (param.MemberRef == null) | ||||
|                 { | ||||
|                     throw new Exception($"Parameter {param.Name} does not have a valid member reference"); | ||||
|                 } | ||||
|  | ||||
|                 nativeDataType.PrepareForRewrite(invokerMethodDefinition.DeclaringType, param, param.MemberRef); | ||||
|             } | ||||
|  | ||||
|             FunctionProcessor.WriteParametersToNative(invokerMethodProcessor, | ||||
|                 invokerMethodDefinition, | ||||
|                 functionMetaData, | ||||
|                 functionMetaData.RewriteInfo.FunctionParamSizeField, | ||||
|                 functionMetaData.RewriteInfo.FunctionParams, | ||||
|                 out var loadArguments, | ||||
|                 out _, allCleanupInstructions); | ||||
|  | ||||
|             invokerMethodProcessor.Emit(OpCodes.Ldarg_0); | ||||
|             invokerMethodProcessor.Emit(OpCodes.Ldloc, loadArguments); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             invokerMethodProcessor.Emit(OpCodes.Ldarg_0); | ||||
|             invokerMethodProcessor.Emit(OpCodes.Ldsfld, WeaverImporter.Instance.IntPtrZero); | ||||
|         } | ||||
|          | ||||
|         invokerMethodProcessor.Emit(OpCodes.Callvirt, declaredType); | ||||
|         invokerMethodDefinition.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     public static MethodReference FindOrCreateInitializeDelegate(TypeDefinition delegateType) | ||||
|     { | ||||
|         MethodReference? initializeDelegate = delegateType.FindMethod(InitializeUnrealDelegate, false); | ||||
|          | ||||
|         if (initializeDelegate == null) | ||||
|         { | ||||
|             initializeDelegate = delegateType.AddMethod(InitializeUnrealDelegate,  | ||||
|                 WeaverImporter.Instance.VoidTypeRef, MethodAttributes.Public | MethodAttributes.Static, WeaverImporter.Instance.IntPtrType); | ||||
|         } | ||||
|          | ||||
|         return initializeDelegate.ImportMethod(); | ||||
|     } | ||||
|      | ||||
|     static void ProcessInitialize(TypeDefinition type, FunctionMetaData functionMetaData) | ||||
|     { | ||||
|         MethodDefinition initializeMethod = FindOrCreateInitializeDelegate(type).Resolve(); | ||||
|         ILProcessor? processor = initializeMethod.Body.GetILProcessor(); | ||||
|          | ||||
|         processor.Emit(OpCodes.Ldarg_0); | ||||
|         processor.Emit(OpCodes.Call, WeaverImporter.Instance.GetSignatureFunction); | ||||
|         processor.Emit(OpCodes.Stsfld, functionMetaData.FunctionPointerField); | ||||
|          | ||||
|         Instruction loadFunctionPointer = processor.Create(OpCodes.Ldsfld, functionMetaData.FunctionPointerField); | ||||
|         functionMetaData.EmitFunctionParamOffsets(processor, loadFunctionPointer); | ||||
|         functionMetaData.EmitFunctionParamSize(processor, loadFunctionPointer); | ||||
|         functionMetaData.EmitParamNativeProperty(processor, loadFunctionPointer); | ||||
|          | ||||
|         initializeMethod.FinalizeMethod(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| using Mono.Cecil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| namespace UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| public static class UnrealEnumProcessor | ||||
| {  | ||||
|     public static void ProcessEnums(List<TypeDefinition> foundEnums, ApiMetaData assemblyMetadata) | ||||
|     { | ||||
|         assemblyMetadata.EnumMetaData.Capacity = foundEnums.Count; | ||||
|          | ||||
|         for (var i = 0; i < foundEnums.Count; i++) | ||||
|         { | ||||
|             assemblyMetadata.EnumMetaData.Add(new EnumMetaData(foundEnums[i])); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,77 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| public static class UnrealInterfaceProcessor | ||||
| {  | ||||
|     public static void ProcessInterfaces(List<TypeDefinition> interfaces, ApiMetaData assemblyMetadata) | ||||
|     { | ||||
|         assemblyMetadata.InterfacesMetaData.Capacity = interfaces.Count; | ||||
|          | ||||
|         for (var i = 0; i < interfaces.Count; ++i) | ||||
|         { | ||||
|             TypeDefinition interfaceType = interfaces[i]; | ||||
|             assemblyMetadata.InterfacesMetaData.Add(new InterfaceMetaData(interfaceType)); | ||||
|              | ||||
|             CreateInterfaceMarshaller(interfaceType); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void CreateInterfaceMarshaller(TypeDefinition interfaceType) | ||||
|     { | ||||
|         TypeDefinition structMarshallerClass = WeaverImporter.Instance.CurrentWeavingAssembly.CreateNewClass(interfaceType.Namespace, interfaceType.GetMarshallerClassName(),  | ||||
|             TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.BeforeFieldInit); | ||||
|          | ||||
|         FieldDefinition nativePointerField = structMarshallerClass.AddField("NativeInterfaceClassPtr",  | ||||
|             WeaverImporter.Instance.IntPtrType, FieldAttributes.Public | FieldAttributes.Static); | ||||
|          | ||||
|         string interfaceName = interfaceType.GetEngineName(); | ||||
|         const bool finalizeMethod = true; | ||||
|          | ||||
|         ConstructorBuilder.CreateTypeInitializer(structMarshallerClass, Instruction.Create(OpCodes.Stsfld, nativePointerField),  | ||||
|             [Instruction.Create(OpCodes.Call, WeaverImporter.Instance.GetNativeClassFromNameMethod)], interfaceName, finalizeMethod); | ||||
|          | ||||
|         MakeToNativeMethod(interfaceType, structMarshallerClass, nativePointerField); | ||||
|         MakeFromNativeMethod(interfaceType, structMarshallerClass, nativePointerField); | ||||
|     } | ||||
|      | ||||
|     public static void MakeToNativeMethod(TypeDefinition interfaceType, TypeDefinition structMarshallerClass, FieldDefinition nativePointerField) | ||||
|     { | ||||
|         MethodDefinition toNativeMarshallerMethod = interfaceType.AddMethod("ToNative",  | ||||
|             WeaverImporter.Instance.VoidTypeRef, | ||||
|             MethodAttributes.Public | MethodAttributes.Static, WeaverImporter.Instance.IntPtrType, WeaverImporter.Instance.Int32TypeRef, interfaceType); | ||||
|          | ||||
|         MethodReference toNativeMethod = WeaverImporter.Instance.ScriptInterfaceMarshaller.FindMethod("ToNative")!; | ||||
|         toNativeMethod = FunctionProcessor.MakeMethodDeclaringTypeGeneric(toNativeMethod, interfaceType); | ||||
|          | ||||
|         ILProcessor toNativeMarshallerProcessor = toNativeMarshallerMethod.Body.GetILProcessor(); | ||||
|         toNativeMarshallerProcessor.Emit(OpCodes.Ldarg_0); | ||||
|         toNativeMarshallerProcessor.Emit(OpCodes.Ldarg_1); | ||||
|         toNativeMarshallerProcessor.Emit(OpCodes.Ldarg_2); | ||||
|         toNativeMarshallerProcessor.Emit(OpCodes.Ldsfld, nativePointerField); | ||||
|         toNativeMarshallerProcessor.Emit(OpCodes.Call, toNativeMethod); | ||||
|          | ||||
|         toNativeMarshallerMethod.FinalizeMethod(); | ||||
|     } | ||||
|      | ||||
|     public static void MakeFromNativeMethod(TypeDefinition interfaceType, TypeDefinition structMarshallerClass, FieldDefinition nativePointerField) | ||||
|     { | ||||
|         MethodDefinition fromNativeMarshallerMethod = structMarshallerClass.AddMethod("FromNative",  | ||||
|             interfaceType, | ||||
|             MethodAttributes.Public | MethodAttributes.Static, | ||||
|             [WeaverImporter.Instance.IntPtrType, WeaverImporter.Instance.Int32TypeRef]); | ||||
|          | ||||
|         MethodReference fromNativeMethod = WeaverImporter.Instance.ScriptInterfaceMarshaller.FindMethod("FromNative")!; | ||||
|         fromNativeMethod = FunctionProcessor.MakeMethodDeclaringTypeGeneric(fromNativeMethod, interfaceType); | ||||
|          | ||||
|         ILProcessor fromNativeMarshallerProcessor = fromNativeMarshallerMethod.Body.GetILProcessor(); | ||||
|         fromNativeMarshallerProcessor.Emit(OpCodes.Ldarg_0); | ||||
|         fromNativeMarshallerProcessor.Emit(OpCodes.Ldarg_1); | ||||
|         fromNativeMarshallerProcessor.Emit(OpCodes.Call, fromNativeMethod); | ||||
|         fromNativeMarshallerProcessor.Emit(OpCodes.Ret); | ||||
|         fromNativeMarshallerMethod.OptimizeMethod(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,257 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
| using AssemblyDefinition = Mono.Cecil.AssemblyDefinition; | ||||
| using FieldDefinition = Mono.Cecil.FieldDefinition; | ||||
| using MethodDefinition = Mono.Cecil.MethodDefinition; | ||||
| using TypeDefinition = Mono.Cecil.TypeDefinition; | ||||
|  | ||||
| namespace UnrealSharpWeaver.TypeProcessors; | ||||
|  | ||||
| public static class UnrealStructProcessor | ||||
| { | ||||
|     public static void ProcessStructs(IEnumerable<TypeDefinition> structs, ApiMetaData assemblyMetadata, AssemblyDefinition assembly) | ||||
|     { | ||||
|         // We need to create struct metadata in the correct order to ensure that blittable structs have | ||||
|         // their UStruct attributes updated before other referencing structs use them to create UnrealTypes. | ||||
|         var structStack = new Stack<TypeDefinition>(); | ||||
|         var pushedStructs = new HashSet<TypeDefinition>(); | ||||
|         var structHandlingOrder = new List<TypeDefinition>(); | ||||
|         var structMetadata = new Dictionary<TypeDefinition, StructMetaData>(); | ||||
|  | ||||
|         var sortedStructs = structs.ToList(); | ||||
|         sortedStructs.Sort((a, b) => | ||||
|         { | ||||
|             var aMetadata = new StructMetaData(a); | ||||
|             var bMetadata = new StructMetaData(b); | ||||
|  | ||||
|             foreach (var Field in aMetadata.Fields) | ||||
|             { | ||||
|                 if (Field.PropertyDataType.CSharpType.FullName.Contains(bMetadata.TypeRef.FullName)) | ||||
|                 { | ||||
|                     return 1; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             foreach (var Field in bMetadata.Fields) | ||||
|             { | ||||
|                 if (Field.PropertyDataType.CSharpType.FullName.Contains(aMetadata.TypeRef.Name)) | ||||
|                 { | ||||
|                     return -1; | ||||
|                 } | ||||
|             } | ||||
|             return 0; | ||||
|         }); | ||||
|  | ||||
|         foreach (var unrealStruct in sortedStructs.Where(unrealStruct => !pushedStructs.Contains(unrealStruct))) | ||||
|         { | ||||
|             structStack.Push(unrealStruct); | ||||
|             pushedStructs.Add(unrealStruct); | ||||
|  | ||||
|             PushReferencedStructsFromAssembly(assembly, unrealStruct, structStack, pushedStructs); | ||||
|  | ||||
|             while (structStack.Count > 0)  | ||||
|             { | ||||
|                 var currentStruct = structStack.Pop(); | ||||
|                 try  | ||||
|                 { | ||||
|                     if (structMetadata.ContainsKey(currentStruct))  | ||||
|                     { | ||||
|                         throw new RewriteException (currentStruct, "Attempted to create struct metadata twice"); | ||||
|                     } | ||||
|                      | ||||
|                     var currentMetadata = new StructMetaData(currentStruct); | ||||
|                     structHandlingOrder.Add(currentStruct); | ||||
|                     structMetadata.Add(currentStruct, currentMetadata); | ||||
|                 }  | ||||
|                 catch (WeaverProcessError error)  | ||||
|                 { | ||||
|                     ErrorEmitter.Error (error); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         assemblyMetadata.StructMetaData = structMetadata.Values.ToList(); | ||||
|          | ||||
|         foreach (var currentStruct in structHandlingOrder) | ||||
|         { | ||||
|             ProcessStruct(currentStruct, structMetadata[currentStruct]); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     private static void ProcessStruct(TypeDefinition structTypeDefinition, StructMetaData metadata) | ||||
|     { | ||||
|         MethodReference? foundConstructor = structTypeDefinition.FindMethod(".ctor", false, WeaverImporter.Instance.IntPtrType); | ||||
|  | ||||
|         var marshalledStructInterface = structTypeDefinition.AddMarshalledStructInterface(); | ||||
|          | ||||
|         if (foundConstructor != null) | ||||
|         { | ||||
|             throw new RewriteException(structTypeDefinition, "Structs cannot have a constructor that takes an IntPtr"); | ||||
|         } | ||||
|          | ||||
|         var propertyOffsetsToInitialize = new List<Tuple<FieldDefinition, PropertyMetaData>>(); | ||||
|         var propertyPointersToInitialize = new List<Tuple<FieldDefinition, PropertyMetaData>>(); | ||||
|         PropertyProcessor.ProcessClassMembers(ref propertyOffsetsToInitialize, ref propertyPointersToInitialize, structTypeDefinition, metadata.Fields); | ||||
|  | ||||
|         MethodDefinition structConstructor = ConstructorBuilder.CreateConstructor(structTypeDefinition, MethodAttributes.Public, WeaverImporter.Instance.IntPtrType); | ||||
|         var toNativeMethod = structTypeDefinition.GetOrAddMethod("ToNative",  null, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final, WeaverImporter.Instance.IntPtrType); | ||||
|          | ||||
|         ILProcessor constructorBody = structConstructor.Body.GetILProcessor(); | ||||
|         ILProcessor toNativeBody = toNativeMethod.Body.GetILProcessor(); | ||||
|         Instruction loadBufferInstruction = Instruction.Create(OpCodes.Ldarg_1); | ||||
|          | ||||
|         foreach (PropertyMetaData prop in metadata.Fields) | ||||
|         { | ||||
|             if (prop.MemberRef == null) | ||||
|             { | ||||
|                 throw new InvalidDataException($"Property '{prop.Name}' does not have a member reference"); | ||||
|             } | ||||
|              | ||||
|             if (prop.PropertyOffsetField == null) | ||||
|             { | ||||
|                 throw new InvalidDataException($"Property '{prop.Name}' does not have an offset field"); | ||||
|             } | ||||
|              | ||||
|             FieldDefinition fieldDefinition = (FieldDefinition) prop.MemberRef.Resolve(); | ||||
|             prop.PropertyDataType.WriteLoad(constructorBody, structTypeDefinition, loadBufferInstruction, prop.PropertyOffsetField, fieldDefinition); | ||||
|             prop.PropertyDataType.WriteStore(toNativeBody, structTypeDefinition, loadBufferInstruction, prop.PropertyOffsetField, fieldDefinition); | ||||
|         } | ||||
|          | ||||
|         structConstructor.FinalizeMethod(); | ||||
|         toNativeMethod.FinalizeMethod(); | ||||
|          | ||||
|         var fromNativeMethod = structTypeDefinition.GetOrAddMethod("FromNative", structTypeDefinition, MethodAttributes.Public | MethodAttributes.Static  | MethodAttributes.HideBySig, [WeaverImporter.Instance.IntPtrType]); | ||||
|         if (fromNativeMethod.Overrides.Count == 0) | ||||
|         { | ||||
|             fromNativeMethod.Overrides.Add(marshalledStructInterface.FindMethod("FromNative")!); | ||||
|         } | ||||
|         ILProcessor fromNativeBody = fromNativeMethod.Body.GetILProcessor(); | ||||
|         fromNativeBody.Emit(OpCodes.Ldarg_0); | ||||
|         fromNativeBody.Emit(OpCodes.Newobj, structConstructor); | ||||
|          | ||||
|         fromNativeMethod.FinalizeMethod(); | ||||
|  | ||||
|          | ||||
|         // Field to cache the native size of the struct. | ||||
|         FieldDefinition nativeClassField = structTypeDefinition.AddField("NativeClassPtr", WeaverImporter.Instance.IntPtrType, FieldAttributes.Public | FieldAttributes.Static); | ||||
|         FieldDefinition nativeStructSizeField = structTypeDefinition.AddField("NativeDataSize", WeaverImporter.Instance.Int32TypeRef, FieldAttributes.Public | FieldAttributes.Static); | ||||
|          | ||||
|         var getNativeClassPtrMethod = structTypeDefinition.GetOrAddMethod("GetNativeClassPtr", WeaverImporter.Instance.IntPtrType, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig); | ||||
|         if (getNativeClassPtrMethod.Overrides.Count == 0) | ||||
|         { | ||||
|             getNativeClassPtrMethod.Overrides.Add(marshalledStructInterface.FindMethod("GetNativeClassPtr")!); | ||||
|         } | ||||
|         getNativeClassPtrMethod.Body.GetILProcessor().Emit(OpCodes.Ldsfld, nativeClassField); | ||||
|         getNativeClassPtrMethod.FinalizeMethod(); | ||||
|          | ||||
|         var getNativeStructSizeMethod = structTypeDefinition.GetOrAddMethod("GetNativeDataSize", WeaverImporter.Instance.Int32TypeRef, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig); | ||||
|         if (getNativeStructSizeMethod.Overrides.Count == 0) | ||||
|         { | ||||
|             getNativeStructSizeMethod.Overrides.Add(marshalledStructInterface.FindMethod("GetNativeDataSize")!); | ||||
|         } | ||||
|         getNativeStructSizeMethod.Body.GetILProcessor().Emit(OpCodes.Ldsfld, nativeStructSizeField); | ||||
|         getNativeStructSizeMethod.FinalizeMethod(); | ||||
|          | ||||
|         Instruction callGetNativeStructFromNameMethod = Instruction.Create(OpCodes.Call, WeaverImporter.Instance.GetNativeStructFromNameMethod); | ||||
|         Instruction setNativeClassField = Instruction.Create(OpCodes.Stsfld, nativeClassField); | ||||
|         Instruction loadNativeClassField = Instruction.Create(OpCodes.Ldsfld, nativeClassField); | ||||
|         Instruction callGetNativeStructSizeMethod = Instruction.Create(OpCodes.Call, WeaverImporter.Instance.GetNativeStructSizeMethod); | ||||
|         Instruction setNativeStructSizeField = Instruction.Create(OpCodes.Stsfld, nativeStructSizeField); | ||||
|         ConstructorBuilder.CreateTypeInitializer(structTypeDefinition, setNativeStructSizeField, [callGetNativeStructFromNameMethod, setNativeClassField, loadNativeClassField, callGetNativeStructSizeMethod]); | ||||
|          | ||||
|         CreateStructMarshaller(structTypeDefinition, nativeStructSizeField, toNativeMethod, structConstructor); | ||||
|         CreateStructStaticConstructor(metadata, structTypeDefinition, nativeClassField); | ||||
|     } | ||||
|  | ||||
|     private static void CreateStructStaticConstructor(StructMetaData metadata, TypeDefinition structTypeDefinition, FieldDefinition nativeStructClass) | ||||
|     { | ||||
|         MethodDefinition staticConstructor = ConstructorBuilder.MakeStaticConstructor(structTypeDefinition); | ||||
|          | ||||
|         ConstructorBuilder.InitializeFields(staticConstructor, [.. metadata.Fields], Instruction.Create(OpCodes.Ldsfld, nativeStructClass)); | ||||
|         staticConstructor.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     private static void CreateStructMarshaller(TypeDefinition structTypeDefinition, FieldDefinition nativeStructSizeField, MethodDefinition toNativeMethod, MethodDefinition structConstructor) | ||||
|     { | ||||
|         // Create a marshaller class for the struct. | ||||
|         TypeDefinition structMarshallerClass = WeaverImporter.Instance.CurrentWeavingAssembly.CreateNewClass(structTypeDefinition.Namespace, structTypeDefinition.GetMarshallerClassName(),  | ||||
|             TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.BeforeFieldInit); | ||||
|              | ||||
|         AddToNativeMarshallingMethod(structMarshallerClass, structTypeDefinition, nativeStructSizeField, toNativeMethod); | ||||
|         AddFromNativeMarshallingMethod(structMarshallerClass, structTypeDefinition, structConstructor, nativeStructSizeField); | ||||
|     } | ||||
|      | ||||
|     private static void AddToNativeMarshallingMethod(TypeDefinition marshaller, TypeDefinition structTypeDefinition, FieldDefinition nativeDataSizeField, MethodDefinition toNativeMethod) | ||||
|     { | ||||
|         MethodDefinition toNativeMarshallerMethod = marshaller.AddMethod("ToNative",  | ||||
|             WeaverImporter.Instance.VoidTypeRef, | ||||
|             MethodAttributes.Public | MethodAttributes.Static, | ||||
|             [WeaverImporter.Instance.IntPtrType, WeaverImporter.Instance.Int32TypeRef, structTypeDefinition]); | ||||
|          | ||||
|         ILProcessor toNativeMarshallerBody = toNativeMarshallerMethod.Body.GetILProcessor(); | ||||
|         toNativeMarshallerBody.Emit(OpCodes.Ldarga, toNativeMarshallerMethod.Parameters[2]); | ||||
|  | ||||
|         toNativeMarshallerBody.Emit(OpCodes.Ldarg_0); | ||||
|  | ||||
|         toNativeMarshallerBody.Emit(OpCodes.Ldarg_1); | ||||
|         toNativeMarshallerBody.Emit(OpCodes.Ldsfld, nativeDataSizeField); | ||||
|         toNativeMarshallerBody.Emit(OpCodes.Mul); | ||||
|  | ||||
|         toNativeMarshallerBody.Emit(OpCodes.Call, WeaverImporter.Instance.IntPtrAdd); | ||||
|  | ||||
|         toNativeMarshallerBody.Emit(OpCodes.Call, toNativeMethod); | ||||
|          | ||||
|         toNativeMarshallerMethod.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     //Create a marshaller method to Native code with signature: | ||||
|     //public static <StructureType> FromNative(IntPtr nativeBuffer, int arrayIndex, UnrealObject owner) | ||||
|     private static void AddFromNativeMarshallingMethod(TypeDefinition marshaller, TypeDefinition structTypeDefinition, MethodDefinition ctor, FieldDefinition nativeDataSizeField) | ||||
|     { | ||||
|         MethodDefinition fromNativeMarshallerMethod = marshaller.GetOrAddMethod("FromNative",  | ||||
|             structTypeDefinition, | ||||
|             MethodAttributes.Public | MethodAttributes.Static,  | ||||
|             [WeaverImporter.Instance.IntPtrType, WeaverImporter.Instance.Int32TypeRef]); | ||||
|  | ||||
|         ILProcessor fromNativeMarshallerBody = fromNativeMarshallerMethod.Body.GetILProcessor(); | ||||
|         fromNativeMarshallerBody.Emit(OpCodes.Ldarg_0); | ||||
|         fromNativeMarshallerBody.Emit(OpCodes.Ldarg_1); | ||||
|         fromNativeMarshallerBody.Emit(OpCodes.Ldsfld, nativeDataSizeField); | ||||
|         fromNativeMarshallerBody.Emit(OpCodes.Mul); | ||||
|         fromNativeMarshallerBody.Emit(OpCodes.Call, WeaverImporter.Instance.IntPtrAdd); | ||||
|         fromNativeMarshallerBody.Emit(OpCodes.Newobj, ctor); | ||||
|          | ||||
|         fromNativeMarshallerMethod.FinalizeMethod(); | ||||
|     } | ||||
|  | ||||
|     private static void PushReferencedStructsFromAssembly(AssemblyDefinition assembly, TypeDefinition unrealStruct, Stack<TypeDefinition> structStack, HashSet<TypeDefinition> pushedStructs) | ||||
|     { | ||||
|         var referencedStructs = new List<TypeDefinition>(); | ||||
|          | ||||
|         foreach (var field in unrealStruct.Fields)  | ||||
|         { | ||||
|             TypeDefinition fieldType = field.FieldType.Resolve(); | ||||
|              | ||||
|             // if it's not in the same assembly, it will have been processed already | ||||
|             if (assembly != fieldType.Module.Assembly)  | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (!fieldType.IsValueType || !fieldType.IsUStruct() || pushedStructs.Contains(fieldType)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             referencedStructs.Add(fieldType); | ||||
|             structStack.Push(fieldType); | ||||
|             pushedStructs.Add(fieldType); | ||||
|         } | ||||
|  | ||||
|         foreach (var referencedStruct in referencedStructs)  | ||||
|         { | ||||
|             PushReferencedStructsFromAssembly(assembly, referencedStruct, structStack, pushedStructs); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,16 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|     <PropertyGroup> | ||||
|         <OutputType>Exe</OutputType> | ||||
|         <TargetFramework>net9.0</TargetFramework> | ||||
|         <ImplicitUsings>enable</ImplicitUsings> | ||||
|         <Nullable>enable</Nullable> | ||||
|         <OutDir>../../../Binaries/Managed</OutDir> | ||||
|     </PropertyGroup> | ||||
|  | ||||
|     <ItemGroup> | ||||
|         <PackageReference Include="CommandLineParser" /> | ||||
|         <PackageReference Include="Mono.Cecil" /> | ||||
|         <PackageReference Include="Newtonsoft.Json" /> | ||||
|     </ItemGroup> | ||||
| </Project> | ||||
| @ -0,0 +1,62 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Rocks; | ||||
|  | ||||
| namespace UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| public static class AssemblyUtilities | ||||
| { | ||||
|     public static TypeReference? FindGenericType(this AssemblyDefinition assembly, string typeNamespace, string typeName, TypeReference[] typeParameters, bool bThrowOnException = true) | ||||
|     { | ||||
|         TypeReference? typeRef = FindType(assembly, typeName, typeNamespace, bThrowOnException); | ||||
|         return typeRef == null ? null : typeRef.Resolve().MakeGenericInstanceType(typeParameters).ImportType(); | ||||
|     } | ||||
|  | ||||
|     public static TypeReference? FindType(this AssemblyDefinition assembly, string typeName, string typeNamespace = "", bool throwOnException = true) | ||||
|     { | ||||
|         foreach (var module in assembly.Modules) | ||||
|         { | ||||
|             foreach (var type in module.GetAllTypes()) | ||||
|             { | ||||
|                 if ((typeNamespace.Length > 0 && type.Namespace != typeNamespace) || type.Name != typeName) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|                  | ||||
|                 return type.ImportType(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (throwOnException) | ||||
|         { | ||||
|             throw new TypeAccessException($"Type \"{typeNamespace}.{typeName}\" not found in userAssembly {assembly.Name}"); | ||||
|         } | ||||
|  | ||||
|         return null; | ||||
|     } | ||||
|      | ||||
|     public static TypeDefinition CreateNewClass(this AssemblyDefinition assembly, string classNamespace, string className, TypeAttributes attributes, TypeReference? parentClass = null) | ||||
|     { | ||||
|         if (parentClass == null) | ||||
|         { | ||||
|             parentClass = assembly.MainModule.TypeSystem.Object; | ||||
|         } | ||||
|          | ||||
|         TypeDefinition newType = new TypeDefinition(classNamespace, className, attributes, parentClass); | ||||
|         assembly.MainModule.Types.Add(newType); | ||||
|         return newType; | ||||
|     } | ||||
|      | ||||
|     public static void ForEachAssembly(Func<AssemblyDefinition, bool> action) | ||||
|     { | ||||
|         List<AssemblyDefinition> assemblies = [WeaverImporter.Instance.UnrealSharpAssembly, WeaverImporter.Instance.UnrealSharpCoreAssembly]; | ||||
|         assemblies.AddRange(WeaverImporter.Instance.AllProjectAssemblies); | ||||
|          | ||||
|         foreach (AssemblyDefinition assembly in assemblies) | ||||
|         { | ||||
|             if (!action(assembly)) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| public static class AttributeUtilities | ||||
| { | ||||
|     public static readonly string UMetaDataAttribute = "UMetaDataAttribute"; | ||||
|     public static readonly string MetaTagsNamespace = WeaverImporter.AttributeNamespace + ".MetaTags"; | ||||
|      | ||||
|     public static List<CustomAttribute> FindMetaDataAttributes(this IEnumerable<CustomAttribute> customAttributes) | ||||
|     { | ||||
|         return FindAttributesByType(customAttributes, WeaverImporter.AttributeNamespace, UMetaDataAttribute); | ||||
|     } | ||||
|  | ||||
|     public static List<CustomAttribute> FindMetaDataAttributesByNamespace(this IEnumerable<CustomAttribute> customAttributes) | ||||
|     { | ||||
|         return FindAttributesByNamespace(customAttributes, MetaTagsNamespace); | ||||
|     } | ||||
|  | ||||
|     public static CustomAttributeArgument? FindAttributeField(this CustomAttribute attribute, string fieldName) | ||||
|     { | ||||
|         foreach (var field in attribute.Fields)  | ||||
|         { | ||||
|             if (field.Name == fieldName)  | ||||
|             { | ||||
|                 return field.Argument; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|      | ||||
|     public static CustomAttribute? FindAttributeByType(this IEnumerable<CustomAttribute> customAttributes, string typeNamespace, string typeName) | ||||
|     { | ||||
|         List<CustomAttribute> attribs = FindAttributesByType(customAttributes, typeNamespace, typeName); | ||||
|         return attribs.Count == 0 ? null : attribs[0]; | ||||
|     } | ||||
|  | ||||
|     public static List<CustomAttribute> FindAttributesByType(this IEnumerable<CustomAttribute> customAttributes, string typeNamespace, string typeName) | ||||
|     { | ||||
|         List<CustomAttribute> attribs = new List<CustomAttribute>(); | ||||
|         foreach (CustomAttribute attrib in customAttributes) | ||||
|         { | ||||
|             if (attrib.AttributeType.Namespace == typeNamespace && attrib.AttributeType.Name == typeName) | ||||
|             { | ||||
|                 attribs.Add(attrib); | ||||
|             } | ||||
|         } | ||||
|         return attribs; | ||||
|     } | ||||
|  | ||||
|     public static List<CustomAttribute> FindAttributesByNamespace(this IEnumerable<CustomAttribute> customAttributes, string typeNamespace) | ||||
|     { | ||||
|         List<CustomAttribute> attribs = new List<CustomAttribute>(); | ||||
|         foreach (var attrib in customAttributes) | ||||
|         { | ||||
|             if (attrib.AttributeType.Namespace == typeNamespace) | ||||
|             { | ||||
|                 attribs.Add(attrib); | ||||
|             } | ||||
|         } | ||||
|         return attribs; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,34 @@ | ||||
| using Mono.Cecil; | ||||
|  | ||||
| namespace UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| public static class DelegateUtilities | ||||
| { | ||||
|     public static MethodDefinition GetDelegateInvokeMethod(TypeDefinition typeDefinition) | ||||
|     { | ||||
|         foreach (MethodDefinition method in typeDefinition.Methods) | ||||
|         { | ||||
|             if (method.Name != "Invoke") | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|              | ||||
|             if (!method.ReturnsVoid()) | ||||
|             { | ||||
|                 throw new Exception($"{typeDefinition.FullName} is exposed to Unreal Engine, and must have a void return type."); | ||||
|             } | ||||
|              | ||||
|             return method; | ||||
|         } | ||||
|          | ||||
|         throw new Exception($"Delegate type {typeDefinition.FullName} does not have an Invoke method."); | ||||
|     } | ||||
|      | ||||
|     public static string GetUnrealDelegateName(TypeReference typeDefinition) | ||||
|     { | ||||
|         string functionName = typeDefinition.FullName; | ||||
|         functionName = functionName.Replace(".", "_"); | ||||
|         functionName += "__DelegateSignature"; | ||||
|         return functionName; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| namespace UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| public static class EnumUtilities | ||||
| { | ||||
|     public static bool HasAnyFlags(this Enum flags, Enum testFlags) | ||||
|     { | ||||
|         return (Convert.ToUInt64(flags) & Convert.ToUInt64(testFlags)) != 0; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,146 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.MetaData; | ||||
|  | ||||
| namespace UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| public static class MethodUtilities | ||||
| { | ||||
|     public static readonly EFunctionFlags RpcFlags = EFunctionFlags.NetServer | EFunctionFlags.NetClient | EFunctionFlags.NetMulticast; | ||||
|     public static readonly string UFunctionAttribute = "UFunctionAttribute"; | ||||
|      | ||||
|     /// <param name="name">name the method copy will have</param> | ||||
|     /// <param name="method">original method</param> | ||||
|     /// <param name="addMethod">Add the method copy to the declaring type. this allows to use the original sources to be matched to the copy.</param> | ||||
|     /// <param name="copyMetadataToken"></param> | ||||
|     /// <returns>new instance of as copy of the original</returns> | ||||
|     public static MethodDefinition CopyMethod(string name, MethodDefinition method, bool addMethod = true, bool copyMetadataToken = true) | ||||
|     { | ||||
|         MethodDefinition newMethod = new MethodDefinition(name, method.Attributes, method.ReturnType) | ||||
|         { | ||||
|             HasThis = true, | ||||
|             ExplicitThis = method.ExplicitThis, | ||||
|             CallingConvention = method.CallingConvention, | ||||
|             Body = method.Body | ||||
|         }; | ||||
|  | ||||
|         if (copyMetadataToken) | ||||
|         { | ||||
|             newMethod.MetadataToken = method.MetadataToken; | ||||
|         } | ||||
|  | ||||
|         foreach (ParameterDefinition parameter in method.Parameters) | ||||
|         { | ||||
|             TypeReference importedType = parameter.ParameterType.ImportType(); | ||||
|             newMethod.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, importedType)); | ||||
|         } | ||||
|          | ||||
|         if (addMethod) | ||||
|         { | ||||
|             method.DeclaringType.Methods.Add(newMethod); | ||||
|         } | ||||
|  | ||||
|         return newMethod; | ||||
|     } | ||||
|      | ||||
|     public static VariableDefinition AddLocalVariable(this MethodDefinition method, TypeReference typeReference) | ||||
|     { | ||||
|         var variable = new VariableDefinition(typeReference); | ||||
|         method.Body.Variables.Add(variable); | ||||
|         method.Body.InitLocals = true; | ||||
|         return variable; | ||||
|     } | ||||
|      | ||||
|     public static void FinalizeMethod(this MethodDefinition method) | ||||
|     { | ||||
|         method.Body.GetILProcessor().Emit(OpCodes.Ret); | ||||
|         OptimizeMethod(method); | ||||
|     } | ||||
|      | ||||
|     public static bool MethodIsCompilerGenerated(this ICustomAttributeProvider method) | ||||
|     { | ||||
|         return method.CustomAttributes.FindAttributeByType("System.Runtime.CompilerServices", "CompilerGeneratedAttribute") != null; | ||||
|     } | ||||
|  | ||||
|     public static EFunctionFlags GetFunctionFlags(this MethodDefinition method) | ||||
|     { | ||||
|         EFunctionFlags flags = (EFunctionFlags) BaseMetaData.GetFlags(method, "FunctionFlagsMapAttribute"); | ||||
|  | ||||
|         if (method.IsPublic) | ||||
|         { | ||||
|             flags |= EFunctionFlags.Public; | ||||
|         } | ||||
|         else if (method.IsFamily) | ||||
|         { | ||||
|             flags |= EFunctionFlags.Protected; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             flags |= EFunctionFlags.Private; | ||||
|         } | ||||
|  | ||||
|         if (method.IsStatic) | ||||
|         { | ||||
|             flags |= EFunctionFlags.Static; | ||||
|         } | ||||
|          | ||||
|         if (flags.HasAnyFlags(RpcFlags)) | ||||
|         { | ||||
|             flags |= EFunctionFlags.Net; | ||||
|              | ||||
|             if (!method.ReturnsVoid()) | ||||
|             { | ||||
|                 throw new InvalidUnrealFunctionException(method, "RPCs can't have return values."); | ||||
|             } | ||||
|              | ||||
|             if (flags.HasFlag(EFunctionFlags.BlueprintNativeEvent)) | ||||
|             { | ||||
|                 throw new InvalidUnrealFunctionException(method, "BlueprintEvents methods cannot be replicated!"); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // This represents both BlueprintNativeEvent and BlueprintImplementableEvent | ||||
|         if (flags.HasFlag(EFunctionFlags.BlueprintNativeEvent)) | ||||
|         { | ||||
|             flags |= EFunctionFlags.Event; | ||||
|         } | ||||
|          | ||||
|         // Native is needed to bind the function pointer of the UFunction to our own invoke in UE. | ||||
|         return flags | EFunctionFlags.Native; | ||||
|     } | ||||
|      | ||||
|     public static void OptimizeMethod(this MethodDefinition method) | ||||
|     { | ||||
|         if (method.Body.CodeSize == 0) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         method.Body.Optimize(); | ||||
|         method.Body.SimplifyMacros(); | ||||
|     } | ||||
|      | ||||
|     public static void RemoveReturnInstruction(this MethodDefinition method) | ||||
|     { | ||||
|         if (method.Body.Instructions.Count > 0 && method.Body.Instructions[^1].OpCode == OpCodes.Ret) | ||||
|         { | ||||
|             method.Body.Instructions.RemoveAt(method.Body.Instructions.Count - 1); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public static CustomAttribute? GetUFunction(this MethodDefinition function) | ||||
|     { | ||||
|         return function.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, UFunctionAttribute); | ||||
|     } | ||||
|      | ||||
|     public static bool IsUFunction(this MethodDefinition method) | ||||
|     { | ||||
|         return GetUFunction(method) != null; | ||||
|     } | ||||
|      | ||||
|     public static MethodReference ImportMethod(this MethodReference method) | ||||
|     { | ||||
|         return WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference(method); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,156 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Cil; | ||||
| using Mono.Cecil.Rocks; | ||||
|  | ||||
| namespace UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| public static class ParameterUtilities | ||||
| { | ||||
|     public static Instruction CreateLoadInstructionOutParam(this ParameterDefinition param, PropertyType paramTypeCode) | ||||
|     { | ||||
|         while (true) | ||||
|         { | ||||
|             switch (paramTypeCode) | ||||
|             { | ||||
|                 case PropertyType.Enum: | ||||
|                     var param1 = param; | ||||
|                     param = null!; | ||||
|                     paramTypeCode = param1.ParameterType.Resolve().GetEnumUnderlyingType().GetPrimitiveTypeCode(); | ||||
|                     continue; | ||||
|  | ||||
|                 case PropertyType.Bool: | ||||
|                 case PropertyType.Int8: | ||||
|                 case PropertyType.Byte: | ||||
|                     return Instruction.Create(OpCodes.Ldind_I1); | ||||
|  | ||||
|                 case PropertyType.Int16: | ||||
|                 case PropertyType.UInt16: | ||||
|                     return Instruction.Create(OpCodes.Ldind_I2); | ||||
|  | ||||
|                 case PropertyType.Int: | ||||
|                 case PropertyType.UInt32: | ||||
|                     return Instruction.Create(OpCodes.Ldind_I4); | ||||
|  | ||||
|                 case PropertyType.Int64: | ||||
|                 case PropertyType.UInt64: | ||||
|                     return Instruction.Create(OpCodes.Ldind_I8); | ||||
|  | ||||
|                 case PropertyType.Float: | ||||
|                     return Instruction.Create(OpCodes.Ldind_R4); | ||||
|  | ||||
|                 case PropertyType.Double: | ||||
|                     return Instruction.Create(OpCodes.Ldind_R8); | ||||
|  | ||||
|                 case PropertyType.Struct: | ||||
|                     return Instruction.Create(OpCodes.Ldobj, param.ParameterType.GetElementType()); | ||||
|  | ||||
|                 case PropertyType.LazyObject: | ||||
|                 case PropertyType.WeakObject: | ||||
|                 case PropertyType.SoftClass: | ||||
|                 case PropertyType.SoftObject: | ||||
|                 case PropertyType.Class: | ||||
|                     return Instruction.Create(OpCodes.Ldobj, param.ParameterType.GetElementType()); | ||||
|  | ||||
|                 case PropertyType.Delegate: | ||||
|                 case PropertyType.MulticastInlineDelegate: | ||||
|                 case PropertyType.MulticastSparseDelegate: | ||||
|                     // Delegate/multicast delegates in C# are implemented as classes, use Ldind_Ref | ||||
|                     return Instruction.Create(OpCodes.Ldind_Ref); | ||||
|  | ||||
|                 case PropertyType.InternalManagedFixedSizeArray: | ||||
|                 case PropertyType.InternalNativeFixedSizeArray: | ||||
|                     throw new NotImplementedException(); // Fixed size arrays not supported as args | ||||
|  | ||||
|                 case PropertyType.Array: | ||||
|                 case PropertyType.Set: | ||||
|                 case PropertyType.Map: | ||||
|                     // Assumes this will be always be an object (IList, List, ISet, HashSet, IDictionary, Dictionary) | ||||
|                     return Instruction.Create(OpCodes.Ldind_Ref); | ||||
|  | ||||
|                 case PropertyType.Unknown: | ||||
|                 case PropertyType.Interface: | ||||
|                 case PropertyType.Object: | ||||
|                 case PropertyType.ObjectPtr: | ||||
|                 case PropertyType.String: | ||||
|                 case PropertyType.Name: | ||||
|                 case PropertyType.Text: | ||||
|                 case PropertyType.DefaultComponent: | ||||
|                 default: | ||||
|                     return Instruction.Create(OpCodes.Ldind_Ref); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static Instruction CreateSetInstructionOutParam(this ParameterDefinition param, PropertyType paramTypeCode) | ||||
|     { | ||||
|         while (true) | ||||
|         { | ||||
|             switch (paramTypeCode) | ||||
|             { | ||||
|                 case PropertyType.Enum: | ||||
|                     paramTypeCode = param.ParameterType.Resolve().GetEnumUnderlyingType().GetPrimitiveTypeCode(); | ||||
|                     continue; | ||||
|  | ||||
|                 case PropertyType.Bool: | ||||
|                 case PropertyType.Int8: | ||||
|                 case PropertyType.Byte: | ||||
|                     return Instruction.Create(OpCodes.Stind_I1); | ||||
|  | ||||
|                 case PropertyType.Int16: | ||||
|                 case PropertyType.UInt16: | ||||
|                     return Instruction.Create(OpCodes.Stind_I2); | ||||
|  | ||||
|                 case PropertyType.Int: | ||||
|                 case PropertyType.UInt32: | ||||
|                     return Instruction.Create(OpCodes.Stind_I4); | ||||
|  | ||||
|                 case PropertyType.Int64: | ||||
|                 case PropertyType.UInt64: | ||||
|                     return Instruction.Create(OpCodes.Stind_I8); | ||||
|  | ||||
|                 case PropertyType.Float: | ||||
|                     return Instruction.Create(OpCodes.Stind_R4); | ||||
|  | ||||
|                 case PropertyType.Double: | ||||
|                     return Instruction.Create(OpCodes.Stind_R8); | ||||
|  | ||||
|                 case PropertyType.Struct: | ||||
|                     return Instruction.Create(OpCodes.Stobj, param.ParameterType.GetElementType()); | ||||
|  | ||||
|                 case PropertyType.LazyObject: | ||||
|                 case PropertyType.WeakObject: | ||||
|                 case PropertyType.SoftClass: | ||||
|                 case PropertyType.SoftObject: | ||||
|                 case PropertyType.Class: | ||||
|                 case PropertyType.Name: | ||||
|                 case PropertyType.Text: | ||||
|                     return Instruction.Create(OpCodes.Stobj, param.ParameterType.GetElementType()); | ||||
|  | ||||
|                 case PropertyType.Delegate: | ||||
|                 case PropertyType.MulticastSparseDelegate: | ||||
|                 case PropertyType.MulticastInlineDelegate: | ||||
|                     // Delegate/multicast delegates in C# are implemented as classes, use Stind_Ref | ||||
|                     return Instruction.Create(OpCodes.Stind_Ref); | ||||
|  | ||||
|                 case PropertyType.InternalManagedFixedSizeArray: | ||||
|                 case PropertyType.InternalNativeFixedSizeArray: | ||||
|                     throw new NotImplementedException(); // Fixed size arrays not supported as args | ||||
|  | ||||
|                 case PropertyType.Array: | ||||
|                 case PropertyType.Set: | ||||
|                 case PropertyType.Map: | ||||
|                     // Assumes this will be always be an object (IList, List, ISet, HashSet, IDictionary, Dictionary) | ||||
|                     return Instruction.Create(OpCodes.Stind_Ref); | ||||
|  | ||||
|                 case PropertyType.Unknown: | ||||
|                 case PropertyType.Interface: | ||||
|                 case PropertyType.Object: | ||||
|                 case PropertyType.ObjectPtr: | ||||
|                 case PropertyType.String: | ||||
|                 case PropertyType.DefaultComponent: | ||||
|                 default: | ||||
|                     return Instruction.Create(OpCodes.Stind_Ref); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,24 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Collections.Generic; | ||||
|  | ||||
| namespace UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| public static class PropertyUtilities | ||||
| { | ||||
|     public static readonly string UPropertyAttribute = "UPropertyAttribute"; | ||||
|      | ||||
|     public static CustomAttribute? GetUProperty(Collection<CustomAttribute> attributes) | ||||
|     { | ||||
|         return attributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, UPropertyAttribute); | ||||
|     } | ||||
|      | ||||
|     public static CustomAttribute? GetUProperty(this IMemberDefinition typeDefinition) | ||||
|     { | ||||
|         return typeDefinition.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, UPropertyAttribute); | ||||
|     } | ||||
|      | ||||
|     public static bool IsUProperty(this IMemberDefinition property) | ||||
|     { | ||||
|         return GetUProperty(property.CustomAttributes) != null; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,656 @@ | ||||
| using System.Reflection.Metadata; | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using Mono.Collections.Generic; | ||||
| using UnrealSharpWeaver.NativeTypes; | ||||
| using CustomAttribute = Mono.Cecil.CustomAttribute; | ||||
| using FieldDefinition = Mono.Cecil.FieldDefinition; | ||||
| using InterfaceImplementation = Mono.Cecil.InterfaceImplementation; | ||||
| using MethodDefinition = Mono.Cecil.MethodDefinition; | ||||
| using PropertyDefinition = Mono.Cecil.PropertyDefinition; | ||||
| using SequencePoint = Mono.Cecil.Cil.SequencePoint; | ||||
| using TypeDefinition = Mono.Cecil.TypeDefinition; | ||||
| using TypeReference = Mono.Cecil.TypeReference; | ||||
|  | ||||
| namespace UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| public static class TypeDefinitionUtilities | ||||
| { | ||||
|     public static readonly string UClassCallbacks = "UClassExporter"; | ||||
|     public static readonly string UClassAttribute = "UClassAttribute"; | ||||
|      | ||||
|     public static readonly string UEnumAttribute = "UEnumAttribute"; | ||||
|     public static readonly string UStructAttribute = "UStructAttribute"; | ||||
|     public static readonly string UInterfaceAttribute = "UInterfaceAttribute"; | ||||
|     public static readonly string BlittableTypeAttribute = "BlittableTypeAttribute"; | ||||
|      | ||||
|     public static CustomAttribute? GetUClass(this IMemberDefinition definition) | ||||
|     { | ||||
|         return definition.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, UClassAttribute); | ||||
|     } | ||||
|      | ||||
|     public static bool IsUClass(this IMemberDefinition definition) | ||||
|     { | ||||
|         return GetUClass(definition) != null; | ||||
|     } | ||||
|      | ||||
|     public static bool IsUInterface(this TypeDefinition typeDefinition) | ||||
|     { | ||||
|         return GetUInterface(typeDefinition) != null; | ||||
|     } | ||||
|      | ||||
|     public static bool IsUEnum(this TypeDefinition typeDefinition) | ||||
|     { | ||||
|         return GetUEnum(typeDefinition) != null; | ||||
|     } | ||||
|      | ||||
|     public static CustomAttribute? GetUStruct(this IMemberDefinition type) | ||||
|     { | ||||
|         return type.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, UStructAttribute); | ||||
|     } | ||||
|      | ||||
|     public static bool IsUStruct(this IMemberDefinition definition) | ||||
|     { | ||||
|         return GetUStruct(definition) != null; | ||||
|     } | ||||
|      | ||||
|     public static string GetEngineName(this IMemberDefinition memberDefinition) | ||||
|     { | ||||
|         IMemberDefinition currentMemberIteration = memberDefinition; | ||||
|         while (currentMemberIteration != null) | ||||
|         { | ||||
|             CustomAttribute? genTypeAttribute = currentMemberIteration.CustomAttributes | ||||
|                 .FirstOrDefault(x => x.AttributeType.Name == WeaverImporter.GeneratedTypeAttribute); | ||||
|              | ||||
|             if (genTypeAttribute is not null) | ||||
|             { | ||||
|                 return (string) genTypeAttribute.ConstructorArguments[0].Value; | ||||
|             } | ||||
|  | ||||
|             if (memberDefinition.IsUClass() && memberDefinition.Name.StartsWith('U') || | ||||
|                 memberDefinition.IsUStruct() && memberDefinition.Name.StartsWith('F')) | ||||
|             { | ||||
|                 return memberDefinition.Name[1..]; | ||||
|             } | ||||
|              | ||||
|             if (currentMemberIteration is MethodDefinition { IsVirtual: true } virtualMethodDefinition) | ||||
|             { | ||||
|                 if (currentMemberIteration == virtualMethodDefinition.GetBaseMethod()) | ||||
|                 { | ||||
|                     break; | ||||
|                 } | ||||
|                  | ||||
|                 currentMemberIteration = virtualMethodDefinition.GetBaseMethod(); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // Same name in engine as in managed code | ||||
|         return memberDefinition.Name; | ||||
|     } | ||||
|      | ||||
|     public static CustomAttribute? GetUEnum(this TypeDefinition type) | ||||
|     { | ||||
|         return type.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, UEnumAttribute); | ||||
|     } | ||||
|      | ||||
|     public static CustomAttribute? GetBlittableType(this TypeDefinition type) | ||||
|     { | ||||
|         return type.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpCoreAttributesNamespace, BlittableTypeAttribute); | ||||
|     } | ||||
|      | ||||
|     public static bool IsUnmanagedType(this TypeReference typeRef) | ||||
|     { | ||||
|         var typeDef = typeRef.Resolve(); | ||||
|      | ||||
|         // Must be a value type | ||||
|         if (!typeDef.IsValueType) | ||||
|             return false; | ||||
|  | ||||
|         // Primitive types and enums are unmanaged | ||||
|         if (typeDef.IsPrimitive || typeDef.IsEnum) | ||||
|             return true; | ||||
|  | ||||
|         // For structs, recursively check all fields | ||||
|         return typeDef.Fields | ||||
|             .Where(f => !f.IsStatic) | ||||
|             .Select(f => f.FieldType.Resolve()) | ||||
|             .All(IsUnmanagedType); | ||||
|     } | ||||
|  | ||||
|      | ||||
|     public static CustomAttribute? GetUInterface(this TypeDefinition type) | ||||
|     { | ||||
|         return type.CustomAttributes.FindAttributeByType(WeaverImporter.UnrealSharpAttributesNamespace, UInterfaceAttribute); | ||||
|     } | ||||
|      | ||||
|     public static void AddGeneratedTypeAttribute(this TypeDefinition type) | ||||
|     { | ||||
|         CustomAttribute attribute = new CustomAttribute(WeaverImporter.Instance.GeneratedTypeCtor); | ||||
|         string typeName = type.Name.Substring(1); | ||||
|         string fullTypeName = type.Namespace + "." + typeName; | ||||
|         attribute.ConstructorArguments.Add(new CustomAttributeArgument(WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.TypeSystem.String, typeName)); | ||||
|         attribute.ConstructorArguments.Add(new CustomAttributeArgument(WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.TypeSystem.String, fullTypeName)); | ||||
|          | ||||
|         type.CustomAttributes.Add(attribute); | ||||
|     } | ||||
|      | ||||
|     public static PropertyDefinition? FindPropertyByName(this TypeDefinition classOuter, string propertyName) | ||||
|     { | ||||
|         foreach (var property in classOuter.Properties) | ||||
|         { | ||||
|             if (property.Name == propertyName) | ||||
|             { | ||||
|                 return property; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return default; | ||||
|     } | ||||
|      | ||||
|     public static bool IsChildOf(this TypeDefinition type, TypeDefinition parentType) | ||||
|     { | ||||
|         TypeDefinition? currentType = type; | ||||
|         while (currentType != null) | ||||
|         { | ||||
|             if (currentType == parentType) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             currentType = currentType.BaseType?.Resolve(); | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     public static TypeReference FindNestedType(this TypeDefinition typeDef, string typeName) | ||||
|     { | ||||
|         foreach (var nestedType in typeDef.NestedTypes) | ||||
|         { | ||||
|             if (nestedType.Name != typeName) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             return WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference(nestedType); | ||||
|         } | ||||
|          | ||||
|         throw new Exception($"{typeName} not found in {typeDef}."); | ||||
|     } | ||||
|      | ||||
|     public static MethodDefinition AddMethod(this TypeDefinition type, string name, TypeReference? returnType, MethodAttributes attributes = MethodAttributes.Private, params TypeReference[] parameterTypes) | ||||
|     { | ||||
|         returnType ??= WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.TypeSystem.Void; | ||||
|          | ||||
|         var method = new MethodDefinition(name, attributes, returnType); | ||||
|          | ||||
|         foreach (var parameterType in parameterTypes) | ||||
|         { | ||||
|             method.Parameters.Add(new ParameterDefinition(parameterType)); | ||||
|         } | ||||
|         type.Methods.Add(method); | ||||
|         return method; | ||||
|     } | ||||
|      | ||||
|     public static MethodDefinition GetOrAddMethod(this TypeDefinition type, string name, TypeReference? returnType, MethodAttributes attributes = MethodAttributes.Private, params TypeReference[] parameterTypes) | ||||
|     { | ||||
|         returnType ??= WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.TypeSystem.Void; | ||||
|       | ||||
|         var existingMethod = FindMethod(type, name, throwIfNotFound: false, parameterTypes); | ||||
|         if (existingMethod is not null) | ||||
|         { | ||||
|             return existingMethod.Resolve(); | ||||
|         } | ||||
|          | ||||
|         var method = new MethodDefinition(name, attributes, returnType); | ||||
|          | ||||
|         foreach (var parameterType in parameterTypes) | ||||
|         { | ||||
|             method.Parameters.Add(new ParameterDefinition(parameterType)); | ||||
|         } | ||||
|         type.Methods.Add(method); | ||||
|         return method; | ||||
|     } | ||||
|  | ||||
|     private static readonly MethodAttributes MethodAttributes = MethodAttributes.Public | MethodAttributes.Static; | ||||
|      | ||||
|     public static MethodDefinition AddToNativeMethod(this TypeDefinition type, TypeDefinition valueType, TypeReference[]? parameters = null) | ||||
|     { | ||||
|         if (parameters == null) | ||||
|         { | ||||
|             parameters = [WeaverImporter.Instance.IntPtrType, WeaverImporter.Instance.Int32TypeRef, valueType]; | ||||
|         } | ||||
|          | ||||
|         MethodDefinition toNativeMethod = type.AddMethod("ToNative", WeaverImporter.Instance.VoidTypeRef, MethodAttributes, parameters); | ||||
|         return toNativeMethod; | ||||
|     } | ||||
|      | ||||
|     public static MethodDefinition AddFromNativeMethod(this TypeDefinition type, TypeDefinition returnType, TypeReference[]? parameters = null) | ||||
|     { | ||||
|         if (parameters == null) | ||||
|         { | ||||
|             parameters = [WeaverImporter.Instance.IntPtrType, WeaverImporter.Instance.Int32TypeRef]; | ||||
|         } | ||||
|          | ||||
|         MethodDefinition fromNative = type.AddMethod("FromNative", returnType, MethodAttributes, parameters); | ||||
|         return fromNative; | ||||
|     } | ||||
|      | ||||
|     public static FieldDefinition AddField(this TypeDefinition type, string name, TypeReference typeReference, FieldAttributes attributes = 0) | ||||
|     { | ||||
|         if (attributes == 0) | ||||
|         { | ||||
|             attributes = FieldAttributes.Static | FieldAttributes.Private; | ||||
|         } | ||||
|          | ||||
|         var field = new FieldDefinition(name, attributes, typeReference); | ||||
|         type.Fields.Add(field); | ||||
|         return field; | ||||
|     } | ||||
|      | ||||
|     public static FieldReference FindField(this TypeDefinition typeDef, string fieldName) | ||||
|     { | ||||
|         foreach (var field in typeDef.Fields) | ||||
|         { | ||||
|             if (field.Name != fieldName) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             return WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference(field); | ||||
|         } | ||||
|          | ||||
|         throw new Exception($"{fieldName} not found in {typeDef}."); | ||||
|     } | ||||
|      | ||||
|     public static bool IsUObject(this TypeDefinition typeDefinition) | ||||
|     { | ||||
|         if (!typeDefinition.IsUClass()) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|          | ||||
|         while (typeDefinition != null) | ||||
|         { | ||||
|             if (typeDefinition.BaseType == null) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
|              | ||||
|             if (typeDefinition == WeaverImporter.Instance.UObjectDefinition) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|  | ||||
|             typeDefinition = typeDefinition.BaseType.Resolve(); | ||||
|         } | ||||
|          | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     public static TypeReference ImportType(this TypeReference type) | ||||
|     { | ||||
|         return WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference(type); | ||||
|     } | ||||
|      | ||||
|     public static bool HasMethod(this TypeDefinition typeDef, string methodName, bool throwIfNotFound = true, params TypeReference[] parameterTypes) | ||||
|     { | ||||
|         return FindMethod(typeDef, methodName, throwIfNotFound, parameterTypes) != null; | ||||
|     } | ||||
|  | ||||
|     public static MethodReference? FindMethod(this TypeReference typeReference, string methodName, | ||||
|         bool throwIfNotFound = true, params TypeReference[] parameterTypes) | ||||
|     { | ||||
|         var method = FindMethod(typeReference.Resolve(), methodName, throwIfNotFound, parameterTypes); | ||||
|         if (method is null) return null; | ||||
|          | ||||
|         // If the declaring type is generic instance, we need to create a new method reference | ||||
|         if (typeReference is GenericInstanceType genericInstance) | ||||
|         { | ||||
|             // Create new method reference on the generic instance | ||||
|             var newMethod = new MethodReference(method.Name, method.ReturnType, genericInstance) | ||||
|             { | ||||
|                 HasThis = method.HasThis, | ||||
|                 ExplicitThis = method.ExplicitThis, | ||||
|                 CallingConvention = method.CallingConvention | ||||
|             }; | ||||
|  | ||||
|             // Copy the parameters | ||||
|             foreach (var parameter in method.Parameters) | ||||
|             { | ||||
|                 newMethod.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); | ||||
|             } | ||||
|  | ||||
|             // If the method itself is generic, handle its generic parameters | ||||
|             if (method.HasGenericParameters) | ||||
|             { | ||||
|                 var genericInstanceMethod = new GenericInstanceMethod(newMethod); | ||||
|                 foreach (var genericParam in method.GenericParameters) | ||||
|                 { | ||||
|                     // Map the generic parameter to the concrete type from the declaring type | ||||
|                     var concreteType = genericInstance.GenericArguments[genericParam.Position]; | ||||
|                     genericInstanceMethod.GenericArguments.Add(concreteType); | ||||
|                 } | ||||
|                 return genericInstanceMethod; | ||||
|             } | ||||
|  | ||||
|             return newMethod; | ||||
|         } | ||||
|  | ||||
|         return method; | ||||
|     } | ||||
|  | ||||
|     public static MethodReference? FindMethod(this TypeDefinition typeDef, string methodName, bool throwIfNotFound = true, params TypeReference[] parameterTypes) | ||||
|     { | ||||
|         TypeDefinition? currentClass = typeDef; | ||||
|         while (currentClass != null) | ||||
|         { | ||||
|             MethodReference? method = FindOwnMethod(currentClass, methodName, throwIfNotFound: false, parameterTypes); | ||||
|             if (method != null) | ||||
|             { | ||||
|                 return method; | ||||
|             } | ||||
|  | ||||
|             currentClass = currentClass.BaseType?.Resolve(); | ||||
|         } | ||||
|  | ||||
|         if (throwIfNotFound) | ||||
|         { | ||||
|             throw new Exception("Couldn't find method " + methodName + " in " + typeDef + "."); | ||||
|         } | ||||
|  | ||||
|         return default; | ||||
|     } | ||||
|      | ||||
|     public static MethodReference? FindOwnMethod(TypeDefinition typeDef, string methodName, bool throwIfNotFound = true, params TypeReference[] parameterTypes) | ||||
|     { | ||||
|         foreach (var classMethod in typeDef.Methods) | ||||
|         { | ||||
|             if (classMethod.Name != methodName) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (parameterTypes.Length > 0 && classMethod.Parameters.Count != parameterTypes.Length) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             bool found = true; | ||||
|             for (int i = 0; i < parameterTypes.Length; i++) | ||||
|             { | ||||
|                 if (classMethod.Parameters[i].ParameterType.FullName != parameterTypes[i].FullName) | ||||
|                 { | ||||
|                     found = false; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (found) | ||||
|             { | ||||
|                 return classMethod.ImportMethod(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (throwIfNotFound) | ||||
|         { | ||||
|             throw new Exception("Couldn't find method " + methodName + " in " + typeDef + "."); | ||||
|         } | ||||
|  | ||||
|         return default; | ||||
|     } | ||||
|      | ||||
|     public static NativeDataType GetDataType(this TypeReference typeRef, string propertyName, Collection<CustomAttribute>? customAttributes) | ||||
|     { | ||||
|         int arrayDim = 1; | ||||
|         TypeDefinition typeDef = typeRef.Resolve(); | ||||
|         SequencePoint? sequencePoint = ErrorEmitter.GetSequencePointFromMemberDefinition(typeDef); | ||||
|  | ||||
|         if (customAttributes != null) | ||||
|         { | ||||
|             CustomAttribute? propertyAttribute = typeDef.GetUProperty(); | ||||
|              | ||||
|             if (propertyAttribute != null) | ||||
|             { | ||||
|                 CustomAttributeArgument? arrayDimArg = propertyAttribute.FindAttributeField("ArrayDim"); | ||||
|  | ||||
|                 if (typeRef is GenericInstanceType genericType && genericType.GetElementType().FullName == "UnrealSharp.FixedSizeArrayReadWrite`1") | ||||
|                 { | ||||
|                     if (arrayDimArg.HasValue) | ||||
|                     { | ||||
|                         arrayDim = (int) arrayDimArg.Value.Value; | ||||
|  | ||||
|                         // Unreal doesn't have a separate type for fixed arrays, so we just want to generate the inner UProperty type with an arrayDim. | ||||
|                         typeRef = genericType.GenericArguments[0]; | ||||
|                         typeDef = typeRef.Resolve(); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         throw new InvalidPropertyException(propertyName, sequencePoint, "Fixed array properties must specify an ArrayDim in their [UProperty] attribute"); | ||||
|                     } | ||||
|                 } | ||||
|                 else if (arrayDimArg.HasValue) | ||||
|                 { | ||||
|                     throw new InvalidPropertyException(propertyName, sequencePoint, "ArrayDim is only valid for FixedSizeArray properties."); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         switch (typeDef.FullName) | ||||
|         { | ||||
|             case "System.Double": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.Double); | ||||
|             case "System.Single": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.Float); | ||||
|  | ||||
|             case "System.SByte": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.Int8); | ||||
|             case "System.Int16": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.Int16); | ||||
|             case "System.Int32": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.Int); | ||||
|             case "System.Int64": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.Int64); | ||||
|  | ||||
|             case "System.Byte": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.Byte); | ||||
|             case "System.UInt16": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.UInt16); | ||||
|             case "System.UInt32": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.UInt32); | ||||
|             case "System.UInt64": | ||||
|                 return new NativeDataBuiltinType(typeRef, arrayDim, PropertyType.UInt64); | ||||
|  | ||||
|             case "System.Boolean": | ||||
|                 return new NativeDataBooleanType(typeRef, arrayDim); | ||||
|  | ||||
|             case "System.String": | ||||
|                 return new NativeDataStringType(typeRef, arrayDim); | ||||
|  | ||||
|             default: | ||||
|  | ||||
|                 if (typeRef.IsGenericInstance || typeRef.IsByReference) | ||||
|                 { | ||||
|                     GenericInstanceType? instanceType = null; | ||||
|                     if (typeRef is GenericInstanceType genericInstanceType) | ||||
|                     { | ||||
|                         instanceType = genericInstanceType; | ||||
|                     } | ||||
|                     if (typeRef is ByReferenceType byReferenceType) | ||||
|                     { | ||||
|                         instanceType = byReferenceType.ElementType as GenericInstanceType; | ||||
|                         typeRef = byReferenceType.ElementType; | ||||
|                     } | ||||
|  | ||||
|                     if (instanceType != null) | ||||
|                     { | ||||
|                         TypeReference[] genericArguments = instanceType.GenericArguments.ToArray(); | ||||
|                         string? genericTypeName = instanceType.ElementType.Name; | ||||
|                          | ||||
|                         if (genericTypeName.Contains("TArray`1") || genericTypeName.Contains("List`1")) | ||||
|                         { | ||||
|                             return new NativeDataArrayType(typeRef, arrayDim, genericArguments[0]); | ||||
|                         } | ||||
|  | ||||
|                         if (genericTypeName.Contains("TNativeArray`1") || genericTypeName.Contains("ReadOnlySpan`1")) | ||||
|                         { | ||||
|                             return new NativeDataNativeArrayType(typeRef, arrayDim, genericArguments[0]); | ||||
|                         } | ||||
|  | ||||
|                         if (genericTypeName.Contains("TMap`2") || genericTypeName.Contains("Dictionary`2")) | ||||
|                         { | ||||
|                             return new NativeDataMapType(typeRef, arrayDim, genericArguments[0], genericArguments[1]); | ||||
|                         } | ||||
|                          | ||||
|                         if (genericTypeName.Contains("TSet`1") || genericTypeName.Contains("HashSet`1")) | ||||
|                         { | ||||
|                             return new NativeDataSetType(typeRef, arrayDim, genericArguments[0]); | ||||
|                         } | ||||
|  | ||||
|                         if (genericTypeName.Contains("TSubclassOf`1")) | ||||
|                         { | ||||
|                             return new NativeDataClassType(typeRef, genericArguments[0], arrayDim); | ||||
|                         } | ||||
|  | ||||
|                         if (genericTypeName.Contains("TWeakObjectPtr`1")) | ||||
|                         { | ||||
|                             return new NativeDataWeakObjectType(typeRef, genericArguments[0], arrayDim); | ||||
|                         } | ||||
|  | ||||
|                         if (genericTypeName.Contains("TSoftObjectPtr`1")) | ||||
|                         { | ||||
|                             return new NativeDataSoftObjectType(typeRef, genericArguments[0], arrayDim); | ||||
|                         } | ||||
|  | ||||
|                         if (genericTypeName.Contains("TSoftClassPtr`1")) | ||||
|                         { | ||||
|                             return new NativeDataSoftClassType(typeRef, genericArguments[0], arrayDim); | ||||
|                         } | ||||
|  | ||||
|                         if (genericTypeName.Contains("TOptional`1")) | ||||
|                         { | ||||
|                             return new NativeDataOptionalType(typeRef, genericArguments[0], arrayDim); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (typeDef.IsEnum && typeDef.IsUEnum()) | ||||
|                 { | ||||
|                     CustomAttribute? enumAttribute = typeDef.GetUEnum(); | ||||
|                  | ||||
|                     if (enumAttribute == null) | ||||
|                     { | ||||
|                         throw new InvalidPropertyException(propertyName, sequencePoint, "Enum properties must use an UEnum enum: " + typeRef.FullName); | ||||
|                     } | ||||
|                  | ||||
|                     // TODO: This is just true for properties, not for function parameters they can be int. Need a good way to differentiate. | ||||
|                     // if (typeDef.GetEnumUnderlyingType().Resolve() != ByteTypeRef.Resolve()) | ||||
|                     // { | ||||
|                     //     throw new InvalidPropertyException(propertyName, sequencePoint, "Enum's exposed to Blueprints must have an underlying type of System.Byte: " + typeRef.FullName); | ||||
|                     // } | ||||
|  | ||||
|                     return new NativeDataEnumType(typeDef, arrayDim); | ||||
|                 } | ||||
|  | ||||
|                 if (typeDef.IsInterface && typeDef.IsUInterface()) | ||||
|                 { | ||||
|                     return new NativeDataInterfaceType(typeRef, typeDef.Name + "Marshaller"); | ||||
|                 } | ||||
|                  | ||||
|                 if (typeDef.FullName == "UnrealSharp.FText") | ||||
|                 { | ||||
|                     return new NativeDataTextType(typeDef); | ||||
|                 } | ||||
|                  | ||||
|                 if (typeDef.FullName == "UnrealSharp.FName") | ||||
|                 { | ||||
|                     return new NativeDataNameType(typeDef, arrayDim); | ||||
|                 } | ||||
|              | ||||
|                 if (typeDef.Name == "TMulticastDelegate`1") | ||||
|                 { | ||||
|                     return new NativeDataMulticastDelegate(typeRef); | ||||
|                 } | ||||
|              | ||||
|                 if (typeDef.Name == "TDelegate`1") | ||||
|                 { | ||||
|                     return new NativeDataDelegateType(typeRef); | ||||
|                 } | ||||
|              | ||||
|                 if (customAttributes != null && NativeDataDefaultComponent.IsDefaultComponent(customAttributes)) | ||||
|                 { | ||||
|                     return new NativeDataDefaultComponent(customAttributes, typeDef, arrayDim); | ||||
|                 } | ||||
|              | ||||
|                 TypeDefinition? superType = typeDef; | ||||
|                 while (superType != null && superType.FullName != "UnrealSharp.Core.UnrealSharpObject") | ||||
|                 { | ||||
|                     TypeReference superTypeRef = superType.BaseType; | ||||
|                     superType = superTypeRef != null ? superTypeRef.Resolve() : null; | ||||
|                 } | ||||
|  | ||||
|                 if (superType != null) | ||||
|                 { | ||||
|                     return new NativeDataObjectType(typeRef, typeDef, arrayDim); | ||||
|                 } | ||||
|  | ||||
|                 // See if this is a struct | ||||
|                 CustomAttribute? structAttribute = typeDef.GetUStruct(); | ||||
|                  | ||||
|                 if (structAttribute == null) | ||||
|                 { | ||||
|                     return typeDef.IsUnmanagedType() ? new NativeDataUnmanagedType(typeDef, arrayDim) : new NativeDataManagedObjectType(typeRef, arrayDim); | ||||
|                 } | ||||
|                  | ||||
|                 return typeDef.GetBlittableType() != null ? new NativeDataBlittableStructType(typeDef, arrayDim) : new NativeDataStructType(typeDef, "StructMarshaller`1", arrayDim); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public static string GetWrapperClassName(this TypeReference typeRef) | ||||
|     { | ||||
|         return typeRef.Name + "Wrapper"; | ||||
|     } | ||||
|      | ||||
|     public static string GetMarshallerClassName(this TypeReference typeRef) | ||||
|     { | ||||
|         return typeRef.Name + "Marshaller"; | ||||
|     } | ||||
|      | ||||
|     public static PropertyType GetPrimitiveTypeCode(this TypeReference type) | ||||
|     { | ||||
|         // Is there a better way to do this? The private member e_type on TypeReference has what we want | ||||
|         return type.FullName switch | ||||
|         { | ||||
|             "System.Byte" => PropertyType.Byte, | ||||
|             "System.SByte" => PropertyType.Int8, | ||||
|             "System.Int16" => PropertyType.Int16, | ||||
|             "System.UInt16" => PropertyType.UInt16, | ||||
|             "System.Int32" => PropertyType.Int, | ||||
|             "System.UInt32" => PropertyType.UInt32, | ||||
|             "System.Int64" => PropertyType.Int64, | ||||
|             "System.UInt64" => PropertyType.UInt64, | ||||
|             "System.Float" => PropertyType.Float, | ||||
|             "System.Double" => PropertyType.Double, | ||||
|             _ => throw new NotImplementedException() | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public static GenericInstanceType AddMarshalledStructInterface(this TypeDefinition typeDefinition) | ||||
|     { | ||||
|         var marshalledStructRef = WeaverImporter.Instance.CurrentWeavingAssembly.MainModule.ImportReference( | ||||
|             WeaverImporter.Instance.MarshalledStructReference); | ||||
|         var instancedInterface = marshalledStructRef.MakeGenericInstanceType(typeDefinition); | ||||
|         ArgumentNullException.ThrowIfNull(instancedInterface); | ||||
|  | ||||
|         if (typeDefinition.Interfaces.All(i => i.InterfaceType.FullName != instancedInterface.FullName)) | ||||
|         { | ||||
|             typeDefinition.Interfaces.Add(new InterfaceImplementation(instancedInterface)); | ||||
|         } | ||||
|          | ||||
|         return instancedInterface; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,206 @@ | ||||
| using Mono.Cecil; | ||||
| using Mono.Cecil.Rocks; | ||||
| using UnrealSharpWeaver.Utilities; | ||||
|  | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| public class WeaverImporter | ||||
| { | ||||
|     private static WeaverImporter? _instance; | ||||
|     public static WeaverImporter Instance => _instance ??= new WeaverImporter(); | ||||
|  | ||||
|     private const string Attributes = ".Attributes"; | ||||
|  | ||||
|     public const string UnrealSharpNamespace = "UnrealSharp"; | ||||
|     public const string UnrealSharpAttributesNamespace = UnrealSharpNamespace + Attributes; | ||||
|  | ||||
|     public const string UnrealSharpCoreNamespace = UnrealSharpNamespace + ".Core"; | ||||
|     public const string UnrealSharpCoreAttributesNamespace = UnrealSharpCoreNamespace + Attributes; | ||||
|     public const string UnrealSharpCoreMarshallers = UnrealSharpCoreNamespace + ".Marshallers"; | ||||
|  | ||||
|     public const string InteropNameSpace = UnrealSharpNamespace + ".Interop"; | ||||
|     public const string AttributeNamespace = UnrealSharpNamespace + Attributes; | ||||
|     public const string CoreUObjectNamespace = UnrealSharpNamespace + ".CoreUObject"; | ||||
|     public const string EngineNamespace = UnrealSharpNamespace + ".Engine"; | ||||
|  | ||||
|     public const string UnrealSharpObject = "UnrealSharpObject"; | ||||
|     public const string FPropertyCallbacks = "FPropertyExporter"; | ||||
|  | ||||
|     public const string CoreUObjectCallbacks = "UCoreUObjectExporter"; | ||||
|     public const string UObjectCallbacks = "UObjectExporter"; | ||||
|     public const string UScriptStructCallbacks = "UScriptStructExporter"; | ||||
|     public const string UFunctionCallbacks = "UFunctionExporter"; | ||||
|     public const string MulticastDelegatePropertyCallbacks = "FMulticastDelegatePropertyExporter"; | ||||
|     public const string UStructCallbacks = "UStructExporter"; | ||||
|  | ||||
|     public const string GeneratedTypeAttribute = "GeneratedTypeAttribute"; | ||||
|      | ||||
|     public MethodReference? UFunctionAttributeConstructor => UnrealSharpAssembly.FindType("UFunctionAttribute", "UnrealSharp.Attributes")?.FindMethod(".ctor"); | ||||
|     public MethodReference? BlueprintInternalUseAttributeConstructor => UnrealSharpAssembly.FindType("BlueprintInternalUseOnlyAttribute", "UnrealSharp.Attributes.MetaTags")?.FindMethod(".ctor"); | ||||
|      | ||||
|     public AssemblyDefinition UnrealSharpAssembly => FindAssembly(UnrealSharpNamespace); | ||||
|     public AssemblyDefinition UnrealSharpCoreAssembly => FindAssembly(UnrealSharpNamespace + ".Core"); | ||||
|      | ||||
|     public AssemblyDefinition CurrentWeavingAssembly = null!; | ||||
|     public List<AssemblyDefinition> AllProjectAssemblies = []; | ||||
|      | ||||
|     public MethodReference NativeObjectGetter = null!; | ||||
|     public TypeDefinition IntPtrType = null!; | ||||
|     public MethodReference IntPtrAdd = null!; | ||||
|     public FieldReference IntPtrZero = null!; | ||||
|     public MethodReference IntPtrEqualsOperator = null!; | ||||
|     public TypeReference UnrealSharpObjectType = null!; | ||||
|     public TypeDefinition IInterfaceType = null!; | ||||
|     public MethodReference GetNativeFunctionFromInstanceAndNameMethod = null!; | ||||
|     public TypeReference Int32TypeRef = null!; | ||||
|     public TypeReference UInt64TypeRef = null!; | ||||
|     public TypeReference VoidTypeRef = null!; | ||||
|     public TypeReference ByteTypeRef = null!; | ||||
|      | ||||
|     public TypeReference MarshalledStructReference = null!; | ||||
|      | ||||
|     public MethodReference GetNativeClassFromNameMethod = null!; | ||||
|     public MethodReference GetNativeInterfaceFromNameMethod = null!; | ||||
|     public MethodReference GetNativeStructFromNameMethod = null!; | ||||
|     public MethodReference GetPropertyOffsetFromNameMethod = null!; | ||||
|     public MethodReference GetPropertyOffset = null!; | ||||
|     public MethodReference GetNativePropertyFromNameMethod = null!; | ||||
|     public MethodReference GetNativeFunctionFromClassAndNameMethod = null!; | ||||
|     public MethodReference GetNativeFunctionParamsSizeMethod = null!; | ||||
|     public MethodReference GetNativeStructSizeMethod = null!; | ||||
|     public MethodReference GetSignatureFunction = null!; | ||||
|     public MethodReference InitializeStructMethod = null!; | ||||
|      | ||||
|     public MethodReference InvokeNativeFunctionMethod = null!; | ||||
|     public MethodReference InvokeNativeNetFunction = null!; | ||||
|     public MethodReference InvokeNativeFunctionOutParms = null!; | ||||
|  | ||||
|     public MethodReference GeneratedTypeCtor = null!; | ||||
|      | ||||
|     public TypeDefinition UObjectDefinition = null!; | ||||
|     public TypeDefinition UActorComponentDefinition = null!; | ||||
|      | ||||
|     public TypeDefinition ScriptInterfaceWrapper = null!; | ||||
|     public TypeDefinition ScriptInterfaceMarshaller = null!; | ||||
|     public TypeReference ManagedObjectHandle = null!; | ||||
|     public TypeReference UnmanagedDataStore = null!; | ||||
|      | ||||
|     public MethodReference BlittableTypeConstructor = null!; | ||||
|  | ||||
|     public DefaultAssemblyResolver AssemblyResolver = null!; | ||||
|      | ||||
|     public static void Shutdown() | ||||
|     { | ||||
|         if (_instance == null) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|          | ||||
|         foreach (AssemblyDefinition assembly in _instance.AllProjectAssemblies) | ||||
|         { | ||||
|             assembly.Dispose(); | ||||
|         } | ||||
|          | ||||
|         _instance.AllProjectAssemblies = []; | ||||
|         _instance.CurrentWeavingAssembly = null!; | ||||
|         _instance = null; | ||||
|     } | ||||
|      | ||||
|     static AssemblyDefinition FindAssembly(string assemblyName) | ||||
|     { | ||||
|         return Instance.AssemblyResolver.Resolve(new AssemblyNameReference(assemblyName, new Version(0, 0))); | ||||
|     } | ||||
|  | ||||
|     public void ImportCommonTypes(AssemblyDefinition userAssembly) | ||||
|     { | ||||
|         CurrentWeavingAssembly = userAssembly; | ||||
|          | ||||
|         TypeSystem typeSystem = CurrentWeavingAssembly.MainModule.TypeSystem; | ||||
|          | ||||
|         Int32TypeRef = typeSystem.Int32; | ||||
|         UInt64TypeRef = typeSystem.UInt64; | ||||
|         VoidTypeRef = typeSystem.Void; | ||||
|         ByteTypeRef = typeSystem.Byte; | ||||
|          | ||||
|         IntPtrType = typeSystem.IntPtr.Resolve(); | ||||
|         IntPtrAdd = IntPtrType.FindMethod("Add")!; | ||||
|         IntPtrZero = IntPtrType.FindField("Zero"); | ||||
|         IntPtrEqualsOperator = IntPtrType.FindMethod("op_Equality")!; | ||||
|  | ||||
|         UnrealSharpObjectType = UnrealSharpCoreAssembly.FindType(UnrealSharpObject, UnrealSharpCoreNamespace)!; | ||||
|         IInterfaceType = UnrealSharpAssembly.FindType("IInterface", CoreUObjectNamespace)!.Resolve(); | ||||
|          | ||||
|         MarshalledStructReference = UnrealSharpCoreAssembly.FindType("MarshalledStruct`1", "UnrealSharp")!.Resolve(); | ||||
|          | ||||
|         TypeDefinition unrealSharpObjectType = UnrealSharpObjectType.Resolve(); | ||||
|         NativeObjectGetter = unrealSharpObjectType.FindMethod("get_NativeObject")!; | ||||
|  | ||||
|         GetNativeFunctionFromInstanceAndNameMethod = FindExporterMethod(TypeDefinitionUtilities.UClassCallbacks, "CallGetNativeFunctionFromInstanceAndName"); | ||||
|          | ||||
|         GetNativeStructFromNameMethod = FindExporterMethod(CoreUObjectCallbacks, "CallGetNativeStructFromName"); | ||||
|         GetNativeClassFromNameMethod = FindExporterMethod(CoreUObjectCallbacks, "CallGetNativeClassFromName"); | ||||
|         GetNativeInterfaceFromNameMethod = FindExporterMethod(CoreUObjectCallbacks, "CallGetNativeInterfaceFromName"); | ||||
|          | ||||
|         GetPropertyOffsetFromNameMethod = FindExporterMethod(FPropertyCallbacks, "CallGetPropertyOffsetFromName"); | ||||
|         GetPropertyOffset = FindExporterMethod(FPropertyCallbacks, "CallGetPropertyOffset"); | ||||
|          | ||||
|         GetNativePropertyFromNameMethod = FindExporterMethod(FPropertyCallbacks, "CallGetNativePropertyFromName"); | ||||
|          | ||||
|         GetNativeFunctionFromClassAndNameMethod = FindExporterMethod(TypeDefinitionUtilities.UClassCallbacks, "CallGetNativeFunctionFromClassAndName"); | ||||
|         GetNativeFunctionParamsSizeMethod = FindExporterMethod(UFunctionCallbacks, "CallGetNativeFunctionParamsSize"); | ||||
|          | ||||
|         GetNativeStructSizeMethod = FindExporterMethod(UScriptStructCallbacks, "CallGetNativeStructSize"); | ||||
|          | ||||
|         InvokeNativeFunctionMethod = FindExporterMethod(UObjectCallbacks, "CallInvokeNativeFunction"); | ||||
|         InvokeNativeNetFunction = FindExporterMethod(UObjectCallbacks, "CallInvokeNativeNetFunction"); | ||||
|         InvokeNativeFunctionOutParms = FindExporterMethod(UObjectCallbacks, "CallInvokeNativeFunctionOutParms"); | ||||
|          | ||||
|         GetSignatureFunction = FindExporterMethod(MulticastDelegatePropertyCallbacks, "CallGetSignatureFunction"); | ||||
|          | ||||
|         InitializeStructMethod = FindExporterMethod(UStructCallbacks, "CallInitializeStruct"); | ||||
|          | ||||
|         UObjectDefinition = UnrealSharpAssembly.FindType("UObject", CoreUObjectNamespace)!.Resolve(); | ||||
|         UActorComponentDefinition = UnrealSharpAssembly.FindType("UActorComponent", EngineNamespace)!.Resolve(); | ||||
|          | ||||
|         TypeReference blittableType = UnrealSharpCoreAssembly.FindType(TypeDefinitionUtilities.BlittableTypeAttribute, UnrealSharpCoreAttributesNamespace)!; | ||||
|         BlittableTypeConstructor = blittableType.FindMethod(".ctor")!; | ||||
|  | ||||
|         TypeReference generatedType = UnrealSharpCoreAssembly.FindType(GeneratedTypeAttribute, UnrealSharpCoreAttributesNamespace)!; | ||||
|         GeneratedTypeCtor = generatedType.FindMethod(".ctor")!; | ||||
|          | ||||
|         ScriptInterfaceWrapper = UnrealSharpAssembly.FindType("IScriptInterface", CoreUObjectNamespace)!.Resolve(); | ||||
|         ScriptInterfaceMarshaller = UnrealSharpAssembly.FindType("ScriptInterfaceMarshaller`1", CoreUObjectNamespace)!.Resolve(); | ||||
|          | ||||
|         ManagedObjectHandle = UnrealSharpAssembly.FindType("FSharedGCHandle", "UnrealSharp.UnrealSharpCore")!.Resolve(); | ||||
|         UnmanagedDataStore = UnrealSharpAssembly.FindType("FUnmanagedDataStore", "UnrealSharp.UnrealSharpCore")!.Resolve(); | ||||
|     } | ||||
|  | ||||
|     private static MethodReference FindBindingsStaticMethod(string findNamespace, string findClass, string findMethod) | ||||
|     { | ||||
|         foreach (var module in Instance.UnrealSharpAssembly.Modules) | ||||
|         { | ||||
|             foreach (var type in module.GetAllTypes()) | ||||
|             { | ||||
|                 if (type.Namespace != findNamespace || type.Name != findClass) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 foreach (var method in type.Methods) | ||||
|                 { | ||||
|                     if (method.IsStatic && method.Name == findMethod) | ||||
|                     { | ||||
|                         return Instance.CurrentWeavingAssembly.MainModule.ImportReference(method); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         throw new Exception("Could not find method " + findMethod + " in class " + findClass + " in namespace " + findNamespace); | ||||
|     } | ||||
|  | ||||
|     private static MethodReference FindExporterMethod(string exporterName, string functionName) | ||||
|     { | ||||
|         return FindBindingsStaticMethod(InteropNameSpace, exporterName, functionName); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,58 @@ | ||||
| using System.Reflection; | ||||
| using CommandLine; | ||||
| using CommandLine.Text; | ||||
|  | ||||
| namespace UnrealSharpWeaver; | ||||
|  | ||||
| public class WeaverOptions | ||||
| { | ||||
|     [Option('p', "path", Required = true, HelpText = "Search paths for assemblies.")] | ||||
|     public required IEnumerable<string> AssemblyPaths { get; set; } | ||||
|  | ||||
|     [Option('o', "output", Required = true, HelpText = "DLL output directory.")] | ||||
|     public required string OutputDirectory { get; set; } | ||||
|  | ||||
|     public WeaverOptions(IEnumerable<string> assemblyPaths, string outputDirectory) | ||||
|     { | ||||
|         AssemblyPaths = assemblyPaths; | ||||
|         OutputDirectory = outputDirectory; | ||||
|     } | ||||
|  | ||||
|     public WeaverOptions() : this([], string.Empty) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     private static void PrintHelp(ParserResult<WeaverOptions> result) | ||||
|     { | ||||
|         if (result.Tag != ParserResultType.NotParsed) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         string name = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly()!.Location); | ||||
|         Console.Error.WriteLine($"Usage: {name}"); | ||||
|         Console.Error.WriteLine("Commands: "); | ||||
|  | ||||
|         var helpText = HelpText.AutoBuild(result, h => h, e => e); | ||||
|         Console.WriteLine(helpText); | ||||
|     } | ||||
|  | ||||
|     public static WeaverOptions ParseArguments(IEnumerable<string> args) | ||||
|     { | ||||
|         Parser parser = new Parser(settings => | ||||
|         { | ||||
|             settings.AllowMultiInstance = true; | ||||
|             settings.HelpWriter = null; | ||||
|         }); | ||||
|  | ||||
|         ParserResult<WeaverOptions> result = parser.ParseArguments<WeaverOptions>(args); | ||||
|  | ||||
|         if (result.Tag != ParserResultType.NotParsed) | ||||
|         { | ||||
|             return result.Value; | ||||
|         } | ||||
|  | ||||
|         PrintHelp(result); | ||||
|         throw new InvalidOperationException("Invalid arguments."); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user