// voice_bridge_v7.js — Voice Bridge with Audio Injection // Hook point: libantaudio.so MFAntAudio3AV2Filter::process(micIn, spkRef, out, size, &result) // TCP :18901 // Frame: 4-byte len + 1-byte type + payload // type 0: speaker/AI audio (spkRef, downstream to client) // type 1: text/JSON command // type 2: mic audio (micIn, downstream to client) // type 3: inject audio (upstream from client, replaces micIn) var voiceActive = false; var clientOS = null; var capturedSpk = 0, capturedMic = 0, spkBytes = 0, micBytes = 0; var injectMode = false; // true = replace mic with injected audio var injectQueue = []; // queue of PCM frames to inject function wf(os, type, jArr) { try { var len = jArr.length; var h = Java.array("byte", [(len>>24)&0xFF,(len>>16)&0xFF,(len>>8)&0xFF,len&0xFF, type]); os.write(h); os.write(jArr); os.flush(); } catch(e) {} } function wt(os, text) { wf(os, 1, Java.use("java.lang.String").$new(text).getBytes("UTF-8")); } // === Hook libantaudio.so === var hooked = false; function tryHook() { if (hooked) return; var m = Process.findModuleByName("libantaudio.so"); if (!m) return; var addr = m.findExportByName("_ZN8antaudio20MFAntAudio3AV2Filter7processEPhS1_S1_iRi"); if (!addr) return; hooked = true; Interceptor.attach(addr, { onEnter: function(args) { if (!voiceActive || !clientOS) return; var size = args[4].toInt32(); if (size <= 0) return; try { // If inject mode, replace micIn with queued or silence if (injectMode) { if (injectQueue.length > 0) { var frame = injectQueue.shift(); // Only write if frame size matches expected size if (frame.byteLength === size) { args[1].writeByteArray(frame); } else if (frame.byteLength > 0) { // Size mismatch — pad or truncate var buf = new ArrayBuffer(size); var dst = new Uint8Array(buf); var src = new Uint8Array(frame); var copyLen = Math.min(size, frame.byteLength); for (var k = 0; k < copyLen; k++) dst[k] = src[k]; args[1].writeByteArray(buf); } } else { // No data queued — inject silence to avoid mic leak var silence = new ArrayBuffer(size); args[1].writeByteArray(silence); } } // Always capture speaker/AI output (type 0) var spkPcm = args[2].readByteArray(size); var spkArr = Java.array("byte", Array.from(new Uint8Array(spkPcm))); wf(clientOS, 0, spkArr); capturedSpk++; spkBytes += size; // Capture mic (type 2) only when not injecting if (!injectMode) { var micPcm = args[1].readByteArray(size); var micArr = Java.array("byte", Array.from(new Uint8Array(micPcm))); wf(clientOS, 2, micArr); } capturedMic++; micBytes += size; if (capturedMic <= 3 || capturedMic % 500 === 0) console.log("[VOICE] mic=" + capturedMic + " spk=" + capturedSpk + " inject=" + injectQueue.length); } catch(e) {} } }); console.log("[VOICE] 3AV2Filter.process hooked @ " + addr); } [0, 1000, 3000, 5000, 10000, 15000, 20000].forEach(function(ms) { setTimeout(tryHook, ms); }); try { new ApiResolver("module").enumerateMatches("exports:linker*!*dlopen*").forEach(function(d) { Interceptor.attach(d.address, { onLeave: function() { setTimeout(tryHook, 500); } }); }); } catch(e) {} // === TCP Server === Java.perform(function() { var SS = Java.use("java.net.ServerSocket"); var JS = Java.use("java.lang.String"); var server = SS.$new(18901); console.log("[VOICE] Listening :18901"); function openVoice(os) { Java.scheduleOnMainThread(function() { try { Java.choose("com.antgroup.aijk.android.ijklauncher.biz.activity.IJKActivity", { onMatch: function(a) { var fm = a.getSupportFragmentManager(); var f = Java.use("com.antgroup.aijk.android.ijkchat.biz.voicechat.IjkVoiceChatFragment").$new(); f.show(fm, "v"); console.log("[VOICE] Opened"); }, onComplete: function() {} }); setTimeout(function() { wt(os, JSON.stringify({event:"voice_opened"})); }, 2000); } catch(e) { wt(os, JSON.stringify({event:"error",msg:""+e})); } }); } function closeVoice(os) { Java.scheduleOnMainThread(function() { try { Java.choose("com.antgroup.aijk.android.ijkchat.biz.voicechat.IjkVoiceChatFragment", { onMatch: function(f) { f.dismiss(); console.log("[VOICE] Closed"); }, onComplete: function() {} }); setTimeout(function() { wt(os, JSON.stringify({event:"voice_closed"})); }, 1000); } catch(e) { wt(os, JSON.stringify({event:"error",msg:""+e})); } }); } var Srv = Java.registerClass({ name: "com.antaf.voice.S7", implements: [Java.use("java.lang.Runnable")], methods: { run: function() { while (true) { try { console.log("[VOICE] Waiting..."); var c = server.accept(); var is = c.getInputStream(); var os = c.getOutputStream(); clientOS = os; console.log("[VOICE] Connected"); wt(os, JSON.stringify({ event:"connected", protocol:"antaf-voice-v8", commands:["open_voice","close_voice","start","stop","status","inject_on","inject_off"], audio:"pcm-16bit-960b-frames", frameTypes:{0:"spk_ai",1:"text",2:"mic",3:"inject"} })); while (true) { var hb = []; for (var i=0;i<5;i++) { var b=is.read(); if(b<0) throw "EOF"; hb.push(b); } var fl=(hb[0]<<24)|(hb[1]<<16)|(hb[2]<<8)|hb[3], ft=hb[4]; if (fl>1048576) break; var pb = []; for (var i=0;i