19 private const string StoreFreeVersionUrl =
"https://assetstore.unity.com/packages/tools/integration/spacebanneradmobile-free-278681";
20 private const string StoreUrl =
"https://assetstore.unity.com/packages/tools/integration/spaceadmobile-204424";
21 private const string DocsUrl =
"https://space-cat.games/SpaceAdMobile/index.html";
22 private const int WindowMenuPriority = 2100;
24 private static bool firstRun;
28 private static AdMigrationWindow adMigrationWindow;
30 private static byte selectedAggregator;
33 private Rect? lastPosition;
34 private bool suppressReopenOnDisable;
35 private bool isYandexUpdaterLaunching;
37 private const float InstalledInstallButtonExtraWidth = 8f;
38 private const float MinWidth = 520f;
39 private const float MinHeight = 400f;
40 private static readonly Vector2 s_minSize =
new(MinWidth, MinHeight);
41 private static readonly Vector2 s_maxSize =
new(MinWidth * 2f, MinHeight * 2f);
43 private static readonly GUIContent s_deleteOtherFoldersContent =
new(
44 "Delete other ad folders",
45 "Remove folders belonging to other ad aggregators that are not currently selected.");
47 private static readonly GUIContent s_deleteDemosContent =
new(
49 "Remove demo scenes and example scripts bundled with the selected aggregator SDK.");
51 private static readonly GUIContent s_deleteDocumentationContent =
new(
52 "Delete documentation",
53 "Remove documentation files (PDF, TXT) shipped with the selected aggregator SDK.");
55 private static readonly GUIContent s_enhancedYandexDummiesContent =
new(
56 "Enhanced Yandex editor dummies",
57 "Replace stock silent Yandex dummies with enhanced ones that fire ad-lifecycle events in the Editor.");
59 private static readonly GUIContent s_suppressYandexCs0414WarningsContent =
new(
61 "Add or remove warning code 414 in the selected Yandex SDK files that suppress warning 67.");
63 private static readonly GUIContent s_installContent =
new(
"Install");
64 private static GUIContent UpdateContent {
get; } =
new(
66 "Update the installed SDK to a newer version using .unitypackage files in your Downloads folder.");
67 private static GUIContent UpdateUnavailableContent {
get; } =
new(
69 $
"Python {YandexSdkUpdaterLauncher.MinimumPythonVersion} or newer was not found. Add python, python3, or the Windows Python Launcher to PATH.");
70 private static GUIContent UpdateCheckingContent {
get; } =
new(
72 $
"Checking for Python {YandexSdkUpdaterLauncher.MinimumPythonVersion} or newer.");
73 private static GUIContent UpdateStartingContent {
get; } =
new(
75 "Waiting for the terminal and updater controller to start.");
80 [InitializeOnLoadMethod]
88 EditorApplication.delayCall -=
OnLoad;
90 if (Application.isPlaying)
return;
93 if (adAggregatorResource ==
null)
95 throw new UnityException($
"The resource file '{nameof(AdAggregatorResource)}' is missing. Please reinstall the package.");
101 adAggregatorResource.UseEnhancedYandexEditorDummies =
115 if (AssetDatabase.IsAssetImportWorkerProcess())
return true;
116 if (EditorApplication.isCompiling)
return true;
117 if (adAggregatorResource ==
null)
return false;
119#if UNITY_6000_3_OR_NEWER
120 var named = NamedBuildTarget.FromBuildTargetGroup(
121 BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget));
122 PlayerSettings.GetScriptingDefineSymbols(named, out var defines);
123#elif UNITY_2021_2_OR_NEWER
124 var named = NamedBuildTarget.FromBuildTargetGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
125 PlayerSettings.GetScriptingDefineSymbols(named, out var defines);
127 var definesLegacy = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup);
128 var defines = definesLegacy.Split(
new[] {
';' }, StringSplitOptions.RemoveEmptyEntries);
130 if (adMigrationWindow ==
null)
131 currentAggregator = adAggregatorResource.Aggregators
132 .FirstOrDefault(e => defines.Contains(e.Define, StringComparer.Ordinal));
134 var result = currentAggregator !=
null;
143 selectedAggregator = (byte)Mathf.Max(0, Array.FindIndex(adAggregatorResource.Aggregators, e => defines.Contains(e.Define)));
150 [MenuItem(
"Window/" + nameof(
SCG) +
"/" + nameof(
SpaceAdMobile) +
": Select Aggregator &S", priority = WindowMenuPriority)]
156 YandexAssemblyUpdater.Validate0414WarningSuppressionPreference();
160 window = GetWindow<AdAggregatorSelector>(
true,
"Select Ad Aggregator",
true);
161 window.minSize = s_minSize;
162 window.maxSize = s_maxSize;
169 YandexSdkUpdaterLauncher.RefreshPythonAvailability(() =>
171 if (window !=
null) window.Repaint();
180 YandexAssemblyUpdater.Validate0414WarningSuppressionPreference();
181 YandexSdkUpdaterLauncher.RefreshPythonAvailability(Repaint);
189 if (adAggregatorResource ==
null)
194 EditorGUILayout.HelpBox(adAggregatorResource.WelcomeText, MessageType.Info);
197 using (
new EditorGUI.DisabledScope(adMigrationWindow !=
null))
199 if (adMigrationWindow ==
null && lastPosition !=
null)
201 EditorApplication.delayCall += () =>
203 if (adMigrationWindow ==
null && lastPosition !=
null) ApplyLastPosition();
207 GUILayout.BeginHorizontal();
208 GUILayout.Label(
"Select an Ad Aggregator", EditorStyles.boldLabel);
210 if (adAggregatorResource.IsFreeVersion)
212 if (GUILayout.Button(
"Asset Store (Free)", GUILayout.MaxWidth(110)))
214 Application.OpenURL(StoreFreeVersionUrl);
218 if (GUILayout.Button(
"Asset Store", GUILayout.MaxWidth(200)))
220 Application.OpenURL(StoreUrl);
223 if (GUILayout.Button(
"Open Docs", GUILayout.MaxWidth(150)))
225 Application.OpenURL(DocsUrl);
227 GUILayout.EndHorizontal();
229 const int elementsInRow = 3;
230 var buttonWidth = EditorGUIUtility.currentViewWidth / elementsInRow - GUI.skin.button.padding.right + 2f;
232 for (
byte aggrIndex = 0; aggrIndex < adAggregatorResource.Aggregators.Length; aggrIndex++)
234 var aggregatorObject = adAggregatorResource.Aggregators[aggrIndex];
235 if (aggregatorObject.Folder ==
null)
238 if (aggrIndex % elementsInRow == 0)
239 GUILayout.BeginHorizontal();
241 var buttonSkin =
new GUIStyle(GUI.skin.button)
243 imagePosition = ImagePosition.ImageAbove
246 if (selectedAggregator == aggrIndex)
248 buttonSkin.active.textColor = Color.green;
249 buttonSkin.hover.textColor = Color.green;
250 buttonSkin.normal.textColor = Color.green;
253 var buttonContent =
new GUIContent(aggregatorObject.Name, aggregatorObject.Texture);
255 if (GUILayout.Button(buttonContent, buttonSkin, GUILayout.Width(buttonWidth), GUILayout.Height(buttonWidth)))
257 selectedAggregator = aggrIndex;
260 if (aggrIndex % elementsInRow == elementsInRow - 1 || aggrIndex == adAggregatorResource.Aggregators.Length - 1)
262 GUILayout.EndHorizontal();
266 using (
new EditorGUILayout.VerticalScope())
268 GUILayout.FlexibleSpace();
270 var toAggregator = GetSelectedAggregator();
271 var isYandexAggregator = IsYandexAggregator(toAggregator);
272 var shouldShowYandex0414Suppression = IsYandexAggregator(currentAggregator) && isYandexAggregator;
273 var shouldShowYandexFixButton = EditorUtility.scriptCompilationFailed && isYandexAggregator;
275#if !LEGACY_YANDEX_MOBILE_ADS
276 if (isYandexAggregator || shouldShowYandex0414Suppression)
278 if (shouldShowYandex0414Suppression)
281 using (
new EditorGUILayout.HorizontalScope())
283#if !LEGACY_YANDEX_MOBILE_ADS
284 if (isYandexAggregator)
286 adAggregatorResource.UseEnhancedYandexEditorDummies = GUILayout.Toggle(
287 adAggregatorResource.UseEnhancedYandexEditorDummies,
288 s_enhancedYandexDummiesContent);
292 if (shouldShowYandex0414Suppression)
294 GUILayout.FlexibleSpace();
295 GUILayout.Label(s_suppressYandexCs0414WarningsContent, GUILayout.ExpandWidth(
false));
296 var suppressYandexCs0414Warnings = YandexAssemblyUpdater.SuppressYandex0414Warnings;
297 var updatedSuppressYandexCs0414Warnings = GUILayout.Toggle(
298 suppressYandexCs0414Warnings,
300 GUILayout.Width(EditorGUIUtility.singleLineHeight));
301 if (updatedSuppressYandexCs0414Warnings != suppressYandexCs0414Warnings)
302 YandexAssemblyUpdater.SuppressYandex0414Warnings = updatedSuppressYandexCs0414Warnings;
307 using (
new EditorGUILayout.HorizontalScope())
309 adAggregatorResource.DeleteOtherFolders = GUILayout.Toggle(
310 adAggregatorResource.DeleteOtherFolders,
311 s_deleteOtherFoldersContent,
312 GUILayout.ExpandWidth(
false));
314 GUILayout.FlexibleSpace();
315 GUILayout.Label(s_deleteDemosContent, GUILayout.ExpandWidth(
false));
316 var deleteDemo = adAggregatorResource.DeleteDemo = GUILayout.Toggle(
317 adAggregatorResource.DeleteDemo,
319 GUILayout.Width(EditorGUIUtility.singleLineHeight));
320 adAggregatorResource.DeleteDocumentation =
321 deleteDemo && EditorGUILayout.Toggle(s_deleteDocumentationContent, adAggregatorResource.DeleteDocumentation);
324 var isAggregatorSelectable = toAggregator !=
null &&
325 (currentAggregator ==
null ||
326 !
string.Equals(currentAggregator.Name, toAggregator.Name, StringComparison.Ordinal));
327 var isAggregatorInstalled = toAggregator !=
null && IsAggregatorInstalled(toAggregator);
328 var shouldShowInstallOnly = isAggregatorSelectable && !isAggregatorInstalled;
329 var shouldShowInstall = shouldShowInstallOnly || isAggregatorInstalled;
330 var isInstallActionConfigured = !shouldShowInstall || IsInstallActionConfigured(toAggregator);
332 if (shouldShowInstallOnly && !isInstallActionConfigured)
334 EditorGUILayout.HelpBox(
335 $
"Install action is not configured for '{toAggregator.Name ?? "Unknown
"}'.",
336 MessageType.Warning);
339 using (
new EditorGUILayout.HorizontalScope())
341#if UNITY_2022_3_OR_NEWER
342 var heigth = GUI.skin.button.lineHeight * 1.5f;
344 var heigth = GUI.skin.button.lineHeight * 2f;
347 if (shouldShowInstallOnly)
349 using (
new EditorGUI.DisabledScope(!isInstallActionConfigured))
351 if (GUILayout.Button(s_installContent, GUILayout.Height(heigth)))
353 OpenInstall(toAggregator);
359 if (shouldShowInstall)
361 using (
new EditorGUI.DisabledScope(!isInstallActionConfigured))
363 var installWidth = GUI.skin.button.CalcSize(s_installContent).x + InstalledInstallButtonExtraWidth;
364 if (GUILayout.Button(s_installContent, GUILayout.Width(installWidth), GUILayout.Height(heigth)))
366 OpenInstall(toAggregator);
371 if (GUILayout.Button(
"OK", GUILayout.Height(heigth)))
373 if (currentAggregator !=
null && toAggregator !=
null &&
374 currentAggregator.Name != toAggregator.Name)
376 lastPosition = window.position;
378 window.maxSize = window.minSize;
379 window.position =
new Rect(Vector2.zero, minSize);
381 EditorApplication.delayCall += () =>
383 adMigrationWindow = AdMigrationWindow.Open(adAggregatorResource,
386 () => ApplyChanges(toAggregator),
391 else if (toAggregator !=
null)
393 ApplyChanges(toAggregator);
399 if (shouldShowYandexFixButton && GUILayout.Button(
"Fix Yandex Assembly", GUILayout.Height(heigth)))
401 RunYandexUpdaterIfNeeded(toAggregator);
404 if (!IsYandexAggregator(currentAggregator) || !IsYandexAggregator(toAggregator))
return;
405 var pythonAvailabilityChecked = YandexSdkUpdaterLauncher.IsPythonAvailabilityChecked;
406 var updateAvailable = pythonAvailabilityChecked && YandexSdkUpdaterLauncher.IsPythonAvailable;
407 var updateContent = isYandexUpdaterLaunching
408 ? UpdateStartingContent
409 : !pythonAvailabilityChecked
410 ? UpdateCheckingContent
411 : updateAvailable ? UpdateContent : UpdateUnavailableContent;
412 var updateButtonWidth = GUI.skin.button.CalcSize(s_installContent).x + InstalledInstallButtonExtraWidth;
413 using (
new EditorGUI.DisabledScope(!updateAvailable || isYandexUpdaterLaunching))
416 if (GUILayout.Button(
418 GUILayout.Width(updateButtonWidth),
419 GUILayout.Height(heigth)))
421 isYandexUpdaterLaunching =
true;
423 if (!YandexSdkUpdaterLauncher.TryLaunch(HandleYandexUpdaterLaunchCompleted, out var error))
425 isYandexUpdaterLaunching =
false;
426 EditorUtility.DisplayDialog(
"SDK Updater", error,
"OK");
435 private void HandleYandexUpdaterLaunchCompleted(
bool started,
string error)
437 if (
this ==
null || !isYandexUpdaterLaunching)
440 isYandexUpdaterLaunching =
false;
443 EditorUtility.DisplayDialog(
"SDK Updater", error ??
"The updater terminal could not be started.",
"OK");
448 suppressReopenOnDisable =
true;
458 if (isYandexUpdaterLaunching)
460 YandexSdkUpdaterLauncher.CancelPendingLaunch();
461 isYandexUpdaterLaunching =
false;
465 SaveEditorPreferences();
469 adAggregatorResource =
null;
473 Debug.LogWarning(
"Please select one of the ad aggregators.");
477 private void ApplyLastPosition()
479 if (lastPosition ==
null)
return;
480 window.maxSize = s_maxSize;
481 window.position = lastPosition.Value;
485 private void ApplyChanges(Aggregator aggregator)
487 if (aggregator ==
null)
491 ChangeDefine(aggregator.Define);
492 DeleteOtherAdFolders(!adAggregatorResource.
DeleteDemo);
494 RunYandexUpdaterIfNeeded(aggregator);
496 EditorApplication.delayCall += () =>
498 if (adMigrationWindow !=
null) adMigrationWindow.Close();
499 CompilationPipeline.RequestScriptCompilation();
503 private static bool IsYandexAggregator(Aggregator aggregator) =>
504 aggregator !=
null &&
505 string.Equals(aggregator.Define, DefineSymbolsManager.YandexMobileAds, StringComparison.Ordinal);
507 private static void RunYandexUpdaterIfNeeded(Aggregator aggregator)
509 if (!IsYandexAggregator(aggregator))
512 SaveEditorPreferences();
513 YandexAssemblyUpdater.Update();
516 private static void SaveEditorPreferences()
518 if (adAggregatorResource ==
null)
521 EditorPrefs.SetBool(AdAggregatorResource.KeyDeleteOtherFolders, adAggregatorResource.DeleteOtherFolders);
522 EditorPrefs.SetBool(AdAggregatorResource.KeyDeleteDemo, adAggregatorResource.DeleteDemo);
523 EditorPrefs.SetBool(AdAggregatorResource.KeyDeleteDocumentation, adAggregatorResource.DeleteDocumentation);
524 EditorPrefs.SetBool(AdAggregatorResource.KeyUseEnhancedYandexEditorDummies, adAggregatorResource.UseEnhancedYandexEditorDummies);
527 private static void ChangeDefine(
string define)
529 for (
byte aggrIndex = 0; aggrIndex < adAggregatorResource.Aggregators.Length; aggrIndex++)
531 if (aggrIndex == selectedAggregator)
532 DefineSymbolsManager.AddDefineSymbol(define);
534 DefineSymbolsManager.RemoveDefineSymbol(adAggregatorResource.Aggregators[aggrIndex].Define);
538 private static void DeleteOtherAdFolders(
bool refresh)
540 if (!adAggregatorResource.DeleteOtherFolders)
return;
542 for (
byte aggrIndex = 0; aggrIndex < adAggregatorResource.Aggregators.Length; aggrIndex++)
544 if (aggrIndex == selectedAggregator)
547 var folderPath = GetAssetPath(adAggregatorResource.Aggregators[aggrIndex].Folder);
548 if (!IsProjectAssetsPath(folderPath) || !Directory.Exists(folderPath))
continue;
550 TryDeleteEntireFolder(folderPath);
554 AssetDatabase.Refresh();
557 private static void DeleteDemo(
bool refresh)
559 if (!adAggregatorResource.DeleteDemo)
return;
560 var deleteDocumentation = adAggregatorResource.DeleteDocumentation;
562 const string pdf =
".pdf";
563 const string txt =
".txt";
565 var baseFolder = TryGetPackageRootPath(out var rootPath) ? rootPath :
string.Empty;
566 var packageVersion = TryReadPackageVersion(baseFolder);
568 for (
byte aggrIndex = 0; aggrIndex < adAggregatorResource.Aggregators.Length; aggrIndex++)
570 var folderPath = SampleLocator.GetDemoFolder(
572 adAggregatorResource.Aggregators[aggrIndex].DemoFolderName,
575 if (!IsProjectAssetsPath(folderPath) || !Directory.Exists(folderPath))
continue;
577 var hasPdf = Directory.EnumerateFiles(folderPath,
"*" + pdf, SearchOption.AllDirectories).Any();
579 if (deleteDocumentation || !hasPdf)
581 TryDeleteEntireFolder(folderPath);
585 DeleteFolderContentsExceptDocs(folderPath, pdf, txt);
588 if (!deleteDocumentation)
continue;
589 folderPath = SampleLocator.GetDocsFolder(
591 adAggregatorResource.Aggregators[aggrIndex].DemoFolderName,
594 if (!IsProjectAssetsPath(folderPath) || !Directory.Exists(folderPath))
continue;
596 TryDeleteEntireFolder(folderPath);
600 AssetDatabase.Refresh();
603 private static bool TryGetPackageRootPath(out
string rootPath)
605 rootPath =
string.Empty;
607 if (adAggregatorResource ==
null)
610 var resourcePath = AssetDatabase.GetAssetPath(adAggregatorResource);
611 if (
string.IsNullOrEmpty(resourcePath))
614 var current = Path.GetDirectoryName(resourcePath)?.Replace(
'\\',
'/');
615 while (!
string.IsNullOrEmpty(current))
617 var packageJsonPath = $
"{current}/package.json";
618 if (AssetDatabase.LoadAssetAtPath<TextAsset>(packageJsonPath) !=
null)
624 var parent = Path.GetDirectoryName(current)?.Replace(
'\\',
'/');
625 if (
string.IsNullOrEmpty(parent) || parent == current)
634 private static string TryReadPackageVersion(
string baseFolder)
636 if (
string.IsNullOrEmpty(baseFolder))
639 var packageJsonPath = $
"{baseFolder}/package.json";
640 var packageAsset = AssetDatabase.LoadAssetAtPath<TextAsset>(packageJsonPath);
641 if (packageAsset ==
null)
644 return ExtractPackageVersion(packageAsset.text);
647 private static string ExtractPackageVersion(
string packageJson)
649 if (
string.IsNullOrEmpty(packageJson))
652 const string key =
"\"version\"";
653 var index = packageJson.IndexOf(key, StringComparison.Ordinal);
657 index = packageJson.IndexOf(
':', index);
662 while (index < packageJson.Length &&
char.IsWhiteSpace(packageJson[index]))
665 if (index >= packageJson.Length || packageJson[index] !=
'"')
669 var end = packageJson.IndexOf(
'"', index);
670 return end < 0 ? string.Empty : packageJson[index..end].Trim();
673 private static string GetAssetPath(Object asset)
678 var unityPath = AssetDatabase.GetAssetPath(asset);
679 if (
string.IsNullOrEmpty(unityPath))
682 var normalized = unityPath.Replace(
'\\',
'/');
684 const string assetsPrefix =
"Assets/";
685 if (normalized.StartsWith(assetsPrefix, StringComparison.Ordinal))
686 return Path.Combine(Application.dataPath, normalized[assetsPrefix.Length..]).Replace(
'\\',
'/');
688 const string packagesPrefix =
"Packages/";
689 if (normalized.StartsWith(packagesPrefix, StringComparison.Ordinal))
691 var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath,
".."));
692 return Path.Combine(projectRoot, normalized).Replace(
'\\',
'/');
703 private static bool IsProjectAssetsPath(
string absoluteOrUnityPath)
705 if (
string.IsNullOrEmpty(absoluteOrUnityPath))
708 var normalized = absoluteOrUnityPath.Replace(
'\\',
'/');
710 if (normalized.StartsWith(
"Packages/", StringComparison.Ordinal))
713 if (normalized.StartsWith(
"Assets/", StringComparison.Ordinal) ||
714 string.Equals(normalized,
"Assets", StringComparison.Ordinal))
717 var dataPath = Application.dataPath.Replace(
'\\',
'/');
718 return normalized.StartsWith(dataPath, StringComparison.OrdinalIgnoreCase);
721 #region Delete Folder Helpers
727 private static void TryDeleteEntireFolder(
string folderPath)
731 if (Directory.Exists(folderPath))
733 Directory.Delete(folderPath,
true);
740 var metaPath = folderPath.TrimEnd(
'/',
'\\') +
".meta";
741 if (File.Exists(metaPath))
743 File.Delete(metaPath);
756 private static void DeleteFolderContentsExceptDocs(
string rootFolder, params
string[] extensions)
759 foreach (var file
in Directory.EnumerateFiles(rootFolder,
"*", SearchOption.AllDirectories))
761 var ext = Path.GetExtension(file);
762 var extensionEqual = extensions.Any(e =>
string.Equals(ext, e, StringComparison.OrdinalIgnoreCase));
763 if (extensionEqual)
continue;
767 if (File.Exists(file))
772 var metaPath = file +
".meta";
773 if (File.Exists(metaPath))
775 File.Delete(metaPath);
782 var allDirs = Directory.EnumerateDirectories(rootFolder,
"*", SearchOption.AllDirectories)
783 .OrderByDescending(d => d.Length);
785 foreach (var dir
in allDirs)
790 if (Directory.EnumerateFileSystemEntries(dir).Any())
continue;
792 Directory.Delete(dir,
false);
794 var folderMeta = dir.TrimEnd(
'/',
'\\') +
".meta";
795 if (File.Exists(folderMeta))
797 File.Delete(folderMeta);