diff --git a/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/AdmobAdsManager.cs b/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/AdmobAdsManager.cs index ae2ae34..1a2aeb1 100644 --- a/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/AdmobAdsManager.cs +++ b/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/AdmobAdsManager.cs @@ -6,7 +6,6 @@ using Script.SDKManager.AdsSDKManager.Utils; using Script.Utils; using SDK.Utils; using SDKManager.AdsSDKManager.Constant; -using Unity.VisualScripting; namespace Script.SDKManager.AdsSDKManager.AdmobAdsManager { @@ -63,8 +62,8 @@ namespace Script.SDKManager.AdsSDKManager.AdmobAdsManager #region 激励广告功能 public void LoadRewarded() { - _admobRewardedAdManager = new AdmobRewardedAdManager(); _admobRewardedAdManager.Destroy(); + _admobRewardedAdManager = new AdmobRewardedAdManager(); _admobRewardedAdManager.InitializeAdUnits( _rewardedAdUnits, OnRewardedAdLoaded, @@ -115,7 +114,6 @@ namespace Script.SDKManager.AdsSDKManager.AdmobAdsManager _rvCloseCallback?.Invoke(true); _rvCloseCallback = null; - _admobRewardedAdManager.Destroy(); LoadRewarded(); } @@ -123,7 +121,6 @@ namespace Script.SDKManager.AdsSDKManager.AdmobAdsManager { _rvShowFailedCallback?.Invoke(); _rvShowFailedCallback = null; - _admobRewardedAdManager.Destroy(); LoadRewarded(); } @@ -142,8 +139,8 @@ namespace Script.SDKManager.AdsSDKManager.AdmobAdsManager #region 插页广告功能 public void LoadInterstitial() { - _admobInterstitialAdManager = new AdmobInterstitialAdManager(); _admobInterstitialAdManager.Destroy(); + _admobInterstitialAdManager = new AdmobInterstitialAdManager(); _admobInterstitialAdManager.InitializeAdUnits( _interstitialAdUnits, OnInterstitialAdLoaded, @@ -201,7 +198,6 @@ namespace Script.SDKManager.AdsSDKManager.AdmobAdsManager { _ivCloseCallback?.Invoke(); _ivCloseCallback = null; - _admobInterstitialAdManager.Destroy(); LoadInterstitial(); } @@ -209,7 +205,6 @@ namespace Script.SDKManager.AdsSDKManager.AdmobAdsManager { _ivCloseCallback?.Invoke(); _ivCloseCallback = null; - _admobInterstitialAdManager.Destroy(); LoadInterstitial(); } #endregion @@ -219,8 +214,8 @@ namespace Script.SDKManager.AdsSDKManager.AdmobAdsManager #region 开屏广告功能 public void LoadSplash() { - _admobSplashAdManager = new AdmobSplashAdManager(); _admobSplashAdManager.Destroy(); + _admobSplashAdManager = new AdmobSplashAdManager(); _admobSplashAdManager.InitializeAdUnits( _splashAdUnits, OnSplashAdLoaded, diff --git a/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobUtils.cs b/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobTools.cs similarity index 100% rename from Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobUtils.cs rename to Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobTools.cs diff --git a/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobTools.cs.meta b/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobTools.cs.meta new file mode 100644 index 0000000..b42d2cb --- /dev/null +++ b/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobTools.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3a7b10d4700bc4808b22cd6be81314e8 \ No newline at end of file diff --git a/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobUtils.cs.meta b/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobUtils.cs.meta deleted file mode 100644 index 0a6b527..0000000 --- a/Assets/Script/SDKManager/AdsSDKManager/AdmobAdsManager/Utils/AdmobUtils.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: cc11ff1f480fa4d58969d2154bf532ec \ No newline at end of file diff --git a/Assets/Script/SDKManager/AdsSDKManager/AdsSDKManager.cs b/Assets/Script/SDKManager/AdsSDKManager/AdsSDKManager.cs index 0cf6fd4..5f648a7 100644 --- a/Assets/Script/SDKManager/AdsSDKManager/AdsSDKManager.cs +++ b/Assets/Script/SDKManager/AdsSDKManager/AdsSDKManager.cs @@ -8,6 +8,7 @@ using Script.SDKManager.AdsSDKManager.BigoAdsManager; using Script.SDKManager.AdsSDKManager.MaxAdsManager; using Script.SDKManager.AdsSDKManager.TpnAdsManager; using Script.SDKManager.AdsSDKManager.Utils; +using Script.Utils; using SDK.Utils; using SDKManager.AdsSDKManager.Constant; using UnityEngine; @@ -27,15 +28,22 @@ public class AdsSDKManager : NormalSingleton private void InitSDK() { - KwaiAdsManager.Instance.Init(); - AdmobAdsManager.Instance.Initialize(); - BigoAdsManager.Instance.Initialize(); - TpnAdsManager.Instance.Initialize(); - MaxAdsManager.Instance.Initialize(); + // 初始化广告平台状态 + BidPlatformManager.Instance.InitializePlatformStates(AdConfigParser.GetAdExpireInSec()); + InitializeAdNetworks(); + } + + private void InitializeAdNetworks() + { _adNetworks.Add(AdmobAdsManager.Instance); _adNetworks.Add(BigoAdsManager.Instance); _adNetworks.Add(TpnAdsManager.Instance); _adNetworks.Add(MaxAdsManager.Instance); + + foreach (var network in _adNetworks) + { + network.Initialize(); + } } public bool IsRewardAdReady() @@ -54,8 +62,9 @@ public class AdsSDKManager : NormalSingleton _showFailedCallback?.Invoke(); return; } - + PlatformType result = GetBestPlatformType(false); + BidPlatformManager.Instance.RecordBidSuccess(result, AdsType.Rewarded); if (result == PlatformType.Max) { MaxAdsManager.Instance.DisplayRewarded(_adPos, _rewardCallback, _showFailedCallback); @@ -73,13 +82,14 @@ public class AdsSDKManager : NormalSingleton TpnAdsManager.Instance.DisplayRewarded(_adPos, _rewardCallback, _showFailedCallback); } else if (result == PlatformType.Kwai) - { + { } else { _showFailedCallback?.Invoke(); - } + } + CheckAndRefreshExpiredBids(AdsType.Rewarded); } public bool IsInterstitialReady() @@ -99,15 +109,16 @@ public class AdsSDKManager : NormalSingleton _closeCallback = null; return; } - + PlatformType result = GetBestPlatformType(true); + BidPlatformManager.Instance.RecordBidSuccess(result, AdsType.Interstitial); if (result == PlatformType.Max) { - MaxAdsManager.Instance.DisplayInterstitial(_adPos, _IvType, _closeCallback); + MaxAdsManager.Instance.DisplayInterstitial(_adPos, _IvType,_closeCallback); } else if (result == PlatformType.Admob) { - AdmobAdsManager.Instance.DisplayInterstitial(_adPos, _IvType, _closeCallback); + AdmobAdsManager.Instance.DisplayInterstitial(_adPos, _IvType,_closeCallback); } else if (result == PlatformType.Bigo) { @@ -118,13 +129,15 @@ public class AdsSDKManager : NormalSingleton TpnAdsManager.Instance.DisplayInterstitial(_adPos, _IvType, _closeCallback); } else if (result == PlatformType.Kwai) - { + { } else { _closeCallback?.Invoke(); } + // 刷新其他类型广告 + CheckAndRefreshExpiredBids(AdsType.Interstitial); } private PlatformType GetBestPlatformType(bool isInterstitial) @@ -135,6 +148,8 @@ public class AdsSDKManager : NormalSingleton maxPrice: MaxAdsManager.Instance.GetInterstitialRevenue(), admobPrice: AdmobAdsManager.Instance.GetInterstitialRevenue(), bigoPrice: BigoAdsManager.Instance.GetInterstitialRevenue(), + // todo : 这里的kwaiPrice暂时设置为0,后续需要根据实际情况设置 + kwaiPrice: 0, toponAdUnitId: TpnAdsManager.Instance.topon_interstitial_units ); return AdsBidResult.GetPlatformType(priceInfo); @@ -145,12 +160,143 @@ public class AdsSDKManager : NormalSingleton maxPrice: MaxAdsManager.Instance.GetRewardedRevenue(), admobPrice: AdmobAdsManager.Instance.GetRewardedRevenue(), bigoPrice: BigoAdsManager.Instance.GetRewardedRevenue(), + // todo : 这里的kwaiPrice暂时设置为0,后续需要根据实际情况设置 + kwaiPrice: 0, toponAdUnitId: TpnAdsManager.Instance.topon_rewarded_units ); return AdsBidResult.GetPlatformType(priceInfo); } } + // 检查并刷新过期竞价 + private void CheckAndRefreshExpiredBids(AdsType _adsType) + { + Dictionary> expiredBids = BidPlatformManager.Instance.GetExpiredBids(); + + foreach (var kvp in expiredBids) + { + PlatformType platformName = kvp.Key; + foreach (AdsType adType in kvp.Value) + { + if (_adsType == adType) + { + LoggerUtils.Debug($"{platformName} 平台 {adType} 广告竞价已过期,重新加载广告"); + RefreshPlatformAds(platformName, adType); + } + } + } + } + private void RefreshPlatformAds(PlatformType platformName, AdsType adType) + { + switch (platformName) + { + case PlatformType.Admob: + RefreshAdmobAds(adType); + break; + case PlatformType.Tpn: + RefreshTopOnAds(adType); + break; + case PlatformType.Max: + RefreshMaxAds(adType); + break; + case PlatformType.Bigo: + RefreshBigoAds(adType); + break; + case PlatformType.Kwai: + RefreshKwaiAds(adType); + break; + default: + break; + } + } + + private void RefreshAdmobAds(AdsType adType) + { + switch (adType) + { + case AdsType.Rewarded: + AdmobAdsManager.Instance.LoadRewarded(); + break; + case AdsType.Interstitial: + AdmobAdsManager.Instance.LoadInterstitial(); + break; + case AdsType.Banner: + AdmobAdsManager.Instance.LoadBanner(); + break; + case AdsType.Native: + AdmobAdsManager.Instance.LoadNative(); + break; + case AdsType.Splash: + AdmobAdsManager.Instance.LoadSplash(); + break; + default: + break; + } + } + + // 刷新TopOn广告 + private void RefreshTopOnAds(AdsType adType) + { + switch (adType) + { + case AdsType.Rewarded: + TpnAdsManager.Instance.LoadRewarded(); + break; + case AdsType.Interstitial: + TpnAdsManager.Instance.LoadInterstitial(); + break; + default: + break; + } + } + + // 刷新Max广告 + private void RefreshMaxAds(AdsType adType) + { + switch (adType) + { + case AdsType.Rewarded: + MaxAdsManager.Instance.LoadRewarded(); + break; + case AdsType.Interstitial: + MaxAdsManager.Instance.LoadInterstitial(); + break; + default: + break; + } + } + + // 刷新Bigo广告 + private void RefreshBigoAds(AdsType adType) + { + switch (adType) + { + case AdsType.Rewarded: + BigoAdsManager.Instance.LoadRewarded(); + break; + case AdsType.Interstitial: + BigoAdsManager.Instance.LoadInterstitial(); + break; + default: + break; + } + } + + private void RefreshKwaiAds (AdsType adType) + { + switch (adType) + { + case AdsType.Rewarded: + // todo: 刷新激励广告 + break; + case AdsType.Interstitial: + // todo: 刷新插屏广告 + break; + default: + break; + } + } + /// /// 广告看完回调 /// diff --git a/Assets/Script/SDKManager/AdsSDKManager/BigoAdsManager/BigoAdsManager.cs b/Assets/Script/SDKManager/AdsSDKManager/BigoAdsManager/BigoAdsManager.cs index 2c498eb..12780e2 100644 --- a/Assets/Script/SDKManager/AdsSDKManager/BigoAdsManager/BigoAdsManager.cs +++ b/Assets/Script/SDKManager/AdsSDKManager/BigoAdsManager/BigoAdsManager.cs @@ -94,7 +94,6 @@ namespace Script.SDKManager.AdsSDKManager.BigoAdsManager { _rvCloseCallback?.Invoke(true); _rvCloseCallback = null; - _rewardedAdManager.Destroy(); LoadRewarded(); } @@ -103,7 +102,6 @@ namespace Script.SDKManager.AdsSDKManager.BigoAdsManager { _rvShowFailedCallback?.Invoke(); _rvShowFailedCallback = null; - _rewardedAdManager.Destroy(); LoadRewarded(); } @@ -114,7 +112,7 @@ namespace Script.SDKManager.AdsSDKManager.BigoAdsManager public void LoadRewarded() { - + _rewardedAdManager.Destroy(); foreach (var adUnitId in _rewardedAdUnits) { _rewardedAdManager.LoadAd(adUnitId); @@ -159,7 +157,6 @@ namespace Script.SDKManager.AdsSDKManager.BigoAdsManager { _ivCloseCallback?.Invoke(); _ivCloseCallback = null; - _interstitialAdManager.Destroy(); LoadInterstitial(); } @@ -167,7 +164,6 @@ namespace Script.SDKManager.AdsSDKManager.BigoAdsManager { _ivCloseCallback?.Invoke(); _ivCloseCallback = null; - _interstitialAdManager.Destroy(); LoadInterstitial(); } @@ -179,6 +175,7 @@ namespace Script.SDKManager.AdsSDKManager.BigoAdsManager public void LoadInterstitial() { + _interstitialAdManager.Destroy(); foreach (var adUnitId in _interstitialAdUnits) { _interstitialAdManager.LoadAd(adUnitId); diff --git a/Assets/Script/SDKManager/AdsSDKManager/Utils/AdConfigParser.cs b/Assets/Script/SDKManager/AdsSDKManager/Utils/AdConfigParser.cs index bc02b5d..e16da87 100644 --- a/Assets/Script/SDKManager/AdsSDKManager/Utils/AdConfigParser.cs +++ b/Assets/Script/SDKManager/AdsSDKManager/Utils/AdConfigParser.cs @@ -177,7 +177,7 @@ namespace Script.SDKManager.AdsSDKManager.Utils /// public static int GetAdExpireInSec() { - return _config?.ad_expire_in_sec ?? 3000; // 默认值 + return _config?.ad_expire_in_sec ?? -1; // 默认值 } /// diff --git a/Assets/Script/SDKManager/AdsSDKManager/Utils/AdsBidResult.cs b/Assets/Script/SDKManager/AdsSDKManager/Utils/AdsBidResult.cs index cce3736..e73e795 100644 --- a/Assets/Script/SDKManager/AdsSDKManager/Utils/AdsBidResult.cs +++ b/Assets/Script/SDKManager/AdsSDKManager/Utils/AdsBidResult.cs @@ -11,13 +11,15 @@ namespace Script.SDKManager.AdsSDKManager.Utils public double MaxPrice; public double AdmobPrice; public double BigoPrice; + public double KwaiPrice; public string ToponAdUnitId; - public AdPriceInfo(double maxPrice, double admobPrice, double bigoPrice, string toponAdUnitId) + public AdPriceInfo(double maxPrice, double admobPrice, double bigoPrice, double kwaiPrice,string toponAdUnitId) { MaxPrice = maxPrice; AdmobPrice = admobPrice; BigoPrice = bigoPrice; + KwaiPrice = kwaiPrice; ToponAdUnitId = toponAdUnitId; } } @@ -27,6 +29,7 @@ namespace Script.SDKManager.AdsSDKManager.Utils private static object m_maxObj = "m_maxObj"; private static object m_admobObj = "m_admobObj"; private static object m_bigoObj = "m_bigoObj"; + private static object m_kwaiObj = "m_kwaiObj"; public static PlatformType GetPlatformType(AdPriceInfo priceInfo) { #if UNITY_ANDROID && !UNITY_EDITOR @@ -37,12 +40,15 @@ namespace Script.SDKManager.AdsSDKManager.Utils AndroidJavaObject admobCustomContentInfo = new AndroidJavaObject("com.anythink.core.api.ATCustomContentInfo", priceInfo.AdmobPrice * 1000, m_admobObj); AndroidJavaObject maxCustomContentInfo = new AndroidJavaObject("com.anythink.core.api.ATCustomContentInfo", priceInfo.MaxPrice * 1000, m_maxObj); AndroidJavaObject bigoCustomContentInfo = new AndroidJavaObject("com.anythink.core.api.ATCustomContentInfo", priceInfo.BigoPrice * 1000, m_bigoObj); + AndroidJavaObject kwaiCustomContentInfo = new AndroidJavaObject("com.anythink.core.api.ATCustomContentInfo", priceInfo.KwaiPrice * 1000, m_kwaiObj); + AndroidJavaObject atCustomContentInfoList = new AndroidJavaObject("java.util.ArrayList"); atCustomContentInfoList.Call("add", toponCustomContentInfo); atCustomContentInfoList.Call("add", admobCustomContentInfo); atCustomContentInfoList.Call("add", maxCustomContentInfo); atCustomContentInfoList.Call("add", bigoCustomContentInfo); + atCustomContentInfoList.Call("add", kwaiCustomContentInfo); AndroidJavaClass sdkGlobalSetting = new AndroidJavaClass("com.anythink.core.api.ATSDKGlobalSetting"); AndroidJavaObject maxPriceCustomContentInfo = sdkGlobalSetting.CallStatic("customContentReviewResult", atCustomContentInfoList); @@ -52,7 +58,7 @@ namespace Script.SDKManager.AdsSDKManager.Utils AndroidJavaObject customContentObject = maxPriceCustomContentInfo.Get("customContentObject"); string customContentObjectString = customContentObject != null ? customContentObject.Call("toString") : "null"; - LoggerUtils.Debug("[AdsBidResult] maxPrice: " + priceInfo.MaxPrice * 1000 + " bigoPrice: " + priceInfo.BigoPrice * 1000 + " admobPrice: " + priceInfo.AdmobPrice * 1000); + LoggerUtils.Debug("[AdsBidResult] maxPrice: " + priceInfo.MaxPrice * 1000 + " bigoPrice: " + priceInfo.BigoPrice * 1000 + " admobPrice: " + priceInfo.AdmobPrice * 1000 + " kwaiPrice: " + priceInfo.KwaiPrice * 1000 + " toponAdUnitId: " + priceInfo.ToponAdUnitId); LoggerUtils.Debug("[AdsBidResult] final maxPriceCustomContentInfo: " + customContentString + " double: " + customContentDouble + " object:" + customContentObjectString); if (customContentObjectString.Equals(m_toponObj)) { @@ -70,6 +76,10 @@ namespace Script.SDKManager.AdsSDKManager.Utils { return PlatformType.Bigo; } + else if (customContentObjectString.Equals(m_kwaiObj)) + { + return PlatformType.Kwai; + } else { return PlatformType.NULL; diff --git a/Assets/Script/SDKManager/AdsSDKManager/Utils/BidPlatformManager.cs b/Assets/Script/SDKManager/AdsSDKManager/Utils/BidPlatformManager.cs new file mode 100644 index 0000000..4354734 --- /dev/null +++ b/Assets/Script/SDKManager/AdsSDKManager/Utils/BidPlatformManager.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Script.Utils; +using SDK.Utils; +using UnityEngine; +using DKManager.AdsSDKManager.Constant; +using SDKManager.AdsSDKManager.Constant; + +namespace Script.SDKManager.AdsSDKManager.Utils +{ + [Serializable] + public class AdTypeBidState + { + public AdsType AdType; + public DateTime LastBidSuccessTime; + + public AdTypeBidState(AdsType adType) + { + AdType = adType; + LastBidSuccessTime = DateTime.MinValue; + } + + public bool IsBidExpired(int expireInSec) + { + // 如果功能关闭,则不视为过期 + if (expireInSec == -1) return false; + + // 如果从未成功竞价过,则视为过期 + // if (LastBidSuccessTime == DateTime.MinValue) return true; + + // 检查是否超过过期时间 + TimeSpan timeSinceLastBid = DateTime.Now - LastBidSuccessTime; + return timeSinceLastBid.TotalSeconds > expireInSec; + } + } + + [Serializable] + public class BidPlatformState + { + public PlatformType PlatformName; + public bool IsEnabled = true; + + // 每种广告类型的竞价状态 + public Dictionary AdTypeStates = new Dictionary(); + + public BidPlatformState(PlatformType platformName) + { + PlatformName = platformName; + + // 初始化所有广告类型的状态 + foreach (AdsType adType in Enum.GetValues(typeof(AdsType))) + { + AdTypeStates.Add(adType, new AdTypeBidState(adType)); + } + } + + // 记录特定广告类型的竞价成功 + public void RecordBidSuccess(AdsType adType) + { + if (AdTypeStates.TryGetValue(adType, out var state)) + { + state.LastBidSuccessTime = DateTime.Now; + Debug.Log($"{PlatformName} 平台 {adType} 广告竞价成功,记录时间: {state.LastBidSuccessTime}"); + } + } + + // 检查特定广告类型的竞价是否过期 + public bool IsBidExpired(AdsType adType, int expireInSec) + { + if (!IsEnabled || expireInSec == -1) return false; + + if (AdTypeStates.TryGetValue(adType, out var state)) + { + return state.IsBidExpired(expireInSec); + } + + return false; + } + + // 获取所有过期的广告类型 + public List GetExpiredAdTypes(int expireInSec) + { + List expiredAdTypes = new List(); + + if (!IsEnabled || expireInSec == -1) return expiredAdTypes; + + foreach (var kvp in AdTypeStates) + { + if (kvp.Value.IsBidExpired(expireInSec)) + { + expiredAdTypes.Add(kvp.Key); + } + } + + return expiredAdTypes; + } + } + public class BidPlatformManager : NormalSingleton + { + private Dictionary _platformStates = new Dictionary(); + private int _bidExpireInSec = -1; + + public void InitializePlatformStates(int expireInSec) + { + _bidExpireInSec = expireInSec; + _platformStates.Clear(); + _platformStates.Add(PlatformType.Admob, new BidPlatformState(PlatformType.Admob)); + _platformStates.Add(PlatformType.Tpn, new BidPlatformState(PlatformType.Tpn)); + _platformStates.Add(PlatformType.Max, new BidPlatformState(PlatformType.Max)); + _platformStates.Add(PlatformType.Bigo, new BidPlatformState(PlatformType.Bigo)); + _platformStates.Add(PlatformType.Kwai, new BidPlatformState(PlatformType.Kwai)); + } + + // 记录平台竞价成功 + public void RecordBidSuccess(PlatformType platformName, AdsType adType) + { + if (_platformStates.TryGetValue(platformName, out var state)) + { + state.RecordBidSuccess(adType); + } + } + // 获取所有过期平台 + public Dictionary> GetExpiredBids() + { + Dictionary> expiredBids = new Dictionary>(); + + if (_bidExpireInSec == -1) return expiredBids; + + foreach (var kvp in _platformStates) + { + List expiredAdTypes = kvp.Value.GetExpiredAdTypes(_bidExpireInSec); + if (expiredAdTypes.Count > 0) + { + expiredBids.Add(kvp.Key, expiredAdTypes); + } + } + + return expiredBids; + } + } +} diff --git a/Assets/Script/SDKManager/AdsSDKManager/Utils/BidPlatformManager.cs.meta b/Assets/Script/SDKManager/AdsSDKManager/Utils/BidPlatformManager.cs.meta new file mode 100644 index 0000000..00cd42f --- /dev/null +++ b/Assets/Script/SDKManager/AdsSDKManager/Utils/BidPlatformManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8b14631fb6b6547ee9890a43a8ecb2a7 \ No newline at end of file