import frida, time, subprocess, sys
sys.path.insert(0,"/root/ants/patch"); import resolve_addr as R
ADB="/opt/android-sdk/platform-tools/adb"
JS=r"""
var B=null, spawned=false;
function rdstr(p){try{if(p.isNull()||p.compare(ptr(0x10000))<0)return "<p"+p+">";var n=p.add(8).readS32();if(n<0||n>200)return "<n"+n+">";return "'"+p.add(0xc).readUtf16String(n)+"'";}catch(e){return "e";}}
function H(a,nm){try{Interceptor.attach(B.add(a),{onEnter:function(){send({g:nm});}});}catch(e){}}
function main(){
 var m=Process.findModuleByName("libil2cpp.so");if(!m){setTimeout(main,150);return;}
 B=m.base;
 // render stubs REMOVED (avatars now come from bmm=ava_1; stubbing left native obj incomplete -> IsNativeObjectAlive crash)
 [[0xd53c49],[0xd3ce0b],[0xd494ee],[0xd4dcda],[0xd29beb],[0xd299a4],[0xd2a24a],[0xd29aea],[0xe4a362]].forEach(function(x){try{var p=B.add(x[0]);Memory.protect(p,16,'rwx');p.writeU8(0xc3);}catch(e){}});
 try{Interceptor.attach(B.add(0xd6fede),{onLeave:function(r){r.replace(ptr(1));}});}catch(e){}
 try{Interceptor.attach(B.add(0xe5b13b),{onEnter:function(){var p=this.context.ecx;try{var rk=p.add(0xe0).readS32();p.add(0xd0).writeU8(rk>=0?1:2);}catch(e){}}});}catch(e){}
 try{Interceptor.attach(B.add(0xe4be5a),{onEnter:function(){var it=this.context.esi;try{var rk=it.add(0xe0).readS32();it.add(0xd0).writeU8(rk>=0?1:2);}catch(e){}}});}catch(e){}
 // FindAvatar / FindThiefBag / FindMask: key + return
 try{Interceptor.attach(B.add(0x2351f7),{onEnter:function(){this.k=this.context.esp.add(8).readPointer();},onLeave:function(r){send({g:"FindAvatar("+rdstr(this.k)+") -> "+(r.isNull()?"NULL":"ok")});}});}catch(e){}
 try{Interceptor.attach(B.add(0x22ba01),{onEnter:function(){this.k=this.context.esp.add(4).readPointer();},onLeave:function(r){send({g:"FindThiefBag("+rdstr(this.k)+") -> "+(r.isNull()?"NULL":"ok")});}});}catch(e){}
 try{Interceptor.attach(B.add(0xfde3f3),{onEnter:function(){this.r=this.context.esp.add(4).readS32();},onLeave:function(r){send({g:"FindBotDefForRank("+this.r+") -> "+(r.isNull()?"NULL":"OK")});}});}catch(e){}
 try{Interceptor.attach(B.add(0xfde55e),{onEnter:function(){this.r=this.context.esp.add(4).readS32();},onLeave:function(r){send({g:"FindBotDefForLevel("+this.r+") -> "+(r.isNull()?"NULL":"OK")});}});}catch(e){}
 var getClass=new NativeFunction(m.findExportByName("il2cpp_object_get_class"),'pointer',['pointer']);
 var clsName=new NativeFunction(m.findExportByName("il2cpp_class_get_name"),'pointer',['pointer']);
 function cname(o){try{return o.isNull()?"NULL":clsName(getClass(o)).readUtf8String();}catch(e){return "?";}}
 // GetBotDef: what TYPE of def do thief bots actually get? (SniperBotDef => the cast bug)
 try{Interceptor.attach(B.add(0xfde228),{onLeave:function(r){send({g:"Thief::GetBotDef -> "+cname(r)});}});}catch(e){}
 try{Interceptor.attach(B.add(0xfda88b),{onLeave:function(r){send({g:"Sniper::GetBotDef -> "+cname(r)});}});}catch(e){}
 // SetThiefBotDef: does the assign happen? with what type?
 try{Interceptor.attach(B.add(0x103094a),{onEnter:function(){var d=this.context.esp.add(8).readPointer();send({g:"SetThiefBotDef(def="+cname(d)+")"});}});}catch(e){}
 try{Interceptor.attach(B.add(0xe4a362),{onEnter:function(){send({g:"StartThiefIntro ENTER"});}});}catch(e){}
 H(0xdf9152,"Thief.Awake");H(0xdf9369,"Thief.Start");
 try{Interceptor.attach(B.add(0xdf9152),{onEnter:function(){spawned=true;}});}catch(e){}
 try{Interceptor.attach(B.add(0xdf9eed),{onLeave:function(){send({g:"SetupBag OK"});}});}catch(e){}
 H(0xe0b90c,"Thief.OnHealthChange");
 // match-start tracing: find the last method before the wild jump
 H(0xe47cab,"GameController.Awake");H(0xe4bd6a,"SpawnPlayers");H(0xe4c511,"SpawnThief");
 var acN=0; try{Interceptor.attach(B.add(0xdfc918),{onEnter:function(){acN++;
   var thief=this.context.esp.add(4).readPointer();
   send({g:"AddController ENTER #"+acN+" thief="+thief+" cls="+cname(thief)});
   // scan thief fields for garbage/invalid Unity-object pointers
   var bad="";
   for(var o=0;o<0x2a0;o+=4){try{var v=thief.add(o).readPointer();
     if(!v.isNull()){ var lo=v.compare(ptr("0x1000"))<0, hi=v.compare(ptr("0xd8000000"))>0;
       if(!lo && (hi || (v.compare(ptr("0xc0000000"))>0))){ // suspicious high ptr
         var probe=null; try{probe=v.readPointer();}catch(e){probe="FAULT";}
         if(probe=="FAULT") bad+="+0x"+o.toString(16)+"="+v+"(dead) ";
       }
     }
   }catch(e){}}
   send({g:"  thief bad-fields: "+(bad||"none")});
 }});}catch(e){}
 // pinpoint the garbage object AddController null-checks (crash in IsNativeObjectAlive)
 try{Interceptor.attach(B.add(0xd6482a),{onLeave:function(r){send({g:"get_pawnID -> "+r});}});}catch(e){}
 try{Interceptor.attach(B.add(0xe5c278),{onLeave:function(r){send({g:"FindPlayerWithPawnID -> "+(r.isNull()?"NULL":cname(r))});}});}catch(e){}
 try{Interceptor.attach(B.add(0xe45bd2),{onLeave:function(r){send({g:"get_gamePlayer -> "+(r.isNull()?"NULL":cname(r))});}});}catch(e){}
 try{Interceptor.attach(B.add(0xe4bfb4),{onEnter:function(){send({g:"get_isYou(this="+this.context.ecx+")"});}});}catch(e){}
 var upN=0; try{Interceptor.attach(B.add(0xdfc2bb),{onEnter:function(){upN++; if(upN<=6)send({g:"Thief.Update #"+upN});}});}catch(e){}
 H(0xd4164b,"MatchPersona.SetBot");H(0xd4168f,"MatchPersona.BecomeRobotic");
 try{Interceptor.attach(B.add(0xbd06f4),{onEnter:function(){ if(!spawned)return; var ra=this.returnAddress;var mm=Process.findModuleByAddress(ra); if(mm&&mm.name=="libil2cpp.so"){var o=ra.sub(mm.base); if(o.compare(ptr(0xd00000))>=0&&o.compare(ptr(0xf00000))<=0)send({g:"THROW_AT 0x"+o.toString(16)});}}});}catch(e){}
 var crashed=false;
 Process.setExceptionHandler(function(d){var a=d.address;var mm=Process.findModuleByAddress(a);var loc=mm?mm.name+"+0x"+a.sub(mm.base).toString(16):""+a;
   if(!crashed){crashed=true; var c=d.context;
     var regs="eax="+c.eax+" ebx="+c.ebx+" ecx="+c.ecx+" edx="+c.edx+" esi="+c.esi+" edi="+c.edi+" ebp="+c.ebp+" esp="+c.esp;
     var ib="";try{ib=a.readByteArray(8);}catch(e){}
     send({g:"CRASH @"+loc+" access="+(d.memory?d.memory.address:"?")+" "+regs});
     try{var insn=Instruction.parse(a);send({g:"  faulting insn: "+insn.mnemonic+" "+insn.opStr});}catch(e){}
   }
   return false;});
 send({g:"__ready"});
}
main();
"""
def sh(*a): return subprocess.run([ADB]+list(a),capture_output=True,text=True)
def tap(x,y): subprocess.run([ADB,"shell","input","tap",str(x),str(y)],capture_output=True)
def cap(): return subprocess.run([ADB,"exec-out","screencap","-p"],capture_output=True).stdout
def shot(n):
    d=cap(); open("/root/ants/shots/"+n,"wb").write(d); print("  shot",n,len(d),flush=True)
