// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; namespace Microsoft.MixedReality.Toolkit.Utilities.Editor { [InitializeOnLoad] static class OnLoadUtilities { private const string ShaderSentinelGuid = "05852dd420bb9ec4cb7318bfa529d37c"; private const string ShaderSentinelFile = "MRTK.Shaders.sentinel"; private const string ShaderImportDestination = "MRTK/Shaders"; private const string IgnoreFileName = "IgnoreUpdateCheck.sentinel"; static OnLoadUtilities() { EnsureShaders(false); } /// /// Checks for updated shaders and bypasses the ignore update check. /// [MenuItem("Mixed Reality/Toolkit/Utilities/Check for Shader Updates")] private static void CheckForShaderUpdates() { EnsureShaders(true); } /// /// Ensures that MRTK shader files are present in a writable location. To support the /// Universal Render Pipeline, shader modifications must be persisted. /// /// Causes the shader update code to disregard the ignore file. private static void EnsureShaders(bool bypassIgnore) { DirectoryInfo packageShaderFolder = FindShaderFolderInPackage(); if (bypassIgnore) { // The customer is manually checking for updates, delete the ignore file string sentinelPath = AssetDatabase.GUIDToAssetPath(ShaderSentinelGuid); if (!string.IsNullOrWhiteSpace(sentinelPath)) { FileInfo ignoreFile = new FileInfo(Path.Combine(new FileInfo(sentinelPath).Directory.FullName, IgnoreFileName)); if (ignoreFile.Exists) { ignoreFile.Delete(); } ignoreFile.Refresh(); } } if (!AssetsContainsShaders(packageShaderFolder)) { ImportShaderFiles(packageShaderFolder); } } /// /// Checks to see if the Assets or Packages (if embedded) folder trees contains the MRTK shaders. /// /// True if the shader sentinel file is found, otherwise false. private static bool AssetsContainsShaders(DirectoryInfo packageShaderFolder) { string sentinelPath = AssetDatabase.GUIDToAssetPath(ShaderSentinelGuid); // If we do not find the sentinel, we need to import the shaders. if (string.IsNullOrWhiteSpace(sentinelPath)) { return false; } // Getting here indicates that the project's Assets folder contains the shader sentinel. // Check for the "ignore this check" file, if present we do NOT import FileInfo ignoreFile = new FileInfo(Path.Combine(new FileInfo(sentinelPath).Directory.FullName, IgnoreFileName)); if (ignoreFile.Exists) { return true; } // If the package shader folder does not exist, there is nothing for us to do. if ((packageShaderFolder == null) || !packageShaderFolder.Exists) { return true; } // Get the versions of the sentinel files, int packageVer = ReadSentinelVersion(Path.Combine(packageShaderFolder.FullName, ShaderSentinelFile)); int assetVer = ReadSentinelVersion(sentinelPath); // No need to copy if the versions are the same. if (packageVer == assetVer) { return true; } string message = (packageVer < assetVer) ? "The MRTK shaders older than those in your project, do you wish to overwrite the existing shaders?" : "Updated MRTK shaders are available, do you wish to overwrite the existing shaders?"; int dialogResponse = EditorUtility.DisplayDialogComplex( "Mixed Reality Toolkit Standard Assets", message + "\n\nNOTE: Overwriting will lose any customizations and may require reconfiguring the render pipeline.", "Yes", // returns 0 "Ignore", // returns 1 - placed in the cancel slot to force the button order as Yes, No, Ignore "No"); // returns 2 if (dialogResponse == 1) { // Write an "ignore this check" file to prevent future prompting if (!ignoreFile.Directory.Exists) { ignoreFile.Directory.Create(); } ignoreFile.Create(); } ignoreFile.Refresh(); // Return the inverse of the dialog result. Result of true means we want to overwrite, this method returns false // to cause an overwrite. return (dialogResponse != 0); } /// /// Finds the shader folder within an installed or embedded package. /// /// /// DirectoryInfo object representing the shader folder in the package cache. /// If not found, returns null. /// private static DirectoryInfo FindShaderFolderInPackage() { List searchPaths = new List { Path.GetFullPath(Path.Combine("Library", "PackageCache")), Path.GetFullPath("Packages") }; foreach (string path in searchPaths) { DirectoryInfo di = new DirectoryInfo(path); if (!di.Exists) { continue; } FileInfo[] files = di.GetFiles(ShaderSentinelFile, SearchOption.AllDirectories); if (files.Length > 0) { return new DirectoryInfo(files[0].DirectoryName); } } return null; } /// /// Copies the shader files from the package cache to the Assets folder tree. /// private static void ImportShaderFiles(DirectoryInfo packageShaderFolder) { if (packageShaderFolder == null) { Debug.LogError("Unable to locate the shader source folder in the package"); return; } DirectoryInfo destination = new DirectoryInfo(Path.Combine(Application.dataPath, ShaderImportDestination)); if (!destination.Exists) { destination.Create(); } FileInfo[] sourceFiles = packageShaderFolder.GetFiles(); foreach (FileInfo fi in sourceFiles) { fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true); } } /// /// Reads the version number out of the shader /// /// The path to the sentinel file. /// The version number found in the file, or -1. private static int ReadSentinelVersion(string sentinelPath) { using (FileStream fs = new FileStream(sentinelPath, FileMode.Open, FileAccess.Read)) { using (StreamReader reader = new StreamReader(fs)) { const string token = "ver:"; while (!reader.EndOfStream) { string line = reader.ReadLine(); if (line.StartsWith(token)) { line = line.Substring(token.Length).Trim(); if (!int.TryParse(line, out int ver)) { break; } return ver; } } } } return -1; } } }