login_attempt3.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. """Full CS 2001 login attempt following cq2-01 protocol exactly:
  2. 1. Fetch watermark Sequence from NetStampHost: engine-game.dhsf.996db.cn/{md5(sid)}.txt
  3. 2. Encode password with Encode6BitBuf (our SDK implementation)
  4. 3. attach = "account/Encode6BitBuf(token)/channel-encoded/otpType/otpCode"
  5. 4. recog=900000, param1=<from sequence table>, param2=sid, param3=port
  6. 5. Send to gateway (gfgame.dhsf.xqhuyu.com:12000 proxy mode)
  7. """
  8. import asyncio
  9. import base64
  10. import hashlib
  11. import json
  12. import os
  13. import sys
  14. from pathlib import Path
  15. sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, "src"))
  16. from cq2_sdk.env_loader import load_env
  17. load_env()
  18. import httpx
  19. from cq2_sdk.utils.encoding import encode6bit
  20. from cq2_sdk.transport.framing import encode_frame
  21. if hasattr(sys.stdout, "reconfigure"):
  22. sys.stdout.reconfigure(encoding="utf-8", errors="replace")
  23. NETSTAMP_HOST = "http://engine-game.dhsf.996db.cn"
  24. GATEWAY = "gfgame.dhsf.xqhuyu.com"
  25. PROXY_PORT = 12000
  26. SID = 14414738
  27. CHANNEL = "110585"
  28. def get_creds() -> tuple[str, str]:
  29. text = Path(r"D:\ProgramFiles\996box\GameBoxDownload\1\env.json").read_bytes().decode("utf-8", errors="replace")
  30. idx = text.find("eyJ")
  31. b64 = text[idx:].rstrip()
  32. padded = b64 + "=" * (-len(b64) % 4)
  33. obj, _ = json.JSONDecoder().raw_decode(base64.urlsafe_b64decode(padded).decode("utf-8", errors="replace"))
  34. return str(obj.get("uid", "")), str(obj.get("token", ""))
  35. async def fetch_sequence(sid: int) -> dict | None:
  36. md5 = hashlib.md5(str(sid).encode()).hexdigest()
  37. url = f"{NETSTAMP_HOST}/{md5}.txt"
  38. print(f"netstamp: GET {url}")
  39. try:
  40. async with httpx.AsyncClient(timeout=10) as c:
  41. r = await c.get(url)
  42. print(f" -> {r.status_code} len={len(r.text)}")
  43. if r.status_code == 200 and r.text.strip():
  44. print(f" content[:300]: {r.text[:300]}")
  45. try:
  46. return r.json()
  47. except Exception:
  48. return {"raw": r.text}
  49. except Exception as e:
  50. print(f" -> error: {e}")
  51. return None
  52. async def attempt(uid: str, token: str, seq_val: int | None) -> None:
  53. # Encode password with Encode6BitBuf
  54. enc_token = encode6bit(token.encode()).decode("ascii")
  55. # attach per cq2-01: "account/password(Encode6BitBuf)/channel(encoded)/otpType/otpCode"
  56. # channel also "encoded" — try Encode6BitBuf on channel too
  57. enc_channel = encode6bit(CHANNEL.encode()).decode("ascii")
  58. attach = f"{uid}/{enc_token}/{enc_channel}/1/0"
  59. print(f"attach ({len(attach)} chars): {uid}/{enc_token[:20]}.../{enc_channel[:12]}.../1/0")
  60. frame = encode_frame(2001, 900000, seq_val or 0, SID, PROXY_PORT, attach.encode())
  61. print(f"frame: {len(frame)}B msg=2001 recog=900000 p1={seq_val} p2={SID} p3={PROXY_PORT}")
  62. reader, writer = await asyncio.wait_for(asyncio.open_connection(GATEWAY, PROXY_PORT), timeout=5)
  63. try:
  64. writer.write(frame)
  65. await writer.drain()
  66. data = await asyncio.wait_for(reader.read(4096), timeout=8)
  67. if data:
  68. print(f"REPLY {len(data)}B hex[:60]: {data[:60].hex()}")
  69. # Try parse as frame
  70. from cq2_sdk.transport.framing import decode_frame
  71. try:
  72. hdr, body = decode_frame(data)
  73. print(f" msg_id={hdr.msg_id} recog={hdr.recog} p2={hdr.param2}")
  74. print(f" body: {body[:150]!r}")
  75. if hdr.msg_id == 529:
  76. print("*** SC 529 — LOGIN KEY RECEIVED! ***")
  77. elif hdr.msg_id == 503:
  78. print("*** 503 auth rejected — framing WORKS, creds may need refresh ***")
  79. except Exception:
  80. print(" (does not parse as plaintext frame — may be encrypted)")
  81. else:
  82. print("server closed connection (0 bytes)")
  83. finally:
  84. writer.close()
  85. try:
  86. await writer.wait_closed()
  87. except Exception:
  88. pass
  89. async def main() -> None:
  90. uid, token = get_creds()
  91. print(f"uid={uid} token_len={len(token)}")
  92. print()
  93. seq = await fetch_sequence(SID)
  94. print()
  95. seq_val = None
  96. if seq:
  97. # sequence table format unknown — try common shapes
  98. if isinstance(seq, dict):
  99. for k in ("sequence", "seq", "value", "s"):
  100. if k in seq:
  101. try:
  102. seq_val = int(seq[k])
  103. break
  104. except Exception:
  105. pass
  106. if seq_val is None and seq.get("raw"):
  107. try:
  108. seq_val = int(str(seq["raw"]).strip())
  109. except Exception:
  110. pass
  111. elif isinstance(seq, list) and seq:
  112. try:
  113. seq_val = int(seq[0])
  114. except Exception:
  115. pass
  116. print(f"sequence value: {seq_val}")
  117. print()
  118. await attempt(uid, token, seq_val)
  119. asyncio.run(main())