// voice_bridge_v8.js — Voice Bridge with Audio Injection (attach after voice chat opened) // // STARTUP ORDER: // 1. Launch app: adb shell monkey -p com.antgroup.aijk.android ... // 2. Open voice chat manually or via adb tap // 3. Wait for libantaudio.so to load // 4. Attach frida with this script // // 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; var injectQueue = []; 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 (should already be loaded) === 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 (injectMode) { if (injectQueue.length > 0) { var frame = injectQueue.shift(); if (frame.byteLength === size) { args[1].writeByteArray(frame); } else { 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 { 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; 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] process hooked @ " + addr); } // Hook immediately — lib should already be loaded since voice chat is open tryHook(); if (!hooked) { // Retry a few times in case of timing [500, 1000, 2000, 5000].forEach(function(ms) { setTimeout(tryHook, ms); }); } // === 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"); var Srv = Java.registerClass({ name: "com.antaf.voice.S8", implements: [Java.use("java.lang.Runnable")], methods: { run: function() { while (true) { try { console.log("[VOICE] Waiting for client..."); var c = server.accept(); var is = c.getInputStream(); var os = c.getOutputStream(); clientOS = os; console.log("[VOICE] Client connected"); wt(os, JSON.stringify({ event:"connected", protocol:"antaf-voice-v8", hooked: hooked, commands:["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