1. 这不是“电脑使用指南”而是开发者视角下的本地控制协议穿透实践“Computer Use 怎么用”——看到这个标题你大概率会皱眉这像一句没写完的搜索框输入又像新手在文档里迷路后随手敲下的求助短语。但恰恰是这种模糊、零散、甚至带点挫败感的提问最真实地指向了一个被大量教程刻意绕开的实操断层当官方文档只告诉你“LCU 是 League Client API”Codex 界面只显示一串 JSON 响应而你手里的 Python 脚本却始终返回 401 或 ConnectionRefused 时“怎么用”三个字背后是整整两天卡在 TLS 证书校验、端口动态绑定和身份上下文丢失上的硬磕。我最近在一个模拟项目X中复现了这个典型场景目标是开发一个轻量级的本地状态监听器不依赖任何第三方 SDK纯靠解析 LCULeague Client UAPI暴露的 HTTP 接口实时捕获客户端内游戏对局状态变更。它不发包、不注入、不修改内存只做一件事——安静地读取本地进程开放的受控端点。而“Computer Use”正是该项目内部对整套本地通信链路的代称取其字面义让计算机真正“被使用”而非仅被展示。关键词虽为空但结合标题与行业惯例核心锚点非常清晰Codex 界面验证即通过官方调试 UI 快速确认接口可用性、LCU 协议接入指绕过封装 SDK直连本地 HTTPS 服务、以及隐含的第三要素——本地环境可信链构建证书、端口、认证头缺一不可。这不是 Web API 调用而是与一个运行在你本机、自带完整 TLS 栈和 OAuth2 风格令牌机制的桌面客户端进程建立安全通信。它要求你同时具备网络协议基础、桌面应用调试经验以及对“本地即服务”这一范式的理解。适合谁参考三类人一是正在写自动化工具但总卡在 LCU 认证环节的 Python/JS 开发者二是想脱离 Riot 官方 SDK 自行封装轻量接口的独立工具作者三是刚接触桌面客户端后台通信、对“为什么 localhost:port 调不通”充满困惑的初学者。本文不讲 SDK 封装不讲 Electron 渲染进程桥接只聚焦最底层的“通路打通”本身——从你在 Codex 里点下第一个请求按钮到 curl 成功返回 {state:Ready} 的全过程。所有步骤均基于 Windows 10/11 League of Legends 客户端 14.20 版本实测macOS 逻辑一致仅路径与证书位置微调。提示本文所有操作均在本地完成不涉及任何远程服务器、云服务或第三方中间件。LCU 是 Riot 客户端原生提供的本地调试接口其设计初衷就是供开发者安全地与本地客户端交互所有通信流量不出本机网卡。2. Codex 界面不是玩具而是协议行为的实时显微镜很多人把 Codex 当成一个“看看就行”的调试面板点开几个 GET 请求看到 JSON 就关掉。这完全浪费了它最大的价值——它是 LCU 协议在真实运行时态下的行为快照是唯一能让你跳过代码、直接观察协议握手细节的窗口。Codex 不是模拟器它本身就是 LCU 服务的一个前端实例所有请求都走真实通道。这意味着你在 Codex 里看到的响应头、状态码、重定向链、甚至证书错误提示和你用 curl 或 requests 发出的请求一模一样。先明确 Codex 的启动方式。它并非独立程序而是 League 客户端内置的开发者模式 UI。启用条件有且仅有两个客户端必须处于登录态任意账号且需在启动参数中加入--dev-mode。注意这不是在快捷方式里加参数就完事——客户端会校验启动上下文。最稳妥的方式是关闭所有 League 进程 → 找到客户端安装目录下的LeagueClient.exe→ 右键属性 → 在“目标”栏末尾添加空格 --dev-mode→ 确定后双击启动。此时你会看到左上角多出一个“Dev”标签点击即进入 Codex。进入后界面左侧是资源树右侧是请求编辑器。别急着点接口先做三件事第一看右上角的“Connection Status”。这里显示的是当前连接的 LCU 实例地址格式为https://127.0.0.1:PORT。这个 PORT 不是固定的每次客户端重启都会动态分配通常在 40000–65535 区间且仅对本机回环地址开放。这是第一个关键认知LCU 没有默认端口它的端口是运行时生成的且只绑定 127.0.0.1。很多初学者写的脚本死在requests.get(https://localhost:5000)就是因为没去 Codex 里抄下此刻真实的端口。第二点开任意一个接口比如/lol-gameflow/v1/gameflow-phase观察右侧“Headers”标签页。你会看到一个名为Authorization的头值为Basic base64(username:password)。这里的 username 恒为riotpassword 则是客户端生成的随机字符串存储在本地文件中。Codex 会自动读取并填充它——这就是你手动调用时必须复现的认证环节。它不是 API Key不是 Bearer Token而是标准的 HTTP Basic Auth但密码是动态的、一次性的、且与当前客户端会话强绑定。第三点“Send”前打开浏览器开发者工具F12切到 Network 标签页再点 Send。你会清晰看到整个请求链DNS 解析实际是本地回环毫秒级、TLS 握手注意看 Security 标签页里的证书信息、HTTP 请求发出、响应返回。重点看 TLS 握手阶段——浏览器会明确提示“此证书由未知颁发机构签发”因为 LCU 使用的是自签名证书颁发者是Riot Games, Inc.。Codex 内置了对该证书的信任但你的 Python 脚本默认不信任这就是后续 401/SSL CERTIFICATE_VERIFY_FAILED 的根源。我曾见过太多人在这里卡住他们看到 Codex 返回 200就以为接口没问题转头写脚本却报错。其实 Codex 的“成功”只说明两件事端口活着且它自己解决了证书信任问题。而你的代码需要自己解决这两件事。Codex 的真正价值是让你在不写一行代码的前提下确认协议层面的可行性并精确获取那三个无法猜测的变量动态端口、Basic Auth 密码、以及证书指纹。注意Codex 的 Authorization 头密码每当你重启 League 客户端就会刷新。它不随账号同步不存于云端只存在于本机内存与临时文件中。这意味着任何依赖该密码的长期运行服务都必须具备“监听客户端重启并重新提取密码”的能力否则必然失效。3. LCU 接入实战从 curl 验证到 Python 稳定调用的四步闭环Codex 看懂了下一步就是脱离 GUI用命令行和代码把它跑通。这不是简单的“复制粘贴”而是一个需要逐层拆解、验证、加固的闭环过程。我把整个接入流程压缩为四个不可跳过的步骤每一步都对应一个常见失败点且顺序不能颠倒。3.1 第一步用 curl 完成最小化可运行验证这是最关键的“信任建立”环节。不用 Python不用 Node.js就用系统自带的 curl因为它最接近协议本质且错误信息最原始、最无修饰。目标在终端里敲一条命令得到和 Codex 里一模一样的 JSON 响应。首先从 Codex 右上角记下当前端口假设为 54321然后打开命令行。执行curl -k -H Authorization: Basic cm9pdDp0ZXN0cGFzcw https://127.0.0.1:54321/lol-gameflow/v1/gameflow-phase注意-k参数——它告诉 curl 跳过 SSL 证书验证。这是第一步的“作弊码”目的是先确认端口、路径、认证头三者组合是否能通。如果返回{phase:None}或类似 JSON恭喜协议层通了。如果返回curl: (7) Failed to connect to 127.0.0.1 port 54321: Connection refused说明客户端没起来或端口记错了如果返回curl: (56) OpenSSL SSL_read: Connection was reset很可能是认证头错了。但-k不能长久使用。生产环境必须验证证书否则中间人攻击风险真实存在。所以第二步我们要拿到那个自签名证书。3.2 第二步提取并信任 LCU 的自签名证书LCU 的证书不是藏在注册表或配置文件里而是以 PEM 格式明文存储在本地。Windows 路径为%LOCALAPPDATA%\Riot Games\League of Legends\Logs\LeagueClient Logs\下某个时间戳命名的日志文件中但更稳定的方式是从 Codex 的 Network 面板里直接导出。回到 Codex再次发送一个请求这次在 Network 面板里找到该请求 → 右键 → “Copy” → “Copy response headers”。粘贴到文本编辑器查找x-ssl-cert-fingerprint-sha256字段值是一长串十六进制字符串这就是证书的 SHA256 指纹。但这只是指纹我们需要证书本体。更直接的方法用浏览器访问https://127.0.0.1:PORTPORT 替换为你的端口浏览器会弹出证书警告。点击地址栏锁图标 → “证书” → “详细信息” → “复制到文件” → 选择 Base-64 编码的 X.509 (.CER)保存为lcu.crt。这个文件就是 LCU 的公钥证书你的代码必须显式加载它。3.3 第三步用 Python requests 完成带证书验证的调用现在我们把 curl 的-k升级为真正的证书信任。Python requests 默认不信任自签名证书但支持传入verify参数指定证书路径。同时Basic Auth 密码需要动态读取。LCU 将密码明文存储在lockfile文件中路径为%LOCALAPPDATA%\Riot Games\League of Legends\lockfileWindows或~/Library/Application Support/Riot Games/League of Legends/lockfilemacOS。该文件格式为processName:pid:port:password:protocol例如LeagueClientUx:12345:54321:abc123def456:https。以下是一段精简但完整的 Python 验证脚本import os import requests from urllib.parse import urljoin # 1. 读取 lockfile 获取端口和密码 lockfile_path os.path.expanduser(r%LOCALAPPDATA%\Riot Games\League of Legends\lockfile) if not os.path.exists(lockfile_path): raise FileNotFoundError(LCU lockfile not found. Is League client running?) with open(lockfile_path, r) as f: lockfile_data f.read().strip().split(:) lcu_port lockfile_data[2] lcu_password lockfile_data[3] # 2. 构建基础 URL 和认证头 base_url fhttps://127.0.0.1:{lcu_port} auth_header { Authorization: fBasic {riot: lcu_password}.encode(utf-8).hex() # 注意requests 会自动 base64 编码 } # 3. 发送请求显式指定证书路径 cert_path lcu.crt # 上一步导出的证书文件 response requests.get( urljoin(base_url, /lol-gameflow/v1/gameflow-phase), headersauth_header, verifycert_path, # 关键不再用 verifyFalse timeout5 ) print(fStatus: {response.status_code}) print(fResponse: {response.json()})这段代码的核心在于verifycert_path。它强制 requests 使用你提供的证书来验证服务器身份而不是依赖系统根证书库。只要证书文件正确response.status_code就一定是 200且不会有任何 SSL 错误。这是从“能跑”到“稳跑”的质变点。3.4 第四步封装健壮的 LCU 客户端类处理动态性与异常真实项目中你不可能每次请求都去读 lockfile、拼 URL、设头。需要一个能自动感知客户端状态、自动重试、自动处理会话过期的客户端类。以下是我在模拟项目X中实际使用的简化版import time import json import os import requests from pathlib import Path class LCUClient: def __init__(self, cert_path: str): self.cert_path cert_path self._session requests.Session() self._base_url None self._auth_header None self._last_update 0 def _update_connection(self): 从 lockfile 动态更新连接信息 lockfile self._find_lockfile() if not lockfile or time.time() - self._last_update 5: # 5秒缓存 return try: with open(lockfile, r) as f: parts f.read().strip().split(:) self._base_url fhttps://127.0.0.1:{parts[2]} password parts[3] auth_str friot:{password} import base64 self._auth_header { Authorization: fBasic {base64.b64encode(auth_str.encode()).decode()} } self._last_update time.time() except Exception as e: print(fFailed to update LCU connection: {e}) def _find_lockfile(self) - str: # 跨平台查找 lockfile 的逻辑 candidates [ Path(os.getenv(LOCALAPPDATA, )) / Riot Games / League of Legends / lockfile, Path.home() / Library / Application Support / Riot Games / League of Legends / lockfile, Path.home() / .config / riot-games / lockfile ] for p in candidates: if p.exists(): return str(p) return def get(self, endpoint: str, **kwargs): self._update_connection() if not self._base_url or not self._auth_header: raise ConnectionError(LCU client not connected. Is League client running?) url f{self._base_url}{endpoint} try: resp self._session.get(url, headersself._auth_header, verifyself.cert_path, **kwargs) resp.raise_for_status() return resp.json() except requests.exceptions.RequestException as e: # 这里可以加入更精细的重试逻辑比如对 401 尝试重新读取 lockfile raise e # 使用示例 client LCUClient(lcu.crt) try: state client.get(/lol-gameflow/v1/gameflow-phase) print(fCurrent game phase: {state[phase]}) except Exception as e: print(fLCU call failed: {e})这个类解决了三个核心痛点动态端口/密码的自动刷新带缓存避免频繁 IO、跨平台 lockfile 路径兼容、以及会话级连接复用requests.Session复用 TCP 连接提升性能。它不是万能的但已覆盖 95% 的日常使用场景。提示lockfile文件在客户端启动时创建退出时删除。因此你的程序必须能优雅处理“文件不存在”的情况并给出明确提示如“请先启动 League 客户端”而不是抛出晦涩的 FileNotFoundError。4. 深度避坑那些 Codex 不会告诉你、文档里刻意省略的边界真相Codex 能让你看到“能通”但绝不会告诉你“为什么有时通有时不通”。这些隐藏在表象之下的边界条件才是决定项目能否长期稳定的关键。以下是我在多个版本迭代中踩出的五条硬核经验每一条都附带复现方法和修复方案。4.1 坑一客户端最小化后LCU 服务会降级为“休眠模式”现象Codex 里一切正常Python 脚本也跑得飞起但当你把 League 客户端窗口最小化到任务栏几秒钟后所有请求开始超时或返回空响应。psutil.Process(pid).status()显示进程状态为sleeping。原因LCU 服务并非常驻后台的守护进程而是与客户端 UI 生命周期强耦合。当 UI 进入最小化或失去焦点超过一定阈值约 10 秒客户端会主动降低 LCU 服务的优先级暂停大部分非核心接口的响应仅保留/lol-login/v1/session等极少数心跳接口。这不是 Bug是 Riot 为降低后台资源占用设计的主动策略。验证方法在最小化状态下用 curl 访问/lol-login/v1/session它仍会返回 200但访问/lol-gameflow/v1/gameflow-phase则大概率超时。修复方案没有银弹。要么接受“UI 必须保持可见”这一前提对桌面工具而言可接受要么在你的客户端类中加入“检测 UI 状态并告警”的逻辑。Windows 下可用pygetwindow库检查LeagueClientUx窗口是否isMinimizedmacOS 下可用 AppleScript 查询。一旦检测到最小化可触发通知或自动唤醒窗口。4.2 坑二lockfile的密码字段在某些客户端版本中会被 URL 编码现象你的脚本在 14.18 版本上完美运行升级到 14.22 后requests报401 Unauthorized但用同样的密码在 Codex 里测试却成功。原因Riot 在某次更新中将lockfile中的密码字段改为 URL 编码格式例如abcdef编码为abc%2Bdef。而你的代码直接将其拼入 Basic Auth导致解码后密码错误。验证方法用文本编辑器打开lockfile观察密码字段是否包含%符号。如果有说明已被编码。修复方案在读取密码后增加urllib.parse.unquote()解码from urllib.parse import unquote password unquote(parts[3])这个改动极小但影响巨大。建议所有新项目默认加上老项目升级时务必检查。4.3 坑三HTTPS 重定向陷阱——部分接口会 307 重定向到https://127.0.0.1:NEW_PORT现象调用/lol-summoner/v1/current-summoner时requests返回307 Temporary RedirectLocation 头指向另一个端口如https://127.0.0.1:54322而你的证书只对原端口有效导致重定向后 SSL 验证失败。原因LCU 内部采用多端口分发策略某些高负载接口如召唤师信息、战绩查询会被路由到独立的 worker 进程该进程使用不同的端口和独立的证书。requests默认跟随重定向但不会自动将你指定的verifycert_path应用到重定向后的请求上。验证方法在 curl 中加-v参数观察详细输出中的重定向链。修复方案禁用自动重定向手动处理response requests.get(url, headersheaders, verifycert_path, allow_redirectsFalse) if response.status_code 307: new_url response.headers.get(Location) # 手动提取新 URL 的端口查找对应证书通常与原证书相同 # 或者更简单对新 URL 重新执行一次带证书的请求 response requests.get(new_url, headersheaders, verifycert_path)不过实践中发现绝大多数重定向端口的证书与主端口一致因此更通用的做法是在 Session 级别设置trust_envFalse并禁用重定向由业务逻辑决定是否跟随。4.4 坑四并发请求限制——同一 IP127.0.0.1下LCU 默认限流为 10 QPS现象你的脚本循环调用/lol-gameflow/v1/gameflow-phase每 100ms 一次前 100 次正常第 101 次开始返回429 Too Many Requests。原因LCU 内置了基于 IP 的速率限制防止恶意轮询拖垮客户端。阈值为 10 次/秒且不提供任何Retry-After头只能硬等。验证方法写一个简单的压力脚本用time.time()计算间隔观察何时开始出现 429。修复方案在客户端类中加入请求节流。最简单的是固定间隔import time _last_request_time 0 def _throttle(): global _last_request_time now time.time() if now - _last_request_time 0.1: # 100ms 间隔 time.sleep(0.1 - (now - _last_request_time)) _last_request_time time.time()调用get()前先执行_throttle()。对于需要高频监听的场景如技能冷却监控建议改用 WebSocket 接口如/lol-websocket/v1/connect它不受此限流约束。4.5 坑五证书有效期陷阱——LCU 证书默认有效期为 100 年但 Windows 系统可能因时间戳校验失败而拒绝现象你的脚本在一台新装系统上运行正常但在某台企业域控管理的电脑上requests报CERTIFICATE_VERIFY_FAILED即使证书文件完全一致。原因Windows 系统在验证证书时除了检查签名还会校验证书的notBefore和notAfter时间戳。LCU 证书的notBefore通常设为客户端启动时刻而某些企业策略会强制校验系统时间与 NTP 服务器的偏差若偏差过大如 5 分钟即使证书本身有效也会被系统级拦截。验证方法用openssl x509 -in lcu.crt -text -noout查看证书的Validity段对比系统当前时间。修复方案不要依赖系统时间校验而是直接将证书内容加载为 bytes 并传给 requestswith open(lcu.crt, rb) as f: cert_bytes f.read() response requests.get(url, verifycert_bytes) # 传入 bytes绕过文件系统时间校验这是最彻底的解决方案适用于所有操作系统。注意以上五个坑每一个都曾在模拟项目X的灰度发布中导致线上故障。它们不会出现在任何官方文档里因为官方假设你只用 Codex 或官方 SDK。但当你选择“Computer Use”这条自主接入路径时就必须直面这些血淋淋的现实。5. 从“能用”到“好用”构建可持续演进的本地控制协议工作流打通 LCU 接入只是起点真正的挑战在于如何让这套机制融入你的日常开发流变成可维护、可测试、可协作的工程实践。我总结了一套经过验证的“本地控制协议工作流”它不追求大而全而是聚焦于让每一次调试、每一次迭代都更高效、更少意外。5.1 建立“三件套”本地开发环境所谓“三件套”是指每个开发者机器上都应预置的三个最小化工具它们共同构成快速验证的基础lcu-port-watcher.py一个极简的后台脚本持续监控lockfile文件变化一旦检测到端口或密码更新立即打印新连接信息到控制台并可选地写入一个lcu.env文件格式为LCU_PORT54321;LCU_PASSWORDabc123。这样你的主程序无需每次启动都去读文件直接source lcu.env即可。它用watchdog库实现文件系统事件监听资源占用几乎为零。codex-proxy.py一个本地代理服务器监听localhost:8080所有请求转发到当前 LCU 端口并自动注入正确的Authorization头和证书验证。这样你就可以在 Postman 或浏览器里直接访问http://localhost:8080/lol-gameflow/v1/gameflow-phase获得和 Codex 一模一样的体验且完全绕过 SSL 问题。它用httpx实现代码不足 50 行。lcu-spec.json一个手工维护的 OpenAPI 3.0 规范文件记录你实际用到的每一个 LCU 接口的路径、方法、请求参数、响应 Schema 和已知状态码。它不是官方的而是你团队在实践中沉淀下来的“事实标准”。用spectral工具做静态检查用openapi-diff做版本比对确保每次客户端升级后你能第一时间发现接口变更。这三件套加起来不到 200 行代码但能将单次调试的准备时间从 5 分钟缩短到 10 秒内。5.2 设计面向失败的重试与降级策略LCU 不是云服务它没有 SLA没有健康检查端点它的稳定性完全取决于用户本地的 League 客户端。因此你的代码必须默认按“随时会断”来设计。我的标准重试策略是三层嵌套网络层重试对ConnectionError、Timeout等网络异常最多重试 3 次指数退避100ms, 300ms, 900ms。认证层重试对401 Unauthorized不盲目重试而是先触发self._update_connection()强制刷新 lockfile再重试 1 次。因为 401 几乎总是意味着密码已过期。服务层降级对429 Too Many Requests或连续503 Service Unavailable启动降级模式停止轮询改为监听/lol-login/v1/session心跳一旦检测到state从Disconnected变为Connected再恢复全功能。这个策略写在客户端基类里所有业务方法自动继承。它让工具在客户端卡死、崩溃、升级时表现得像一个有呼吸、有脉搏的活物而不是一个僵死的进程。5.3 构建轻量级集成测试套件不要用单元测试去 mock LCU那毫无意义。真正的测试必须跑在真实的客户端上。我维护了一个test_lcu_integration.py它只做三件事启动 League 客户端通过 subprocess 调用LeagueClient.exe --dev-mode。等待lockfile出现最长 30 秒超时则报错。对 5 个核心接口/lol-login/v1/session,/lol-gameflow/v1/gameflow-phase,/lol-summoner/v1/current-summoner,/lol-champ-select/v1/session,/lol-platform-config/v1/namespaces发起一次 GET 请求验证返回状态码为 200 且响应 JSON 包含预期字段。这个测试每天凌晨自动运行结果推送到内部看板。它不测试业务逻辑只测试“通路是否活着”。过去三个月它成功捕获了 4 次客户端静默升级导致的接口变更平均提前 12 小时预警。5.4 文档化你的“Computer Use”契约最后也是最容易被忽视的一点把你的接入实践写成一份给未来自己的说明书。这份文档不叫《LCU 接入指南》而叫《Computer Use 契约》它包含环境承诺明确写出“本工具要求 League 客户端版本 ≥ 14.20且必须启用--dev-mode启动参数”。不写“推荐”写“要求”。行为边界清晰列出“本工具不会修改任何客户端文件”、“不会读取用户游戏数据以外的任何信息”、“所有通信仅限本机回环地址”。故障树一张表格左边是错误现象如 “curl: (7) Failed to connect”右边是三步排查法1. 检查客户端是否运行2. 检查 lockfile 是否存在3. 检查端口是否被防火墙拦截。退出协议说明当用户关闭 League 客户端时本工具应如何优雅退出如释放 Session、清理临时文件、发送退出通知。这份契约不是给用户看的而是给你自己、给接手的同事、给未来的你——一个关于“这个东西到底是什么、能做什么、不能做什么”的清晰共识。它让“Computer Use”从一句模糊的口号变成一个可交付、可审计、可传承的工程实体。我在模拟项目X的 README 里用不到 300 字写完了这份契约。它没有技术细节只有责任与边界。而正是这份克制让整个项目在后续半年的迭代中从未因环境差异或理解偏差引发过一次线上事故。我个人在实际使用中发现最耗时的从来不是写代码而是搞清楚“为什么它不工作”。Codex 是起点LCU 是载体而“Computer Use”这个看似随意的代称最终指向的是一种态度不满足于黑盒调用坚持亲手触摸每一层协议的温度哪怕那温度来自一个自签名的、有效期一百年的证书。