Lua向C#逻辑迁移 一期 #13

将整个插件代码上传
This commit is contained in:
2025-10-26 21:48:39 +08:00
parent 56994b3927
commit 648386cd73
785 changed files with 53683 additions and 2 deletions

View File

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

View File

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

View File

@ -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)
{
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>

View File

@ -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,
}

View File

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

View File

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

View File

@ -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,
};

View File

@ -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,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
namespace UnrealSharpWeaver.MetaData;
public class UnrealSharpMetadata
{
public ICollection<string> AssemblyLoadingOrder { get; set; } = [];
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
using Mono.Cecil;
namespace UnrealSharpWeaver.NativeTypes;
class NativeDataBlittableStructType(TypeReference structType, int arrayDim) : NativeDataBlittableStructTypeBase(structType, arrayDim);

View File

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

View File

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

View File

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

View File

@ -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()];
}
};

View File

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

View File

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

View File

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

View File

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

View File

@ -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!");
}
}
};

View File

@ -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!");
}
}
}

View File

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

View File

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

View File

@ -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";
}
}

View File

@ -0,0 +1,5 @@
using Mono.Cecil;
namespace UnrealSharpWeaver.NativeTypes;
class NativeDataNameType(TypeReference structType, int arrayDim) : NativeDataBlittableStructTypeBase(structType, arrayDim, PropertyType.Name);

View File

@ -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) { }
}

View File

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

View File

@ -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";
}
}

View File

@ -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";
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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!");
}
}
}

View File

@ -0,0 +1,5 @@
using Mono.Cecil;
namespace UnrealSharpWeaver.NativeTypes;
class NativeDataTextType(TypeReference textType) : NativeDataSimpleType(textType, "TextMarshaller", 1, PropertyType.Text);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,10 @@
namespace UnrealSharpWeaver;
public enum ParameterType
{
None,
Value,
Ref,
Out,
ReturnValue
}

View File

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

View File

@ -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
}

View File

@ -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
}

View File

@ -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,
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.");
}
}