Commit 4b6e1572ef46120a307a55ef898974747a732289
1 parent
cfac4e65
feat(xhshook): v3.1 签名代理模式 — OkHttp proxy poller + body 捕获
Hook.java v3.1 (315 行):
- body 捕获:okio.Buffer 读取 POST/PUT body(截断 2000 字符)
- Proxy poller(后台线程,1s 轮询 /xhs_proxy/pending):
executeOkHttp() 在 XHS 进程内用 OkHttpClient 执行代理请求,
自动经过 XHS 拦截器签名,结果 POST /xhs_proxy/result
- 手写 JSON 解析器(无外部依赖)
- 保留 /xhs_params 转发(每条 edith 请求实时上报)
event_server.py(Hermes 编写):
- proxy_requests 表(pending→ok/error 状态机)
- POST /xhs_proxy — Hermes 下发代理请求
- GET /xhs_proxy/pending — Hook 轮询取下一个任务
- POST /xhs_proxy/result — Hook 上报执行结果
- GET /xhs_proxy/result/{id} — Hermes 轮询取结果
- DELETE /xhs_proxy/{id} — 清理完成的任务
架构:Hermes → event_server → Hook(XHS进程内OkHttp签名) → event_server → Hermes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Showing
3 changed files
with
318 additions
and
15 deletions
phoneuse/event_server.py
| ... | ... | @@ -219,7 +219,7 @@ class XhsParams(BaseModel): |
| 219 | 219 | method: str |
| 220 | 220 | url: str |
| 221 | 221 | headers: dict[str, str] = {} |
| 222 | - | |
| 222 | + body: str = "" | |
| 223 | 223 | |
| 224 | 224 | @app.post("/xhs_params", status_code=200) |
| 225 | 225 | def post_xhs_params(params: XhsParams): |
| ... | ... | @@ -228,7 +228,10 @@ def post_xhs_params(params: XhsParams): |
| 228 | 228 | with get_db() as conn: |
| 229 | 229 | cursor = conn.execute( |
| 230 | 230 | "INSERT INTO events (type, app, data, ts, source) VALUES (?, ?, ?, ?, ?)", |
| 231 | - ("xhs_request", "com.xingin.xhs", json.dumps({"method": params.method, "url": params.url, "headers": params.headers}), time.time(), "xhshook") | |
| 231 | + ("xhs_request", "com.xingin.xhs", | |
| 232 | + json.dumps({"method": params.method, "url": params.url, | |
| 233 | + "headers": params.headers, "body": params.body}), | |
| 234 | + time.time(), "xhshook") | |
| 232 | 235 | ) |
| 233 | 236 | conn.commit() |
| 234 | 237 | return {"id": cursor.lastrowid} |
| ... | ... | @@ -243,3 +246,102 @@ def get_xhs_params(limit: int = Query(default=10, le=100)): |
| 243 | 246 | (limit,) |
| 244 | 247 | ).fetchall() |
| 245 | 248 | return [{"id": r["id"], "ts": r["ts"], **json.loads(r["data"])} for r in reversed(rows)] |
| 249 | + | |
| 250 | + | |
| 251 | +# ─── XHS Proxy (Hermes → xhshook → OkHttp) ─── | |
| 252 | + | |
| 253 | +class XhsProxyRequest(BaseModel): | |
| 254 | + url: str | |
| 255 | + method: str = "GET" | |
| 256 | + body: str = "" | |
| 257 | + headers_extra: dict[str, str] = {} | |
| 258 | + | |
| 259 | +class XhsProxyResult(BaseModel): | |
| 260 | + proxy_id: int | |
| 261 | + status: str # "ok" | "error" | |
| 262 | + http_code: int = 0 | |
| 263 | + response_body: str = "" | |
| 264 | + error: str = "" | |
| 265 | + | |
| 266 | +def _init_proxy_table(): | |
| 267 | + with get_db() as conn: | |
| 268 | + conn.execute(""" | |
| 269 | + CREATE TABLE IF NOT EXISTS proxy_requests ( | |
| 270 | + id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| 271 | + status TEXT DEFAULT 'pending', | |
| 272 | + created_ts REAL, | |
| 273 | + req_url TEXT, | |
| 274 | + req_method TEXT, | |
| 275 | + req_body TEXT, | |
| 276 | + req_headers_extra TEXT, | |
| 277 | + resp_http_code INTEGER, | |
| 278 | + resp_body TEXT, | |
| 279 | + resp_error TEXT | |
| 280 | + ) | |
| 281 | + """) | |
| 282 | + conn.commit() | |
| 283 | + | |
| 284 | +_init_proxy_table() | |
| 285 | + | |
| 286 | +@app.post("/xhs_proxy", status_code=201) | |
| 287 | +def submit_proxy_request(req: XhsProxyRequest): | |
| 288 | + """Hermes submits a request to be proxied through XHS's OkHttp (with auto-signing).""" | |
| 289 | + with get_db() as conn: | |
| 290 | + cur = conn.execute( | |
| 291 | + "INSERT INTO proxy_requests (status, created_ts, req_url, req_method, req_body, req_headers_extra) " | |
| 292 | + "VALUES ('pending', ?, ?, ?, ?, ?)", | |
| 293 | + (time.time(), req.url, req.method, req.body, json.dumps(req.headers_extra)) | |
| 294 | + ) | |
| 295 | + conn.commit() | |
| 296 | + return {"proxy_id": cur.lastrowid} | |
| 297 | + | |
| 298 | +@app.get("/xhs_proxy/pending") | |
| 299 | +def get_pending_proxy(): | |
| 300 | + """xhshook polls this. Returns oldest pending request (null JSON if none).""" | |
| 301 | + with get_db() as conn: | |
| 302 | + row = conn.execute( | |
| 303 | + "SELECT id, req_url, req_method, req_body, req_headers_extra FROM proxy_requests " | |
| 304 | + "WHERE status='pending' ORDER BY id ASC LIMIT 1" | |
| 305 | + ).fetchone() | |
| 306 | + if not row: | |
| 307 | + return None | |
| 308 | + # Mark as processing | |
| 309 | + with get_db() as conn: | |
| 310 | + conn.execute("UPDATE proxy_requests SET status='processing' WHERE id=?", (row["id"],)) | |
| 311 | + conn.commit() | |
| 312 | + return { | |
| 313 | + "proxy_id": row["id"], | |
| 314 | + "url": row["req_url"], | |
| 315 | + "method": row["req_method"], | |
| 316 | + "body": row["req_body"], | |
| 317 | + "headers_extra": json.loads(row["req_headers_extra"] or "{}") | |
| 318 | + } | |
| 319 | + | |
| 320 | +@app.post("/xhs_proxy/result", status_code=200) | |
| 321 | +def submit_proxy_result(result: XhsProxyResult): | |
| 322 | + """xhshook posts the API response here.""" | |
| 323 | + with get_db() as conn: | |
| 324 | + conn.execute( | |
| 325 | + "UPDATE proxy_requests SET status=?, resp_http_code=?, resp_body=?, resp_error=? WHERE id=?", | |
| 326 | + (result.status, result.http_code, result.response_body, result.error, result.proxy_id) | |
| 327 | + ) | |
| 328 | + conn.commit() | |
| 329 | + return {"ok": True} | |
| 330 | + | |
| 331 | +@app.get("/xhs_proxy/result/{proxy_id}") | |
| 332 | +def get_proxy_result(proxy_id: int): | |
| 333 | + """Hermes polls this for the result.""" | |
| 334 | + with get_db() as conn: | |
| 335 | + row = conn.execute( | |
| 336 | + "SELECT * FROM proxy_requests WHERE id=?", (proxy_id,) | |
| 337 | + ).fetchone() | |
| 338 | + if not row: | |
| 339 | + raise HTTPException(404, "proxy_id not found") | |
| 340 | + return { | |
| 341 | + "proxy_id": row["id"], | |
| 342 | + "status": row["status"], | |
| 343 | + "created_ts": row["created_ts"], | |
| 344 | + "http_code": row["resp_http_code"], | |
| 345 | + "response_body": row["resp_body"], | |
| 346 | + "error": row["resp_error"] | |
| 347 | + } | ... | ... |
xhs_lsposed_a16_fix/xhshook_src/Hook.java
| ... | ... | @@ -6,20 +6,28 @@ import de.robv.android.xposed.XposedBridge; |
| 6 | 6 | import de.robv.android.xposed.XposedHelpers; |
| 7 | 7 | import de.robv.android.xposed.callbacks.XC_LoadPackage; |
| 8 | 8 | |
| 9 | +import java.io.InputStream; | |
| 9 | 10 | import java.io.OutputStream; |
| 11 | +import java.io.ByteArrayOutputStream; | |
| 10 | 12 | import java.net.HttpURLConnection; |
| 11 | 13 | import java.net.URL; |
| 12 | 14 | import java.nio.charset.StandardCharsets; |
| 15 | +import java.util.Scanner; | |
| 13 | 16 | |
| 14 | 17 | public class Hook implements IXposedHookLoadPackage { |
| 15 | - // Hermes event_server endpoint — POST xhs_params events here | |
| 18 | + // Hermes event_server endpoints | |
| 16 | 19 | private static final String HERMES_URL = "http://127.0.0.1:8765/xhs_params"; |
| 20 | + private static final String PROXY_POLL_URL = "http://127.0.0.1:8765/xhs_proxy/pending"; | |
| 21 | + private static final String PROXY_RESULT_URL = "http://127.0.0.1:8765/xhs_proxy/result"; | |
| 22 | + private static ClassLoader appClassLoader; | |
| 23 | + private static boolean proxyRunning = false; | |
| 17 | 24 | |
| 18 | 25 | @Override |
| 19 | 26 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { |
| 20 | 27 | if (!lpparam.packageName.equals("com.xingin.xhs")) return; |
| 21 | 28 | |
| 22 | 29 | XposedBridge.log("[XHSHook] Loaded into XHS!"); |
| 30 | + appClassLoader = lpparam.classLoader; | |
| 23 | 31 | |
| 24 | 32 | try { |
| 25 | 33 | Class<?> clz = XposedHelpers.findClass("okhttp3.Request$Builder", lpparam.classLoader); |
| ... | ... | @@ -42,13 +50,25 @@ public class Hook implements IXposedHookLoadPackage { |
| 42 | 50 | headersStr = headers != null ? headers.toString() : ""; |
| 43 | 51 | } |
| 44 | 52 | |
| 45 | - XposedBridge.log("[XHSHook] " + methodStr + " " + urlStr); | |
| 46 | - XposedBridge.log("[XHSHook] Headers:\n" + headersStr); | |
| 53 | + // Capture request body (for POST/PUT requests) | |
| 54 | + String bodyStr = ""; | |
| 55 | + try { | |
| 56 | + Object bodyObj = XposedHelpers.getObjectField(param.thisObject, "body"); | |
| 57 | + if (bodyObj != null) { | |
| 58 | + Class<?> bufferClz = XposedHelpers.findClass("okio.Buffer", lpparam.classLoader); | |
| 59 | + Object buffer = bufferClz.newInstance(); | |
| 60 | + XposedHelpers.callMethod(bodyObj, "writeTo", buffer); | |
| 61 | + bodyStr = (String) XposedHelpers.callMethod(buffer, "readUtf8"); | |
| 62 | + if (bodyStr != null && bodyStr.length() > 2000) { | |
| 63 | + bodyStr = bodyStr.substring(0, 2000); | |
| 64 | + } | |
| 65 | + } | |
| 66 | + } catch (Throwable t) { | |
| 67 | + bodyStr = ""; | |
| 68 | + } | |
| 47 | 69 | |
| 48 | - final String json = buildJson(methodStr, urlStr, headersStr); | |
| 49 | - new Thread(new Runnable() { | |
| 50 | - public void run() { postToHermes(json); } | |
| 51 | - }).start(); | |
| 70 | + final String json = buildJson(methodStr, urlStr, headersStr, bodyStr); | |
| 71 | + new Thread(() -> postToHermes(json)).start(); | |
| 52 | 72 | |
| 53 | 73 | } catch (Throwable t) { |
| 54 | 74 | XposedBridge.log("[XHSHook] Error in hook: " + t.getMessage()); |
| ... | ... | @@ -56,17 +76,201 @@ public class Hook implements IXposedHookLoadPackage { |
| 56 | 76 | } |
| 57 | 77 | }); |
| 58 | 78 | XposedBridge.log("[XHSHook] OkHttp Request.Builder.build() hooked!"); |
| 79 | + | |
| 80 | + // Start proxy poller in background | |
| 81 | + startProxyPoller(); | |
| 82 | + | |
| 59 | 83 | } catch (Throwable t) { |
| 60 | 84 | XposedBridge.log("[XHSHook] Failed to hook OkHttp: " + t.getMessage()); |
| 61 | 85 | XposedBridge.log(t); |
| 62 | 86 | } |
| 63 | 87 | } |
| 64 | 88 | |
| 65 | - private static String buildJson(String method, String url, String headersStr) { | |
| 89 | + // ─── Proxy Poller ───────────────────────────────────────────── | |
| 90 | + | |
| 91 | + private static void startProxyPoller() { | |
| 92 | + if (proxyRunning) return; | |
| 93 | + proxyRunning = true; | |
| 94 | + new Thread(() -> { | |
| 95 | + XposedBridge.log("[XHSHook] Proxy poller started"); | |
| 96 | + while (true) { | |
| 97 | + try { | |
| 98 | + String json = httpGet(PROXY_POLL_URL); | |
| 99 | + if (json != null && !json.equals("null") && !json.isEmpty()) { | |
| 100 | + int proxyId = parseJsonInt(json, "proxy_id"); | |
| 101 | + String reqUrl = parseJsonString(json, "url"); | |
| 102 | + String method = parseJsonString(json, "method"); | |
| 103 | + String body = parseJsonString(json, "body"); | |
| 104 | + | |
| 105 | + XposedBridge.log("[XHSHook] Proxy exec #" + proxyId + " " + method + " " + reqUrl); | |
| 106 | + | |
| 107 | + String[] result = executeOkHttp(proxyId, method, reqUrl, body); | |
| 108 | + String status = result[0]; | |
| 109 | + int httpCode = Integer.parseInt(result[1]); | |
| 110 | + String respBody = result[2]; | |
| 111 | + String error = result[3]; | |
| 112 | + | |
| 113 | + postProxyResult(proxyId, status, httpCode, respBody, error); | |
| 114 | + } | |
| 115 | + Thread.sleep(1000); | |
| 116 | + } catch (Throwable t) { | |
| 117 | + XposedBridge.log("[XHSHook] Proxy poller error: " + t.getMessage()); | |
| 118 | + try { Thread.sleep(3000); } catch (Throwable tt) {} | |
| 119 | + } | |
| 120 | + } | |
| 121 | + }).start(); | |
| 122 | + } | |
| 123 | + | |
| 124 | + private static String[] executeOkHttp(int proxyId, String method, String url, String body) { | |
| 125 | + // Returns [status, httpCode, responseBody, error] | |
| 126 | + try { | |
| 127 | + Class<?> okHttpClz = XposedHelpers.findClass("okhttp3.OkHttpClient", appClassLoader); | |
| 128 | + Class<?> requestClz = XposedHelpers.findClass("okhttp3.Request", appClassLoader); | |
| 129 | + Class<?> requestBuilderClz = XposedHelpers.findClass("okhttp3.Request$Builder", appClassLoader); | |
| 130 | + Class<?> mediaTypeClz = XposedHelpers.findClass("okhttp3.MediaType", appClassLoader); | |
| 131 | + Class<?> requestBodyClz = XposedHelpers.findClass("okhttp3.RequestBody", appClassLoader); | |
| 132 | + | |
| 133 | + // Create OkHttpClient (new instance — auto-injects interceptors from XHS) | |
| 134 | + Object client = okHttpClz.newInstance(); | |
| 135 | + | |
| 136 | + // Build request | |
| 137 | + Object builder = requestBuilderClz.newInstance(); | |
| 138 | + builder = XposedHelpers.callMethod(builder, "url", url); | |
| 139 | + builder = XposedHelpers.callMethod(builder, "method", method, | |
| 140 | + body != null && !body.isEmpty() ? | |
| 141 | + XposedHelpers.callStaticMethod(requestBodyClz, "create", | |
| 142 | + XposedHelpers.callStaticMethod(mediaTypeClz, "parse", "application/json; charset=utf-8"), | |
| 143 | + body) | |
| 144 | + : null); | |
| 145 | + | |
| 146 | + // Add standard XHS headers (User-Agent, Referer) | |
| 147 | + builder = XposedHelpers.callMethod(builder, "addHeader", "User-Agent", | |
| 148 | + "Dalvik/2.1.0 (Linux; U; Android 16; Pixel 10 Pro Fold Build/CP1A.260505.005) " + | |
| 149 | + "Resolution/1080*2364 Version/9.35.1 Build/9351803 Device/(Google;Pixel 10 Pro Fold) " + | |
| 150 | + "discover/9.35.1 NetType/WiFi"); | |
| 151 | + builder = XposedHelpers.callMethod(builder, "addHeader", "Referer", "https://app.xhs.cn/"); | |
| 152 | + | |
| 153 | + Object request = XposedHelpers.callMethod(builder, "build"); | |
| 154 | + | |
| 155 | + // Execute | |
| 156 | + Object call = XposedHelpers.callMethod(client, "newCall", request); | |
| 157 | + Object response = XposedHelpers.callMethod(call, "execute"); | |
| 158 | + | |
| 159 | + int httpCode = (Integer) XposedHelpers.callMethod(response, "code"); | |
| 160 | + Object respBodyObj = XposedHelpers.callMethod(response, "body"); | |
| 161 | + Object source = XposedHelpers.callMethod(respBodyObj, "source"); | |
| 162 | + Object buffer = XposedHelpers.findClass("okio.Buffer", appClassLoader).newInstance(); | |
| 163 | + XposedHelpers.callMethod(source, "readAll", buffer); | |
| 164 | + String respBody = (String) XposedHelpers.callMethod(buffer, "readUtf8"); | |
| 165 | + | |
| 166 | + // Truncate | |
| 167 | + if (respBody != null && respBody.length() > 10000) { | |
| 168 | + respBody = respBody.substring(0, 10000); | |
| 169 | + } | |
| 170 | + | |
| 171 | + XposedHelpers.callMethod(response, "close"); | |
| 172 | + return new String[]{"ok", String.valueOf(httpCode), respBody, ""}; | |
| 173 | + | |
| 174 | + } catch (Throwable t) { | |
| 175 | + String errMsg = t.getMessage(); | |
| 176 | + if (errMsg == null) errMsg = t.getClass().getName(); | |
| 177 | + return new String[]{"error", "0", "", errMsg}; | |
| 178 | + } | |
| 179 | + } | |
| 180 | + | |
| 181 | + private static void postProxyResult(int proxyId, String status, int httpCode, String respBody, String error) { | |
| 182 | + String json = "{\"proxy_id\":" + proxyId + | |
| 183 | + ",\"status\":\"" + status + "\"" + | |
| 184 | + ",\"http_code\":" + httpCode + | |
| 185 | + ",\"response_body\":" + jsonStr(respBody) + | |
| 186 | + ",\"error\":" + jsonStr(error) + "}"; | |
| 187 | + try { | |
| 188 | + HttpURLConnection conn = (HttpURLConnection) new URL(PROXY_RESULT_URL).openConnection(); | |
| 189 | + conn.setRequestMethod("POST"); | |
| 190 | + conn.setRequestProperty("Content-Type", "application/json"); | |
| 191 | + conn.setDoOutput(true); | |
| 192 | + conn.setConnectTimeout(2000); | |
| 193 | + conn.setReadTimeout(2000); | |
| 194 | + byte[] bodyBytes = json.getBytes(StandardCharsets.UTF_8); | |
| 195 | + conn.setFixedLengthStreamingMode(bodyBytes.length); | |
| 196 | + try (OutputStream os = conn.getOutputStream()) { | |
| 197 | + os.write(bodyBytes); | |
| 198 | + } | |
| 199 | + int code = conn.getResponseCode(); | |
| 200 | + conn.disconnect(); | |
| 201 | + if (code != 200) { | |
| 202 | + XposedBridge.log("[XHSHook] Proxy result POST " + code); | |
| 203 | + } | |
| 204 | + } catch (Throwable t) { | |
| 205 | + XposedBridge.log("[XHSHook] Failed to post proxy result: " + t.getMessage()); | |
| 206 | + } | |
| 207 | + } | |
| 208 | + | |
| 209 | + // ─── HTTP helpers ───────────────────────────────────────────── | |
| 210 | + | |
| 211 | + private static String httpGet(String urlStr) { | |
| 212 | + try { | |
| 213 | + HttpURLConnection conn = (HttpURLConnection) new URL(urlStr).openConnection(); | |
| 214 | + conn.setRequestMethod("GET"); | |
| 215 | + conn.setConnectTimeout(2000); | |
| 216 | + conn.setReadTimeout(2000); | |
| 217 | + int code = conn.getResponseCode(); | |
| 218 | + if (code == 200) { | |
| 219 | + Scanner s = new Scanner(conn.getInputStream(), "UTF-8").useDelimiter("\\A"); | |
| 220 | + String result = s.hasNext() ? s.next() : ""; | |
| 221 | + s.close(); | |
| 222 | + conn.disconnect(); | |
| 223 | + return result; | |
| 224 | + } | |
| 225 | + conn.disconnect(); | |
| 226 | + } catch (Throwable t) {} | |
| 227 | + return null; | |
| 228 | + } | |
| 229 | + | |
| 230 | + // ─── Minimal JSON parsing (no external deps) ────────────────── | |
| 231 | + | |
| 232 | + private static int parseJsonInt(String json, String key) { | |
| 233 | + int idx = json.indexOf("\"" + key + "\""); | |
| 234 | + if (idx < 0) return 0; | |
| 235 | + idx = json.indexOf(":", idx); | |
| 236 | + if (idx < 0) return 0; | |
| 237 | + idx++; | |
| 238 | + while (idx < json.length() && (json.charAt(idx) == ' ' || json.charAt(idx) == '\t')) idx++; | |
| 239 | + StringBuilder num = new StringBuilder(); | |
| 240 | + while (idx < json.length() && (Character.isDigit(json.charAt(idx)) || json.charAt(idx) == '-')) { | |
| 241 | + num.append(json.charAt(idx)); | |
| 242 | + idx++; | |
| 243 | + } | |
| 244 | + try { return Integer.parseInt(num.toString()); } catch (Throwable t) { return 0; } | |
| 245 | + } | |
| 246 | + | |
| 247 | + private static String parseJsonString(String json, String key) { | |
| 248 | + int idx = json.indexOf("\"" + key + "\""); | |
| 249 | + if (idx < 0) return ""; | |
| 250 | + idx = json.indexOf("\"", idx + key.length() + 2); | |
| 251 | + if (idx < 0) return ""; | |
| 252 | + int start = idx; | |
| 253 | + idx++; | |
| 254 | + while (idx < json.length()) { | |
| 255 | + char c = json.charAt(idx); | |
| 256 | + if (c == '\\') { idx += 2; continue; } | |
| 257 | + if (c == '"') break; | |
| 258 | + idx++; | |
| 259 | + } | |
| 260 | + return json.substring(start + 1, idx) | |
| 261 | + .replace("\\\"", "\"") | |
| 262 | + .replace("\\\\", "\\") | |
| 263 | + .replace("\\n", "\n"); | |
| 264 | + } | |
| 265 | + | |
| 266 | + // ─── JSON builders ──────────────────────────────────────────── | |
| 267 | + | |
| 268 | + private static String buildJson(String method, String url, String headersStr, String body) { | |
| 66 | 269 | StringBuilder sb = new StringBuilder(); |
| 67 | 270 | sb.append("{"); |
| 68 | 271 | sb.append("\"method\":").append(jsonStr(method)).append(","); |
| 69 | 272 | sb.append("\"url\":").append(jsonStr(url)).append(","); |
| 273 | + sb.append("\"body\":").append(jsonStr(body)).append(","); | |
| 70 | 274 | sb.append("\"headers\":{"); |
| 71 | 275 | boolean first = true; |
| 72 | 276 | for (String line : headersStr.split("\n")) { |
| ... | ... | @@ -103,13 +307,10 @@ public class Hook implements IXposedHookLoadPackage { |
| 103 | 307 | try (OutputStream os = conn.getOutputStream()) { |
| 104 | 308 | os.write(body); |
| 105 | 309 | } |
| 106 | - int code = conn.getResponseCode(); | |
| 310 | + conn.getResponseCode(); | |
| 107 | 311 | conn.disconnect(); |
| 108 | - if (code != 200 && code != 201 && code != 204) { | |
| 109 | - XposedBridge.log("[XHSHook] Hermes POST " + code); | |
| 110 | - } | |
| 111 | 312 | } catch (Throwable t) { |
| 112 | - // Hermes server not running — silent fail, logcat capture still works | |
| 313 | + // Hermes server not running — silent fail | |
| 113 | 314 | } |
| 114 | 315 | } |
| 115 | 316 | } | ... | ... |
xhs_lsposed_a16_fix/xhshook_v2_signed.apk
No preview for this file type