com.amanotes.gdk 0.2.63 → 0.2.65-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/Editor/AmaGDKEditor.cs +23 -7
  3. package/Editor/HideInNormalInspectorDrawer.cs +22 -0
  4. package/Editor/HideInNormalInspectorDrawer.cs.meta +3 -0
  5. package/Extra/AmaGDKInstaller.unitypackage +0 -0
  6. package/Extra/CheckDiskSpace.unitypackage +0 -0
  7. package/Extra/ForceUpdate.unitypackage +0 -0
  8. package/Extra/GoogleCMP.unitypackage +0 -0
  9. package/Extra/GoogleCMP.unitypackage.meta +7 -0
  10. package/Extra/PostProcessor.unitypackage +0 -0
  11. package/Extra/SDKVersionTracking.unitypackage +0 -0
  12. package/Packages/AmaGDKConfig.unitypackage +0 -0
  13. package/Packages/AmaGDKExample.unitypackage +0 -0
  14. package/Packages/AmaGDKTest.unitypackage +0 -0
  15. package/Packages/AppsFlyerAdapter.PurchaseConnector.unitypackage +0 -0
  16. package/Packages/AppsFlyerAdapter.PurchaseConnector.unitypackage.meta +7 -0
  17. package/Packages/AppsFlyerAdapter.unitypackage +0 -0
  18. package/Packages/FirebaseAnalyticsAdapter.unitypackage +0 -0
  19. package/Packages/FirebaseRemoteConfigAdapter.unitypackage +0 -0
  20. package/Packages/IronSourceAdapter.unitypackage +0 -0
  21. package/Packages/RevenueCatAdapter.unitypackage +0 -0
  22. package/Packages/SqliteAnalyticsAdapter.unitypackage +0 -0
  23. package/Runtime/AmaGDK.Adapters.cs +14 -3
  24. package/Runtime/AmaGDK.Ads.cs +29 -2
  25. package/Runtime/AmaGDK.Analytics.cs +9 -20
  26. package/Runtime/AmaGDK.Mono.cs +111 -0
  27. package/Runtime/AmaGDK.Mono.cs.meta +3 -0
  28. package/Runtime/AmaGDK.RemoteConfig.cs +3 -0
  29. package/Runtime/AmaGDK.Singleton.cs +27 -0
  30. package/Runtime/AmaGDK.Singleton.cs.meta +3 -0
  31. package/Runtime/AmaGDK.UserProfile.cs +119 -40
  32. package/Runtime/AmaGDK.cs +38 -59
  33. package/Runtime/Core/GDKRoutine.cs +61 -0
  34. package/Runtime/Core/GDKRoutine.cs.meta +3 -0
  35. package/Runtime/Core/GDKUnityCallback.cs +141 -0
  36. package/Runtime/Core/GDKUnityCallback.cs.meta +3 -0
  37. package/Runtime/Core.meta +8 -0
  38. package/Runtime/Internal/AmaGDK.Internal.AmaGUI.cs +19 -2
  39. package/Runtime/Internal/AmaGDK.Internal.cs +4 -8
  40. package/Runtime/Internal/AmaGDK.Utils.cs +3 -56
  41. package/Runtime/Internal/HideInNormalInspectorAttribute.cs +9 -0
  42. package/Runtime/Internal/HideInNormalInspectorAttribute.cs.meta +3 -0
  43. package/Runtime/Klavar/Attributes/AccumulatedCountAttribute.cs +0 -1
  44. package/Runtime/Klavar/Attributes/MinMaxAttribute.cs +86 -79
  45. package/Runtime/Klavar/Attributes/MinMaxLengthAttribute.cs +47 -26
  46. package/Runtime/Klavar/Attributes/NotNullAttribute.cs +1 -1
  47. package/Runtime/Klavar/Attributes/RegexPatternAttribute.cs +10 -7
  48. package/Runtime/Klavar/Attributes/ToStringAttribute.cs +1 -1
  49. package/Runtime/Klavar/KlavarContainer.cs +21 -5
  50. package/Runtime/Klavar/KlavarEvent.cs +4 -1
  51. package/Runtime/Utils/GDKUtils.cs +35 -0
  52. package/Runtime/Utils/GDKUtils.cs.meta +3 -0
  53. package/Runtime/Utils.meta +8 -0
  54. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.66 - 2024-06-13]
