# -*- coding: utf-8 -*- """ mine_bot.py —— 无客户端挖矿链路: 创角 → 进世界 → 喝药 → NPC领锄头 → 挖矿。 协议要点(本轮实测/逆向): CS1010 NPC对话: body=JSON{"UserID":"","index":""} — UserID 是 SC10 info[1] 的NPC配置ID字符串, 不是actorID数字 CS3014 挖矿: recog=skillID(999=SKILL_INDEX_DIG), body=JSON{skillID,dir,srcX,srcY,destX,destY,targetID} CS1014 NPC购买: body=JSON{"UserID":npcCfgID,"Name":货品名,"MakeIndex":0} 用法: .venv/bin/python mine_bot.py --zone 14492165 [--name 名字] [--minutes 6] """ import argparse import json import random import socket import struct import sys import threading import time import zlib import requests as rq import urllib3 urllib3.disable_warnings() HERE = __file__.rsplit("/", 1)[0] sys.path.insert(0, HERE) from encode6bitbuf_final import encode6, decode6 # noqa: E402 import login_996pc as L # noqa: E402 import cq2_pure_enter as P # noqa: E402 from combat_bot import (CombatBot, enter_game_gate_v2, # noqa: E402 send_init_frames, log, DIRS, dir_to) SKILL_DIG = 999 MINING_MONSTER_RACE = 304 # ACTOR_RACE? 待实测修正: 矿石作为 type=50/race=304 采集物 def npc_talk(bot, npc_cfg_id, index, timeout=6): body = json.dumps({"UserID": str(npc_cfg_id), "index": str(index)}, separators=(",", ":")).encode() bot.send(1010, 0, 0, 0, bot.sid, body) deadline = time.time() + timeout while time.time() < deadline and bot.alive: for e in bot.pump(0.3): if e[0] == "npc_talk": return e[1] return None def npc_store_buy(bot, npc_cfg_id, item_name): body = json.dumps({"UserID": str(npc_cfg_id), "Name": item_name, "MakeIndex": 0}, separators=(",", ":")).encode() bot.send(1014, 0, 0, 0, bot.sid, body) def npc_task_click(bot, npc_cfg_id, act, index, common_input="", input_id=""): payload = {"UserID": str(npc_cfg_id), "Act": act, "commonInput": common_input, "inputID": input_id, "index": str(index)} body = json.dumps(payload, separators=(",", ":")).encode() bot.send(1011, 0, 0, 0, bot.sid, body) def mine(bot, tx, ty): d = dir_to(bot.me["x"], bot.me["y"], tx, ty) payload = {"skillID": SKILL_DIG, "dir": d, "srcX": bot.me["x"], "srcY": bot.me["y"], "destX": tx, "destY": ty, "targetID": None} body = json.dumps(payload, separators=(",", ":")).encode() bot.send(3014, 0, 0, 0, 0, body) def setup(): ap = argparse.ArgumentParser() ap.add_argument("-u", "--user", default="longwu988") ap.add_argument("-p", "--password", default="Longwu1988") ap.add_argument("--zone", type=int, default=14492165) ap.add_argument("--name", default=None) ap.add_argument("--port", type=int, default=0) ap.add_argument("--minutes", type=float, default=6) return ap.parse_args() def http_login_with_retry(args): for attempt in range(5): try: s = rq.Session() j, _ = L.do_login(s, args.user, args.password) if j.get("code") == 200: return s, j except Exception as e: log(f" 登录异常(第{attempt+1}次): {str(e)[:60]}") time.sleep(12) raise SystemExit("[!] 登录失败") def enter_game_with_retry(s, jwt, zone): for attempt in range(5): try: j2 = P.enter_game(s, jwt, zone) if j2.get("code") == 200: return except Exception as e: log(f" enter_game 异常(第{attempt+1}次): {str(e)[:60]}") time.sleep(12) raise SystemExit("[!] enter_game 失败") def login_gate_with_role(zone, jwt, name, uid): ports = [8041, 8021, 8061, 8001] for pt in ports: try: key32, sid, rname, tkey, gp = P.login_gate(zone, jwt, name, pt, uid) if key32: return key32, sid, rname, tkey, gp except SystemExit as e: log(f" 端口{pt}: {e}") except Exception as e: log(f" 端口{pt}: {str(e)[:50]}") raise SystemExit("[!] 所有端口失败") def main(): args = setup() name = args.name or ("矿工" + str(random.randint(1000, 9999))) log(f"[1/6] HTTP登录...") s, j = http_login_with_retry(args) jwt = j["data"]["token"] uid = str(j["data"].get("userInfo", {}).get("id") or "") log(f"[2/6] 选区 {args.zone}...") enter_game_with_retry(s, jwt, args.zone) log(f"[3/6] 登录服+创角/复用 {name}...") key32, sid, rname, tkey, gp = login_gate_with_role(args.zone, jwt, name, uid) log(f" 角色={rname} 游戏端口={gp}") log("[4/6] 进世界...") g = enter_game_gate_v2(args.zone, rname, tkey, sid, gp, uid) bot = CombatBot(g, sid, rname) P.heartbeat_loop(g, sid, bot.lock) ok658 = False t0 = time.time() while time.time() - t0 < 10: bot.pump(0.3) if 658 in bot.frame_log: ok658 = True break log(f" SC658 {'✓' if ok658 else '✗'}") send_init_frames(bot, uid) for _ in range(20): bot.pump(0.5) if bot.me["id"] and bot.me.get("mapname"): break log(f" 主玩家 #{bot.me['id']} @({bot.me['x']},{bot.me['y']}) map={bot.me.get('mapname')}") if not bot.me["id"]: g.close() raise SystemExit("[!] 未收到 SC50") log("[5/6] 开喝药 + 收集NPC列表...") # NPC 收集: SC10 type != 50 的进视野项 npcs = {} for e in bot.pump(2): if e[0] == "npc_in": npcs[e[1]] = e[2] if not npcs: # SC10 已在进世界时消费, 重扫视野: 用 _dispatch 已丢原始信息, 这里从 bot.frame_log 无法还原 log(f" (视野内NPC: 等待图应含 矿场总兵/新手接待; frame_log={sorted(bot.frame_log.items())[:10]})") state = P.enable_auto_heal(g, sid, want_on=True) log(f" 喝药={state}") log("[6/6] 尝试NPC对话(矿场总兵)...") # 矿场总兵 cfgID=10041 (SC10 info[1]), typeIndex=p1; 新手接待=10032/10033 for cfg_id, idx in [(10041, 10041), (10032, 10032), (10033, 10033)]: log(f" 对话 NPC {cfg_id}...") resp = npc_talk(bot, cfg_id, idx) if resp: log(f" ✓ NPC {cfg_id} 回应: {json.dumps(resp, ensure_ascii=False)[:300]}") break log(f" ✗ NPC {cfg_id} 无回应") else: log(" 所有NPC均无回应, 保存现场帧继续等...") bot.pump(3) log(f"[等待] 挂机 {args.minutes} 分钟收帧观察...") deadline = time.time() + args.minutes * 60 report_n = 0 while time.time() < deadline and bot.alive: evs = bot.pump(0.5) for e in evs: if e[0] == "map": log(f" !! 地图切换: {e}") report_n += 1 if report_n > 3: break log(f"[完成] 收帧统计: {sorted(bot.frame_log.items(), key=lambda kv: -kv[1])[:15]}") g.close() if __name__ == "__main__": main()