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