123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using UnityEngine;
- namespace UnityEditor.Purchasing
- {
- static class UnityIAPInstaller
- {
- static readonly string k_ServiceName = "IAP";
- static readonly string k_PackageName = "Unity IAP";
- static readonly string k_PackageFile = "Plugins/UnityPurchasing/UnityIAP.unitypackage";
- static readonly string k_InstallerFile = "Plugins/UnityPurchasing/Editor/UnityIAPInstaller.cs";
- static readonly string k_ObsoleteFilesCSVFile = "Plugins/UnityPurchasing/Editor/ObsoleteFilesOrDir.csv";
- static readonly string k_ObsoleteGUIDsCSVFile = "Plugins/UnityPurchasing/Editor/ObsoleteGUIDs.csv";
- static readonly string k_IAPHelpURL = "https://docs.unity3d.com/Manual/UnityIAPSettingUp.html";
- static readonly string k_ProjectHelpURL = "https://docs.unity3d.com/Manual/SettingUpProjectServices.html";
- static readonly string k_PrefsKey_ImportingAssetPackage = "UnityIAPInstaller_ImportingAssetPackage"; // Prevent multiple simultaneous installs
- static readonly string k_PrefsKey_LastAssetPackageImport = "UnityIAPInstaller_LastAssetPackageImportDateTimeBinary";
- static readonly double k_MaxLastImportReasonableTicks = 30 * 10000000; // Installs started n seconds from 'now' are not considered 'simultaneous'
- static readonly string[] k_ObsoleteFilesOrDirectories = GetFromCSV(GetAbsoluteFilePath(k_ObsoleteFilesCSVFile));
- static readonly string[] k_ObsoleteGUIDs = GetFromCSV(GetAbsoluteFilePath(k_ObsoleteGUIDsCSVFile));
- static readonly bool k_RunningInBatchMode = Environment.CommandLine.ToLower().Contains(" -batchmode");
- static readonly Type k_Purchasing = (
- from assembly in AppDomain.CurrentDomain.GetAssemblies()
- from type in assembly.GetTypes()
- where type.Name == "UnityPurchasing" && type.GetMethods().Any(m => m.Name == "Initialize")
- select type).FirstOrDefault();
- #if UNITY_5_3 || UNITY_5_3_OR_NEWER
- static readonly bool k_IsIAPSupported = true;
- #else
- static readonly bool k_IsIAPSupported = false;
- #endif
- #if UNITY_5_5_OR_NEWER && false // Service window prevents this from working properly. Disabling for now.
- static readonly bool k_IsEditorSettingsSupported = true;
- #else
- static readonly bool k_IsEditorSettingsSupported = false;
- #endif
- #if !DISABLE_UNITY_IAP_INSTALLER
- [Callbacks.DidReloadScripts]
- #endif
- /// <summary>
- /// * Install may be called multiple times during the AssetDatabase.ImportPackage
- /// process. Detect this and avoid restarting installation.
- /// * Install may fail unexpectedly in the middle due to crash. Detect
- /// this heuristically with a timestamp, deleting mutex for multiple
- /// install detector.
- /// </summary>
- static void Install ()
- {
- // Detect and fix interrupted installation
- FixInterruptedInstall();
- // Detect multiple calls to this method and ignore
- if (PlayerPrefs.HasKey(k_PrefsKey_ImportingAssetPackage))
- {
- // Resubscribe to "I'm done installing" callback as it's lost
- // on each Reload.
- EditorApplication.delayCall += OnComplete;
- return;
- }
-
- if (!DisplayInstallerDialog())
- {
- DisplayCanceledInstallerDialog();
- OnComplete();
- return;
- }
- string packageAsset = GetAssetPath(k_PackageFile);
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("Preparing to install the {0} asset package...", k_PackageName);
- }
- if (CanInstall(packageAsset))
- {
- // Record fact installation has started
- PlayerPrefs.SetInt(k_PrefsKey_ImportingAssetPackage, 1);
- // Record time installation started
- PlayerPrefs.SetString(k_PrefsKey_LastAssetPackageImport, DateTime.UtcNow.ToBinary().ToString());
- // Start async ImportPackage operation, causing one or more
- // Domain Reloads as a side-effect
- AssetDatabase.ImportPackage(packageAsset, false);
- // All in-memory values hereafter may be cleared due to Domain
- // Reloads by async ImportPackage operation
- EditorApplication.delayCall += OnComplete;
- }
- else
- {
- OnComplete();
- }
- }
- /// <summary>
- /// Determines if can install the specified packageAsset.
- /// </summary>
- /// <returns><c>true</c> if can install the specified packageAsset; otherwise, <c>false</c>.</returns>
- /// <param name="packageAsset">Package asset.</param>
- static bool CanInstall(string packageAsset)
- {
- return k_IsIAPSupported && AssetExists(packageAsset) &&
- (k_Purchasing != null || EnableServices()) &&
- DeleteObsoleteAssets(k_ObsoleteFilesOrDirectories, k_ObsoleteGUIDs);
- }
- /// <summary>
- /// Detects and fixes the interrupted install.
- /// </summary>
- static void FixInterruptedInstall()
- {
- if (PlayerPrefs.HasKey(k_PrefsKey_LastAssetPackageImport))
- {
- string lastImportDateTimeBinary = PlayerPrefs.GetString(k_PrefsKey_LastAssetPackageImport);
- long lastImportLong = 0;
- try {
- lastImportLong = Convert.ToInt64(lastImportDateTimeBinary);
- } catch (SystemException e) {
- // Ignoring exception converting long
- // By default '0' value will trigger install-cleanup
- }
- DateTime lastImport = DateTime.FromBinary(lastImportLong);
- double dt = Math.Abs(DateTime.UtcNow.Ticks - lastImport.Ticks);
- if (dt > k_MaxLastImportReasonableTicks)
- {
- Debug.Log("Detected interrupted installation, " + dt / 10000000 + " seconds ago. Reenabling install.");
- // Fix it!
- PlayerPrefs.DeleteKey(k_PrefsKey_ImportingAssetPackage);
- PlayerPrefs.DeleteKey(k_PrefsKey_LastAssetPackageImport);
- }
- else
- {
- // dt is not too large, installation okay to proceed
- }
- }
- }
- static void OnComplete ()
- {
- if (PlayerPrefs.HasKey(k_PrefsKey_ImportingAssetPackage))
- {
- // Cleanup mutexes for next install
- PlayerPrefs.DeleteKey(k_PrefsKey_ImportingAssetPackage);
- PlayerPrefs.DeleteKey(k_PrefsKey_LastAssetPackageImport);
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("Successfully imported the {0} asset package.", k_PackageName);
- }
- }
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("Deleting {0} package installer files...", k_PackageName);
- }
- AssetDatabase.DeleteAsset(GetAssetPath(k_PackageFile));
- AssetDatabase.DeleteAsset(GetAssetPath(k_InstallerFile));
- AssetDatabase.DeleteAsset(GetAssetPath(k_ObsoleteFilesCSVFile));
- AssetDatabase.DeleteAsset(GetAssetPath(k_ObsoleteGUIDsCSVFile));
- AssetDatabase.Refresh();
- SaveAssets();
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("{0} asset package install complete.", k_PackageName);
- EditorApplication.Exit(0);
- }
- }
- static bool EnableServices ()
- {
- if (!k_IsEditorSettingsSupported)
- {
- if (!DisplayEnableServiceManuallyDialog())
- {
- Application.OpenURL(k_IAPHelpURL);
- }
- return false;
- }
- if (string.IsNullOrEmpty(PlayerSettings.cloudProjectId))
- {
- if (!DisplayProjectConfigDialog())
- {
- Application.OpenURL(k_ProjectHelpURL);
- }
- return false;
- }
- if (DisplayEnableServiceDialog())
- {
- #if UNITY_5_5_OR_NEWER
- Analytics.AnalyticsSettings.enabled = true;
- PurchasingSettings.enabled = true;
- #endif
- SaveAssets();
- return true;
- }
- if (!DisplayCanceledEnableServiceDialog())
- {
- Application.OpenURL(k_IAPHelpURL);
- }
- return false;
- }
- static bool DisplayInstallerDialog ()
- {
- if (k_RunningInBatchMode) return true;
- return EditorUtility.DisplayDialog(
- k_PackageName + " Installer",
- "The " + k_PackageName + " installer will determine if your project is configured properly " +
- "before importing the " + k_PackageName + " asset package.\n\n" +
- "Would you like to run the " + k_PackageName + " installer now?",
- "Install Now",
- "Cancel"
- );
- }
- static bool DisplayCanceledInstallerDialog ()
- {
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("User declined to run the {0} installer. Canceling installer process now...", k_PackageName);
- return true;
- }
- return EditorUtility.DisplayDialog(
- k_PackageName + " Installer",
- "The " + k_PackageName + " installer has been canceled. " +
- "Please import the " + k_PackageName + " asset package again to continue the install.",
- "OK"
- );
- }
- static bool DisplayProjectConfigDialog ()
- {
- if (k_RunningInBatchMode)
- {
- Debug.Log("Unity Project ID is not currently set. Canceling installer process now...");
- return true;
- }
- return EditorUtility.DisplayDialog(
- k_PackageName + " Installer",
- "A Unity Project ID is not currently configured for this project.\n\n" +
- "Before the " + k_ServiceName + " service can be enabled, a Unity Project ID must first be " +
- "linked to this project. Once linked, please import the " + k_PackageName + " asset package again" +
- "to continue the install.\n\n" +
- "Select 'Help...' to see further instructions.",
- "OK",
- "Help..."
- );
- }
- static bool DisplayEnableServiceDialog ()
- {
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("The {0} service is currently disabled. Enabling the {0} Service now...", k_ServiceName);
- return true;
- }
- return EditorUtility.DisplayDialog(
- k_PackageName + " Installer",
- "The " + k_ServiceName + " service is currently disabled.\n\n" +
- "To avoid encountering errors when importing the " + k_PackageName + " asset package, " +
- "the " + k_ServiceName + " service must be enabled first before importing the latest " +
- k_PackageName + " asset package.\n\n" +
- "Would you like to enable the " + k_ServiceName + " service now?",
- "Enable Now",
- "Cancel"
- );
- }
- static bool DisplayEnableServiceManuallyDialog ()
- {
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("The {0} service is currently disabled. Canceling installer process now...", k_ServiceName);
- return true;
- }
- return EditorUtility.DisplayDialog(
- k_PackageName + " Installer",
- "The " + k_ServiceName + " service is currently disabled.\n\n" +
- "Canceling the install process now to avoid encountering errors when importing the " +
- k_PackageName + " asset package. The " + k_ServiceName + " service must be enabled first " +
- "before importing the latest " + k_PackageName + " asset package.\n\n" +
- "Please enable the " + k_ServiceName + " service through the Services window. " +
- "Then import the " + k_PackageName + " asset package again to continue the install.\n\n" +
- "Select 'Help...' to see further instructions.",
- "OK",
- "Help..."
- );
- }
- static bool DisplayCanceledEnableServiceDialog ()
- {
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("User declined to enable the {0} service. Canceling installer process now...", k_ServiceName);
- return true;
- }
- return EditorUtility.DisplayDialog(
- k_PackageName + " Installer",
- "The " + k_PackageName + " installer has been canceled.\n\n" +
- "Please enable the " + k_ServiceName + " service through the Services window. " +
- "Then import the " + k_PackageName + " asset package again to continue the install.\n\n" +
- "Select 'Help...' to see further instructions.",
- "OK",
- "Help..."
- );
- }
- static bool DisplayDeleteAssetsDialog ()
- {
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("Found obsolete {0} assets. Deleting obsolete assets now...", k_PackageName);
- return true;
- }
- return EditorUtility.DisplayDialog(
- k_PackageName + " Installer",
- "Found obsolete assets from an older version of the " + k_PackageName + " asset package.\n\n" +
- "Would you like to remove these obsolete " + k_PackageName + " assets now?",
- "Delete Now",
- "Cancel"
- );
- }
- static bool DisplayCanceledDeleteAssetsDialog ()
- {
- if (k_RunningInBatchMode)
- {
- Debug.LogFormat("User declined to remove obsolete {0} assets. Canceling installer process now...", k_PackageName);
- return true;
- }
- return EditorUtility.DisplayDialog(
- k_PackageName + " Installer",
- "The " + k_PackageName + " installer has been canceled.\n\n" +
- "Please delete any previously imported " + k_PackageName + " assets from your project. " +
- "Then import the " + k_PackageName + " asset package again to continue the install.",
- "OK"
- );
- }
- static string GetAssetPath (string path)
- {
- return string.Concat("Assets/", path);
- }
- static string GetAbsoluteFilePath (string path)
- {
- return Path.Combine(Application.dataPath, path.Replace('/', Path.DirectorySeparatorChar));
- }
- static string[] GetFromCSV (string filePath)
- {
- var lines = new List<string>();
- int row = 0;
- if (File.Exists(filePath))
- {
- try
- {
- using (var reader = new StreamReader(filePath))
- {
- while (!reader.EndOfStream)
- {
- string[] line = reader.ReadLine().Split(',');
- lines.Add(line[0].Trim().Trim('"'));
- row++;
- }
- }
- }
- catch (Exception e)
- {
- Debug.LogException(e);
- }
- }
- return lines.ToArray();
- }
- static bool AssetExists (string path)
- {
- if (path.Length > 7)
- path = path.Substring(7);
- else return false;
- if (Application.platform == RuntimePlatform.WindowsEditor)
- {
- path = path.Replace("/", @"\");
- }
- path = Path.Combine(Application.dataPath, path);
- return File.Exists(path) || Directory.Exists(path);
- }
- static bool AssetsExist (string[] legacyAssetPaths, string[] legacyAssetGUIDs, out string[] existingAssetPaths)
- {
- var paths = new List<string>();
- for (int i = 0; i < legacyAssetPaths.Length; i++)
- {
- if (AssetExists(legacyAssetPaths[i]))
- {
- paths.Add(legacyAssetPaths[i]);
- }
- }
- for (int i = 0; i < legacyAssetGUIDs.Length; i++)
- {
- string path = AssetDatabase.GUIDToAssetPath(legacyAssetGUIDs[i]);
- if (AssetExists(path) && !paths.Contains(path))
- {
- paths.Add(path);
- }
- }
- existingAssetPaths = paths.ToArray();
- return paths.Count > 0;
- }
- static bool DeleteObsoleteAssets (string[] paths, string[] guids)
- {
- var assets = new string[0];
- if (!AssetsExist(paths, guids, out assets)) return true;
- if (DisplayDeleteAssetsDialog())
- {
- for (int i = 0; i < assets.Length; i++)
- {
- FileUtil.DeleteFileOrDirectory(assets[i]);
- }
- AssetDatabase.Refresh();
- SaveAssets();
- return true;
- }
- DisplayCanceledDeleteAssetsDialog();
- return false;
- }
- /// <summary>
- /// Solves issues seen in projects when deleting other files in projects
- /// after installation but before project is closed and reopened.
- /// Script continue to live as compiled entities but are not stored in
- /// the AssetDatabase.
- /// </summary>
- static void SaveAssets ()
- {
- #if UNITY_5_5_OR_NEWER
- AssetDatabase.SaveAssets(); // Not reliable prior to major refactoring in Unity 5.5.
- #else
- EditorApplication.SaveAssets(); // Reliable, but removed in Unity 5.5.
- #endif
- }
- }
- }
|