diff --git a/.gitmodules b/.gitmodules
index d89e42270..c9728a6fc 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,6 +4,9 @@
[submodule "Lidgren.Network"]
path = Lidgren.Network/Lidgren.Network
url = https://github.com/space-wizards/lidgren-network-gen3.git
+[submodule "XamlX"]
+ path = XamlX
+ url = https://github.com/space-wizards/XamlX
[submodule "Robust.LoaderApi"]
path = Robust.LoaderApi
url = https://github.com/space-wizards/Robust.LoaderApi.git
diff --git a/MSBuild/XamlIL.targets b/MSBuild/XamlIL.targets
new file mode 100644
index 000000000..ac0b11c79
--- /dev/null
+++ b/MSBuild/XamlIL.targets
@@ -0,0 +1,24 @@
+
+
+
+ %(Filename)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(IntermediateOutputPath)XAML/references
+ $(IntermediateOutputPath)XAML/original.dll
+
+
+
+
+
diff --git a/Robust.Client.Injectors/CompileRobustXamlTask.cs b/Robust.Client.Injectors/CompileRobustXamlTask.cs
new file mode 100644
index 000000000..ae2465f47
--- /dev/null
+++ b/Robust.Client.Injectors/CompileRobustXamlTask.cs
@@ -0,0 +1,86 @@
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using Microsoft.Build.Framework;
+
+namespace Robust.Build.Tasks
+{
+ ///
+ /// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
+ ///
+ public class CompileRobustXamlTask : ITask
+ {
+ public bool Execute()
+ {
+ //Debugger.Launch();
+ OutputPath = OutputPath ?? AssemblyFile;
+ var outputPdb = GetPdbPath(OutputPath);
+ var input = AssemblyFile;
+ var inputPdb = GetPdbPath(input);
+ // Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK
+ if (OriginalCopyPath != null)
+ {
+ File.Copy(AssemblyFile, OriginalCopyPath, true);
+ input = OriginalCopyPath;
+ File.Delete(AssemblyFile);
+
+ if (File.Exists(inputPdb))
+ {
+ var copyPdb = GetPdbPath(OriginalCopyPath);
+ File.Copy(inputPdb, copyPdb, true);
+ File.Delete(inputPdb);
+ inputPdb = copyPdb;
+ }
+ }
+
+ var msg = $"CompileRobustXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
+ BuildEngine.LogMessage(msg, MessageImportance.High);
+
+ var res = XamlCompiler.Compile(BuildEngine, input,
+ File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
+ ProjectDirectory, OutputPath,
+ (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null);
+ if (!res.success)
+ return false;
+ if (!res.writtentofile)
+ {
+ File.Copy(input, OutputPath, true);
+ if(File.Exists(inputPdb))
+ File.Copy(inputPdb, outputPdb, true);
+ }
+ return true;
+ }
+
+ [Required]
+ public string ReferencesFilePath { get; set; }
+
+ [Required]
+ public string ProjectDirectory { get; set; }
+
+ [Required]
+ public string AssemblyFile { get; set; }
+
+ [Required]
+ public string OriginalCopyPath { get; set; }
+
+ public string OutputPath { get; set; }
+
+ public string AssemblyOriginatorKeyFile { get; set; }
+ public bool SignAssembly { get; set; }
+ public bool DelaySign { get; set; }
+
+ // shamelessly copied from avalonia
+ string GetPdbPath(string p)
+ {
+ var d = Path.GetDirectoryName(p);
+ var f = Path.GetFileNameWithoutExtension(p);
+ var rv = f + ".pdb";
+ if (d != null)
+ rv = Path.Combine(d, rv);
+ return rv;
+ }
+
+ public IBuildEngine BuildEngine { get; set; }
+ public ITaskHost HostObject { get; set; }
+ }
+}
diff --git a/Robust.Client.Injectors/Extensions.cs b/Robust.Client.Injectors/Extensions.cs
new file mode 100644
index 000000000..31d80cca7
--- /dev/null
+++ b/Robust.Client.Injectors/Extensions.cs
@@ -0,0 +1,16 @@
+using Microsoft.Build.Framework;
+
+namespace Robust.Build.Tasks
+{
+ ///
+ /// Taken from https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/Extensions.cs
+ ///
+ public static class Extensions
+ {
+ //shamefully copied from avalonia
+ public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
+ {
+ engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
+ }
+ }
+}
diff --git a/Robust.Client.Injectors/Program.cs b/Robust.Client.Injectors/Program.cs
new file mode 100644
index 000000000..c3bf6e5b7
--- /dev/null
+++ b/Robust.Client.Injectors/Program.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections;
+using System.IO;
+using Microsoft.Build.Framework;
+
+namespace Robust.Build.Tasks
+{
+ ///
+ /// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/Program.cs
+ ///
+ class Program
+ {
+ static int Main(string[] args)
+ {
+ if (args.Length != 3)
+ {
+ Console.Error.WriteLine("expected: input references output");
+ return 1;
+ }
+
+ return new CompileRobustXamlTask
+ {
+ AssemblyFile = args[0],
+ ReferencesFilePath = args[1],
+ OutputPath = args[2],
+ BuildEngine = new ConsoleBuildEngine(),
+ ProjectDirectory = Directory.GetCurrentDirectory()
+ }.Execute() ? 0 : 2;
+ }
+ }
+
+ class ConsoleBuildEngine : IBuildEngine
+ {
+ public void LogErrorEvent(BuildErrorEventArgs e)
+ {
+ Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
+ }
+
+ public void LogWarningEvent(BuildWarningEventArgs e)
+ {
+ Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
+ }
+
+ public void LogMessageEvent(BuildMessageEventArgs e)
+ {
+ Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}");
+ }
+
+ public void LogCustomEvent(CustomBuildEventArgs e)
+ {
+ Console.WriteLine($"CUSTOM: {e.Message}");
+ }
+
+ public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties,
+ IDictionary targetOutputs) => throw new NotSupportedException();
+
+ public bool ContinueOnError { get; }
+ public int LineNumberOfTaskNode { get; }
+ public int ColumnNumberOfTaskNode { get; }
+ public string ProjectFileOfTaskNode { get; }
+ }
+}
diff --git a/Robust.Client.Injectors/Robust.Client.Injectors.csproj b/Robust.Client.Injectors/Robust.Client.Injectors.csproj
new file mode 100644
index 000000000..151201139
--- /dev/null
+++ b/Robust.Client.Injectors/Robust.Client.Injectors.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net5.0
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Robust.Client.Injectors/RobustXamlILCompiler.cs b/Robust.Client.Injectors/RobustXamlILCompiler.cs
new file mode 100644
index 000000000..11862a73b
--- /dev/null
+++ b/Robust.Client.Injectors/RobustXamlILCompiler.cs
@@ -0,0 +1,201 @@
+using System.Linq;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Robust.Build.Tasks
+{
+ ///
+ /// Emitters & Transformers based on:
+ /// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlRootObjectScopeTransformer.cs
+ /// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
+ ///
+ public class RobustXamlILCompiler : XamlILCompiler
+ {
+ public RobustXamlILCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings emitMappings, bool fillWithDefaults) : base(configuration, emitMappings, fillWithDefaults)
+ {
+ Transformers.Add(new AddNameScopeRegistration());
+ Transformers.Add(new RobustMarkRootObjectScopeNode());
+
+ Emitters.Add(new AddNameScopeRegistration.Emitter());
+ Emitters.Add(new RobustMarkRootObjectScopeNode.Emitter());
+ }
+
+ class AddNameScopeRegistration : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlPropertyAssignmentNode pa)
+ {
+ if (pa.Property.Name == "Name"
+ && pa.Property.DeclaringType.FullName == "Robust.Client.UserInterface.Control")
+ {
+ if (context.ParentNodes().FirstOrDefault() is XamlManipulationGroupNode mg
+ && mg.Children.OfType().Any())
+ return node;
+
+ IXamlAstValueNode value = null;
+ for (var c = 0; c < pa.Values.Count; c++)
+ if (pa.Values[c].Type.GetClrType().Equals(context.Configuration.WellKnownTypes.String))
+ {
+ value = pa.Values[c];
+ if (!(value is XamlAstTextNode))
+ {
+ var local = new XamlAstCompilerLocalNode(value);
+ // Wrap original in local initialization
+ pa.Values[c] = new XamlAstLocalInitializationNodeEmitter(value, value, local);
+ // Use local
+ value = local;
+ }
+
+ break;
+ }
+
+ if (value != null)
+ {
+ var objectType = context.ParentNodes().OfType().FirstOrDefault()?.Type.GetClrType();
+ return new XamlManipulationGroupNode(pa)
+ {
+ Children =
+ {
+ pa,
+ new RobustNameScopeRegistrationXamlIlNode(value, objectType)
+ }
+ };
+ }
+ }
+ /*else if (pa.Property.CustomAttributes.Select(attr => attr.Type).Intersect(context.Configuration.TypeMappings.DeferredContentPropertyAttributes).Any())
+ {
+ pa.Values[pa.Values.Count - 1] =
+ new NestedScopeMetadataNode(pa.Values[pa.Values.Count - 1]);
+ }*/
+ }
+
+ return node;
+ }
+
+ class RobustNameScopeRegistrationXamlIlNode : XamlAstNode, IXamlAstManipulationNode
+ {
+ public IXamlAstValueNode Name { get; set; }
+ public IXamlType TargetType { get; }
+
+ public RobustNameScopeRegistrationXamlIlNode(IXamlAstValueNode name, IXamlType targetType) : base(name)
+ {
+ TargetType = targetType;
+ Name = name;
+ }
+
+ public override void VisitChildren(IXamlAstVisitor visitor)
+ => Name = (IXamlAstValueNode)Name.Visit(visitor);
+ }
+
+ internal class Emitter : IXamlAstLocalsNodeEmitter
+ {
+ public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContextWithLocals context, IXamlILEmitter codeGen)
+ {
+ if (node is RobustNameScopeRegistrationXamlIlNode registration)
+ {
+
+ var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
+ f.Name == XamlCompiler.ContextNameScopeFieldName);
+ var namescopeRegisterFunction = context.Configuration.TypeSystem
+ .FindType("Robust.Client.UserInterface.XAML.NameScope").Methods
+ .First(m => m.Name == "Register");
+
+ using (var targetLoc = context.GetLocalOfType(context.Configuration.TypeSystem.FindType("Robust.Client.UserInterface.Control")))
+ {
+
+ codeGen
+ // var target = {pop}
+ .Stloc(targetLoc.Local)
+ // _context.NameScope.Register(Name, target)
+ .Ldloc(context.ContextLocal)
+ .Ldfld(scopeField);
+
+ context.Emit(registration.Name, codeGen, registration.Name.Type.GetClrType());
+
+ codeGen
+ .Ldloc(targetLoc.Local)
+ .EmitCall(namescopeRegisterFunction, true);
+ }
+
+ return XamlILNodeEmitResult.Void(1);
+ }
+ return default;
+ }
+ }
+ }
+
+ class RobustMarkRootObjectScopeNode : IXamlAstTransformer
+ {
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (!context.ParentNodes().Any()
+ && node is XamlValueWithManipulationNode mnode)
+ {
+ mnode.Manipulation = new XamlManipulationGroupNode(mnode,
+ new[]
+ {
+ mnode.Manipulation,
+ new HandleRootObjectScopeNode(mnode)
+ });
+ }
+ return node;
+ }
+ class HandleRootObjectScopeNode : XamlAstNode, IXamlAstManipulationNode
+ {
+ public HandleRootObjectScopeNode(IXamlLineInfo lineInfo) : base(lineInfo)
+ {
+ }
+ }
+ internal class Emitter : IXamlILAstNodeEmitter
+ {
+ public XamlILNodeEmitResult Emit(IXamlAstNode node, XamlEmitContext context, IXamlILEmitter codeGen)
+ {
+ if (!(node is HandleRootObjectScopeNode))
+ {
+ return null;
+ }
+
+ var controlType = context.Configuration.TypeSystem.FindType("Robust.Client.UserInterface.Control");
+
+ var next = codeGen.DefineLabel();
+ var dontAbsorb = codeGen.DefineLabel();
+ var end = codeGen.DefineLabel();
+ var contextScopeField = context.RuntimeContext.ContextType.Fields.First(f =>
+ f.Name == XamlCompiler.ContextNameScopeFieldName);
+ var controlNameScopeField = controlType.Fields.First(f => f.Name == "NameScope");
+ var nameScopeType = context.Configuration.TypeSystem
+ .FindType("Robust.Client.UserInterface.XAML.NameScope");
+ var nameScopeCompleteMethod = nameScopeType.Methods.First(m => m.Name == "Complete");
+ var nameScopeAbsorbMethod = nameScopeType.Methods.First(m => m.Name == "Absorb");
+ using (var local = codeGen.LocalsPool.GetLocal(controlType))
+ {
+ codeGen
+ .Isinst(controlType)
+ .Dup()
+ .Stloc(local.Local) //store control in local field
+ .Brfalse(next) //if control is null, move to next (this should never happen but whatev, avalonia does it)
+ .Ldloc(context.ContextLocal)
+ .Ldfld(contextScopeField)
+ .Ldloc(local.Local) //load control from local field
+ .Ldfld(controlNameScopeField) //load namescope field from control
+ .EmitCall(nameScopeAbsorbMethod, true)
+ .Ldloc(local.Local) //load control
+ .Ldloc(context.ContextLocal) //load contextObject
+ .Ldfld(contextScopeField) //load namescope field from context obj
+ .Stfld(controlNameScopeField) //store namescope field in control
+ .MarkLabel(next)
+ .Ldloc(context.ContextLocal)
+ .Ldfld(contextScopeField)
+ .EmitCall(nameScopeCompleteMethod, true); //set the namescope as complete
+ }
+
+ return XamlILNodeEmitResult.Void(1);
+ }
+ }
+ }
+ }
+}
diff --git a/Robust.Client.Injectors/XamlCompiler.Helpers.cs b/Robust.Client.Injectors/XamlCompiler.Helpers.cs
new file mode 100644
index 000000000..73a5f72ce
--- /dev/null
+++ b/Robust.Client.Injectors/XamlCompiler.Helpers.cs
@@ -0,0 +1,78 @@
+using System.Collections.Generic;
+using System.Linq;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.Collections.Generic;
+using XamlX.TypeSystem;
+
+namespace Robust.Build.Tasks
+{
+ ///
+ /// Helpers taken from:
+ /// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+ /// - https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs
+ ///
+ public partial class XamlCompiler
+ {
+ static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml")
+ || r.Name.ToLowerInvariant().EndsWith(".paml")
+ || r.Name.ToLowerInvariant().EndsWith(".axaml");
+
+ private static bool MatchThisCall(Collection instructions, int idx)
+ {
+ var i = instructions[idx];
+ // A "normal" way of passing `this` to a static method:
+
+ // ldarg.0
+ // call void [Avalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object)
+
+ return i.OpCode == OpCodes.Ldarg_0 || (i.OpCode == OpCodes.Ldarg && i.Operand?.Equals(0) == true);
+ }
+
+ interface IResource : IFileSource
+ {
+ string Uri { get; }
+ string Name { get; }
+ void Remove();
+
+ }
+
+ interface IResourceGroup
+ {
+ string Name { get; }
+ IEnumerable Resources { get; }
+ }
+
+ class EmbeddedResources : IResourceGroup
+ {
+ private readonly AssemblyDefinition _asm;
+ public string Name => "EmbeddedResource";
+
+ public IEnumerable Resources => _asm.MainModule.Resources.OfType()
+ .Select(r => new WrappedResource(_asm, r)).ToList();
+
+ public EmbeddedResources(AssemblyDefinition asm)
+ {
+ _asm = asm;
+ }
+ class WrappedResource : IResource
+ {
+ private readonly AssemblyDefinition _asm;
+ private readonly EmbeddedResource _res;
+
+ public WrappedResource(AssemblyDefinition asm, EmbeddedResource res)
+ {
+ _asm = asm;
+ _res = res;
+ }
+
+ public string Uri => $"resm:{Name}?assembly={_asm.Name.Name}";
+ public string Name => _res.Name;
+ public string FilePath => Name;
+ public byte[] FileContents => _res.GetResourceData();
+
+ public void Remove() => _asm.MainModule.Resources.Remove(_res);
+ }
+ }
+ }
+}
diff --git a/Robust.Client.Injectors/XamlCompiler.cs b/Robust.Client.Injectors/XamlCompiler.cs
new file mode 100644
index 000000000..f7d3bd047
--- /dev/null
+++ b/Robust.Client.Injectors/XamlCompiler.cs
@@ -0,0 +1,317 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Build.Framework;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.Cecil.Rocks;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Parsers;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Robust.Build.Tasks
+{
+ ///
+ /// Based on https://github.com/AvaloniaUI/Avalonia/blob/c85fa2b9977d251a31886c2534613b4730fbaeaf/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+ /// Adjusted for our UI-Framework
+ ///
+ public partial class XamlCompiler
+ {
+ public static (bool success, bool writtentofile) Compile(IBuildEngine engine, string input, string[] references,
+ string projectDirectory, string output, string strongNameKey)
+ {
+ var typeSystem = new CecilTypeSystem(references
+ .Where(r => !r.ToLowerInvariant().EndsWith("robust.build.tasks.dll"))
+ .Concat(new[] { input }), input);
+
+ var asm = typeSystem.TargetAssemblyDefinition;
+
+ var compileRes = CompileCore(engine, typeSystem);
+ if (compileRes == null)
+ return (true, false);
+ if (compileRes == false)
+ return (false, false);
+
+ var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols };
+ if (!string.IsNullOrWhiteSpace(strongNameKey))
+ writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey);
+
+ asm.Write(output, writerParameters);
+
+ return (true, true);
+
+ }
+
+ static bool? CompileCore(IBuildEngine engine, CecilTypeSystem typeSystem)
+ {
+ var asm = typeSystem.TargetAssemblyDefinition;
+ var embrsc = new EmbeddedResources(asm);
+
+ if (embrsc.Resources.Count(CheckXamlName) == 0)
+ // Nothing to do
+ return null;
+
+ var xamlLanguage = new XamlLanguageTypeMappings(typeSystem)
+ {
+ XmlnsAttributes =
+ {
+ typeSystem.GetType("Robust.Client.UserInterface.XAML.XmlnsDefinitionAttribute"),
+
+ },
+ ContentAttributes =
+ {
+ typeSystem.GetType("Robust.Client.UserInterface.XAML.ContentAttribute")
+ },
+ UsableDuringInitializationAttributes =
+ {
+ typeSystem.GetType("Robust.Client.UserInterface.XAML.UsableDuringInitializationAttribute")
+ },
+ DeferredContentPropertyAttributes =
+ {
+ typeSystem.GetType("Robust.Client.UserInterface.XAML.DeferredContentAttribute")
+ },
+ RootObjectProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestRootObjectProvider"),
+ UriContextProvider = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestUriContext"),
+ ProvideValueTarget = typeSystem.GetType("Robust.Client.UserInterface.XAML.ITestProvideValueTarget"),
+ };
+ var emitConfig = new XamlLanguageEmitMappings
+ {
+ ContextTypeBuilderCallback = (b,c) => EmitNameScopeField(xamlLanguage, typeSystem, b, c)
+ };
+
+ var transformerconfig = new TransformerConfiguration(
+ typeSystem,
+ typeSystem.TargetAssembly,
+ xamlLanguage,
+ XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage));
+
+ var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext",
+ TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+ asm.MainModule.Types.Add(contextDef);
+ var contextClass = XamlILContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem,
+ xamlLanguage, emitConfig);
+
+ var compiler =
+ new RobustXamlILCompiler(transformerconfig, emitConfig, true);
+
+ var loaderDispatcherDef = new TypeDefinition("CompiledRobustXaml", "!XamlLoader",
+ TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
+
+ var loaderDispatcherMethod = new MethodDefinition("TryLoad",
+ MethodAttributes.Static | MethodAttributes.Public,
+ asm.MainModule.TypeSystem.Object)
+ {
+ Parameters = {new ParameterDefinition(asm.MainModule.TypeSystem.String)}
+ };
+ loaderDispatcherDef.Methods.Add(loaderDispatcherMethod);
+ asm.MainModule.Types.Add(loaderDispatcherDef);
+
+ var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First(
+ m =>
+ m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 2 &&
+ m.ReturnType.FullName == "System.Boolean"
+ && m.Parameters[0].ParameterType.FullName == "System.String"
+ && m.Parameters[1].ParameterType.FullName == "System.String"));
+
+ bool CompileGroup(IResourceGroup group)
+ {
+ var typeDef = new TypeDefinition("CompiledRobustXaml", "!" + group.Name, TypeAttributes.Class,
+ asm.MainModule.TypeSystem.Object);
+
+ //typeDef.CustomAttributes.Add(new CustomAttribute(ed));
+ asm.MainModule.Types.Add(typeDef);
+ var builder = typeSystem.CreateTypeBuilder(typeDef);
+
+ foreach (var res in group.Resources.Where(CheckXamlName))
+ {
+ try
+ {
+ engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", MessageImportance.Low);
+
+ var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
+ var parsed = XDocumentXamlParser.Parse(xaml);
+
+ var initialRoot = (XamlAstObjectNode) parsed.Root;
+
+ var classDirective = initialRoot.Children.OfType()
+ .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class");
+ string classname;
+ if (classDirective != null && classDirective.Values[0] is XamlAstTextNode tn)
+ {
+ classname = tn.Text;
+ }
+ else
+ {
+ classname = res.Name.Replace(".xaml","");
+ }
+
+ var classType = typeSystem.TargetAssembly.FindType(classname);
+ if (classType == null)
+ throw new Exception($"Unable to find type '{classname}'");
+
+ compiler.Transform(parsed);
+
+ var populateName = $"Populate:{res.Name}";
+ var buildName = $"Build:{res.Name}";
+
+ var classTypeDefinition = typeSystem.GetTypeReference(classType).Resolve();
+
+ var populateBuilder = typeSystem.CreateTypeBuilder(classTypeDefinition);
+
+ compiler.Compile(parsed, contextClass,
+ compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
+ classTypeDefinition == null),
+ compiler.DefineBuildMethod(builder, parsed, buildName, true),
+ null,
+ (closureName, closureBaseType) =>
+ populateBuilder.DefineSubType(closureBaseType, closureName, false),
+ res.Uri, res
+ );
+
+ //add compiled populate method
+ var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve().Methods
+ .First(m => m.Name == populateName);
+
+ const string TrampolineName = "!XamlIlPopulateTrampoline";
+ var trampoline = new MethodDefinition(TrampolineName,
+ MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void);
+ trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
+ classTypeDefinition.Methods.Add(trampoline);
+
+ trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
+ trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
+ trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod));
+ trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
+
+ var foundXamlLoader = false;
+ // Find RobustXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this)
+ foreach (var method in classTypeDefinition.Methods
+ .Where(m => !m.Attributes.HasFlag(MethodAttributes.Static)))
+ {
+ var i = method.Body.Instructions;
+ for (var c = 1; c < i.Count; c++)
+ {
+ if (i[c].OpCode == OpCodes.Call)
+ {
+ var op = i[c].Operand as MethodReference;
+
+ if (op != null
+ && op.Name == TrampolineName)
+ {
+ foundXamlLoader = true;
+ break;
+ }
+
+ if (op != null
+ && op.Name == "Load"
+ && op.Parameters.Count == 1
+ && op.Parameters[0].ParameterType.FullName == "System.Object"
+ && op.DeclaringType.FullName == "Robust.Client.UserInterface.XAML.RobustXamlLoader")
+ {
+ if (MatchThisCall(i, c - 1))
+ {
+ i[c].Operand = trampoline;
+ foundXamlLoader = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (!foundXamlLoader)
+ {
+ var ctors = classTypeDefinition.GetConstructors()
+ .Where(c => !c.IsStatic).ToList();
+ // We can inject xaml loader into default constructor
+ if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3)
+ {
+ var i = ctors[0].Body.Instructions;
+ var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
+ i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline));
+ i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
+ }
+ else
+ {
+ throw new InvalidProgramException(
+ $"No call to RobustXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors.");
+ }
+ }
+
+ //add compiled build method
+ var compiledBuildMethod = typeSystem.GetTypeReference(builder).Resolve().Methods
+ .First(m => m.Name == buildName);
+ var parameterlessCtor = classTypeDefinition.GetConstructors()
+ .FirstOrDefault(c => c.IsPublic && !c.IsStatic && !c.HasParameters);
+
+ if (compiledBuildMethod != null && parameterlessCtor != null)
+ {
+ var i = loaderDispatcherMethod.Body.Instructions;
+ var nop = Instruction.Create(OpCodes.Nop);
+ i.Add(Instruction.Create(OpCodes.Ldarg_0));
+ i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri));
+ i.Add(Instruction.Create(OpCodes.Call, stringEquals));
+ i.Add(Instruction.Create(OpCodes.Brfalse, nop));
+ if (parameterlessCtor != null)
+ i.Add(Instruction.Create(OpCodes.Newobj, parameterlessCtor));
+ else
+ {
+ i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod));
+ }
+
+ i.Add(Instruction.Create(OpCodes.Ret));
+ i.Add(nop);
+ }
+ }
+ catch (Exception e)
+ {
+ engine.LogWarningEvent(new BuildWarningEventArgs("XAMLIL", "", res.Uri, 0, 0, 0, 0,
+ e.ToString(), "", "CompileRobustXaml"));
+ }
+ }
+ return true;
+ }
+
+ if (embrsc.Resources.Count(CheckXamlName) != 0)
+ {
+ if (!CompileGroup(embrsc))
+ return false;
+ }
+
+ loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
+ loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
+ return true;
+ }
+
+ public const string ContextNameScopeFieldName = "RobustNameScope";
+
+ private static void EmitNameScopeField(XamlLanguageTypeMappings xamlLanguage, CecilTypeSystem typeSystem, IXamlTypeBuilder typeBuilder, IXamlILEmitter constructor)
+ {
+ var nameScopeType = typeSystem.FindType("Robust.Client.UserInterface.XAML.NameScope");
+ var field = typeBuilder.DefineField(nameScopeType,
+ ContextNameScopeFieldName, true, false);
+ constructor
+ .Ldarg_0()
+ .Newobj(nameScopeType.GetConstructor())
+ .Stfld(field);
+ }
+ }
+
+ interface IResource : IFileSource
+ {
+ string Uri { get; }
+ string Name { get; }
+ void Remove();
+
+ }
+
+ interface IResourceGroup
+ {
+ string Name { get; }
+ IEnumerable Resources { get; }
+ }
+}
diff --git a/Robust.Client.NameGenerator/NameReferenceSyntaxReceiver.cs b/Robust.Client.NameGenerator/NameReferenceSyntaxReceiver.cs
new file mode 100644
index 000000000..ac8d83176
--- /dev/null
+++ b/Robust.Client.NameGenerator/NameReferenceSyntaxReceiver.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Robust.Client.NameGenerator
+{
+ ///
+ /// Taken from https://github.com/AvaloniaUI/Avalonia.NameGenerator/blob/ecc9677a23de5cbc90af07ccac14e31c0da41d6a/src/Avalonia.NameGenerator/NameReferenceSyntaxReceiver.cs
+ ///
+ internal class NameReferenceSyntaxReceiver : ISyntaxReceiver
+ {
+ public List CandidateClasses { get; } = new List();
+
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&
+ classDeclarationSyntax.AttributeLists.Count > 0)
+ CandidateClasses.Add(classDeclarationSyntax);
+ }
+ }
+}
diff --git a/Robust.Client.NameGenerator/Robust.Client.NameGenerator.csproj b/Robust.Client.NameGenerator/Robust.Client.NameGenerator.csproj
new file mode 100644
index 000000000..e93e53ee4
--- /dev/null
+++ b/Robust.Client.NameGenerator/Robust.Client.NameGenerator.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Robust.Client.NameGenerator/RoslynTypeSystem.cs b/Robust.Client.NameGenerator/RoslynTypeSystem.cs
new file mode 100644
index 000000000..1d6af521b
--- /dev/null
+++ b/Robust.Client.NameGenerator/RoslynTypeSystem.cs
@@ -0,0 +1,287 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using XamlX.TypeSystem;
+
+namespace Robust.Client.NameGenerator
+{
+ ///
+ /// Taken from https://github.com/AvaloniaUI/Avalonia.NameGenerator/blob/ecc9677a23de5cbc90af07ccac14e31c0da41d6a/src/Avalonia.NameGenerator/Infrastructure/RoslynTypeSystem.cs
+ ///
+ public class RoslynTypeSystem : IXamlTypeSystem
+ {
+ private readonly List _assemblies = new List();
+
+ public RoslynTypeSystem(CSharpCompilation compilation)
+ {
+ _assemblies.Add(new RoslynAssembly(compilation.Assembly));
+
+ var assemblySymbols = compilation
+ .References
+ .Select(compilation.GetAssemblyOrModuleSymbol)
+ .OfType()
+ .Select(assembly => new RoslynAssembly(assembly))
+ .ToList();
+
+ _assemblies.AddRange(assemblySymbols);
+ }
+
+ public IReadOnlyList Assemblies => _assemblies;
+
+ public IXamlAssembly FindAssembly(string substring) => _assemblies[0];
+
+ public IXamlType FindType(string name)
+ {
+ foreach (var assembly in _assemblies)
+ {
+ var type = assembly.FindType(name);
+ if (type != null)
+ return type;
+ }
+
+ return null;
+ }
+
+ public IXamlType FindType(string name, string assembly)
+ {
+ foreach (var assemblyInstance in _assemblies)
+ {
+ var type = assemblyInstance.FindType(name);
+ if (type != null)
+ return type;
+ }
+
+ return null;
+ }
+ }
+
+ public class RoslynAssembly : IXamlAssembly
+ {
+ private readonly IAssemblySymbol _symbol;
+
+ public RoslynAssembly(IAssemblySymbol symbol) => _symbol = symbol;
+
+ public bool Equals(IXamlAssembly other) =>
+ other is RoslynAssembly roslynAssembly &&
+ SymbolEqualityComparer.Default.Equals(_symbol, roslynAssembly._symbol);
+
+ public string Name => _symbol.Name;
+
+ public IReadOnlyList CustomAttributes =>
+ _symbol.GetAttributes()
+ .Select(data => new RoslynAttribute(data, this))
+ .ToList();
+
+ public IXamlType FindType(string fullName)
+ {
+ var type = _symbol.GetTypeByMetadataName(fullName);
+ return type is null ? null : new RoslynType(type, this);
+ }
+ }
+
+ public class RoslynAttribute : IXamlCustomAttribute
+ {
+ private readonly AttributeData _data;
+ private readonly RoslynAssembly _assembly;
+
+ public RoslynAttribute(AttributeData data, RoslynAssembly assembly)
+ {
+ _data = data;
+ _assembly = assembly;
+ }
+
+ public bool Equals(IXamlCustomAttribute other) =>
+ other is RoslynAttribute attribute &&
+ _data == attribute._data;
+
+ public IXamlType Type => new RoslynType(_data.AttributeClass, _assembly);
+
+ public List