4
+ - [Fix] Missing Google CMP in package release
5
+
6
+ ## [0.2.65 - 2024-06-13]
7
+ - [Dev] Add GDKRoutine
8
+ - [Dev] Add option to preload banners
9
+ - [Fix] Handle GoogleCMP callback in main-thread
10
+ - Also: Ensure Logging.LogError is thead-safe
11
+ - [Fix] Manage banner status so not to request banner load multiple times
12
+ - [Add] GDKUnityCallback with thread-safe
13
+ - [Feature] Add fullads_impression & videoads_impression events for better fill rate tracking
14
+ - [Klavar] Improve parameter validation
15
+
16
+ ## [0.2.64 - 2024-06-06]
17
+ - [Dev] Log gdk_init
18
+ - [Fix] map_user_id must contain FRB, AF, [GAID, IDFA]
19
+ - [Dev] Check duplicated SKAdNetwork
20
+ - [Dev] New editor attribute [HideInNormalInspector]
21
+ - [Dev] Public ClearCacheData method
22
+ - [Feature] Add adapter config validation
23
+ - [Feature] Add DevMode for remote config fetching
24
+ - [Fix] Create button did not create a new AmaGDKConfig asset in Resources
25
+ - Also: Hide config details
26
+ - [Feature] Support AF Purchase Connector and deeplinking
27
+
3
28
  ## [0.2.63 - 2024-06-01]
4
29
  - [Fix] Do not include default GDK config asset
5
30
  - [Fix] Support compare SemVer with an invalid value
@@ -100,6 +100,11 @@ namespace Amanotes.Editor
100
100
  if (AmaGUI.BigButton("CREATE", 40f, BLUE))
101
101
  {
102
102
  AssetDatabase.ImportPackage(Res.AMAGDK_CONFIG_PACKAGE, false);
103
+
104
+ ScriptableObject config = CreateInstance("Amanotes.Core.AmaGDKConfigAsset");
105
+ AssetDatabase.CreateAsset(config, "Assets/AmaGDK/Resources/AmaGDKConfig.asset");
106
+ AssetDatabase.SaveAssets();
107
+ TryLoadConfigAsset();
103
108
  adapterScanned = false;
104
109
  }
105
110
 
@@ -301,17 +306,16 @@ namespace Amanotes.Editor
301
306
 
302
307
  configSO = new SerializedObject(configAsset);
303
308
  configSO.Update();
304
-
305
309
  DrawProperty(configSO, "common");
310
+ EditorGUILayout.ObjectField("Config", configAsset, typeof(ScriptableObject), false);
306
311
  GUILayout.Space(8f);
307
312
  for (var i = 0; i < allAdapters.Count; i++)
308
313
  {
309
314
  var item = allAdapters[i];
310
315
  DrawAdapterDetail(item.id, item.adapter);
311
316
  }
312
-
313
317
  EditorGUILayout.Space();
314
- AmaGUI.Foldout("Config", ref showConfigDetail, DrawGUI_ConfigDetail, DrawGUI_ConfigAsset);
318
+
315
319
  AmaGUI.Foldout("Adapters", ref showAdapterInstaller, DrawGUI_AdapterList);
316
320
  AmaGUI.Foldout("Extras", ref showExtra, DrawGUI_ExtraList);
317
321
  Profiler.EndSample();
@@ -370,6 +374,11 @@ namespace Amanotes.Editor
370
374
  configOpen.Add(adapterId, isOpen = false);
371
375
  }
372
376
 
377
+ var configStatus = adapter?.configStatus ?? Adapter2.ConfigStatus.Ok;
378
+ Texture2D icon =
379
+ configStatus == Adapter2.ConfigStatus.Invalid ? (Texture2D)EditorGUIUtility.IconContent("console.erroricon.sml").image :
380
+ configStatus == Adapter2.ConfigStatus.Warning ? (Texture2D)EditorGUIUtility.IconContent("console.warnicon.sml").image : null;
381
+
373
382
  SDKStatus status = SDKStatus.Get(adapterId);