sh("shell","am","force-stop","se.foglo.svt"); time.sleep(1)
sh("shell","input","keyevent","3"); time.sleep(1)
sh("shell","monkey","-p","se.foglo.svt","-c","android.intent.category.LAUNCHER","1")
print("polling menu",flush=True); t0=time.time()
while time.time()-t0<140:
    time.sleep(4)
    if len(cap())>600000: break
print("menu",int(time.time()-t0),flush=True); time.sleep(3)
dev=frida.get_usb_device(timeout=10)
pid=int(sh("shell","pidof","se.foglo.svt").stdout.split()[0]); print("pid",pid,flush=True)
tf=[]
def onm(m,d):
    if isinstance(m.get("payload"),dict):
        g=m["payload"].get("g",""); print("  ",g,flush=True)
        if g.startswith("THROW_AT"): tf.append(g.split()[1])
s=dev.attach(pid); sc=s.create_script(JS); sc.on("message",onm); sc.load(); time.sleep(2)
tap(700,730); time.sleep(6); tap(1355,960)
# keep frida ATTACHED through the whole match (master-client stays forced)
for k in range(14):
    time.sleep(8); shot("play_%02d.png"%k)
for off in tf[:3]:
    try: print("  THROW ->",R.resolve(int(off,16)),flush=True)
    except Exception: pass
print("done",flush=True)
