Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
namespace Project.Scripts.Editor
{
/// <summary>
/// Adds a 3d icon to the project and appxmanifest.
/// See https://docs.microsoft.com/en-us/windows/mixed-reality/implementing-3d-app-launchers
/// </summary>
public class MixedReality3dAppLauncher : IPostprocessBuildWithReport
{
private const string Uap5Namespace = "http://schemas.microsoft.com/appx/manifest/uap/windows10/5";
private const string AppIconSourcePath = "Assets/app-icon.glb";
private const string AppIconAssetPath = "Assets\\app-icon.glb";
public int callbackOrder => 0;
public void OnPostprocessBuild(BuildReport report)
{
if (report.summary.platform != BuildTarget.WSAPlayer)
return;
PatchProjectAndManifest(report.summary.outputPath, Application.productName);
}
[MenuItem("UWP Tools/Patch Manifest and project")]
public static void ManuallyPatch()
{
var buildLocation = EditorUserBuildSettings.GetBuildLocation(BuildTarget.WSAPlayer);
if (!Directory.Exists(buildLocation))
{
var defaultOptions = new BuildPlayerOptions();
var options = BuildPlayerWindow.DefaultBuildMethods.GetBuildPlayerOptions(defaultOptions);
buildLocation = options.locationPathName;
}
PatchProjectAndManifest(buildLocation, Application.productName);
}
static void PatchProjectAndManifest(string path, string appName)
{
var modelPath = Path.Combine(Directory.GetCurrentDirectory(), AppIconSourcePath);
if (!File.Exists(modelPath))
{
Debug.LogWarning(" MixedReality3dAppLauncher: Cannot add 3d model - please place one in Assets/app-icon.glb");
return;
}
var projectPath = Path.Combine(path, appName);
CopyModelAndPatchXProj(appName, projectPath, modelPath);
PatchAppxWithIcon(projectPath);
Debug.Log(" MixedReality3dAppLauncher: Patched with 3d app launcher");
}
private static void CopyModelAndPatchXProj(string appName, string projectPath, string modelPath)
{
var destinationModelPath = Path.Combine(projectPath, AppIconAssetPath);
if (File.GetLastWriteTimeUtc(modelPath) > File.GetLastWriteTimeUtc(destinationModelPath))
File.Copy(modelPath, destinationModelPath, true);
var projectXmlPath = Path.Combine(projectPath, $"{appName}.vcxproj");
var doc = new XmlDocument();
doc.LoadXml(File.ReadAllText(projectXmlPath));
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ns", doc.DocumentElement.NamespaceURI);
var itemGroup = doc.SelectSingleNode("//ns:AppxManifest", nsmgr).ParentNode;
var hasExisting = itemGroup.ChildNodes.Cast<XmlNode>().Any(n => n.Attributes[0].Value == AppIconAssetPath);
if (!hasExisting)
{
Debug.Log(" MixedReality3dAppLauncher: Adding icon include");
var includeNode = doc.CreateNode(XmlNodeType.Element, "None", doc.DocumentElement.NamespaceURI);
var includeAttribute = doc.CreateAttribute("Include");
includeAttribute.Value = AppIconAssetPath;
includeNode.Attributes.Append(includeAttribute);
itemGroup.AppendChild(includeNode);
var deploymentContentNode =
doc.CreateNode(XmlNodeType.Element, "DeploymentContent", doc.DocumentElement.NamespaceURI);
deploymentContentNode.InnerText = "true";
includeNode.AppendChild(deploymentContentNode);
}
using (var writer = new XmlTextWriter(projectXmlPath, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
doc.WriteTo(writer);
}
}
private static void PatchAppxWithIcon(string projectPath)
{
var manifestPath = Path.Combine(projectPath, "package.appxmanifest");
var doc = new XmlDocument();
doc.LoadXml(File.ReadAllText(manifestPath));
var nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("ns", doc.DocumentElement.NamespaceURI);
var packageNode = doc.SelectSingleNode("/ns:Package", nsmgr);
var uapNamespace = packageNode.Attributes.Cast<XmlAttribute>().First(a => a.Name == "xmlns:uap").Value;
nsmgr.AddNamespace("uap", uapNamespace);
nsmgr.AddNamespace("uap5", Uap5Namespace);
if (!packageNode.Attributes.Cast<XmlAttribute>().Any(a => a.Name == "xmlns:uap5"))
{
var uap5Attribute = doc.CreateAttribute("xmlns:uap5");
uap5Attribute.Value = Uap5Namespace;
packageNode.Attributes.Append(uap5Attribute);
Debug.Log(" MixedReality3dAppLauncher: Added uap5 namspace to root node");
}
var ignoreNamespaces = packageNode.Attributes.Cast<XmlAttribute>().First(a => a.Name == "IgnorableNamespaces");
if (!ignoreNamespaces.Value.Contains("uap5"))
{
ignoreNamespaces.Value = ignoreNamespaces.Value + " uap5";
Debug.Log(" MixedReality3dAppLauncher: Added uap5 to IgnorableNamespaces");
}
var defaultTileNode = doc.SelectSingleNode("//uap:DefaultTile", nsmgr);
if (!defaultTileNode.HasChildNodes)
{
var modelNode = doc.CreateNode(XmlNodeType.Element, "uap5:MixedRealityModel", Uap5Namespace);
var pathAttribute = doc.CreateAttribute("Path");
pathAttribute.Value = AppIconAssetPath;
modelNode.Attributes.Append(pathAttribute);
defaultTileNode.AppendChild(modelNode);
Debug.Log(" MixedReality3dAppLauncher: Added mixed reality model node");
}
else
{
var modelNode = doc.SelectSingleNode("//uap5:MixedRealityModel", nsmgr);
var pathAttribute = modelNode.Attributes.Cast<XmlAttribute>().First(a => a.Name == "Path");
if (pathAttribute.Value != AppIconAssetPath)
{
pathAttribute.Value = AppIconAssetPath;
Debug.Log(" MixedReality3dAppLauncher: Updated model path");
}
}
using (var writer = new XmlTextWriter(manifestPath, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
doc.WriteTo(writer);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.