fix: update window dimensions and background color for improved night mode theme

This commit is contained in:
Nixevol
2026-09-27 06:27:04 +08:00
parent 1b33898fff
commit 7811e6dbaa
2 changed files with 197 additions and 4 deletions
+195
View File
@@ -0,0 +1,195 @@
"""把 Dart 发出的明文 HTTP 显示在本机页面。
Charles 看不到这些请求:它们不走系统代理,在进系统套接字之前就已经加密。
这里直接从进程内存里取出加密前的报文。
"""
import json
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import frida
DEVICE = "emulator-5556"
PORT = 8791
APP_NAMES = ("RaptDrama",)
JS = r"""
function extract(addr) {
var back = addr;
for (var i = 0; i < 160; i++) {
var b = 0;
try { b = back.sub(1).readU8(); } catch (e) { break; }
if (b !== 0x0a && b !== 0x0d && (b < 0x20 || b > 0x7e)) break;
back = back.sub(1);
}
var s = "";
for (var j = 0; j < 2500; j++) {
var c = 0;
try { c = back.add(j).readU8(); } catch (e) { break; }
if (c === 0x0d) continue;
if (c === 0x0a) { s += "\n"; continue; }
if (c < 0x20 || c > 0x7e) break;
s += String.fromCharCode(c);
}
return s;
}
var seen = {};
var scanning = false;
var lastScan = 0;
function scan() {
var now = Date.now();
if (scanning || now - lastScan < 1200) return;
scanning = true;
lastScan = now;
var ranges = Process.enumerateRanges("rw-");
var fresh = [];
for (var i = 0; i < ranges.length; i++) {
var r = ranges[i];
if (r.size > 24 * 1024 * 1024) continue;
var matches;
try { matches = Memory.scanSync(r.base, r.size, "48 54 54 50 2f 31 2e 31"); }
catch (e) { continue; }
for (var j = 0; j < matches.length; j++) {
var text = extract(matches[j].address);
if (!text || text.length < 16 || seen[text]) continue;
if (text.indexOf("package:") === 0) continue;
seen[text] = true;
fresh.push(text);
}
}
if (fresh.length) send(JSON.stringify(fresh));
scanning = false;
}
var writePtr = Process.getModuleByName("libc.so").getExportByName("write");
Interceptor.attach(writePtr, {
onEnter: function (args) {
try {
if (args[1].readU8() === 0x17) scan();
} catch (e) {}
}
});
scan();
send("ready");
"""
FLOWS = []
LOCK = threading.Lock()
def on_message(message, _data):
if message.get("type") != "send":
print(message, flush=True)
return
payload = message["payload"]
if payload == "ready":
print("已挂上进程,等待请求", flush=True)
return
items = json.loads(payload)
with LOCK:
for text in items:
first = text.strip().split("\n", 1)[0][:180]
FLOWS.append({"id": len(FLOWS) + 1, "summary": first, "text": text})
print(first, flush=True)
def attach():
device = frida.get_device(DEVICE)
pid = None
for proc in device.enumerate_processes():
if proc.name in APP_NAMES or "Rapt" in proc.name:
pid = proc.pid
break
if pid is None:
raise SystemExit("RaptDrama 没在运行。先打开 App,再启动这个页面。")
print(f"attach {pid}", flush=True)
session = device.attach(pid)
script = session.create_script(JS)
script.on("message", on_message)
script.load()
return session, script
PAGE = """<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>明文请求</title>
<style>
html, body { height: 100%; margin: 0; background: #161616; color: #eee; font: 14px/1.45 "Segoe UI", sans-serif; }
header { position: sticky; top: 0; display: flex; justify-content: space-between; align-items: center;
padding: 10px 16px; background: #111; border-bottom: 1px solid #333; z-index: 2; }
main { display: flex; height: calc(100% - 49px); }
#list { width: 42%; overflow: auto; border-right: 1px solid #333; }
button.row { display: block; width: 100%; text-align: left; padding: 8px 12px; border: 0;
border-bottom: 1px solid #2a2a2a; background: transparent; color: #ddd; cursor: pointer; }
button.row.on { background: #2c3a28; }
#detail { flex: 1; margin: 0; padding: 12px 16px; overflow: auto; white-space: pre-wrap; word-break: break-all; }
.muted { color: #9aa; }
</style>
</head>
<body>
<header>
<strong>Dart 明文请求</strong>
<span id="count" class="muted">0 条</span>
</header>
<main>
<div id="list"></div>
<pre id="detail">打开 App 并滑动页面,新的请求会出现在左边。</pre>
</main>
<script>
let selected = 0;
async function refresh() {
const rows = await (await fetch("/api/flows")).json();
document.getElementById("count").textContent = rows.length + " 条";
const list = document.getElementById("list");
list.innerHTML = "";
rows.slice().reverse().forEach((row) => {
const button = document.createElement("button");
button.className = "row" + (row.id === selected ? " on" : "");
button.textContent = row.summary;
button.onclick = () => { selected = row.id; document.getElementById("detail").textContent = row.text; refresh(); };
list.appendChild(button);
});
}
refresh();
setInterval(refresh, 1000);
</script>
</body>
</html>
"""
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path.startswith("/api/flows"):
with LOCK:
payload = json.dumps(FLOWS, ensure_ascii=False).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(payload)))
self.end_headers()
self.wfile.write(payload)
return
body = PAGE.encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, fmt, *args):
return
def main():
attach()
server = ThreadingHTTPServer(("127.0.0.1", PORT), Handler)
print(f"打开 http://127.0.0.1:{PORT}", flush=True)
server.serve_forever()
if __name__ == "__main__":
main()