#!/usr/bin/env python3
"""Comprehensive revival harness — drive the client into the vault.

Fixes applied at runtime:
  - sessionId: never empty (unique per process, not a shared constant)
  - TermsAccepted / FacebookTermsAccepted -> true (suppress TOS popup)
  - Check25RoundsPlayed -> no-op (null Stats NRE breaks PlayerProfile.Start)
  - FE.Core.ready (field +0xa0): force set once the boot iterator reaches PC=9
    (its normal setter OnFirstSceneFrame is a 1-byte stub in this build)
  - device-limit / playing-on-another-device handlers -> no-op (the revival
    reuses one identity across reconnects, which reads as multiple devices)
"""
import json, sys, time, subprocess, frida
ADB = "/opt/android-sdk/platform-tools/adb"
A = json.load(open("/root/ants/sid_addr.json"))

JS = r"""
var A=%s;
function ins(){
  var m=Process.findModuleByName("libil2cpp.so"); if(!m) return false;
  var b=m.base;
  var e=m.findExportByName?m.findExportByName("il2cpp_string_new"):Module.getGlobalExportByName("il2cpp_string_new");
  var sn=new NativeFunction(e,'pointer',['pointer']);
  var SID="revived-"+(Date.now()%%1000000);

  Interceptor.attach(b.add(A["get_sessionId"]),{onLeave:function(rv){
    var em=true; try{ if(!rv.isNull()) em=(rv.add(8).readS32()<=0);}catch(e){}
    if(em) rv.replace(sn(Memory.allocUtf8String(SID)));
  }});
  ["get_TermsAccepted","get_FacebookTermsAccepted"].forEach(function(k){
    if(A[k]) Interceptor.attach(b.add(A[k]),{onLeave:function(rv){rv.replace(ptr(1));}});
  });
  if(A["Check25RoundsPlayed"])
    Interceptor.replace(b.add(A["Check25RoundsPlayed"]), new NativeCallback(function(s){},'void',['pointer']));

  // no-op device-limit / another-device handlers
  ["OnDeviceLimit","OnPlayingOnAnotherDevice","Core.OnPlayingOnAnotherDevice"].forEach(function(k){
    if(A[k]) Interceptor.replace(b.add(A[k]), new NativeCallback(function(){ return; },'void',['pointer','pointer']));
  });

  // FE.Core.ready is set by a delegate tied to scene init that never fires in
  // this configuration; its getter reads field +0xa0. Force the getter true.
  if(A["FECore.get_ready"]) Interceptor.attach(b.add(A["FECore.get_ready"]),{onLeave:function(rv){rv.replace(ptr(1));}});
  send({m:"hooks installed, SID="+SID});
  return true;
}
if(!ins()){ var iv=setInterval(function(){ if(ins()) clearInterval(iv); },150); }
""" % json.dumps(A)

dev = frida.get_device_manager().add_remote_device("127.0.0.1:27042")
subprocess.run([ADB,"shell","am","force-stop","se.foglo.svt"])
subprocess.run([ADB,"shell","monkey","-p","se.foglo.svt","-c","android.intent.category.LAUNCHER","1"],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)
pid=None
for _ in range(80):
    o=subprocess.run([ADB,"shell","pidof","se.foglo.svt"],capture_output=True,text=True).stdout.strip()
    if o: pid=int(o.split()[0]); break
    time.sleep(0.25)
print("pid",pid,flush=True)
s=dev.attach(pid); sc=s.create_script(JS)
sc.on("message", lambda m,d: print("[msg]", m.get("payload",m), flush=True))
sc.load()
time.sleep(int(sys.argv[1]) if len(sys.argv)>1 else 600)
