package com.example.wechathook; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.view.View; import java.lang.reflect.Proxy; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class WeChatHook implements IXposedHookLoadPackage { private static final String ACTION_SEND = "com.hermes.wechat.SEND_MSG_INTERNAL"; private static ClassLoader sWeChatCL; private static volatile Context sAppContext; private static volatile boolean sFsGReady = false; // Set true while WE are calling newInstance so r0Hook can distinguish us from WeChat private static volatile boolean sCalledByUs = false; // Pending send: stored when broadcast arrives before gm0.j1 is initialized private static volatile String sPendingTalker = null; private static volatile String sPendingText = null; private static volatile String sPendingProc = null; @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if (!"com.tencent.mm".equals(lpparam.packageName)) return; // Use android.util.Log directly so it appears even before VectorLegacyBridge is ready android.util.Log.i("WeChatHookEarly", "INIT proc=" + lpparam.processName + " pid=" + android.os.Process.myPid()); XposedBridge.log("[WeChatHook] Injected into WeChat " + lpparam.processName + " pid=" + android.os.Process.myPid()); ClassLoader cl = lpparam.classLoader; sWeChatCL = cl; // Tablet login hooks (main process + any process) hookInTabletEnv(cl); hookProbe(cl); hookLoginHistoryUIInitView(cl); hookLoginUIInitView(cl); hookLoginSelectUICheck(cl); hookLoginButtonClick(cl); // WCDB diagnostic: which process writes to WeChat message table? hookWCDB(cl, lpparam.processName); // Hook gm0.j1.g() to detect when DI kernel is ready — runs in ALL processes. // This enables the pending-send mechanism: if broadcast arrives before DI init, // we store the args and retry once g() fires. hookDIInit(cl, lpparam.processName); // Send hook in ALL WeChat processes — we need to find which one can actually send. // The broadcast receiver registered in each process will all receive the intent; // whichever process has gm0.j1 initialized will succeed. hookSendMessage(cl, lpparam.processName); } // Diagnostic: hook WeChat's WCDB to find which process writes to the message table. // com.tencent.wcdb.database.SQLiteDatabase is WeChat's encrypted SQLite wrapper. private void hookWCDB(ClassLoader cl, String procName) { try { XposedHelpers.findAndHookMethod( "com.tencent.wcdb.database.SQLiteDatabase", cl, "insertWithOnConflict", String.class, String.class, android.content.ContentValues.class, int.class, new XC_MethodHook() { private boolean logged = false; @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String table = (String) param.args[0]; if (table == null || (!table.contains("message") && !table.equals("msg"))) return; if (!logged) { logged = true; String stack = android.util.Log.getStackTraceString(new Throwable()); // Truncate to keep logcat readable if (stack.length() > 3000) stack = stack.substring(0, 3000) + "…"; XposedBridge.log("[WeChatHook] WCDB INSERT table=" + table + " proc=" + procName + " pid=" + android.os.Process.myPid() + "\n" + stack); } else { XposedBridge.log("[WeChatHook] WCDB INSERT table=" + table + " proc=" + procName); } } }); XposedBridge.log("[WeChatHook] WCDB hook OK in " + procName); } catch (Throwable t) { XposedBridge.log("[WeChatHook] WCDB hook failed in " + procName + ": " + t); } } // PRIMARY FIX: WeChat's own methods (LoginPasswordUI.onCreate etc.) are AOT-compiled // in a way that bypasses ArtMethod entry point replacement. But framework-dispatched // base class methods like Activity.onResume() always go through the entry point. // We hook onResume, filter for the login screens, and force the tablet button visible. // By onResume time, all views are initialized (onCreate + initView have run). private void hookProbe(ClassLoader cl) { try { final Class m4Class = XposedHelpers.findClass( "com.tencent.mm.plugin.account.ui.m4", cl); XposedHelpers.findAndHookMethod( "android.app.Activity", null, "onResume", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String cls = param.thisObject.getClass().getName(); if (!cls.equals("com.tencent.mm.plugin.account.ui.LoginPasswordUI") && !cls.equals("com.tencent.mm.plugin.account.ui.LoginHistoryUI")) { return; } Object obj = param.thisObject; View tabletBtn = null; try { tabletBtn = (View) XposedHelpers.getObjectField(obj, "N"); } catch (Throwable e) { XposedBridge.log("[WeChatHook] onResume getField N failed: " + e); } if (tabletBtn == null) { XposedBridge.log("[WeChatHook] onResume: N is null for " + cls); return; } tabletBtn.setVisibility(View.VISIBLE); try { // m4 constructor is m4(LoginHistoryUI), but obj is LoginPasswordUI // (a subclass). XposedHelpers.newInstance infers types from runtime // class which is LoginPasswordUI, not LoginHistoryUI — no match. // Use explicit param type to force the right constructor lookup. Class loginHistoryUIClass = XposedHelpers.findClass( "com.tencent.mm.plugin.account.ui.LoginHistoryUI", obj.getClass().getClassLoader()); tabletBtn.setOnClickListener( (View.OnClickListener) XposedHelpers.newInstance( m4Class, new Class[]{loginHistoryUIClass}, obj)); } catch (Throwable e) { XposedBridge.log("[WeChatHook] onResume setListener failed: " + e); } XposedBridge.log("[WeChatHook] onResume -> N VISIBLE + m4 attached for " + cls); } }); XposedBridge.log("[WeChatHook] Hooked Activity.onResume for tablet button"); } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookProbe/onResume failed: " + t); } } private void hookInTabletEnv(ClassLoader cl) { try { Class bkClass = XposedHelpers.findClass("com.tencent.mm.ui.bk", cl); Class x0Class = XposedHelpers.findClass("bm5.x0", cl); XposedHelpers.findAndHookMethod(bkClass, "B", x0Class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { param.setResult(Boolean.TRUE); } }); XposedHelpers.findAndHookMethod(bkClass, "A", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("[WeChatHook] bk.A() called -> TRUE"); param.setResult(Boolean.TRUE); } }); XposedBridge.log("[WeChatHook] Hooked bk.B(x0) + bk.A()"); } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookInTabletEnv failed: " + t); } } // PRIMARY FIX: Hook LoginPasswordUI.onCreate(Bundle) afterHookedMethod. // // Previous attempts to hook initView() and LoginHistoryUI.initView() both failed // because ART AOT devirtualizes the call to this.initView() inside // LoginHistoryUI.onCreate() — the compiled native code jumps directly past the // Xposed entry point replacement. // // onCreate() itself is called by the Android framework via the ArtMethod pointer // directly (no vtable devirtualization possible), so this hook fires reliably. // By the time afterHookedMethod runs, super.onCreate() has completed which means // LoginHistoryUI.initView() has already run and this.N is assigned. private void hookLoginHistoryUIInitView(ClassLoader cl) { final Class m4Class; try { m4Class = XposedHelpers.findClass("com.tencent.mm.plugin.account.ui.m4", cl); } catch (Throwable t) { XposedBridge.log("[WeChatHook] m4 class not found: " + t); return; } try { XposedHelpers.findAndHookMethod( "com.tencent.mm.plugin.account.ui.LoginPasswordUI", cl, "onCreate", Bundle.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object obj = param.thisObject; View tabletBtn = (View) XposedHelpers.getObjectField(obj, "N"); if (tabletBtn == null) { XposedBridge.log("[WeChatHook] LoginPasswordUI.onCreate: N is null"); return; } tabletBtn.setVisibility(View.VISIBLE); tabletBtn.setOnClickListener( (View.OnClickListener) XposedHelpers.newInstance(m4Class, obj)); XposedBridge.log("[WeChatHook] LoginPasswordUI.onCreate() -> N VISIBLE + m4 attached"); } }); XposedBridge.log("[WeChatHook] Hooked LoginPasswordUI.onCreate()"); } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookLoginPasswordUI.onCreate failed: " + t); } // Belt-and-suspenders: also hook LoginHistoryUI.onCreate() for any subclass // that doesn't override onCreate but does have the N button. try { XposedHelpers.findAndHookMethod( "com.tencent.mm.plugin.account.ui.LoginHistoryUI", cl, "onCreate", Bundle.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object obj = param.thisObject; if (obj.getClass().getName().equals( "com.tencent.mm.plugin.account.ui.LoginPasswordUI")) return; View tabletBtn = (View) XposedHelpers.getObjectField(obj, "N"); if (tabletBtn == null) { XposedBridge.log("[WeChatHook] LoginHistoryUI.onCreate: N is null"); return; } tabletBtn.setVisibility(View.VISIBLE); tabletBtn.setOnClickListener( (View.OnClickListener) XposedHelpers.newInstance(m4Class, obj)); XposedBridge.log("[WeChatHook] LoginHistoryUI.onCreate() -> N VISIBLE + m4 attached"); } }); XposedBridge.log("[WeChatHook] Hooked LoginHistoryUI.onCreate()"); } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookLoginHistoryUI.onCreate failed: " + t); } } private void hookLoginUIInitView(ClassLoader cl) { try { final Class wbClass = XposedHelpers.findClass( "com.tencent.mm.plugin.account.ui.wb", cl); XposedHelpers.findAndHookMethod( "com.tencent.mm.plugin.account.ui.MobileInputUI", cl, "initView", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object mobileInputUI = param.thisObject; View tabletBtn = (View) XposedHelpers.getObjectField(mobileInputUI, "w"); if (tabletBtn == null) { XposedBridge.log("[WeChatHook] MobileInputUI.w is null"); return; } tabletBtn.setVisibility(View.VISIBLE); tabletBtn.setOnClickListener( (View.OnClickListener) XposedHelpers.newInstance(wbClass, mobileInputUI)); XposedBridge.log("[WeChatHook] MobileInputUI.initView() -> w VISIBLE + wb attached"); } }); XposedBridge.log("[WeChatHook] Hooked MobileInputUI.initView()"); } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookLoginUIInitView(MobileInputUI) failed: " + t); } try { final Class g7Class = XposedHelpers.findClass( "com.tencent.mm.plugin.account.ui.g7", cl); XposedHelpers.findAndHookMethod( "com.tencent.mm.plugin.account.ui.LoginUI", cl, "initView", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Object loginUI = param.thisObject; View tabletBtn = (View) XposedHelpers.getObjectField(loginUI, "f992u"); if (tabletBtn == null) return; tabletBtn.setVisibility(View.VISIBLE); tabletBtn.setOnClickListener( (View.OnClickListener) XposedHelpers.newInstance(g7Class, loginUI)); XposedBridge.log("[WeChatHook] LoginUI.initView() -> f992u VISIBLE + g7 attached"); } }); XposedBridge.log("[WeChatHook] Hooked LoginUI.initView()"); } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookLoginUIInitView(LoginUI) failed: " + t); } } private void hookLoginSelectUICheck(ClassLoader cl) { try { Class callbackClass = XposedHelpers.findClass( "com.tencent.mm.plugin.account.ui.LoginSelectUI$a", cl); XposedHelpers.findAndHookMethod(callbackClass, "a", boolean.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("[WeChatHook] LoginSelectUI$a.a(" + param.args[0] + ") -> false"); param.args[0] = false; } }); XposedBridge.log("[WeChatHook] Hooked LoginSelectUI$a.a()"); } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookLoginSelectUICheck failed: " + t); } } private void hookLoginButtonClick(ClassLoader cl) { try { Class ljClass = XposedHelpers.findClass( "com.tencent.mm.plugin.account.ui.lj", cl); final Class loginSelectUIClass = XposedHelpers.findClass( "com.tencent.mm.plugin.account.ui.LoginSelectUI", cl); XposedHelpers.findAndHookMethod(ljClass, "onClick", View.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("[WeChatHook] lj.onClick() -> forcing LoginSelectUI"); Activity welcomeActivity = (Activity) XposedHelpers.getObjectField( param.thisObject, "d"); Intent intent = new Intent(welcomeActivity, loginSelectUIClass); intent.putExtra("mobile_input_purpose", 1); welcomeActivity.startActivity(intent); param.setResult(null); } }); XposedBridge.log("[WeChatHook] Hooked lj.onClick() -> LoginSelectUI"); } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookLoginButtonClick failed: " + t); } } // ── Send message via internal API ──────────────────────────────────────── // // Registered in ALL WeChat processes. We don't yet know which process has gm0.j1 // initialized — the first process where the receiver fires and succeeds wins. // // Trigger from ADB (must run as root — RECEIVER_NOT_EXPORTED): // adb shell su -c 'am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL \ // --es to "filehelper" --es text "hello"' private void hookSendMessage(ClassLoader cl, String procName) { try { XposedHelpers.findAndHookMethod( "android.app.Application", null, "onCreate", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Context ctx = (Context) param.thisObject; if (!"com.tencent.mm".equals(ctx.getPackageName())) return; sAppContext = ctx; XposedBridge.log("[WeChatHook] App.onCreate proc=" + procName + " pid=" + android.os.Process.myPid()); registerSendReceiver(ctx, procName); } }); XposedBridge.log("[WeChatHook] Send hook registered in " + procName); } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookSendMessage failed in " + procName + ": " + t); } // ── w11.r0 constructor hooks ─────────────────────────────────────────── // Log "by=US" vs "by=WeChat" to tell if WeChat's own UI send uses this ctor. XC_MethodHook r0Hook = new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String by = sCalledByUs ? "US" : "WeChat"; XposedBridge.log("[WeChatHook] w11.r0 ctor by=" + by + " args=" + param.args.length + " proc=" + procName + " pid=" + android.os.Process.myPid()); if (sCalledByUs) { forceInitDI(); diagMCoreStorage("before-r0-ctor"); } } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String by = sCalledByUs ? "US" : "WeChat"; if (param.hasThrowable()) { Throwable t = param.getThrowable(); XposedBridge.log("[WeChatHook] w11.r0 ctor THREW by=" + by + " proc=" + procName + ": " + t); if (sCalledByUs) XposedBridge.log("[WeChatHook] r0-ctor-stack: " + android.util.Log.getStackTraceString(t)); return; } sFsGReady = true; XposedBridge.log("[WeChatHook] w11.r0 ctor OK by=" + by + " proc=" + procName); } }; for (Class[] sig : new Class[][]{ {String.class, String.class, int.class, int.class, long.class}, {String.class, String.class, int.class, int.class, Object.class}, {long.class, int.class, String.class}, {}}) { try { XposedHelpers.findAndHookConstructor("w11.r0", cl, appendHook(sig, r0Hook)); XposedBridge.log("[WeChatHook] Hooked w11.r0(" + sig.length + " params)"); } catch (Throwable t) { XposedBridge.log("[WeChatHook] w11.r0(" + sig.length + "p) hook failed: " + t); } } // ── w11.r0.H(String) — the actual dispatch method ───────────────────── // Fires for EVERY send (ours AND WeChat UI sends). Shows if WeChat uses r0. try { XposedHelpers.findAndHookMethod("w11.r0", cl, "H", String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { String by = sCalledByUs ? "US" : "WeChat"; XposedBridge.log("[WeChatHook] w11.r0.H() called by=" + by + " proc=" + procName + " pid=" + android.os.Process.myPid()); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (param.hasThrowable()) XposedBridge.log("[WeChatHook] w11.r0.H() threw: " + param.getThrowable()); else XposedBridge.log("[WeChatHook] w11.r0.H() OK proc=" + procName); } }); XposedBridge.log("[WeChatHook] Hooked w11.r0.H(String) in " + procName); } catch (Throwable t) { XposedBridge.log("[WeChatHook] w11.r0.H hook failed in " + procName + ": " + t); } // ── Diagnostic probes on the constructor call chain ─────────────────── // Each probe fires when the method is called from inside the r0 constructor // (by WeChat OR by us). They trace which methods succeed before the NPE. hookProbeMethod(cl, "gm0.j1", "u", "gm0.j1.u"); hookProbeMethod(cl, "c01.z1", "r", "c01.z1.r"); // gm0.b0.c() is a virtual method — probe it too try { XposedHelpers.findAndHookMethod("gm0.b0", cl, "c", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam p) { XposedBridge.log("[WeChatHook] PROBE gm0.b0.c() called"); } @Override protected void afterHookedMethod(MethodHookParam p) { if (p.hasThrowable()) XposedBridge.log("[WeChatHook] PROBE gm0.b0.c() THREW: " + p.getThrowable()); else XposedBridge.log("[WeChatHook] PROBE gm0.b0.c() → " + p.getResult()); } }); } catch (Throwable t) { XposedBridge.log("[WeChatHook] gm0.b0.c probe failed: " + t); } } private static Object[] appendHook(Class[] params, XC_MethodHook hook) { Object[] result = new Object[params.length + 1]; System.arraycopy(params, 0, result, 0, params.length); result[params.length] = hook; return result; } private void hookProbeMethod(ClassLoader cl, String cls, String method, String tag) { try { XposedHelpers.findAndHookMethod(cls, cl, method, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam p) { XposedBridge.log("[WeChatHook] PROBE " + tag + "() called"); } @Override protected void afterHookedMethod(MethodHookParam p) { if (p.hasThrowable()) XposedBridge.log("[WeChatHook] PROBE " + tag + "() THREW: " + p.getThrowable()); else XposedBridge.log("[WeChatHook] PROBE " + tag + "() → " + p.getResult()); } }); XposedBridge.log("[WeChatHook] Probe hooked: " + tag); } catch (Throwable t) { XposedBridge.log("[WeChatHook] Probe " + tag + " failed: " + t); } } private static boolean isDIReady() { if (sWeChatCL == null) return false; try { XposedHelpers.callStaticMethod(XposedHelpers.findClass("gm0.j1", sWeChatCL), "i"); return true; } catch (Throwable e) { return false; } } // Hook gm0.j1.g(km0.c) to detect when WeChat DI kernel is initialized. // gm0.j1.g() sets gm0.j1.m (the singleton) and is called lazily — not during // Application.onCreate, so our broadcast often arrives first. After g() fires, // gm0.j1.i() will succeed and w11.r0 can be constructed. private static void hookDIInit(ClassLoader cl, String procName) { try { Class km0cCls = XposedHelpers.findClass("km0.c", cl); XposedHelpers.findAndHookMethod("gm0.j1", cl, "g", km0cCls, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam p) throws Throwable { XposedBridge.log("[WeChatHook] gm0.j1.g() fired — DI ready in " + procName); String t = sPendingTalker, x = sPendingText, pr = sPendingProc; if (t != null && x != null && procName.equals(pr)) { sPendingTalker = null; sPendingText = null; sPendingProc = null; XposedBridge.log("[WeChatHook] resuming pending send to=" + t + " proc=" + procName); new Handler(Looper.getMainLooper()).postDelayed( () -> trySendWithRetry(t, x, 0, procName), 50); } } }); XposedBridge.log("[WeChatHook] gm0.j1.g hook OK in " + procName); } catch (Throwable t) { XposedBridge.log("[WeChatHook] gm0.j1.g hook failed in " + procName + ": " + t); } } private static void diagMCoreStorage(String where) { if (sWeChatCL == null) return; try { Class j1Cls = XposedHelpers.findClass("gm0.j1", sWeChatCL); Object j1 = XposedHelpers.callStaticMethod(j1Cls, "i"); Object mCS = (j1 != null) ? XposedHelpers.getObjectField(j1, "d") : null; XposedBridge.log("[WeChatHook] " + where + " mCoreStorage=" + mCS); } catch (Throwable e) { XposedBridge.log("[WeChatHook] " + where + " mCoreStorage-check: " + e); } } private static void forceInitDI() { if (sWeChatCL == null) return; try { Class fsgCls = XposedHelpers.findClass("fs.g", sWeChatCL); java.lang.reflect.Field faFld = fsgCls.getDeclaredField("a"); faFld.setAccessible(true); Object[] arr = (Object[]) faFld.get(null); if (arr == null || arr.length == 0 || arr[0] == null) { Class k2Cls = XposedHelpers.findClass("fs.k2", sWeChatCL); java.lang.reflect.Field k2eFld = k2Cls.getDeclaredField("e"); k2eFld.setAccessible(true); Object k2e = k2eFld.get(null); if (k2e == null) { XposedBridge.log("[WeChatHook] forceInitDI: fs.k2.e null"); return; } if (arr == null || arr.length == 0) { arr = new Object[1]; faFld.set(null, arr); } arr[0] = k2e; XposedBridge.log("[WeChatHook] forceInitDI: fs.g.a[0]=" + k2e.getClass().getName()); } } catch (Throwable e) { XposedBridge.log("[WeChatHook] forceInitDI: " + e); } } private static void trySendWithRetry(String talker, String text, int attempt, String procName) { // If DI not ready: park the args and wait for gm0.j1.g() hook to trigger retry. // Only the first process to check parks the pending (others may overwrite, which is fine // — the last write wins and gm0.j1.g() fires per-process anyway). if (!isDIReady()) { if (attempt == 0) { XposedBridge.log("[WeChatHook] DI not ready proc=" + procName + " — parking pending send to=" + talker); sPendingTalker = talker; sPendingText = text; sPendingProc = procName; } else { XposedBridge.log("[WeChatHook] DI still not ready proc=" + procName + " attempt=" + attempt); } return; } try { sendViaR0(talker, text); XposedBridge.log("[WeChatHook] Send OK to=" + talker + " proc=" + procName); } catch (Throwable t) { String tMsg = t.getMessage() != null ? t.getMessage() : t.toString(); XposedBridge.log("[WeChatHook] Send FAILED proc=" + procName + " to=" + talker + ": " + tMsg); if (attempt < 3) { long delay = 1000L << attempt; new Handler(Looper.getMainLooper()).postDelayed( () -> trySendWithRetry(talker, text, attempt + 1, procName), delay); } } } private static void registerSendReceiver(Context ctx, String procName) { BroadcastReceiver recv = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String talker = intent.getStringExtra("to"); String text = intent.getStringExtra("text"); if (talker == null || talker.isEmpty() || text == null || text.isEmpty()) { XposedBridge.log("[WeChatHook] SEND_MSG_INTERNAL: missing 'to' or 'text'"); return; } XposedBridge.log("[WeChatHook] SEND_MSG_INTERNAL proc=" + procName + " pid=" + android.os.Process.myPid() + " to=" + talker + " len=" + text.length()); new Handler(Looper.getMainLooper()).post(() -> trySendWithRetry(talker, text, 0, procName)); } }; ctx.registerReceiver(recv, new IntentFilter(ACTION_SEND), Context.RECEIVER_NOT_EXPORTED); XposedBridge.log("[WeChatHook] BroadcastReceiver registered in " + procName); } private static void sendViaR0(String talker, String text) throws Throwable { Class r0Class = XposedHelpers.findClass("w11.r0", sWeChatCL); long ts = System.currentTimeMillis(); Object msg; sCalledByUs = true; try { try { msg = XposedHelpers.newInstance(r0Class, new Class[]{String.class, String.class, int.class, int.class, long.class}, talker, text, 1, 0, ts); } catch (Throwable t) { msg = XposedHelpers.newInstance(r0Class, new Class[]{String.class, String.class, int.class, int.class, Object.class}, talker, text, 1, 0, (Object) ts); } } finally { sCalledByUs = false; } try { Class u0 = XposedHelpers.findClass("com.tencent.mm.modelbase.u0", sWeChatCL); Object stub = Proxy.newProxyInstance(sWeChatCL, new Class[]{u0}, (proxy, method, args) -> { XposedBridge.log("[WeChatHook] send-cb: " + method.getName()); return null; }); XposedHelpers.setObjectField(msg, "d", stub); } catch (Throwable ignored) {} sCalledByUs = true; try { XposedHelpers.callMethod(msg, "H", ""); } finally { sCalledByUs = false; } } }