| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- """Capture TCP traffic between cq2.exe and gfgame.dhsf.xqhuyu.com:12000
- via Npcap on the loopback adapter. Zero process injection. Zero ban risk.
- Usage (after Npcap is installed and game is running):
- uv run python scripts/npcap_capture.py --out capture.pcap
- Output: standard .pcap (Wireshark-compatible). Then we open it in Python
- (scapy) to extract the cq2 <-> game-server TCP stream and look for the
- CS 2001 login frame.
- """
- import argparse
- import asyncio
- import os
- import sys
- import time
- from pathlib import Path
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, "src"))
- def require_pyshark():
- try:
- import pyshark # noqa
- except ImportError:
- print("pyshark not installed. run: uv add pyshark", file=sys.stderr)
- sys.exit(1)
- async def capture(out: Path, duration: int, host_filter: str, port: int) -> int:
- require_pyshark()
- import pyshark
- # Npcap loopback adapter captures ALL local TCP (127.0.0.1)
- # We filter for: TCP port == 12000 (cq2 game gateway)
- # OR: TCP containing dhsf.xqhuyu.com (server IP resolved to local)
- bpf = f"tcp port {port}"
- if host_filter:
- bpf = f"tcp and (port {port} or host {host_filter})"
- print(f"[npcap] capturing on loopback: '{bpf}'")
- print(f"[npcap] writing to {out} for {duration}s")
- print(f"[npcap] >>> NOW: open 996box, login, select 玖扬复古176, click 启动 <<<")
- # pyshark LiveCapture uses tshark under the hood; needs Npcap installed
- cap = pyshark.LiveCapture(
- interface=r"\Device\NPF_Loopback", # Npcap loopback; first/only loopback
- bpf_filter=bpf,
- output_file=str(out),
- )
- end = time.time() + duration
- cap.start()
- try:
- # cap.consume() in a thread; we wait then stop
- while time.time() < end:
- await asyncio.sleep(1)
- print(f" ... {int(end - time.time())}s left", end="\r")
- finally:
- cap.stop()
- print()
- print(f"[npcap] done -> {out}")
- return 0
- def parse_pcap_for_cs2001(pcap_path: Path) -> list[dict]:
- """Read the pcap and dump cq2 <-> game server TCP segments.
- We need to identify the CS 2001 first frame: 24-byte header, msg_id=2001
- in first int32 little-endian. If we see frames at all we know the
- network path is reachable.
- """
- import pyshark
- print(f"\n[parse] reading {pcap_path}")
- cap = pyshark.FileCapture(str(pcap_path), display_filter="tcp")
- out = []
- for i, pkt in enumerate(cap):
- try:
- ts = pkt.sniff_time.isoformat()
- src = pkt.ip.src + ":" + pkt.tcp.srcport
- dst = pkt.ip.dst + ":" + pkt.tcp.dstport
- payload_hex = bytes.fromhex(pkt.tcp.payload.replace(":", "")).hex() if pkt.tcp.payload else ""
- # Try parse header
- if len(payload_hex) >= 8:
- msg_id = int.from_bytes(bytes.fromhex(payload_hex[:8]), "little") & 0xFFFFFFFF
- else:
- msg_id = None
- entry = {
- "i": i, "ts": ts, "src": src, "dst": dst,
- "len": len(bytes.fromhex(payload_hex)) // 2 if payload_hex else 0,
- "msg_id_guess": msg_id,
- "hex": payload_hex,
- }
- out.append(entry)
- except Exception as e:
- print(f" pkt {i} err: {e}")
- if i > 0 and i % 200 == 0:
- print(f" parsed {i} packets...")
- return out
- def main() -> int:
- parser = argparse.ArgumentParser()
- parser.add_argument("--out", default="cq2_capture.pcap")
- parser.add_argument("--duration", type=int, default=120, help="seconds to capture")
- parser.add_argument("--host", default="dhsf.xqhuyu.com")
- parser.add_argument("--port", type=int, default=12000)
- parser.add_argument("--parse", action="store_true", help="parse pcap after capture")
- args = parser.parse_args()
- out = Path(args.out)
- out.parent.mkdir(parents=True, exist_ok=True)
- rc = asyncio.run(capture(out, args.duration, args.host, args.port))
- if rc != 0:
- return rc
- if args.parse:
- pkts = parse_pcap_for_cs2001(out)
- print(f"\n=== {len(pkts)} TCP segments with payload ===")
- for p in pkts[:30]:
- print(f" {p['ts']} {p['src']:>22} -> {p['dst']:>22} "
- f"len={p['len']:>4} msg_id?={p['msg_id_guess']} "
- f"hex[:40]={p['hex'][:40]}")
- # Look for any msg_id == 2001
- cs2001 = [p for p in pkts if p["msg_id_guess"] == 2001]
- if cs2001:
- print(f"\n*** Found {len(cs2001)} CS 2001 frames! ***")
- for c in cs2001[:3]:
- print(f" hex: {c['hex']}")
- else:
- print("\n(no msg_id=2001 found in payload headers)")
- return 0
- if __name__ == "__main__":
- sys.exit(main())
|