374
383
  bool changed = AmaGUI.Foldout(ObjectNames.NicifyVariableName(adapterId), ref isOpen, () =>
375
384
  {
@@ -392,10 +401,17 @@ namespace Amanotes.Editor
392
401
 
393
402
  }, () =>
394
403
  {
404
+ if (icon != null)
405
+ {
406
+ Rect rect = GUILayoutUtility.GetRect(6f, 6f, 16f, 16f);
407
+ rect.xMin -= 10f;
408
+ rect.y += 1f;
409
+ GUI.DrawTexture(rect, icon);
410
+ }
395
411
  GUILayout.FlexibleSpace();
396
-
412
+
397
413
  bool installed = status.adapterInstalled;
398
- bool adapterActive = installed && adapter.enabled;
414
+ bool adapterActive = installed && adapter != null && adapter.enabled;
399
415
 
400
416
  if (!adapterActive)
401
417
  {
@@ -407,7 +423,7 @@ namespace Amanotes.Editor
407
423
  GUILayout.Label(label, GUILayout.Width(w));
408
424
  GUI.color = c;
409
425
  }
410
-
426
+
411
427
  AmaGUI.DrawVersionTag(status.sdkVersion, adapterActive);
412
428
 
413
429
  if (installed)
@@ -421,7 +437,7 @@ namespace Amanotes.Editor
421
437
  }
422
438
  }
423
439
  });
424
-
440
+
425
441
 
426
442
 
427
443
  if (changed) configOpen[adapterId] = isOpen;
@@ -0,0 +1,22 @@
1
+ using UnityEngine;
2
+ using UnityEngine.UIElements;
3
+ using UnityEditor;
4
+
5
+ namespace Amanotes.Core.Editor
6
+ {
7
+ [CustomPropertyDrawer(typeof(HideInNormalInspectorAttribute))]
8
+ public class HideInNormalInspectorDrawer : PropertyDrawer
9
+ {
10
+ public override VisualElement CreatePropertyGUI(SerializedProperty property)
11
+ {
12
+ var mode = ActiveEditorTracker.sharedTracker.inspectorMode;
13
+ return mode == InspectorMode.Normal ? new VisualElement() : base.CreatePropertyGUI(property);
14
+ }
15
+
16
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
17
+ return 0f;
18
+ }
19
+
20
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {}
21
+ }
22
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 66d88278f0e44820aebc1e5a5e94e15f
3
+ timeCreated: 1717409948
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: 4e220bd58ce0646bcbbb209dc8451d25
3
+ DefaultImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,7 @@
1
+ fileFormatVersion: 2
2
+ guid: d0d621a4b24af4e399401664334f0d2c
3
+ DefaultImporter:
4
+ externalObjects: {}
5
+ userData:
6
+ assetBundleName:
7
+ assetBundleVariant:
@@ -6,16 +6,27 @@ using static Amanotes.Core.Internal.Logging;
6
6
 
7
7
  namespace Amanotes.Core.Internal
