import frida, time, sys

JS = r"""
var B=null;
var cfn,gmn,rinv,unbox,dg,dga,aimg,cffn,fsgv;
function E(m,n){var p=m.findExportByName(n);if(!p)throw new Error("no "+n);return p;}
function cstr(s){return Memory.allocUtf8String(s);}
function findClass(ns,nm){var d=dg();var c=Memory.alloc(8);var a=dga(d,c);var n=c.readU32();var np=cstr(ns),mp=cstr(nm);
 for(var i=0;i<n;i++){var as=a.add(i*4).readPointer();if(as.isNull())continue;var im=aimg(as);if(im.isNull())continue;var k=cfn(im,np,mp);if(!k.isNull())return k;}return ptr(0);}
function main(){
 var m=Process.findModuleByName("libil2cpp.so");if(!m){setTimeout(main,150);return;}
 B=m.base;
 cfn=new NativeFunction(E(m,"il2cpp_class_from_name"),'pointer',['pointer','pointer','pointer']);
 gmn=new NativeFunction(E(m,"il2cpp_class_get_method_from_name"),'pointer',['pointer','pointer','int']);
 rinv=new NativeFunction(E(m,"il2cpp_runtime_invoke"),'pointer',['pointer','pointer','pointer','pointer']);
 unbox=new NativeFunction(E(m,"il2cpp_object_unbox"),'pointer',['pointer']);
 dg=new NativeFunction(E(m,"il2cpp_domain_get"),'pointer',[]);
 dga=new NativeFunction(E(m,"il2cpp_domain_get_assemblies"),'pointer',['pointer','pointer']);
 aimg=new NativeFunction(E(m,"il2cpp_assembly_get_image"),'pointer',['pointer']);
 cffn=new NativeFunction(E(m,"il2cpp_class_get_field_from_name"),'pointer',['pointer','pointer']);
 fsgv=new NativeFunction(E(m,"il2cpp_field_static_get_value"),'void',['pointer','pointer']);
 send({ev:"BASE", base: B.toString()});
 var TSClass=findClass("FE","TitleScreen");
 send({ev:"TSClass", found: !TSClass.isNull()});
 // try to find a live TitleScreen instance via FindObjectsOfTypeAll is hard; instead hook Start again
 // to catch 'this' if it re-Starts, but more usefully: scan for the Object class's live instances is
 // too complex here. Instead just re-hook Start AND OnCanExitTitles AND EnterGame to see if they get hit.
 try{
   Interceptor.attach(B.add(0xDCF5E8),{onEnter:function(){
     var t=this.context.esp.add(4).readPointer();
     send({ev:"START_HIT", this: t.toString()});
   }});
   send({ev:"HOOK_START_OK"});
 }catch(e){send({ev:"HOOK_START_ERR", err:""+e});}
 var onCanExitM = TSClass.isNull()?ptr(0):gmn(TSClass,cstr("OnCanExitTitles"),0);
 send({ev:"onCanExitM", found: !onCanExitM.isNull(), addr: onCanExitM.toString()});
 try{
   Interceptor.attach(B.add(0xDD0586),{onEnter:function(){send({ev:"ONCANEXIT_HIT"});},onLeave:function(){send({ev:"ONCANEXIT_LEAVE"});}});
 }catch(e){send({ev:"HOOK_ONCANEXIT_ERR", err:""+e});}
 // Also try to directly locate a live TitleScreen object via Resources.FindObjectsOfTypeAll equivalent:
 // walk GC heap is complex; instead just wait for next Start (won't happen since already on this screen).
 // So: use il2cpp GC to enumerate live objects of TSClass.
 try{
   var gcWalk = m.findExportByName("il2cpp_gc_collect");
   // Try il2cpp API to find objects: il2cpp doesn't expose enumerate-by-class easily via public API.
   send({ev:"NOTE", msg:"no live-instance enumeration API used"});
 }catch(e){}
 send({ev:"READY"});
}
main();
"""

pid = int(sys.argv[1])
dev = frida.get_usb_device(timeout=10)
s = dev.attach(pid)
msgs = []
def on_msg(m, d):
    msgs.append(m)
    print(m, flush=True)
sc = s.create_script(JS)
sc.on("message", on_msg)
sc.load()
time.sleep(5)
sc.unload()
s.detach()
