using System; using System.Linq.Expressions; using System.Reflection; namespace JustTrack { /// /// Utility class for reflection-based integrations with third-party SDKs. /// public class Reflection { /// /// Retrieves the AppsFlyer ID using reflection. /// /// The AppsFlyer ID, or an empty string if retrieval fails. internal static string GetAppsflyerId() { try { var assembly = GetAppsflyerAssembly(); if (assembly == null) { return string.Empty; } return assembly.GetAppsflyerId(); } catch (System.Exception) { return string.Empty; } } /// /// Checks if Facebook Audience Network Unity integration is available. /// /// true if Facebook Audience Network Unity is available; otherwise, false. public static bool HasFacebookAudienceNetworkUnity() { try { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var type = assembly.GetType("AudienceNetwork.AdSettings", false); if (type != null) { var method = type.GetMethod("SetAdvertiserTrackingEnabled", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Any, new Type[] { typeof(bool) }, null); return method != null; } } return false; } catch (System.Exception) { return false; } } /// /// Sets advertiser tracking enabled status for Facebook Audience Network on iOS. /// /// Whether advertiser tracking is authorized. internal static void SetAdvertiserTrackingEnabled(bool authorized) { #if UNITY_IOS // we only need to call this on iOS (the method also just exists on iOS), so we don't have to search for it on Android try { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var type = assembly.GetType("JustTrackInjected.FacebookAudienceNetworkAdapter", false); if (type != null) { var method = type.GetMethod("SetAdvertiserTrackingEnabled", BindingFlags.Public | BindingFlags.Static, null, CallingConventions.Any, new Type[] { typeof(bool) }, null); if (method != null) { method.Invoke(null, new object[] { authorized }); } return; } } } catch (System.Exception) { return; } #endif } /// /// Represents impression data from IronSource ad network. /// internal class IronSourceImpressionData { /// /// Gets the ad unit identifier. /// internal string AdUnit { get; } /// /// Gets the placement identifier. /// internal string Placement { get; } /// /// Gets the ad network identifier. /// internal string AdNetwork { get; } /// /// Gets the A/B testing identifier. /// internal string AbTesting { get; } /// /// Gets the segment name. /// internal string SegmentName { get; } /// /// Gets the instance name. /// internal string InstanceName { get; } /// /// Gets the revenue amount. /// internal double Revenue { get; } /// /// Initializes a new instance of the class. /// /// The ad unit identifier. /// The placement identifier. /// The ad network identifier. /// The A/B testing identifier. /// The segment name. /// The instance name. /// The revenue amount. internal IronSourceImpressionData(string adUnit, string placement, string adNetwork, string abTesting, string segmentName, string instanceName, double revenue) { AdUnit = adUnit; Placement = placement; AdNetwork = adNetwork; AbTesting = abTesting; SegmentName = segmentName; InstanceName = instanceName; Revenue = revenue; } } /// /// Wrapper class for interacting with IronSource SDK via reflection. /// public class IronSourceAssembly { /// /// Gets the banner ad type identifier. /// internal string BannerType { get; } /// /// Gets the interstitial ad type identifier. /// internal string InterstitialType { get; } /// /// Gets the rewarded video ad type identifier. /// internal string RewardedVideoType { get; } /// /// Gets the offerwall ad type identifier. /// internal string OfferwallType { get; } private readonly PropertyInfo agentField; private readonly MethodInfo init; private readonly MethodInfo setUserId; private readonly Type impressionDataType; private readonly FieldInfo impressionDataAdUnit; private readonly FieldInfo impressionDataPlacement; private readonly FieldInfo impressionDataAdNetwork; private readonly FieldInfo impressionDataAbTesting; private readonly FieldInfo impressionDataSegmentName; private readonly FieldInfo impressionDataInstanceName; private readonly FieldInfo impressionDataRevenue; private readonly EventInfo? onImpressionEvent; private readonly IronSourceAdapter? ironSourceAdapter; /// /// Initializes a new instance of the class. /// /// The IronSource assembly to wrap. internal IronSourceAssembly(Assembly assembly) { var adUnitsType = assembly.GetType("IronSourceAdUnits", true); BannerType = (string)GetStaticField("BANNER", adUnitsType).GetValue(null); InterstitialType = (string)GetStaticField("INTERSTITIAL", adUnitsType).GetValue(null); RewardedVideoType = (string)GetStaticField("REWARDED_VIDEO", adUnitsType).GetValue(null); OfferwallType = (string)GetStaticField("OFFERWALL", adUnitsType).GetValue(null); var ironSourceType = assembly.GetType("IronSource", true); this.agentField = GetStaticField("Agent", ironSourceType); this.init = ironSourceType.GetMethod("init", BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, new Type[] { typeof(string), typeof(string[]) }, null); this.setUserId = ironSourceType.GetMethod("setUserId", BindingFlags.Public | BindingFlags.Instance, null, CallingConventions.Any, new Type[] { typeof(string) }, null); this.impressionDataType = assembly.GetType("IronSourceImpressionData", true); this.impressionDataAdUnit = this.impressionDataType.GetField("adUnit"); this.impressionDataPlacement = this.impressionDataType.GetField("placement"); this.impressionDataAdNetwork = this.impressionDataType.GetField("adNetwork"); this.impressionDataAbTesting = this.impressionDataType.GetField("ab"); this.impressionDataSegmentName = this.impressionDataType.GetField("segmentName"); this.impressionDataInstanceName = this.impressionDataType.GetField("instanceName"); this.impressionDataRevenue = this.impressionDataType.GetField("revenue"); var eventsType = assembly.GetType("IronSourceEvents", true); var onImpressionSuccessInfo = eventsType.GetMember("onImpressionSuccessEvent", BindingFlags.Public | BindingFlags.Static); var onImpressionEvent = IronSourceAssembly.GetOnImpressionEvent(onImpressionSuccessInfo); if (onImpressionEvent == null) { var onImpressionDataReadyInfo = eventsType.GetMember("onImpressionDataReadyEvent", BindingFlags.Public | BindingFlags.Static); onImpressionEvent = IronSourceAssembly.GetOnImpressionEvent(onImpressionDataReadyInfo); } this.onImpressionEvent = onImpressionEvent; this.ironSourceAdapter = GetIronSourceAdapter(); } /// /// Gets the impression event from the provided member info array. /// /// The member info array to search. /// The impression event info, or null if not found. private static EventInfo? GetOnImpressionEvent(System.Reflection.MemberInfo[] onImpressionEventInfo) { foreach (var onImpressionEvent in onImpressionEventInfo) { if (onImpressionEvent.MemberType != MemberTypes.Event) { continue; } return (EventInfo)onImpressionEvent; } return null; } /// /// Initializes the IronSource SDK with the specified app key and ad units. /// /// The IronSource app key. /// The ad units to initialize. internal void Init(string appKey, string[] adUnits) { var agent = agentField.GetValue(null); init.Invoke(agent, new object[] { appKey, adUnits }); } /// /// Sets the user ID for the IronSource SDK. /// /// The user ID to set. internal void SetUserId(string userId) { var agent = agentField.GetValue(null); setUserId.Invoke(agent, new object[] { userId }); } /// /// Converts an IronSource impression object to our impression data format. /// /// The IronSource impression object. /// The converted impression data. internal IronSourceImpressionData ToImpressionData(object impression) { var adUnit = (string)impressionDataAdUnit.GetValue(impression); var placement = (string)impressionDataPlacement.GetValue(impression); var adNetwork = (string)impressionDataAdNetwork.GetValue(impression); var abTesting = (string)impressionDataAbTesting.GetValue(impression); var segmentName = (string)impressionDataSegmentName.GetValue(impression); var instanceName = (string)impressionDataInstanceName.GetValue(impression); var revenue = (double?)impressionDataRevenue.GetValue(impression); return new IronSourceImpressionData(adUnit, placement, adNetwork, abTesting, segmentName, instanceName, revenue ?? 0); } /// /// Adds an impression listener to the IronSource SDK. /// /// The proxy action to handle impressions. internal void AddImpressionListener(Action proxy) { // with IL2CPP, we can't actually use reflection to generate code, so we use another trick: // - first, we use reflection to lookup a type we inject into the main assembly // - that type has direct code references to the ironSource assembly // - thus, we create an instance of that type via reflection and simply call it here if (ironSourceAdapter != null) { ironSourceAdapter.SetIronSourceOnImpressionHandler(proxy); } else if (onImpressionEvent != null) { onImpressionEvent.GetAddMethod().Invoke(null, new object[] { BuildDelegate(proxy, onImpressionEvent.EventHandlerType) }); } } /// /// Builds a delegate for the specified proxy action and delegate type. /// /// The proxy action. /// The target delegate type. /// The built delegate. private static object BuildDelegate(Action proxy, Type delegateType) { ParameterInfo[] methodParams = delegateType.GetMethod("Invoke").GetParameters(); ParameterExpression[] paramsOfDelegate = new ParameterExpression[methodParams.Length]; for (int i = 0; i < methodParams.Length; i++) { paramsOfDelegate[i] = Expression.Parameter(methodParams[i].ParameterType, methodParams[i].Name); } var expr = Expression.Lambda( delegateType, Expression.Invoke(Expression.Constant(proxy), paramsOfDelegate), paramsOfDelegate); return expr.Compile(); } /// /// Gets a static property by name from the specified type. /// /// The name of the static property. /// The type to search. /// The property info. /// Thrown when the static property is not found. private static PropertyInfo GetStaticField(string fieldName, Type type) { var members = type.GetMember(fieldName, BindingFlags.Public | BindingFlags.Static); foreach (var member in members) { if (member.MemberType != MemberTypes.Property) { continue; } return (PropertyInfo)member; } throw new Exception($"Missing static field {fieldName} on {type.ToString()}"); } } private static IronSourceAssembly? ironSourceAssembly = null; /// /// Gets the IronSource assembly wrapper, creating it if necessary. /// /// The IronSource assembly wrapper, or null if IronSource is not available. public static IronSourceAssembly? GetIronSourceAssembly() { if (ironSourceAssembly != null) { return ironSourceAssembly; } try { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var type = assembly.GetType("IronSource", false); if (type != null) { ironSourceAssembly = new IronSourceAssembly(assembly); return ironSourceAssembly; } } return null; } catch (System.Exception) { return null; } } /// /// Gets the injected IronSource adapter implementation. /// /// The IronSource adapter, or null if not available. public static IronSourceAdapter? GetIronSourceAdapter() { try { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var type = assembly.GetType("JustTrackInjected.IronSourceAdapterImpl", false); if (type != null) { var constructor = type.GetConstructor(new Type[0]); return (IronSourceAdapter)constructor.Invoke(null); } } return null; } catch (System.Exception) { return null; } } /// /// Wrapper class for interacting with AppsFlyer SDK via reflection. /// public class AppsflyerAssembly { private readonly MethodInfo getAppsFlyerId; /// /// Initializes a new instance of the class. /// /// The method info for getting AppsFlyer ID. internal AppsflyerAssembly(MethodInfo getAppsFlyerId) { this.getAppsFlyerId = getAppsFlyerId; } /// /// Gets the AppsFlyer ID. /// /// The AppsFlyer ID. internal string GetAppsflyerId() { return (string)getAppsFlyerId.Invoke(null, null); } } private static AppsflyerAssembly? appsflyerAssembly = null; /// /// Gets the AppsFlyer assembly wrapper, creating it if necessary. /// /// The AppsFlyer assembly wrapper, or null if AppsFlyer is not available. public static AppsflyerAssembly? GetAppsflyerAssembly() { if (appsflyerAssembly != null) { return appsflyerAssembly; } try { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (var assembly in assemblies) { var type = assembly.GetType("AppsFlyerSDK.AppsFlyer", false); if (type == null) { continue; } var method = type.GetMethod("getAppsFlyerId", BindingFlags.Public | BindingFlags.Static); if (method == null) { continue; } appsflyerAssembly = new AppsflyerAssembly(method); return appsflyerAssembly; } return null; } catch (System.Exception) { return null; } } } }