using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; #if !UNITY_2020_1_OR_NEWER using System.Reflection; #endif using System.Xml.Linq; using UnityEditor; using UnityEditor.PackageManager; using UnityEngine; namespace AppLovinMax.Scripts.IntegrationManager.Editor { [Serializable] public class PackageInfo { // ReSharper disable InconsistentNaming - For JSON Deserialization public string Name; public string Version; } public interface IPackageManagerClient { IEnumerator AddNetwork(Network network, bool showImport); void RemoveNetwork(Network network); } public static class AppLovinPackageManager { private const string AppLovinMediationAmazonAdapterDependenciesPath = "Amazon/Scripts/Mediations/AppLovinMediation/Editor/Dependencies.xml"; #if UNITY_2019_2_OR_NEWER private static readonly IPackageManagerClient _upmPackageManager = new AppLovinUpmPackageManager(); #endif private static readonly IPackageManagerClient _assetsPackageManager = new AppLovinAssetsPackageManager(); private static IPackageManagerClient PackageManagerClient { get { #if UNITY_2019_2_OR_NEWER return AppLovinIntegrationManager.IsPluginInPackageManager ? _upmPackageManager : _assetsPackageManager; #else return _assetsPackageManager; #endif } } internal static PluginData PluginData { get; set; } /// /// Checks whether or not an adapter with the given version or newer exists. /// /// The name of the network (the root adapter folder name in "MaxSdk/Mediation/" folder. /// The min iOS adapter version to check for. Can be null if we want to check for any version. /// The min android adapter version to check for. Can be null if we want to check for any version. /// true if an adapter with the min version is installed. internal static bool IsAdapterInstalled(string adapterName, string iosVersion = null, string androidVersion = null) { var dependencyFilePathList = GetAssetPathListForExportPath("MaxSdk/Mediation/" + adapterName + "/Editor/Dependencies.xml"); if (dependencyFilePathList.Count <= 0) return false; var currentVersion = GetCurrentVersions(dependencyFilePathList); if (iosVersion != null) { var iosVersionComparison = MaxSdkUtils.CompareVersions(currentVersion.Ios, iosVersion); if (iosVersionComparison == MaxSdkUtils.VersionComparisonResult.Lesser) { return false; } } if (androidVersion != null) { var androidVersionComparison = MaxSdkUtils.CompareVersions(currentVersion.Android, androidVersion); if (androidVersionComparison == MaxSdkUtils.VersionComparisonResult.Lesser) { return false; } } return true; } /// /// Checks whether an adapter is installed using the plugin data. /// /// The plugin data to check for the adapter /// The name of the network. /// Whether an adapter is installed in the plugin data internal static bool IsAdapterInstalled(PluginData pluginData, string adapterName) { var network = pluginData.MediatedNetworks.Where(mediatedNetwork => mediatedNetwork.Name.Equals(adapterName)).ToList().FirstOrDefault(); var networkVersion = network != null ? network.CurrentVersions : null; var currentVersion = networkVersion != null ? networkVersion.Unity : ""; return MaxSdkUtils.IsValidString(currentVersion); } /// /// Gets the mediation networks that are currently installed in the project. If using UPM, checks /// for networks in Packages folder and Mediation folder in case a custom adapter was added to the project. /// /// A list of the installed mediation network names. internal static List GetInstalledMediationNetworks() { var installedNetworks = new List(); var installedNetworksInAssets = AppLovinAssetsPackageManager.GetInstalledMediationNetworks(); installedNetworks.AddRange(installedNetworksInAssets); #if UNITY_2019_2_OR_NEWER var installedNetworksInPackages = AppLovinUpmPackageManager.GetInstalledMediationNetworks(); installedNetworks.AddRange(installedNetworksInPackages); #endif if (IsAmazonAppLovinAdapterInstalled()) { installedNetworks.Add("AmazonAdMarketplace"); } return installedNetworks; } /// /// Adds a network to the project. /// /// The network to add. /// Whether to show the import window (only for non UPM) internal static IEnumerator AddNetwork(Network network, bool showImport) { yield return PackageManagerClient.AddNetwork(network, showImport); AppLovinEditorCoroutine.StartCoroutine(RefreshAssetsAtEndOfFrame(network)); } /// /// Removes a network from the project. /// /// The network to remove. internal static void RemoveNetwork(Network network) { PackageManagerClient.RemoveNetwork(network); AppLovinEditorCoroutine.StartCoroutine(RefreshAssetsAtEndOfFrame(network)); } #region Utility /// /// Gets the list of all asset paths for a given MAX plugin export path. /// /// The actual exported path of the asset. /// The exported path of the MAX plugin asset or an empty list if the asset is not found. private static List GetAssetPathListForExportPath(string exportPath) { var assetLabelToFind = "l:al_max_export_path-" + exportPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); var assetGuids = AssetDatabase.FindAssets(assetLabelToFind); var assetPaths = new List(); foreach (var assetGuid in assetGuids) { assetPaths.Add(AssetDatabase.GUIDToAssetPath(assetGuid)); } return assetPaths.Count <= 0 ? new List() : assetPaths; } /// /// Updates the CurrentVersion fields for a given network data object. /// /// Network for which to update the current versions. internal static void UpdateCurrentVersions(Network network) { var assetPaths = GetAssetPathListForExportPath(network.DependenciesFilePath); #if UNITY_2019_2_OR_NEWER if (HasDuplicateAdapters(assetPaths)) { ShowDeleteDuplicateAdapterPrompt(network); } #endif var currentVersions = GetCurrentVersions(assetPaths); network.CurrentVersions = currentVersions; // If AppLovin mediation plugin, get the version from MaxSdk and the latest and current version comparison. if (network.Name.Equals("APPLOVIN_NETWORK")) { network.CurrentVersions.Unity = MaxSdk.Version; var unityVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Unity, network.LatestVersions.Unity); var androidVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Android, network.LatestVersions.Android); var iosVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Ios, network.LatestVersions.Ios); // Overall version is same if all the current and latest (from db) versions are same. if (unityVersionComparison == MaxSdkUtils.VersionComparisonResult.Equal && androidVersionComparison == MaxSdkUtils.VersionComparisonResult.Equal && iosVersionComparison == MaxSdkUtils.VersionComparisonResult.Equal) { network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Equal; } // One of the installed versions is newer than the latest versions which means that the publisher is on a beta version. else if (unityVersionComparison == MaxSdkUtils.VersionComparisonResult.Greater || androidVersionComparison == MaxSdkUtils.VersionComparisonResult.Greater || iosVersionComparison == MaxSdkUtils.VersionComparisonResult.Greater) { network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Greater; } // We have a new version available if all Android, iOS and Unity has a newer version available in db. else { network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Lesser; } } // For all other mediation adapters, get the version comparison using their Unity versions. else { // If adapter is indeed installed, compare the current (installed) and the latest (from db) versions, so that we can determine if the publisher is on an older, current or a newer version of the adapter. // If the publisher is on a newer version of the adapter than the db version, that means they are on a beta version. if (MaxSdkUtils.IsValidString(currentVersions.Unity)) { network.CurrentToLatestVersionComparisonResult = AppLovinIntegrationManagerUtils.CompareUnityMediationVersions(currentVersions.Unity, network.LatestVersions.Unity); } if (MaxSdkUtils.IsValidString(network.CurrentVersions.Unity) && AppLovinAutoUpdater.MinAdapterVersions.ContainsKey(network.Name)) { var comparisonResult = AppLovinIntegrationManagerUtils.CompareUnityMediationVersions(network.CurrentVersions.Unity, AppLovinAutoUpdater.MinAdapterVersions[network.Name]); // Requires update if current version is lower than the min required version. network.RequiresUpdate = comparisonResult < 0; } else { // Reset value so that the Integration manager can hide the alert icon once adapter is updated. network.RequiresUpdate = false; } } } #if UNITY_2019_2_OR_NEWER /// /// Checks whether a network has duplicate adapters installed in both the Assets folder and via UPM. /// /// The list of paths to the dependencies.xml files /// True if there are adapters in both the Assets folder and installed via UPM private static bool HasDuplicateAdapters(List dependencyPaths) { var inPackagesFolder = dependencyPaths.Any(path => path.Contains("Packages")); var inAssetsFolder = dependencyPaths.Any(path => path.Contains("Assets")); return inPackagesFolder && inAssetsFolder; } /// /// Displays a prompt informing the user that duplicate adapters were detected /// and allows them to choose which version to keep. /// /// The network that has duplicate adapters installed. private static void ShowDeleteDuplicateAdapterPrompt(Network network) { var keepAssetsAdapter = EditorUtility.DisplayDialog("Duplicate Adapters Detected", "The " + network.DisplayName + " adapter is installed in both the Assets folder and via UPM. Please choose which version to keep.", "Keep Assets Folder Version", "Keep UPM Version"); DeleteDuplicateAdapter(network, keepAssetsAdapter); } /// /// Removes a duplicate adapter by either deleting it from the Assets folder /// or uninstalling it from the Unity Package Manager (UPM). /// /// The network for which the duplicate adapter is being removed. /// If true, retains the adapter in the Assets folder and removes the UPM version; /// otherwise, deletes the adapter from the Assets folder. internal static void DeleteDuplicateAdapter(Network network, bool keepAssetsAdapter) { if (keepAssetsAdapter) { var appLovinManifest = AppLovinUpmManifest.Load(); AppLovinUpmPackageManager.RemovePackages(network, appLovinManifest); appLovinManifest.Save(); } else { foreach (var pluginFilePath in network.PluginFilePaths) { var filePath = Path.Combine(AppLovinIntegrationManager.MediationDirectory, pluginFilePath.Replace("MaxSdk/Mediation/", "")); FileUtil.DeleteFileOrDirectory(filePath); FileUtil.DeleteFileOrDirectory(filePath + ".meta"); } } AppLovinUpmPackageManager.ResolvePackageManager(); } #endif /// /// Gets the current versions for a given network's dependency file paths. UPM will have multiple paths /// for each network - one each for iOS and Android. /// /// A list of dependency file paths to extract current versions from. /// Current versions of a given network's dependency files. private static Versions GetCurrentVersions(List dependencyPaths) { var currentVersions = new Versions(); foreach (var dependencyPath in dependencyPaths) { GetCurrentVersion(currentVersions, dependencyPath); } if (currentVersions.Android != null && currentVersions.Ios != null) { currentVersions.Unity = "android_" + currentVersions.Android + "_ios_" + currentVersions.Ios; } else if (currentVersions.Android != null) { currentVersions.Unity = "android_" + currentVersions.Android; } else if (currentVersions.Ios != null) { currentVersions.Unity = "ios_" + currentVersions.Ios; } return currentVersions; } /// /// Extracts the current version of a network from its dependency.xml file. /// /// The Versions object we are using. /// The path to the dependency.xml file. private static void GetCurrentVersion(Versions currentVersions, string dependencyPath) { XDocument dependency; try { dependency = XDocument.Load(dependencyPath); } #pragma warning disable 0168 catch (IOException exception) #pragma warning restore 0168 { // Couldn't find the dependencies file. The plugin is not installed. return; } // // // // // // // // string androidVersion = null; string iosVersion = null; var dependenciesElement = dependency.Element("dependencies"); if (dependenciesElement != null) { var androidPackages = dependenciesElement.Element("androidPackages"); if (androidPackages != null) { var adapterPackage = androidPackages.Descendants().FirstOrDefault(element => element.Name.LocalName.Equals("androidPackage") && element.FirstAttribute.Name.LocalName.Equals("spec") && element.FirstAttribute.Value.StartsWith("com.applovin")); if (adapterPackage != null) { androidVersion = adapterPackage.FirstAttribute.Value.Split(':').Last(); // Hack alert: Some Android versions might have square brackets to force a specific version. Remove them if they are detected. if (androidVersion.StartsWith("[")) { androidVersion = androidVersion.Trim('[', ']'); } } } var iosPods = dependenciesElement.Element("iosPods"); if (iosPods != null) { var adapterPod = iosPods.Descendants().FirstOrDefault(element => element.Name.LocalName.Equals("iosPod") && element.FirstAttribute.Name.LocalName.Equals("name") && element.FirstAttribute.Value.StartsWith("AppLovin")); if (adapterPod != null) { iosVersion = adapterPod.Attributes().First(attribute => attribute.Name.LocalName.Equals("version")).Value; } } } if (androidVersion != null) { currentVersions.Android = androidVersion; } if (iosVersion != null) { currentVersions.Ios = iosVersion; } } /// /// Check for the Amazon AppLovin adapter in the project. /// /// Whether the AppLovin Adapter is installed through the Amazon SDK. private static bool IsAmazonAppLovinAdapterInstalled() { string[] dependenciesFiles = AssetDatabase.FindAssets("t:TextAsset Dependencies", new[] {"Assets"}) .Select(AssetDatabase.GUIDToAssetPath) .ToArray(); // Use regex to search for Amazon and then AppLovin in the file paths of the dependencies.xml files. return dependenciesFiles.Any(filePath => filePath.Contains(AppLovinMediationAmazonAdapterDependenciesPath)); } /// /// Refresh assets and update current versions after a slight delay to allow for Client.Resolve to finish. /// /// The network that was just installed/removed. private static IEnumerator RefreshAssetsAtEndOfFrame(Network network) { yield return new WaitForEndOfFrame(); UpdateCurrentVersions(network); AssetDatabase.Refresh(); } #endregion } #if UNITY_2019_2_OR_NEWER public class AppLovinUpmPackageManager : IPackageManagerClient { public const string PackageNamePrefixAppLovin = "com.applovin.mediation.ads"; private const string PackageNamePrefixNetwork = "com.applovin.mediation.adapters"; private const string PackageNamePrefixDsp = "com.applovin.mediation.dsp"; private const float TimeoutFetchPackageCollectionSeconds = 10f; #if !UNITY_2020_1_OR_NEWER private static Type packageManagerClientType; private static MethodInfo packageManagerResolveMethod; #endif public static List GetInstalledMediationNetworks() { // Return empty list if we failed to get the package list var packageCollection = GetPackageCollectionSync(TimeoutFetchPackageCollectionSeconds); if (packageCollection == null) { return new List(); } return packageCollection.Where(package => package.name.StartsWith(PackageNamePrefixNetwork) || package.name.StartsWith(PackageNamePrefixDsp)) .SelectMany(package => package.keywords) .Where(keyword => keyword.StartsWith("dir:")) .Select(keyword => keyword.Replace("dir:", "")) .Distinct() .ToList(); } public IEnumerator AddNetwork(Network network, bool showImport) { var appLovinManifest = AppLovinUpmManifest.Load(); AddPackages(network, appLovinManifest); appLovinManifest.Save(); // Remove any versions of the adapter in the Assets folder AppLovinPackageManager.DeleteDuplicateAdapter(network, false); ResolvePackageManager(); yield break; } public void RemoveNetwork(Network network) { var appLovinManifest = AppLovinUpmManifest.Load(); RemovePackages(network, appLovinManifest); appLovinManifest.Save(); ResolvePackageManager(); } /// /// Adds a network's packages to the package manager removes any beta version that exists /// /// The network to add. /// The AppLovinUpmManifest instance to edit internal static void AddPackages(Network network, AppLovinUpmManifest appLovinManifest) { foreach (var packageInfo in network.Packages) { appLovinManifest.AddPackageDependency(packageInfo.Name, packageInfo.Version); RemoveBetaPackage(packageInfo.Name, appLovinManifest); } } /// /// Removes a network's packages from the package manager /// /// The network to add. /// The AppLovinUpmManifest instance to edit internal static void RemovePackages(Network network, AppLovinUpmManifest appLovinManifest) { foreach (var packageInfo in network.Packages) { appLovinManifest.RemovePackageDependency(packageInfo.Name); RemoveBetaPackage(packageInfo.Name, appLovinManifest); } } /// /// Removes the beta version of a package name /// /// The name of the package to remove a beta for /// The AppLovinUpmManifest instance to edit private static void RemoveBetaPackage(string packageName, AppLovinUpmManifest appLovinManifest) { var prefix = ""; if (packageName.Contains(PackageNamePrefixNetwork)) { prefix = PackageNamePrefixNetwork; } else if (packageName.Contains(PackageNamePrefixDsp)) { prefix = PackageNamePrefixDsp; } else if (packageName.Contains(PackageNamePrefixAppLovin)) { prefix = PackageNamePrefixAppLovin; } else { return; } var betaPackageName = packageName.Replace(prefix, prefix + ".beta"); appLovinManifest.RemovePackageDependency(betaPackageName); } /// /// Resolves the Unity Package Manager so any changes made to the manifest.json file are reflected in the Unity Editor. /// internal static void ResolvePackageManager() { #if UNITY_2020_1_OR_NEWER Client.Resolve(); #else packageManagerClientType = packageManagerClientType ?? typeof(Client); if (packageManagerClientType != null) { packageManagerResolveMethod = packageManagerResolveMethod ?? packageManagerClientType.GetMethod("Resolve", BindingFlags.NonPublic | BindingFlags.Static); } if (packageManagerResolveMethod != null) { packageManagerResolveMethod.Invoke(null, null); } #endif } /// /// Gets the PackageCollection from the Unity Package Manager synchronously. /// /// How long to wait before exiting with a timeout error /// private static PackageCollection GetPackageCollectionSync(float timeoutSeconds = -1) { var request = Client.List(); // Just wait till the request is complete var now = DateTime.Now; while (!request.IsCompleted) { // Wait indefinitely if there is no timeout set. if (timeoutSeconds < 0) continue; var delta = DateTime.Now - now; if (delta.TotalSeconds > timeoutSeconds) { MaxSdkLogger.UserError("Failed to list UPM packages: Timeout"); break; } } if (!request.IsCompleted) { return null; } if (request.Status >= StatusCode.Failure) { MaxSdkLogger.UserError("Failed to list packages: " + request.Error.message); return null; } return (request.Status == StatusCode.Success) ? request.Result : null; } } #endif public class AppLovinAssetsPackageManager : IPackageManagerClient { public static List GetInstalledMediationNetworks() { var maxMediationDirectory = AppLovinIntegrationManager.MediationDirectory; if (!Directory.Exists(maxMediationDirectory)) return new List(); var mediationNetworkDirectories = Directory.GetDirectories(maxMediationDirectory); return mediationNetworkDirectories.Select(Path.GetFileName).ToList(); } public IEnumerator AddNetwork(Network network, bool showImport) { yield return AppLovinIntegrationManager.Instance.DownloadPlugin(network, showImport); } public void RemoveNetwork(Network network) { foreach (var pluginFilePath in network.PluginFilePaths) { var filePath = Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, pluginFilePath); FileUtil.DeleteFileOrDirectory(filePath); FileUtil.DeleteFileOrDirectory(filePath + ".meta"); } } } }