2using System.Collections.Generic;
6using System.Text.RegularExpressions;
9using PackageInfo = UnityEditor.PackageManager.PackageInfo;
14 internal class YandexAssemblyUpdater
18 private const string AssetsFolder =
"Assets";
19 private const string PackagesFolder =
"Packages";
23 #region Yandex updater asset constants
25 private const string YandexUpdaterScriptFileName =
"update_yandex_sdk.py";
26 private const string PackageImportBridgeTemplateFileName =
"PackageImportBridgeTemplate.cs.txt";
27 private const string PackageImportBridgeFileName =
"PackageImportBridge.cs";
31 #region CS0414 suppression constants
33 private const string SuppressYandex0414WarningsPreferenceKey = nameof(SpaceAdMobile) +
".SuppressYandex0414Warnings";
34 private const string WarningCode0067 =
"67";
35 private const string WarningCode0414 =
"414";
36 private const string Warning414SuppressionMarker =
"// UNITY_SUPPRESS_CS0414";
38 private static readonly
string[] s_cs0414WarningFileNames =
40 "DummyRewardedAdLoaderClient.cs",
41 "DummyInterstitialAdLoaderClient.cs",
42 "DummyAppOpenAdLoaderClient.cs"
45 private static readonly Regex s_warningDisableRegex =
new(
46 @"^(?<prefix>\s*#pragma\s+warning\s+disable\s+)(?<codes>[^/\r\n]+?)(?<comment>\s*//.*)?$",
47 RegexOptions.CultureInvariant);
52 private const string AdRevenueAutoCollectionResolverFileName =
"AdRevenueAutoCollectionResolver.cs";
53 private const string ImportWorkerGuardComment =
"SCG_IMPORT_WORKER_GUARD";
54 private const string ImportWorkerGuardCondition =
"AssetDatabase.IsAssetImportWorkerProcess()";
56 private static readonly
string[] adRevenueAutoCollectionResolverGuardMethods =
58 "internal static void Init()",
59 "private static void ApplyDefines()"
63 #region iOS Fix: Readonly & Constants
64 private static readonly
string[] iosMethodSignatures =
66 "private static void ModifyPlist(string pathToBuildProject)",
67 "public static void OnPostProcessBuild(BuildTarget buildTarget, string pathToBuiltProject)"
70 private const string IosFixIfDefine =
"#if UNITY_IOS || UNITY_TVOS // " + IosWrappedComment;
71 private const string IosFixEndIfDefine =
"#endif // " + IosWrappedComment;
72 private const string IosUsingStarts =
"using UnityEditor.iOS.Xcode";
73 private const string IosIfCondition =
"BuildTarget.iOS";
74 private const string IosWrappedComment =
"UNITY_IOS_WRAPPED";
83 private static YandexData yandexData;
86 private static bool is0414WarningSuppressionUpdateScheduled;
92 private static bool isUpdateScheduled;
93 private const string ActiveSdkSessionPath =
"Library/YandexSdkUpdater/active-session.json";
94 private const double SdkSessionCheckIntervalSeconds = 1;
95 private static double nextSdkSessionCheckTime;
100 private static bool IsSdkUpdateRunning()
102 if (EditorApplication.timeSinceStartup < nextSdkSessionCheckTime)
105 var running = CheckSdkUpdateSession();
107 nextSdkSessionCheckTime = EditorApplication.timeSinceStartup + SdkSessionCheckIntervalSeconds;
115 private static bool CheckSdkUpdateSession()
117 if (!File.Exists(ActiveSdkSessionPath))
122 var marker = File.ReadAllText(ActiveSdkSessionPath);
123 var match = Regex.Match(marker,
@"""ProcessId""\s*:\s*(\d+)");
124 if (!match.Success || !
int.TryParse(match.Groups[1].Value, out var processId))
127 using var process = System.Diagnostics.Process.GetProcessById(processId);
128 return !process.HasExited;
130 catch (ArgumentException)
138 catch (System.ComponentModel.Win32Exception)
144 static YandexAssemblyUpdater()
146 if (AssetDatabase.IsAssetImportWorkerProcess())
149 if (!DefineSymbolsManager.HasDefineSymbol(DefineSymbolsManager.YandexMobileAds))
152 if (IsSdkUpdateRunning())
158 var resource = AdAggregatorResource.Instance;
159 var data = GetConfiguredYandexData(resource);
161 IsRuntimeDefineConstraintConfigured(data) &&
162 IsYandexUpdaterAssetsSynchronized(data) &&
163 Is0414WarningSuppressionApplied(data, SuppressYandex0414Warnings) &&
164 !FixIosReferences(
false))
171 private static void ScheduleUpdate()
173 if (isUpdateScheduled)
176 isUpdateScheduled =
true;
177 EditorApplication.delayCall += UpdateOnDelayCall;
180 private static void UpdateOnDelayCall()
182 EditorApplication.delayCall -= UpdateOnDelayCall;
184 if (AssetDatabase.IsAssetImportWorkerProcess())
186 isUpdateScheduled =
false;
190 if (EditorApplication.isUpdating || EditorApplication.isCompiling || IsSdkUpdateRunning())
192 isUpdateScheduled =
false;
197 isUpdateScheduled =
false;
211 internal static void Update()
213 if (AssetDatabase.IsAssetImportWorkerProcess())
216 if (IsSdkUpdateRunning())
222 var resource = AdAggregatorResource.Instance;
223 if (resource ==
null)
225 throw new UnityException($
"The resource file '{nameof(AdAggregatorResource)}' is missing. Please reinstall the package.");
228 resource.UseEnhancedYandexEditorDummies =
229 EditorPrefs.GetBool(AdAggregatorResource.KeyUseEnhancedYandexEditorDummies,
false);
230 yandexData = GetConfiguredYandexData(resource) ??
231 throw new UnityException(
"Yandex configuration is missing in AdAggregatorResource.");
232 var runtimeDefineConfigured = IsRuntimeDefineConstraintConfigured(yandexData);
234 CreateYandexAssemblyDefinition() |
235 SynchronizeYandexUpdaterAssets() |
236 Synchronize0414WarningSuppressions(yandexData) |
237 ApplyOrRestoreEnhancedEditorDummies(resource.UseEnhancedYandexEditorDummies) |
238 UpdateAssemblyReferences() |
239 PatchAdRevenueAutoCollectionResolverGuard() |
242 if (!runtimeDefineConfigured)
243 ScheduleRuntimeDefineConstraintUpdate();
245 if (changed && !EditorApplication.isUpdating)
246 EditorApplication.delayCall += AssetDatabase.Refresh;
249 #region Yandex configuration
256 private static YandexData GetConfiguredYandexData(AdAggregatorResource resource)
258 if (resource !=
null)
259 yandexData = resource.Yandex;
266 #region Yandex asmdef creation
272 private static bool CreateYandexAssemblyDefinition()
277 var assetsRuntimeDir = ToAssetsPath(yandexData.AssetPath);
278 changed |= EnsureFolderExists(assetsRuntimeDir);
280 var runtimeAsmdefRel = $
"{assetsRuntimeDir}/{yandexData.MainAsmdefName}.asmdef";
281 changed |= EnsureAsmdefExists(runtimeAsmdefRel, yandexData.AsmdefTemplate,
282 $
"[{nameof(YandexAssemblyUpdater)}]: {Path.GetFileName(runtimeAsmdefRel)} has been successfully created!");
285 var assetsEditorDir = ToAssetsPath(yandexData.EditorPath);
287 changed |= EnsureFolderExists(assetsEditorDir);
288 var editorAsmdefRel = $
"{assetsEditorDir}/{yandexData.EditorAsmdefName}.asmdef";
289 changed |= EnsureAsmdefExists(editorAsmdefRel, yandexData.AsmdefEditorTemplate,
290 $
"[{nameof(YandexAssemblyUpdater)}]: {Path.GetFileName(editorAsmdefRel)} has been successfully created!");
295 private static void ScheduleRuntimeDefineConstraintUpdate()
297 EditorApplication.delayCall -= AddRuntimeDefineConstraintOnDelayCall;
298 EditorApplication.delayCall += AddRuntimeDefineConstraintOnDelayCall;
301 private static void AddRuntimeDefineConstraintOnDelayCall()
303 EditorApplication.delayCall -= AddRuntimeDefineConstraintOnDelayCall;
305 if (AssetDatabase.IsAssetImportWorkerProcess() || EditorApplication.isUpdating || EditorApplication.isCompiling || IsSdkUpdateRunning())
307 ScheduleRuntimeDefineConstraintUpdate();
311 if (yandexData ==
null)
314 if (IsRuntimeDefineConstraintConfigured(yandexData))
317 var assetsRuntimeDir = ToAssetsPath(yandexData.AssetPath);
318 var runtimeAsmdefRel = $
"{assetsRuntimeDir}/{yandexData.MainAsmdefName}.asmdef";
319 var runtimeAsmdefAbs = ToAbsolutePath(runtimeAsmdefRel);
320 if (!File.Exists(runtimeAsmdefAbs))
323 var original = File.ReadAllText(runtimeAsmdefAbs, Encoding.UTF8);
324 var updated = AddDefineConstraint(original, DefineSymbolsManager.YandexMobileAds);
325 if (
string.Equals(original, updated, StringComparison.Ordinal))
328 File.WriteAllText(runtimeAsmdefAbs, updated, Encoding.UTF8);
329 Debug.Log($
"[{nameof(YandexAssemblyUpdater)}]: Added {DefineSymbolsManager.YandexMobileAds} define constraint to {Path.GetFileName(runtimeAsmdefRel)}.");
330 AssetDatabase.ImportAsset(runtimeAsmdefRel);
333 private static bool IsRuntimeDefineConstraintConfigured(YandexData data)
338 var assetsRuntimeDir = ToAssetsPath(data.AssetPath);
339 var runtimeAsmdefRel = $
"{assetsRuntimeDir}/{data.MainAsmdefName}.asmdef";
340 var runtimeAsmdefAbs = ToAbsolutePath(runtimeAsmdefRel);
341 return File.Exists(runtimeAsmdefAbs) &&
342 File.ReadAllText(runtimeAsmdefAbs, Encoding.UTF8)
343 .Contains($
"\"{DefineSymbolsManager.YandexMobileAds}\"", StringComparison.Ordinal);
348 #region Yandex SDK updater assets
354 private static bool SynchronizeYandexUpdaterAssets()
356 var runtimeDirectory = ToAssetsPath(yandexData.AssetPath);
357 var editorDirectory = ToAssetsPath(yandexData.EditorPath);
358 var templateDirectory = GetYandexSupportTemplateDirectory();
360 var changed = SynchronizeAssetFile(
361 $
"{templateDirectory}/{YandexUpdaterScriptFileName}",
362 $
"{runtimeDirectory}/{YandexUpdaterScriptFileName}");
363 changed |= SynchronizeAssetFile(
364 $
"{templateDirectory}/{PackageImportBridgeTemplateFileName}",
365 $
"{editorDirectory}/{PackageImportBridgeFileName}");
375 private static bool IsYandexUpdaterAssetsSynchronized(YandexData data)
380 var runtimeDirectory = ToAssetsPath(data.AssetPath);
381 var editorDirectory = ToAssetsPath(data.EditorPath);
382 var templateDirectory = GetYandexSupportTemplateDirectory();
384 return AssetFilesMatch(
385 $
"{templateDirectory}/{YandexUpdaterScriptFileName}",
386 $
"{runtimeDirectory}/{YandexUpdaterScriptFileName}") &&
388 $
"{templateDirectory}/{PackageImportBridgeTemplateFileName}",
389 $
"{editorDirectory}/{PackageImportBridgeFileName}");
392 private static string GetYandexSupportTemplateDirectory()
394 var templateScriptPath = AssetDatabase.FindAssets($
"{nameof(YandexAssemblyUpdater)} t:MonoScript")
395 .Select(AssetDatabase.GUIDToAssetPath)
396 .FirstOrDefault(path =>
398 var script = AssetDatabase.LoadAssetAtPath<MonoScript>(path);
399 return script !=
null && script.GetClass() == typeof(YandexAssemblyUpdater);
401 return string.IsNullOrEmpty(templateScriptPath)
402 ?
throw new UnityException($
"The source file for {nameof(YandexAssemblyUpdater)} could not be found.")
403 : Path.GetDirectoryName(templateScriptPath)?.Replace(
'\\',
'/') ??
404 throw new UnityException(
405 $
"The source directory for {nameof(YandexAssemblyUpdater)} could not be found.");
414 private static bool SynchronizeAssetFile(
string templateAssetPath,
string targetAssetPath)
416 var templatePath = ToAbsolutePath(templateAssetPath);
417 if (!File.Exists(templatePath))
419 Debug.LogWarning($
"[{nameof(YandexAssemblyUpdater)}]: Yandex updater template was not found: {templateAssetPath}");
423 var targetPath = ToAbsolutePath(targetAssetPath);
424 var templateContent = File.ReadAllText(templatePath, Encoding.UTF8);
425 if (File.Exists(targetPath) &&
426 string.Equals(File.ReadAllText(targetPath, Encoding.UTF8), templateContent, StringComparison.Ordinal))
431 var targetDirectory = Path.GetDirectoryName(targetPath);
432 if (!
string.IsNullOrEmpty(targetDirectory))
433 Directory.CreateDirectory(targetDirectory);
435 File.WriteAllText(targetPath, templateContent,
new UTF8Encoding(
false));
445 private static bool AssetFilesMatch(
string templateAssetPath,
string targetAssetPath)
447 var templatePath = ToAbsolutePath(templateAssetPath);
448 var targetPath = ToAbsolutePath(targetAssetPath);
449 return File.Exists(templatePath) && File.Exists(targetPath) &&
451 File.ReadAllText(templatePath, Encoding.UTF8),
452 File.ReadAllText(targetPath, Encoding.UTF8),
453 StringComparison.Ordinal);
458 #region CS0414 warning suppression
463 internal static bool SuppressYandex0414Warnings
465 get => EditorPrefs.GetBool(SuppressYandex0414WarningsPreferenceKey,
false);
468 if (EditorPrefs.GetBool(SuppressYandex0414WarningsPreferenceKey,
false) == value)
471 EditorPrefs.SetBool(SuppressYandex0414WarningsPreferenceKey, value);
472 Schedule0414WarningSuppressionUpdate();
479 private static void Schedule0414WarningSuppressionUpdate()
481 if (is0414WarningSuppressionUpdateScheduled)
484 is0414WarningSuppressionUpdateScheduled =
true;
485 EditorApplication.delayCall += Update0414WarningSuppressionsOnDelayCall;
491 private static void Update0414WarningSuppressionsOnDelayCall()
493 EditorApplication.delayCall -= Update0414WarningSuppressionsOnDelayCall;
495 if (AssetDatabase.IsAssetImportWorkerProcess())
497 is0414WarningSuppressionUpdateScheduled =
false;
501 if (EditorApplication.isUpdating || EditorApplication.isCompiling || IsSdkUpdateRunning())
503 EditorApplication.delayCall += Update0414WarningSuppressionsOnDelayCall;
507 is0414WarningSuppressionUpdateScheduled =
false;
508 var data = GetConfiguredYandexData(AdAggregatorResource.Instance);
509 if (data ==
null || !Synchronize0414WarningSuppressions(data))
512 EditorApplication.delayCall += AssetDatabase.Refresh;
521 private static bool Synchronize0414WarningSuppressions(YandexData data)
526 var shouldSuppress = SuppressYandex0414Warnings;
527 var changedFileNames =
new List<string>();
528 foreach (var assetPath
in Get0414WarningAssetPaths(data))
530 var absolutePath = ToAbsolutePath(assetPath);
531 if (!File.Exists(absolutePath))
534 var original = File.ReadAllText(absolutePath, Encoding.UTF8);
535 var updated = shouldSuppress
536 ? Add0414WarningSuppression(original)
537 : Remove0414WarningSuppression(original);
538 if (
string.Equals(original, updated, StringComparison.Ordinal))
541 File.WriteAllText(absolutePath, updated,
new UTF8Encoding(
false));
542 changedFileNames.Add(Path.GetFileName(assetPath));
545 if (changedFileNames.Count == 0)
548 var action = shouldSuppress ?
"Added" :
"Removed";
549 var direction = shouldSuppress ?
"to" :
"from";
550 Debug.Log($
"[{nameof(YandexAssemblyUpdater)}]: {action} warning 414 suppression {direction} {string.Join(",
", changedFileNames)}.");
560 private static bool Is0414WarningSuppressionApplied(YandexData data,
bool shouldSuppress)
565 foreach (var assetPath
in Get0414WarningAssetPaths(data))
567 var absolutePath = ToAbsolutePath(assetPath);
568 if (!File.Exists(absolutePath))
576 var lines = File.ReadAllLines(absolutePath, Encoding.UTF8);
579 if (HasOwned0414WarningSuppression(lines))
585 var foundWarning67Directive =
false;
586 for (var index = 0; index < lines.Length; index++)
588 var warningCodes = GetDisabledWarningCodes(lines[index]);
589 if (!warningCodes.Contains(WarningCode0067, StringComparer.Ordinal))
592 foundWarning67Directive =
true;
593 if (!warningCodes.Contains(WarningCode0414, StringComparer.Ordinal))
596 var match = s_warningDisableRegex.Match(lines[index]);
597 var hasInlineMarker = Has0414WarningSuppressionMarker(match.Groups[
"comment"].Value);
598 var hasPreviousMarker = index > 0 && IsStandalone0414WarningSuppressionMarker(lines[index - 1]);
599 if (!hasInlineMarker && !hasPreviousMarker)
603 if (!foundWarning67Directive)
613 internal static void Validate0414WarningSuppressionPreference()
615 if (IsSdkUpdateRunning())
618 if (!DefineSymbolsManager.HasDefineSymbol(DefineSymbolsManager.YandexMobileAds))
621 var data = GetConfiguredYandexData(AdAggregatorResource.Instance);
622 EditorPrefs.SetBool(SuppressYandex0414WarningsPreferenceKey,
623 data !=
null && Is0414WarningSuppressionApplied(data,
true));
631 private static string[] Get0414WarningAssetPaths(YandexData data)
633 var commonDirectory = $
"{ToAssetsPath(data.AssetPath)}/Common";
634 return s_cs0414WarningFileNames
635 .Select(fileName => $
"{commonDirectory}/{fileName}")
644 private static string Add0414WarningSuppression(
string source)
646 if (
string.IsNullOrEmpty(source))
649 var newline = source.Contains(
"\r\n", StringComparison.Ordinal) ?
"\r\n" :
"\n";
650 var lines = source.Split(
new[] {
"\r\n",
"\n" }, StringSplitOptions.None).ToList();
653 for (var index = 0; index < lines.Count; index++)
655 var match = s_warningDisableRegex.Match(lines[index]);
659 var warningCodes = match.Groups[
"codes"].Value
661 .Select(code => code.Trim())
663 if (!warningCodes.Contains(WarningCode0067, StringComparer.Ordinal))
668 var comment = match.Groups[
"comment"].Value;
669 var hasInlineMarker = Has0414WarningSuppressionMarker(comment);
670 var hasPreviousMarker = index > 0 && IsStandalone0414WarningSuppressionMarker(lines[index - 1]);
671 var hasWarning414 = warningCodes.Contains(WarningCode0414, StringComparer.Ordinal);
672 if (hasWarning414 && !hasInlineMarker && !hasPreviousMarker)
675 var updatedCodes = match.Groups[
"codes"].Value.TrimEnd();
677 updatedCodes += $
", {WarningCode0414}";
679 if (!hasInlineMarker)
680 comment = Append0414WarningSuppressionMarker(comment);
682 lines[index] = $
"{match.Groups["prefix
"].Value}{updatedCodes}{comment}";
683 if (hasPreviousMarker)
685 lines.RemoveAt(index - 1);
692 return changed ?
string.Join(newline, lines) : source;
700 private static string Remove0414WarningSuppression(
string source)
702 if (
string.IsNullOrEmpty(source))
705 var newline = source.Contains(
"\r\n", StringComparison.Ordinal) ?
"\r\n" :
"\n";
706 var lines = source.Split(
new[] {
"\r\n",
"\n" }, StringSplitOptions.None).ToList();
709 for (var index = 0; index < lines.Count; index++)
711 var match = s_warningDisableRegex.Match(lines[index]);
715 var comment = match.Groups[
"comment"].Value;
716 var hasInlineMarker = Has0414WarningSuppressionMarker(comment);
717 var hasPreviousMarker = index > 0 && IsStandalone0414WarningSuppressionMarker(lines[index - 1]);
718 if (!hasInlineMarker && !hasPreviousMarker)
721 var warningCodes = match.Groups[
"codes"].Value
723 .Select(code => code.Trim())
725 var updatedComment = hasInlineMarker
726 ? Remove0414WarningSuppressionMarker(comment)
728 var updatedCodes = match.Groups[
"codes"].Value.TrimEnd();
729 if (warningCodes.Contains(WarningCode0067, StringComparer.Ordinal) &&
730 warningCodes.Contains(WarningCode0414, StringComparer.Ordinal))
732 updatedCodes =
string.Join(
734 warningCodes.Where(code => !
string.Equals(code, WarningCode0414, StringComparison.Ordinal)));
737 lines[index] = $
"{match.Groups["prefix
"].Value}{updatedCodes}{updatedComment}";
739 if (hasPreviousMarker)
741 lines.RemoveAt(index - 1);
748 return changed ?
string.Join(newline, lines) : source;
756 private static bool HasOwned0414WarningSuppression(
string[] lines)
758 for (var index = 0; index < lines.Length; index++)
760 var warningCodes = GetDisabledWarningCodes(lines[index]);
761 if (!warningCodes.Contains(WarningCode0067, StringComparer.Ordinal) ||
762 !warningCodes.Contains(WarningCode0414, StringComparer.Ordinal))
765 var match = s_warningDisableRegex.Match(lines[index]);
766 if (Has0414WarningSuppressionMarker(match.Groups[
"comment"].Value) ||
767 index > 0 && IsStandalone0414WarningSuppressionMarker(lines[index - 1]))
781 private static bool Has0414WarningSuppressionMarker(
string comment) =>
782 comment.TrimEnd().EndsWith(Warning414SuppressionMarker, StringComparison.Ordinal);
789 private static string Append0414WarningSuppressionMarker(
string comment) =>
790 string.IsNullOrEmpty(comment)
791 ? $
" {Warning414SuppressionMarker}"
792 : $
"{comment.TrimEnd()} {Warning414SuppressionMarker}";
799 private static string Remove0414WarningSuppressionMarker(
string comment)
801 var trimmedComment = comment.TrimEnd();
802 var markerIndex = trimmedComment.LastIndexOf(Warning414SuppressionMarker, StringComparison.Ordinal);
806 var updatedComment = trimmedComment.Remove(markerIndex, Warning414SuppressionMarker.Length);
807 return string.IsNullOrWhiteSpace(updatedComment) ||
808 string.Equals(updatedComment.Trim(),
"//", StringComparison.Ordinal)
810 : updatedComment.TrimEnd();
818 private static bool IsStandalone0414WarningSuppressionMarker(
string line) =>
819 string.Equals(line.Trim(), Warning414SuppressionMarker, StringComparison.Ordinal);
826 private static string[] GetDisabledWarningCodes(
string line)
828 var match = s_warningDisableRegex.Match(line);
830 ? match.Groups[
"codes"].Value.Split(
',').Select(code => code.Trim()).ToArray()
831 : Array.Empty<
string>();
836 #region Enhanced editor dummies
838 private static bool ApplyOrRestoreEnhancedEditorDummies(
bool useEnhancedEditorDummies) =>
839 YandexEditorDummyUpdater.ApplyOrRestore(yandexData, useEnhancedEditorDummies);
843 #region Patch Yandex asmdef
848 private static bool UpdateAssemblyReferences()
850 if (yandexData.Asmdef ==
null)
851 throw new UnityException(
"YandexData.Asmdef is not assigned. Please reference SCG.Yandex.asmdef asset.");
853 var targetAsmdefRel = AssetDatabase.GetAssetPath(yandexData.Asmdef);
854 if (
string.IsNullOrWhiteSpace(targetAsmdefRel))
855 throw new UnityException(
"Unable to resolve path to SCG.Yandex.asmdef via AssetDatabase.");
857 var targetAsmdefAbs = ToAbsolutePath(targetAsmdefRel);
858 if (!File.Exists(targetAsmdefAbs))
859 throw new UnityException($
"The asmdef file was not found: {targetAsmdefRel}");
861 var original = File.ReadAllText(targetAsmdefAbs, Encoding.UTF8);
862 var updated = PatchDefineConstraint(original, yandexData.RemoveDefineConstraint);
864 if (
string.Equals(original, updated, StringComparison.Ordinal))
867 File.WriteAllText(targetAsmdefAbs, updated, Encoding.UTF8);
868 Debug.Log($
"[{nameof(YandexAssemblyUpdater)}]: Assembly references updated and define constraint removed successfully.");
877 private static string PatchDefineConstraint(
string json,
string defineToRemove)
879 if (
string.IsNullOrWhiteSpace(defineToRemove))
882 var escaped = Regex.Escape(defineToRemove);
883 var pattern = $
"\"\\b{escaped}\\b\",?\\s*";
884 var result = Regex.Replace(json, pattern,
string.Empty);
887 result = Regex.Replace(result,
@",\s*\]",
"\n ]");
890 result = Regex.Replace(result,
@"""defineConstraints"":\s*\[\s*\]",
@"""defineConstraints"": [
896 private static string AddDefineConstraint(
string json,
string defineToAdd)
898 if (
string.IsNullOrWhiteSpace(defineToAdd) || json.Contains($
"\"{defineToAdd}\"", StringComparison.Ordinal))
901 var emptyArrayPattern =
@"""defineConstraints"":\s*\[\s*\]";
902 if (Regex.IsMatch(json, emptyArrayPattern))
904 return Regex.Replace(
907 $
"\"defineConstraints\": [\n \"{defineToAdd}\"\n ]");
910 var arrayPattern =
@"""defineConstraints"":\s*\[(?<content>[\s\S]*?)\]";
911 return Regex.Replace(
916 var content = match.Groups[
"content"].Value;
917 var trimmed = content.Trim();
918 var separator =
string.IsNullOrEmpty(trimmed) ? string.Empty :
",";
919 return $
"\"defineConstraints\": [{content}{separator}\n \"{defineToAdd}\"\n ]";
921 RegexOptions.CultureInvariant);
926 #region AdRevenue import worker guard
928 private static bool PatchAdRevenueAutoCollectionResolverGuard()
931 foreach (var file
in GetAdRevenueAutoCollectionResolverPaths())
933 var original = File.ReadAllText(file, Encoding.UTF8);
934 var updated = PatchAdRevenueAutoCollectionResolverGuard(original);
935 if (
string.Equals(original, updated, StringComparison.Ordinal))
938 File.WriteAllText(file, updated, Encoding.UTF8);
941 var assetPath = ToAssetPathFromAbsolute(file);
942 if (!
string.IsNullOrEmpty(assetPath))
943 AssetDatabase.ImportAsset(assetPath);
945 Debug.Log($
"[{nameof(YandexAssemblyUpdater)}]: Updated import worker guard in {Path.GetFileName(file)}.");
951 private static string[] GetAdRevenueAutoCollectionResolverPaths()
953 var dataPath = Application.dataPath.Replace(
'\\',
'/');
954 return Directory.Exists(dataPath)
955 ? Directory.GetFiles(dataPath, AdRevenueAutoCollectionResolverFileName, SearchOption.AllDirectories)
956 .OrderBy(path => path, StringComparer.Ordinal)
958 : Array.Empty<
string>();
961 private static string PatchAdRevenueAutoCollectionResolverGuard(
string source)
963 if (
string.IsNullOrEmpty(source))
966 var newline = source.Contains(
"\r\n", StringComparison.Ordinal) ?
"\r\n" :
"\n";
967 var lines = source.Split(
new[] {
"\r\n",
"\n" }, StringSplitOptions.None).ToList();
968 var changed = adRevenueAutoCollectionResolverGuardMethods.Aggregate(
970 (current, signature) => current | PatchMethodImportWorkerGuard(lines, signature));
972 return changed ?
string.Join(newline, lines) : source;
975 private static bool PatchMethodImportWorkerGuard(List<string> lines,
string signature)
977 if (!TryFindMethodBlock(lines, signature, out _, out var openLine, out var endLine))
980 var changed = RemoveImportWorkerGuard(lines, openLine + 1, endLine);
981 if (!TryFindMethodBlock(lines, signature, out var methodLine, out openLine, out endLine))
984 var contentStart = openLine + 1;
985 if (HasImportWorkerGuard(lines, contentStart, endLine))
988 var indent = GetMethodBodyIndent(lines, methodLine, contentStart, endLine);
989 lines.InsertRange(contentStart,
new[]
991 $
"{indent}if ({ImportWorkerGuardCondition}) {{ // {ImportWorkerGuardComment}",
1000 private static bool TryFindMethodBlock(List<string> lines,
string signature, out
int methodLine, out
int openLine, out
int endLine)
1002 methodLine = FindMethodLine(lines, signature);
1003 openLine = methodLine < 0 ? -1 : FindOpeningBraceLine(lines, methodLine);
1004 endLine = openLine < 0 ? -1 : FindMatchingClosingBrace(lines, openLine);
1005 return endLine > openLine;
1008 private static int FindMethodLine(List<string> lines,
string signature) =>
1009 lines.FindIndex(line => line.TrimStart().StartsWith(signature, StringComparison.Ordinal));
1011 private static int FindOpeningBraceLine(List<string> lines,
int startLine)
1013 for (var i = startLine; i < lines.Count; i++)
1015 var trimmed = lines[i].TrimStart();
1016 if (i > startLine && trimmed.StartsWith(
"[", StringComparison.Ordinal))
1019 if (lines[i].Contains(
"{", StringComparison.Ordinal))
1026 private static bool RemoveImportWorkerGuard(List<string> lines,
int startLine,
int endLine)
1028 var changed =
false;
1030 for (var i = endLine; i >= startLine; i--)
1032 if (!lines[i].Contains(ImportWorkerGuardComment, StringComparison.Ordinal))
1035 var guardOpen = lines[i].Contains(
"{", StringComparison.Ordinal) ? i : FindOpeningBraceLine(lines, i);
1039 var guardEnd = FindMatchingClosingBrace(lines, guardOpen);
1040 if (guardEnd < guardOpen || guardEnd > endLine)
1043 lines.RemoveRange(i, guardEnd - i + 1);
1044 if (i < lines.Count &&
string.IsNullOrWhiteSpace(lines[i]))
1053 private static bool HasImportWorkerGuard(List<string> lines,
int startLine,
int endLine)
1055 for (var i = startLine; i <= endLine && i < lines.Count; i++)
1057 if (lines[i].Contains(ImportWorkerGuardComment, StringComparison.Ordinal))
1064 private static string GetMethodBodyIndent(List<string> lines,
int methodLine,
int startLine,
int endLine)
1066 for (var i = startLine; i <= endLine && i < lines.Count; i++)
1068 if (!
string.IsNullOrWhiteSpace(lines[i]))
1069 return LeadingWhitespace(lines[i]);
1072 return LeadingWhitespace(lines[methodLine]) +
" ";
1079 private static bool FixIosReferences(
bool applyChanges =
true)
1081 if (AssetDatabase.IsAssetImportWorkerProcess())
1084 var assetsDir = ToAssetsPath(yandexData.AssetPath);
1085 if (!AssetDatabase.IsValidFolder(assetsDir))
1088 var path = ToAbsolutePath(assetsDir);
1089 var files = Directory.GetFiles(path,
"*.cs", SearchOption.AllDirectories);
1090 var changed =
false;
1092 foreach (var f
in files)
1094 var original = File.ReadAllText(f);
1095 var text = original;
1097 text = WrapUsings(text);
1098 text = WrapIosMethodDefinitions(text);
1099 text = WrapIosIfContents(text);
1100 if (ReferenceEquals(text, original) || text == original)
continue;
1105 File.WriteAllText(f, text);
1110 Debug.Log($
"[{nameof(YandexAssemblyUpdater)}]: Assembly fixed.");
1115 private static string WrapUsings(
string src)
1118 var lines = src.Split(
new[] {
"\r\n",
"\n" }, StringSplitOptions.None).ToList();
1119 var modified =
false;
1121 for (var i = 0; i < lines.Count;)
1123 var t = lines[i].Trim();
1124 if (!t.StartsWith(IosUsingStarts, StringComparison.Ordinal))
1133 while (end + 1 < lines.Count && lines[end + 1].Trim().StartsWith(IosUsingStarts, StringComparison.Ordinal))
1137 var prev = start - 1 >= 0 ? lines[start - 1].Trim() :
string.Empty;
1138 var next = end + 1 < lines.Count ? lines[end + 1].Trim() :
string.Empty;
1140 prev.StartsWith(
"#if", StringComparison.Ordinal) && prev.Contains(IosWrappedComment) &&
1141 next.StartsWith(
"#endif", StringComparison.Ordinal) && next.Contains(IosWrappedComment);
1145 var indent = LeadingWhitespace(lines[start]);
1146 lines.Insert(start, indent + IosFixIfDefine);
1148 lines.Insert(end + 1, indent + IosFixEndIfDefine);
1158 return modified ?
string.Join(
"\n", lines) : src;
1161 private static string WrapIosIfContents(
string src)
1164 var lines = src.Split(
new[] {
"\r\n",
"\n" }, StringSplitOptions.None).ToList();
1165 var modified =
false;
1167 for (var i = 0; i < lines.Count; i++)
1170 if (!lines[i].Contains(IosIfCondition))
1174 var pos = lines[i].IndexOf(IosIfCondition, StringComparison.Ordinal);
1175 if (pos > 0 &&
char.IsLetterOrDigit(lines[i][pos - 1]))
1180 while (ifLine >= 0 && !lines[ifLine].Contains(
"if"))
1182 if (ifLine < 0)
continue;
1185 if (lines[ifLine].TrimStart().StartsWith(
"#", StringComparison.Ordinal))
1190 for (var j = ifLine; j < lines.Count; j++)
1193 if (lines[j].Contains(
"{")) { openLine = j;
break; }
1195 if (j > ifLine && lines[j].Contains(
"if"))
break;
1197 if (openLine < 0)
continue;
1200 var endLine = FindMatchingClosingBrace(lines, openLine);
1201 if (endLine <= openLine)
continue;
1204 var contentStart = openLine + 1;
1205 var contentEnd = endLine - 1;
1206 if (contentEnd < contentStart)
continue;
1209 if (RegionNearHasMarker(lines, contentStart, IosWrappedComment))
1213 var indent = LeadingWhitespace(lines[contentStart]);
1216 lines.Insert(contentStart, indent + IosFixIfDefine);
1218 lines.Insert(endLine + 1, indent + IosFixEndIfDefine);
1226 return modified ?
string.Join(
"\n", lines) : src;
1229 private static string WrapIosMethodDefinitions(
string src)
1231 var lines = src.Split(
new[] {
"\r\n",
"\n" }, StringSplitOptions.None).ToList();
1232 var modified =
false;
1234 for (var i = 0; i < lines.Count; i++)
1236 var trimmed = lines[i].Trim();
1239 if (RegionNearHasMarker(lines, i, IosWrappedComment))
1242 foreach (var sig
in iosMethodSignatures)
1245 if (!trimmed.Equals(sig, StringComparison.Ordinal))
1250 for (var j = i; j < lines.Count; j++)
1252 if (lines[j].Contains(
"{")) { openLine = j;
break; }
1254 if (j > i && lines[j].TrimStart().StartsWith(
"[", StringComparison.Ordinal))
break;
1256 if (openLine < 0)
break;
1259 var endLine = FindMatchingClosingBrace(lines, openLine);
1260 if (endLine <= openLine)
break;
1262 var contentStart = openLine + 1;
1263 var body = lines.GetRange(contentStart, endLine - contentStart);
1264 body.RemoveAll(line => line.Trim().Equals(IosFixIfDefine, StringComparison.Ordinal) ||
1265 line.Trim().Equals(IosFixEndIfDefine, StringComparison.Ordinal));
1266 body.Insert(0, IosFixIfDefine);
1267 body.Add(IosFixEndIfDefine);
1269 if (!lines.Skip(contentStart).Take(endLine - contentStart).SequenceEqual(body))
1271 lines.RemoveRange(contentStart, endLine - contentStart);
1272 lines.InsertRange(contentStart, body);
1276 i = contentStart + body.Count;
1281 return modified ?
string.Join(
"\n", lines) : src;
1285 private static int FindMatchingClosingBrace(List<string> lines,
int openLine)
1288 for (var i = openLine; i < lines.Count; i++)
1290 depth += CountChar(lines[i],
'{');
1291 depth -= CountChar(lines[i],
'}');
1299 private static int CountChar(
string s,
char ch) => s.Count(t => t == ch);
1301 private static string LeadingWhitespace(
string s)
1304 while (i < s.Length &&
char.IsWhiteSpace(s[i])) i++;
1308 private static bool RegionNearHasMarker(List<string> lines,
int index,
string marker)
1311 var stack =
new Stack<(int start, bool hasMarker)>();
1313 for (var i = 0; i < lines.Count; i++)
1315 var t = lines[i].Trim();
1316 if (t.StartsWith(
"#if"))
1318 var has = t.Contains(marker);
1319 stack.Push((i, has));
1321 else if (t.StartsWith(
"#endif"))
1323 if (stack.Count == 0)
continue;
1324 var (start, has) = stack.Pop();
1325 if (has && index >= start && index <= i)
1341 private static bool EnsureFolderExists(
string assetsRelativePath)
1343 assetsRelativePath = assetsRelativePath.Replace(
"\\",
"/");
1344 if (AssetDatabase.IsValidFolder(assetsRelativePath))
1347 var segments = assetsRelativePath.Split(
'/');
1348 if (segments.Length == 0 || !
string.Equals(segments[0], AssetsFolder, StringComparison.Ordinal))
1349 throw new UnityException($
"Path must be {AssetsFolder}-relative: {assetsRelativePath}");
1351 var current = AssetsFolder;
1352 for (var i = 1; i < segments.Length; i++)
1354 var next = $
"{current}/{segments[i]}";
1355 if (!AssetDatabase.IsValidFolder(next))
1357 AssetDatabase.CreateFolder(current, segments[i]);
1362 return !AssetDatabase.IsValidFolder(assetsRelativePath)
1363 ?
throw new UnityException($
"Unable to create folder: {assetsRelativePath}")
1374 private static bool EnsureAsmdefExists(
string assetsRelativeFile,
string content,
string createdLog)
1376 if (
string.IsNullOrEmpty(assetsRelativeFile))
1379 assetsRelativeFile = assetsRelativeFile.Replace(
"\\",
"/");
1380 var absolute = ToAbsolutePath(assetsRelativeFile);
1381 if (File.Exists(absolute))
1384 var dir = Path.GetDirectoryName(absolute);
1385 if (!Directory.Exists(dir))
1386 Directory.CreateDirectory(dir!);
1388 File.WriteAllText(absolute, content, Encoding.UTF8);
1389 Debug.Log(createdLog);
1390 AssetDatabase.ImportAsset(assetsRelativeFile);
1398 private static string ToAssetsPath(
string possiblyRelativeToAssetsFolder)
1400 var path = possiblyRelativeToAssetsFolder.Replace(
"\\",
"/");
1401 return path.StartsWith($
"{AssetsFolder}/", StringComparison.Ordinal) ? path : $
"{AssetsFolder}/{path}";
1409 private static string ToAssetPathFromAbsolute(
string absolutePath)
1411 var normalized = Path.GetFullPath(absolutePath).Replace(
'\\',
'/');
1412 var dataPath = Application.dataPath.Replace(
'\\',
'/');
1413 if (!normalized.StartsWith(dataPath +
"/", StringComparison.Ordinal))
1414 return string.Empty;
1416 var tail = normalized[dataPath.Length..].TrimStart(
'/');
1417 return string.IsNullOrEmpty(tail) ? AssetsFolder : $
"{AssetsFolder}/{tail}";
1428 private static string ToAbsolutePath(
string assetPath)
1430 if (
string.IsNullOrEmpty(assetPath))
1431 throw new UnityException(
"Empty asset path.");
1433 var normalized = assetPath.Replace(
'\\',
'/');
1436 if (
string.Equals(normalized, AssetsFolder, StringComparison.Ordinal) ||
1437 normalized.StartsWith(AssetsFolder +
"/", StringComparison.Ordinal))
1439 var tail = normalized.Length == AssetsFolder.Length
1441 : normalized[AssetsFolder.Length..].TrimStart(
'/');
1443 var abs = Path.Combine(Application.dataPath, tail);
1444 return Path.GetFullPath(abs).Replace(
'\\',
'/');
1448 if (normalized.StartsWith(PackagesFolder +
"/", StringComparison.Ordinal))
1450 var pinfo = PackageInfo.FindForAssetPath(normalized)
1451 ??
throw new UnityException($
"Unable to resolve package for '{assetPath}'.");
1454 var relInside = normalized.Length > pinfo.assetPath.Length
1455 ? normalized[pinfo.assetPath.Length..].TrimStart(
'/')
1458 var abs =
string.IsNullOrEmpty(relInside)
1459 ? pinfo.resolvedPath
1460 : Path.Combine(pinfo.resolvedPath, relInside);
1462 return Path.GetFullPath(abs).Replace(
'\\',
'/');
1466 if (Path.IsPathRooted(normalized))
1467 return Path.GetFullPath(normalized).Replace(
'\\',
'/');
1470 var projectRoot = Path.GetFullPath(Path.Combine(Application.dataPath,
".."));
1471 return Path.GetFullPath(Path.Combine(projectRoot, normalized)).Replace(
'\\',
'/');