using System.Collections.Concurrent; using System.Globalization; using System.Net; using System.Reflection; using game; using game.Account; using game.PlayerDataStorage; using Newtonsoft.Json; using PlayFab.ClientModels; using tysdk; static class Probe { static int assertions,groups; static void Check(bool condition,string label){assertions++;if(!condition)throw new Exception(label);} sealed class Loop:SynchronizationContext { internal BlockingCollection<(SendOrPostCallback,object)> Queue=new();public override void Post(SendOrPostCallback c,object s)=>Queue.Add((c,s)); } static int Main() { try { RunHost(); return 0; } catch(Exception ex) { Console.WriteLine(ex); return 1; } } static void RunHost() { var loop=new Loop();SynchronizationContext.SetSynchronizationContext(loop); var task=Run();_=task.ContinueWith(_=>loop.Post(_=>{},null),TaskScheduler.Default); while(!task.IsCompleted){var(c,s)=loop.Queue.Take();c(s);}task.GetAwaiter().GetResult(); #if UNITY_ANDROID const string platform="ANDROID+AGG"; #else const string platform="NON_ANDROID"; #endif Console.WriteLine($"RESULT service-host {platform} groups={groups} assertions={assertions} formalRequests=0"); } sealed class Handler:HttpMessageHandler { readonly Func> send; internal Handler(Func> send)=>this.send=send; protected override Task SendAsync(HttpRequestMessage r,CancellationToken t)=>send(r); } static Task Json(object value,HttpStatusCode status=HttpStatusCode.OK)=>Task.FromResult(new HttpResponseMessage(status){Content=new StringContent(JsonConvert.SerializeObject(value))}); static readonly Uri Api=new("http://synthetic.invalid/"); static Config Configuration(){var c=new Config();c.Set("EventApiURL",Api.AbsoluteUri);c.Set("DEBUG","1");return c;} static readonly string[] Core={"3","4","4","5","6","7","101","2026-09-23T00:00:00Z","-1","8","9","9","1","0","10","11","12","13","14"}; static Dictionary Raw()=>new(){["NewPlayerDataDic"]=JsonConvert.SerializeObject(new{newData=string.Join(",",Core)}),["TapJoy_Diamond"]="9"}; static PlayerKVSchemaSnapshot Snapshot()=>new("stub-v1",new[]{101},new[]{1,2},new Dictionary{{"None",0}},CultureInfo.InvariantCulture,new DateTime(2026,9,23,0,0,0,DateTimeKind.Utc),17); static PlayerKVIdentitySnapshot Identity()=>new("FF",false,PlayerKVIdentityChannel.hwGuest,Array.Empty()); static object Private(object target,string method,params object[] args)=>target.GetType().GetMethod(method,BindingFlags.Instance|BindingFlags.NonPublic,null,args.Select(a=>a.GetType()).ToArray(),null).Invoke(target,args); static void Pass(string name){groups++;Console.WriteLine("PASS "+name);} static async Task Run() { foreach(bool agg in new[]{false,true}) { var config=Configuration();PlayFabUserService user=agg?new AggUserService():new PlayFabUserService();user.config=config;user.CustomId="101"; foreach(var backend in new[]{"kv","KV","unknown","",null}) { config.Set("SaveBackend",backend);Effects.Clear(); Check(await user.Login(null)==ELoginStep.Api,"public backend gate"); Check(user.KVLastError==(backend=="kv"?PlayerKVGameLoadError.BackendNotEnabled:PlayerKVGameLoadError.BackendUnsupported),"typed rejection"); Check(Effects.Total==0,"before all SDK/event access"); } } Pass("public-backend-rejection-before-AGG-shortcut"); var pf=new PlayFabMgr();Private(pf,"Awake"); var data=new Dictionary{{"MMTest",new(){Value="MMFailParam#4",LastUpdated=DateTime.UtcNow}},{"TapJoy_Diamond",new(){Value="7",LastUpdated=DateTime.UtcNow}}}; pf.CompareDatas(data,"synthetic");pf.UpdateUserDataValue("local","value",false); Check(pf.GetLocalData("local")=="value","default PF local write unchanged"); Check(await pf.SyncDataImmidiate("remote","value"),"default PF immediate result unchanged"); Pass("default-PF-storage-passthrough"); foreach(bool agg in new[]{false,true}) { var config=Configuration();PlayFabUserService user=agg?new AggUserService():new PlayFabUserService();user.config=config;user.CustomId="101"; var server=new CustomServerMgr(config,_=>new Handler(r=>Json(new{})));user.customServerMgr=server; using var session=new S101AccountSession(Api,jar=>new Handler(r=>{jar.Add(Api,new Cookie("sid","synthetic"));return Json(new{state=0,internalId=17,newCreate=false,createTime="2026-01-01T00:00:00Z",svrTime="2026-01-01T00:00:00Z"});})); Check((await session.LoginAsync("101")).Success,"PF synthetic account"); typeof(PlayFabUserService).GetField("apiSession",BindingFlags.NonPublic|BindingFlags.Instance).SetValue(user,session); typeof(PlayFabUserService).GetProperty("InternalId").SetValue(user,(uint)17);PlayFab.PlayFabClientAPI.Logged=true; int progress=0;Check(await user.Login(v=>progress=v)==ELoginStep.Done && progress==(agg?3:2),"default PF authorized shortcut"); config.Set("SaveBackend","kv");Effects.Clear();Check(await user.Login(null)==ELoginStep.Api && Effects.Total==0,"authorized cannot bypass backend gate"); typeof(PlayFabUserService).GetField("apiSession",BindingFlags.NonPublic|BindingFlags.Instance).SetValue(user,null); } Pass("default-PF-and-AGG-shortcuts"); GContext.container.Values[typeof(ISettingService)]=new Setting();GContext.container.Values[typeof(IChatService)]=new Chat(); GContext.container.Values[typeof(PlayerData)]=new PlayerData();GContext.container.Values[typeof(MMTService)]=new MMTService(); asap.playfab.async.PlayFabClientAsyncAPI.Result=new LoginResult{PlayFabId="FF",NewlyCreated=false, EntityToken=new EntityTokenResponse{Entity=new EntityKey()},InfoResultPayload=new GetPlayerCombinedInfoResultPayload{UserData=data,AccountInfo=new UserAccountInfo{Created=DateTime.UtcNow},PlayerProfile=new PlayerProfileModel{DisplayName="Synthetic",AvatarUrl="synthetic",Locations=new(){new LocationModel()}}}}; // Invalid scheme ensures legacy API stage fails before HttpClient; no real endpoint is contacted. var normalConfig=Configuration();normalConfig.Set("EventApiURL","ftp://synthetic.invalid/"); var normal=new PlayFabUserService{config=normalConfig,CustomId="101",customServerMgr=new CustomServerMgr(normalConfig,_=>new Handler(r=>throw new Exception("no HTTP")))}; PlayFab.PlayFabClientAPI.Logged=false;Check(await normal.Login(null)==ELoginStep.Api,"normal PF reaches API boundary"); Check(((PlayerData)GContext.container.Values[typeof(PlayerData)]).Loads==1 && normal.MMTestForKVValidation==4,"real PF init after adjusted time"); var hotConfig=Configuration();hotConfig.Set("EventApiURL","ftp://synthetic.invalid/"); var hot=new AggUserService{config=hotConfig,customServerMgr=new CustomServerMgr(hotConfig,_=>new Handler(r=>throw new Exception("no HTTP")))}; var sdk=new TaskCompletionSource();TYSdkFacade.IsLoggedIn=false;TYSdkFacade.LoginOverride=()=>sdk.Task;PlayFab.PlayFabClientAPI.Logged=false; var pending=hot.Login(null);hotConfig.Set("SaveBackend","kv");sdk.SetResult(new(){isSuccess=true,userId=101}); Check(await pending==ELoginStep.Api && hot.KVLastError==PlayerKVGameLoadError.None,"captured PF branch continues to API failure"); Check(((PlayerData)GContext.container.Values[typeof(PlayerData)]).Loads==2,"AGG real PF init window");TYSdkFacade.LoginOverride=null; Pass("PF-original-init-order-and-backend-capture"); UnityEngine.GameObject.Found=new TapjoyUnity.Internal.TapjoyComponent();Effects.Clear(); typeof(PlayFabUserService).GetMethod("InitTapJoy",BindingFlags.Static|BindingFlags.NonPublic).Invoke(null,null); #if UNITY_ANDROID Check(Effects.Helpers==2,"PF Android creates and initializes helper"); #else Check(Effects.Helpers==0,"PF non-Android no helper"); #endif Pass("PF-original-TapJoy-platform-branch"); UniRx.Stream.Handlers=null; var unauthorizedUser=new PlayFabUserService{config=normalConfig,CustomId="101",customServerMgr=new CustomServerMgr(normalConfig,_=>new Handler(r=>throw new Exception("no HTTP")))}; typeof(PlayFabUserService).GetProperty("InternalId").SetValue(unauthorizedUser,(uint)17);PlayFab.PlayFabClientAPI.Logged=true; int version=unauthorizedUser.customServerMgr.SessionVersion; GContext.Publish(new EvtAPISvrUnauthorized{SessionVersion=version-1});Check(unauthorizedUser.InternalId==17,"PF stale 401 ignored"); GContext.Publish(new EvtAPISvrUnauthorized{SessionVersion=version}); Check(unauthorizedUser.InternalId==0 && unauthorizedUser.customServerMgr.SessionVersion>version,"PF current 401 retains old reauthentication branch, invalid API prevents HTTP"); UniRx.Stream.Handlers=null; Pass("PF-current-and-stale-401-branches"); Private(pf,"OnDestroy"); typeof(PlayFabMgr).GetField("_instance",BindingFlags.Static|BindingFlags.NonPublic).SetValue(null,null); var fresh=new PlayFabMgr();Check(fresh.IsUnstartedForKVLoad,"fresh PF component"); Check(PlayerKVGameLoadGuard.TryEnter(out var guard),"guard entered"); using(guard) { Effects.Clear(); foreach(Action attempt in new Action[]{()=>fresh.CompareDatas(data,"x"),()=>fresh.GetLocalData("x"),()=>fresh.UpdateUserDataValue("x","y"),()=>fresh.UpdateUserDataValue("x","y",false)}) {try{attempt();throw new Exception("guard missed");}catch(PlayerKVLoadSideEffectException){assertions++;}} Check(!await fresh.SyncDataImmidiate("x","y"),"immediate rejected"); Private(fresh,"SyncData");Private(fresh,"Awake");Private(fresh,"OnApplicationPause",true);Private(fresh,"OnApplicationPause",false);Private(fresh,"OnApplicationQuit");Private(fresh,"OnDestroy");Private(fresh,"UpdateUserDataValue",CancellationToken.None); Check(Effects.Total==0 && fresh.IsUnstartedForKVLoad && guard.Violations==12,"all PF entrances reject before buffer/Prefs/SDK"); } Pass("all-real-PF-guard-entrances");await Controlled(); } sealed class Chat:IChatService {public void WaitingForLogin(){} } sealed class Setting:ISettingService {public string Lang=>"en";} static async Task Controlled() { int auth=0,get=0,borrow=0,unauthorizedEvents=0;Dictionary raw=Raw(); Task delayedBorrow=null; var config=Configuration();var user=new AggUserService(PlayerKVControlledLoadCapability.CreateForValidation(_=>new Handler(r=>throw new Exception()))){config=config,CustomId="101"}; var server=new CustomServerMgr(config,_=>new Handler(r=>throw new Exception("PF transport forbidden"))); server.KVUnauthorized+=_=>unauthorizedEvents++; var modules=Enumerable.Range(1,31).Select(id=>(IPlayerKVLoadModule)new PlayerKVLoadModule(id,()=>{if(id==31)user.ResetForKVLoad();},decoded=>{if(id==31)user.ReplaceFromKVDecoded(decoded.Module(31),decoded.Epoch,decoded.Raw);})).ToArray(); user.ConfigureKVForValidation(new PlayerKVGameLoadTransaction(modules,Environment.CurrentManagedThreadId,()=>user.KVIntents?.Discard()),new PlayFabMgr(),server,()=>true); PlayerKVGameLoadCapability Capability()=>PlayerKVGameLoadCapability.CreateForValidation(Identity(),Snapshot(),Api,Path.Combine(Path.GetTempPath(),"ft-kv-service-probe",Guid.NewGuid().ToString("N")), jar=>new Handler(r=>{auth++;jar.Add(Api,new Cookie("sid","synthetic"));return Json(new{state=0,internalId=17,newCreate=false,createTime="2026-01-01T00:00:00Z",svrTime="2026-01-01T00:00:00Z"});}), jar=>new Handler(r=>{get++;return Json(new{data=raw});}),jar=>new Handler(r=>{borrow++;return delayedBorrow ?? Json(new{},HttpStatusCode.Unauthorized);})); Effects.Clear(); foreach(var invalid in new[]{"0","999","15001","bad","1.5"}) {config.Set("KVLoginCloseObserveMs",invalid);Check((await user.LoadKVForValidationAsync(Capability())).Error==PlayerKVGameLoadError.Input,"invalid option rejected");Check(auth==0 && user.KVReceiverCountForValidation==0,"invalid no send/subscription leak");} config.Values.Remove("KVLoginCloseObserveMs"); var result=await user.LoadKVForValidationAsync(Capability()); Check(result.State==PlayerKVGameLoadState.LoadedForValidation && user.KVLoadedForValidation,"real service controlled success: "+result.Error); Check(auth==1 && get==1 && user.KVNotificationCount==1 && user.KVReceiverCountForValidation==0,"one auth/Get/windowed receiver"); Check(!user.IsLogin && !user.IsAuthorized && user.InternalId==0 && user.KVTapJoyBalance==9 && Effects.Total==0,"zero SDK/Prefs/reward/UI/helpers/events in KV"); var intents=user.KVIntents;await server.SendKVForValidationAsync("__kv_validation/unauthorized");await user.CloseKVForValidationAsync(); Check(!user.KVLoadedForValidation && user.KVIntents==null && intents.Discarded && borrow==1 && auth==1,"401 revokes without reauth"); raw.Clear();var failed=await user.LoadKVForValidationAsync(Capability()); Check(failed.Error==PlayerKVGameLoadError.Schema && user.KVReceiverCountForValidation==0 && user.KVNotificationCount==1,"failure removes receiver without notification"); Check(Effects.Total==0,"all failure paths zero effects"); raw=Raw();Check((await user.LoadKVForValidationAsync(Capability())).State==PlayerKVGameLoadState.LoadedForValidation,"new epoch after successful cleanup"); var late=new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);delayedBorrow=late.Task; var oldRequest=server.SendKVForValidationAsync("__kv_validation/unauthorized");user.Logout();Check(!user.KVLoadedForValidation,"logout synchronously revoked"); late.SetResult(new HttpResponseMessage(HttpStatusCode.Unauthorized){Content=new StringContent("{}")}); Check(!(await oldRequest).IsCurrent,"late 401 body discarded");await user.CloseKVForValidationAsync(); Check(unauthorizedEvents==1 && auth==3 && Effects.Total==0 && user.KVIntents==null,"late 401 never reauthenticates or publishes another unauthorized event"); Pass("real-Agg-User-CustomServer-coordinator-KV-chain"); } }