scan_sourid_pcap.py 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. #!/usr/bin/env python3
  2. """scan_sourid_pcap.py — 从 pcap 重组 TCP 流, 提取全部 SC51/SC634 地图属性。
  3. 方法(交接文档定案): scapy 重组 sport=8043 流 → encode6 帧头切帧 →
  4. SC-1 批量帧 zlib 解压子帧 → 全部 SC51/SC634 decode6 → sourid 提取。
  5. 用法: sudo .venv/bin/python scripts/scan_sourid_pcap.py /tmp/cap_full.pcap
  6. """
  7. from __future__ import annotations
  8. import json
  9. import sys
  10. import zlib
  11. sys.path.insert(0, "src")
  12. from pbot.codec.bitbuf import decode6 # noqa: E402
  13. def frames_from_stream(buf: bytes) -> list[tuple[int, bytes]]:
  14. """从 TCP 字节流按 encode6 帧头切帧(FrameSplitter: 粘包/半包安全)。"""
  15. from pbot.codec.frame import FrameSplitter
  16. sp = FrameSplitter()
  17. return [(f.msg_id, f.body) for f in sp.feed(buf)]
  18. def extract_map_bodies(frames: list[tuple[int, bytes]]) -> list[dict]:
  19. out: list[dict] = []
  20. for msg, body in frames:
  21. if msg == -1:
  22. # 批量帧: zlib 压缩子帧流
  23. try:
  24. raw = zlib.decompress(body)
  25. except zlib.error:
  26. continue
  27. out.extend(extract_map_bodies(frames_from_stream(raw)))
  28. continue
  29. if msg not in (51, 634):
  30. continue
  31. try:
  32. text = decode6(body).decode("utf-8", errors="replace")
  33. start = text.find("{")
  34. if start < 0:
  35. continue
  36. j = json.loads(text[start:text.rfind("}") + 1])
  37. j["_msg"] = f"SC{msg}"
  38. out.append(j)
  39. except Exception:
  40. continue
  41. return out
  42. def main() -> None:
  43. pcap = sys.argv[1] if len(sys.argv) > 1 else "/tmp/cap_full.pcap"
  44. from scapy.all import rdpcap, TCP, Raw # type: ignore
  45. packets = rdpcap(pcap)
  46. streams: dict[tuple, bytearray] = {}
  47. for pkt in packets:
  48. if TCP not in pkt or Raw not in pkt:
  49. continue
  50. tcp = pkt[TCP]
  51. sport, dport = tcp.sport, tcp.dport
  52. # 游戏服端口 8043, 取服务器→客户端方向(sport=8043)
  53. if sport != 8043:
  54. continue
  55. key = (tcp.ack,)
  56. streams.setdefault(key, bytearray()).extend(bytes(pkt[Raw].load))
  57. print(f"流数: {len(streams)}, 总字节: {sum(len(v) for v in streams.values())}")
  58. results: dict[str, dict] = {}
  59. for buf in streams.values():
  60. for j in extract_map_bodies(frames_from_stream(bytes(buf))):
  61. name = str(j.get("MapName") or j.get("MapId") or "?")
  62. # SC51 优先(含 sourid), SC634 兜底
  63. if name in results and j["_msg"] == "SC634" \
  64. and results[name]["_msg"] == "SC51":
  65. continue
  66. results[name] = j
  67. print(f"\n{'显示名':<14} {'MapId':<14} {'sourid':<8} {'Minimap':<8} 来源")
  68. print("-" * 72)
  69. for name in sorted(results):
  70. j = results[name]
  71. print(f"{name:<14} {str(j.get('MapId','')):<14} "
  72. f"{str(j.get('sourid','')):<8} {str(j.get('Minimap','')):<8} {j['_msg']}")
  73. if __name__ == "__main__":
  74. main()