8
8
  {
9
+
10
+
9
11
  public abstract partial class Adapter2
10
12
  {
13
+ internal protected enum ConfigStatus
14
+ {
15
+ Ok,
16
+ Invalid,
17
+ Warning
18
+ }
19
+
11
20
  // internal use
12
- [HideInInspector] [SerializeField] internal int initOrder;
13
- [HideInInspector] [SerializeField] internal bool enabled = true;
21
+ [HideInNormalInspector] [SerializeField] internal int initOrder;
22
+ [HideInNormalInspector] [SerializeField] internal bool enabled = true;
14
23
  [NonSerialized] internal string adapterId;
15
24
  [NonSerialized] internal string adapterVersion;
16
25
  [NonSerialized] internal protected Status status = Status.None;
17
26
 
18
27
  protected abstract void Init(Action<bool> onComplete);
28
+ internal protected abstract string GetServiceKey();
29
+ internal protected abstract ConfigStatus configStatus { get; }
19
30
 
20
31
  public bool IsReady => status == Status.Ready;
21
32
 
@@ -55,7 +66,7 @@ namespace Amanotes.Core.Internal
55
66
 
56
67
  adapter.adapterId = id;
57
68
  adapter.adapterVersion = version;
58
- GDKUtils.RegisterUnityCallbacks(adapter);
69
+ GDKUtils.AddUnityCallbacks(adapter);
59
70
  adapterMap.Add(id, adapter);
60
71
  return true;
61
72
  }
@@ -275,7 +275,7 @@ namespace Amanotes.Core
275
275
 
276
276
  if (Config.ad.enableInterstitial) _adapter.interstitial.StartLoadAd();
277
277
  if (Config.ad.enableRewarded) _adapter.rewardVideo.StartLoadAd();
278
-
278
+ if (Config.ad.preloadBanner) PreloadBanner();
279
279
  if (_isBannerShowing)
280
280
  {
281
281
  _isBannerShowing = false;
@@ -323,6 +323,30 @@ namespace Amanotes.Core
323
323
 
324
324
  [NonSerialized] private static bool _isBannerShowing = false;
325
325
 
326
+ public static void PreloadBanner()
327
+ {
328
+ if (!_config.ad.enableBanner)
329
+ {
330
+ LogWarning($"Banner not enabled! {_config.ad.enableBanner}");
331
+ return;
332
+ }
333
+
334
+ if (_adapter == null)
335
+ {
336
+ LogWarning($"Adapter is null!");
337
+ return;
338
+ }
339
+
340
+ if (_isBannerShowing)
341
+ {
342
+ LogWarning($"Banner is showing!");
343
+ return;
344
+ }
345
+
346
+ _adapter.ShowBanner();
347
+ _adapter.HideBanner();
348
+ }
349
+
326
350
  public static void ShowBanner()
327
351
  {
328
352
  if (!_config.ad.enableBanner)
@@ -426,6 +450,9 @@ namespace Amanotes.Core.Internal
426
450
  [Tooltip("Before showing an ad: set Time.timeScale to 0\nAfter the ad is closed: restore the previously saved Time.timeScale")]
427
451
  public bool autoTimeScale = true;
428
452
 
453
+ [Tooltip("Preload banner right when Ad module init (default = false)")]
454
+ public bool preloadBanner = false;
455
+
429
456
  [Header("EDITOR")]
430
457
  [Tooltip("Only applicable in Editor\n0 = 0% success\n1 = 100% success")]
431
458
  [Range(0f, 1f)] public float rewardAdShowSuccessRate = 1;
@@ -444,7 +471,7 @@ namespace Amanotes.Core.Internal
444
471
  // APIs
445
472
  internal protected abstract AdLogic interstitial { get; }
446
473
  internal protected abstract AdLogic rewardVideo { get; }
447
-
474
+
448
475
  public abstract void ShowBanner();
449
476
  public abstract void HideBanner();
450
477
 
@@ -160,14 +160,6 @@ namespace Amanotes.Core
160
160
  logEventHook = condition;
161
161
  }
162
162
 
163
- public static void SetUserId(string userId)
164
- {
165
- foreach (AnalyticsAdapter s in listAdapters)
166
- {
167
- s.SetUserId(userId);
168
- }
169
- }
170
-
171
163
  public static void SetUserProperty(string key, string value)
172
164
  {
173
165
  foreach (AnalyticsAdapter s in listAdapters)
@@ -351,8 +343,7 @@ namespace Amanotes.Core
351
343
  // All adapters should be ready now!
352
344
 
353
345
  // TODO: Refactor Resolve Early Calls
354
- onFrameUpdate -= ProcessEventQueue;
355
- onFrameUpdate += ProcessEventQueue;
346
+ GDKUtils.OnFrameUpdate(ProcessEventQueue);
356
347
  }
357
348
 
358
349
  internal static EventParams PrepareEvent(string eventName)
@@ -1104,11 +1095,11 @@ namespace Amanotes.Core.Internal
1104
1095
  {
1105
1096
  // [Header("----- MODULE CONFIG -----")]
1106
1097
  public bool showAnalyticsLog;
1107
- [HideInInspector] public bool normalizeEventName = true;
1108
- [HideInInspector] public bool checkMigrationIntegrity = true;
1109
- [HideInInspector] public bool migrateAccumulatedCount = true;
1110
- [HideInInspector] public bool enableEventStat;
1111
- [HideInInspector] public List<string> immediatelyLoggedEvents = new List<string>(){"ad_impression", "ad_impression_ama"};
1098
+ [HideInNormalInspector] public bool normalizeEventName = true;
1099
+ [HideInNormalInspector] public bool checkMigrationIntegrity = true;
1100
+ [HideInNormalInspector] public bool migrateAccumulatedCount = true;
1101
+ [HideInNormalInspector] public bool enableEventStat;
1102
+ [HideInNormalInspector] public List<string> immediatelyLoggedEvents = new List<string>(){"ad_impression", "ad_impression_ama"};
1112
1103
  public AnalyticQualityConfig quality;
1113
1104
  }
1114
1105
 
@@ -1134,8 +1125,8 @@ namespace Amanotes.Core.Internal
1134
1125
 
1135
1126
  public abstract class AnalyticsAdapter : Adapter2
1136
1127
  {
1137
- [HideInInspector] public List<string> NeverSend = new List<string>();
1138
- [HideInInspector] public List<string> AlwaysSend = new List<string>();
1128
+ [HideInNormalInspector] public List<string> NeverSend = new List<string>();
1129
+ [HideInNormalInspector] public List<string> AlwaysSend = new List<string>();
1139
1130
  protected abstract bool SendEventByDefault { get; }
1140
1131
 
1141
1132
  public bool IsForbidden(string eventName)
@@ -1147,10 +1138,8 @@ namespace Amanotes.Core.Internal
1147
1138
 
1148
1139
  public abstract void LogEvent(string eventNameToSend, Dictionary<string, object> parameters);
1149
1140
 
1150
- public abstract void SetUserId(string userId);
1141
+ // public abstract void SetUserId(string userId);
1151
1142
 
1152
1143
  public abstract void SetUserProperty(string key, string value);
1153
-
1154
- public abstract void GetAppInstanceID(Action<string> appId);
1155
1144
  }
1156
1145
  }
@@ -0,0 +1,111 @@
1
+ using Amanotes.Core.Internal;
2
+ using System;
3
+ using System.Collections;
4
+ using System.Collections.Generic;
5
+ using System.Threading;
6
+ using UnityEngine;
7
+
8
+ namespace Amanotes.Core
9
+ {
10
+ public partial class AmaGDK
11
+ {
12
+ internal static int mainThreadId = -1;
13
+ internal static readonly GDKUnityCallback unityCallbacks = new GDKUnityCallback();
14
+
15
+ internal static float realtimeSinceStartup;
16
+ internal static float time;
17
+
18
+ private void Update()
19
+ {
20
+ if (!isReady) return;
21
+
22
+ time = Time.time;
23
+ realtimeSinceStartup = Time.realtimeSinceStartup;
24
+
25
+ var cb = unityCallbacks;
26
+ cb.Invoke(ref cb.OnFrameUpdate);
27
+ cb.Invoke(ref cb.DoOnceNextFrame);
28
+ }
29
+
30
+ private void OnApplicationPause(bool pauseStatus)
31
+ {
32
+ var cb = unityCallbacks;
33
+ cb.Invoke(ref cb.OnApplicationPause, pauseStatus);
34
+ }
35
+
36
+ private void OnApplicationFocus(bool hasFocus)
37
+ {
38
+ var cb = unityCallbacks;
39
+ cb.Invoke(ref cb.OnApplicationFocus, hasFocus);
40
+ }
41
+
42
+ private void OnApplicationQuit()
43
+ {
44
+ var cb = unityCallbacks;
45
+ cb.Invoke(ref cb.OnApplicationQuit);
46
+ }
47
+ }
48
+ }
49
+
50
+ namespace Amanotes.Core.Internal
51
+ {
52
+
53
+
54
+ public static partial class GDKUtils
55
+ {
56
+ public static bool isOnMainThread => Thread.CurrentThread.ManagedThreadId == AmaGDK.mainThreadId;
57
+ public static void DoOnMainThread(Action action)
58
+ {
59
+ if (isOnMainThread)
60
+ {
61
+ SafeInvoke(action);
62
+ return;
63
+ }
64
+
65
+ DoOnceNextFrame(action);
66
+ }
67
+
68
+ public static void OnFrameUpdate(Action action)
69
+ {
70
+ var cb = AmaGDK.unityCallbacks;
71
+ cb.AddCallback(ref cb.OnFrameUpdate, action);
72
+ }
73
+
74
+ public static void DoOnceNextFrame(Action action)
75
+ {
76
+ var cb = AmaGDK.unityCallbacks;
77
+ cb.AddCallback(ref cb.DoOnceNextFrame, action);
78
+ }
79
+
80
+ public static void DelayCall(float seconds, Action action)
81
+ {
82
+ DoOnMainThread(() => AmaGDK._instance.StartCoroutine(DelayCallRoutine(seconds, action)));
83
+ }
84
+
85
+ public static IEnumerator Timeout(float timeoutInSecs, Func<bool> completeCheckFunc)
86
+ {
87
+ float time = Time.realtimeSinceStartup;
88
+ while (completeCheckFunc() == false)
89
+ {
90
+ yield return null;
91
+ if (Time.realtimeSinceStartup - time > timeoutInSecs) yield break;
92
+ }
93
+ }
94
+
95
+ private static IEnumerator DelayCallRoutine(float seconds, Action callback)
96
+ {
97
+ yield return new WaitForSecondsRealtime(seconds);
98
+ callback();
99
+ }
100
+
101
+ public static void AddUnityCallbacks<T>(T observer)
102
+ {
103
+ AmaGDK.unityCallbacks.AddObserver(observer);
104
+ }
105
+
106
+ public static void RemoveUnityCallbacks<T>(T observer)
107
+ {
108
+ AmaGDK.unityCallbacks.RemoveObserver(observer);
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 66a09cd9d2b8423e885352a8441c6468
3
+ timeCreated: 1718074317
@@ -5,6 +5,7 @@ using System.Text;
5
5
  using UnityEngine;
6
6
  using Amanotes.Core.Internal;
7
7
  using UnityEngine.Profiling;
8
+ using UnityEngine.Serialization;
8
9
  using static Amanotes.Core.Internal.Logging;
9
10
 
10
11
  #if UNITY_EDITOR
@@ -369,9 +370,11 @@ namespace Amanotes.Core.Internal
369
370
  [Serializable]
370
371
  public class RemoteConfigConfig
371
372
  {
373
+ [FormerlySerializedAs("firstFetchTimeOutInSeconds")]
372
374
  [Tooltip("Fetch timeout for first session (in secs)")]
373
375
  public float firstFetchTimeOutInSeconds = 10;
374
376
 
377
+ [FormerlySerializedAs("defaultTimeOutInSeconds")]
375
378
  [Tooltip("Fetch timeout for 2nd+ session (in secs)")]
376
379
  public float defaultTimeOutInSeconds = 3;
377
380
 
@@ -0,0 +1,27 @@
1
+
2
+ using System.Threading;
3
+ using static Amanotes.Core.Internal.Logging;
4
+ using static Amanotes.Core.Internal.Messsage;
5
+
6
+ namespace Amanotes.Core
7
+ {
8
+ public partial class AmaGDK
9
+ {
10
+ internal static AmaGDK _instance;
11
+
12
+ private void Awake()
13
+ {
14
+ if (_instance != null)
15
+ {
16
+ LogWarning(MULTIPLE_INSTANCE);
17
+ Destroy(this);
18
+ return;
19
+ }
20
+
21
+ _instance = this;
22
+ mainThreadId = Thread.CurrentThread.ManagedThreadId;
23
+ DontDestroyOnLoad(this);
24
+ StartCoroutine(InitRoutine());
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: 059e9f9cfffd44639fc660df9537fa37
3
+ timeCreated: 1718081104