
▒ 目录 ▒
-
- [🛫 导读](#🛫 导读)
- [1️⃣ 先看我们要连接哪里](#1️⃣ 先看我们要连接哪里)
- [2️⃣ 步骤1:在 PowerShell 找到 Steam](#2️⃣ 步骤1:在 PowerShell 找到 Steam)
- [3️⃣ 步骤2:确保调试端口可用](#3️⃣ 步骤2:确保调试端口可用)
- [4️⃣ 步骤3:进入正确的 Console](#4️⃣ 步骤3:进入正确的 Console)
-
- [没有 Console:用配套只读查询器](#没有 Console:用配套只读查询器)
- [5️⃣ 步骤4:读取账号列表](#5️⃣ 步骤4:读取账号列表)
- [6️⃣ 步骤5:读当前账号是否在线](#6️⃣ 步骤5:读当前账号是否在线)
- [🛬 文章小结](#🛬 文章小结)
🛫 导读
在 Steam 的浏览器环境里运行两条只读查询,看看客户端实际报告了什么。
CDP 是 Chrome DevTools Protocol,也就是 Chromium 的调试协议。Steam 用嵌入式 Chromium 显示界面,因此在开启调试后,可以从浏览器连接到它的调试目标。
本篇只查询账号列表和实时状态,不执行切号、登出或修改配置。读者只需会打开 PowerShell 和浏览器。
| 环境 | 本文基线 |
|---|---|
| 系统 | Windows x64 |
| Steam | 本研究构建10.96.30.42;其他构建的目标名称和接口可能变化 |
| 浏览器 | 可访问本机地址;Console 入口是否可用取决于客户端 |
| 备用查询 | Python 3 与本目录 requirements-cdp.txt;没有 Console 时使用 |
| 输入位置 | PowerShell 用于启动/检查;DevTools Console 用于 JavaScript |
1️⃣ 先看我们要连接哪里
#mermaid-svg-ieSJACzUyP7kiPfp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ieSJACzUyP7kiPfp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ieSJACzUyP7kiPfp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ieSJACzUyP7kiPfp .error-icon{fill:#552222;}#mermaid-svg-ieSJACzUyP7kiPfp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ieSJACzUyP7kiPfp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ieSJACzUyP7kiPfp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ieSJACzUyP7kiPfp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ieSJACzUyP7kiPfp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ieSJACzUyP7kiPfp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ieSJACzUyP7kiPfp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ieSJACzUyP7kiPfp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ieSJACzUyP7kiPfp .marker.cross{stroke:#333333;}#mermaid-svg-ieSJACzUyP7kiPfp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ieSJACzUyP7kiPfp p{margin:0;}#mermaid-svg-ieSJACzUyP7kiPfp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ieSJACzUyP7kiPfp .cluster-label text{fill:#333;}#mermaid-svg-ieSJACzUyP7kiPfp .cluster-label span{color:#333;}#mermaid-svg-ieSJACzUyP7kiPfp .cluster-label span p{background-color:transparent;}#mermaid-svg-ieSJACzUyP7kiPfp .label text,#mermaid-svg-ieSJACzUyP7kiPfp span{fill:#333;color:#333;}#mermaid-svg-ieSJACzUyP7kiPfp .node rect,#mermaid-svg-ieSJACzUyP7kiPfp .node circle,#mermaid-svg-ieSJACzUyP7kiPfp .node ellipse,#mermaid-svg-ieSJACzUyP7kiPfp .node polygon,#mermaid-svg-ieSJACzUyP7kiPfp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ieSJACzUyP7kiPfp .rough-node .label text,#mermaid-svg-ieSJACzUyP7kiPfp .node .label text,#mermaid-svg-ieSJACzUyP7kiPfp .image-shape .label,#mermaid-svg-ieSJACzUyP7kiPfp .icon-shape .label{text-anchor:middle;}#mermaid-svg-ieSJACzUyP7kiPfp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ieSJACzUyP7kiPfp .rough-node .label,#mermaid-svg-ieSJACzUyP7kiPfp .node .label,#mermaid-svg-ieSJACzUyP7kiPfp .image-shape .label,#mermaid-svg-ieSJACzUyP7kiPfp .icon-shape .label{text-align:center;}#mermaid-svg-ieSJACzUyP7kiPfp .node.clickable{cursor:pointer;}#mermaid-svg-ieSJACzUyP7kiPfp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ieSJACzUyP7kiPfp .arrowheadPath{fill:#333333;}#mermaid-svg-ieSJACzUyP7kiPfp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ieSJACzUyP7kiPfp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ieSJACzUyP7kiPfp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ieSJACzUyP7kiPfp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ieSJACzUyP7kiPfp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ieSJACzUyP7kiPfp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ieSJACzUyP7kiPfp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ieSJACzUyP7kiPfp .cluster text{fill:#333;}#mermaid-svg-ieSJACzUyP7kiPfp .cluster span{color:#333;}#mermaid-svg-ieSJACzUyP7kiPfp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ieSJACzUyP7kiPfp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ieSJACzUyP7kiPfp rect.text{fill:none;stroke-width:0;}#mermaid-svg-ieSJACzUyP7kiPfp .icon-shape,#mermaid-svg-ieSJACzUyP7kiPfp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ieSJACzUyP7kiPfp .icon-shape p,#mermaid-svg-ieSJACzUyP7kiPfp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ieSJACzUyP7kiPfp .icon-shape .label rect,#mermaid-svg-ieSJACzUyP7kiPfp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ieSJACzUyP7kiPfp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ieSJACzUyP7kiPfp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ieSJACzUyP7kiPfp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 浏览器打开 127.0.0.1:8080
调试目标列表
选择 SharedJSContext
打开 Console
查询 SteamClient.User
SharedJSContext 可以理解为 Steam 共享的 JavaScript 执行环境。你自己打开的商店网页不是同一个环境,普通浏览器页面里也不会自然出现 SteamClient。
2️⃣ 步骤1:在 PowerShell 找到 Steam
按 Win 键,搜索并打开"Windows PowerShell"。复制下面的整段代码。$steamExe 是本段创建的变量,保存的是程序路径。

powershell
$steamExe = (Get-ItemProperty -LiteralPath 'HKCU:\Software\Valve\Steam' -ErrorAction Stop).SteamExe
if (-not (Test-Path -LiteralPath $steamExe)) {
throw '注册表中的 Steam 程序路径不存在,请先核对安装位置。'
}
$steamExe
预期最后一行显示以 steam.exe 结尾的真实路径。读取失败就先停止,不要把文章示例目录当成你机器上的目录。
3️⃣ 步骤2:确保调试端口可用
先在浏览器地址栏打开:
text
http://127.0.0.1:8080/json
如果已能看到一组调试目标,直接进入步骤3。若连接被拒绝,在 Steam 中正常退出应用,然后回到刚才同一个 PowerShell 窗口执行:
powershell
& $steamExe -silent -cef-enable-debugging
等待启动,再刷新浏览器。-silent 可能只让托盘图标出现,没有自动弹出主窗口;双击托盘图标即可查看。
4️⃣ 步骤3:进入正确的 Console
打开 http://127.0.0.1:8080/。如果这个客户端显示带有 inspect 链接的列表,点击 SharedJSContext 旁边的链接,再切换到 Console。
若根页面只有 JSON 或没有可点击入口,打开 /json,在 SharedJSContext 项中检查是否有可用的 devtoolsFrontendUrl。前端链接这条分支没有在本研究中单独实测,不保证每个构建都有。打不开就使用下面的备用方式,不要选择一个随便能打开的商店页面继续。
在 Console 输入下行并按回车:
javascript
typeof SteamClient
预期是可用对象的类型,通常为 "object"。如果是 "undefined",先检查所选目标。Console 的粘贴保护若阻止输入,按浏览器自身提示处理,或手动键入这条短命令,不在未识别的页面粘贴长脚本。

没有 Console:用配套只读查询器
这是与底稿实测相同的 CDP 连接方式:从 /json 取得 SharedJSContext 的 webSocketDebuggerUrl,再发送 Runtime.evaluate。查询器只内置四个固定选项,不接受任意脚本、不触发登录或切号。
先确认 python --version 可用。在资源管理器找到本系列文件夹 steam-login-lab,点击地址栏复制完整路径。把下面 $seriesRoot 的字符串换成你自己的位置,然后在 PowerShell 执行:
powershell
$seriesRoot = 'J:\_ALL\CODE\gitcode.com\ShyanZh\gitcode_knowledge\ai\mp-sy\steam\doc\steam-login-lab'
Set-Location -LiteralPath $seriesRoot -ErrorAction Stop
if (-not (Test-Path -LiteralPath '.\scripts\query_steam.py')) {
throw '没有找到查询器,请检查系列目录。'
}
python -m pip install -r .\requirements-cdp.txt
if ($LASTEXITCODE -ne 0) { throw '依赖安装失败,先停止查询。' }
python .\scripts\query_steam.py targets
预期列表中有 title: SharedJSContext 和 hasWebSocket: true。查询器只允许连接本机目标;找不到唯一的 SharedJSContext、超时或接口异常都会报错停止,不会拿其他网页凑数。targets 成功仅说明目标可见,接下来仍要完成实际查询。

本轮已用这份查询器完成本机只读验证:目标列表、账号字段、在线状态与原生函数描述都能返回。没有执行登录或切换账号。
requirements-cdp.txt 文件内容如下:
txtwebsocket-client==1.9.0
5️⃣ 步骤4:读取账号列表
接着在同一个 Console 输入。下面整段是一个立即执行的异步函数,重复运行也不会因顶层 const 重复声明而报错。
javascript
(async () => {
const users = await SteamClient.User.GetLoginUsers();
console.log('条目数:', users.length);
console.table(users.map((u, i) => ({
序号: i + 1,
记住状态: u.rememberPassword,
有PIN: u.hasPin,
最近登录时间戳: u.rtLastLogin
})));
console.log('字段名:', users.length ? Object.keys(users[0]) : []);
})();
你应能看到条目数、表格以及字段名。字段包括 accountName / personaName / avatarUrl / rememberPassword / rtLastLogin / hasPin,但这段示例刻意不打印真实账号名。原始列表中的名字与昵称不同,重复条目也可能存在,因此不要把条目数直接当成唯一账号数。
本机最早一次查询是37条、12条记住状态为 true。你的数量不同完全正常。该 API 不返回 token 原文,内部却可能检查缓存,后面的 Hook 实验会看到这一点。
使用备用方式时,不需要打开 Console,直接在刚才的 PowerShell 窗口执行:
powershell
python .\scripts\query_steam.py accounts
输出的 count 对应条目数,fields 对应字段名,rows 对应脱敏表格。它与上面 JavaScript 查询的是同一个接口。
6️⃣ 步骤5:读当前账号是否在线
仍在 SharedJSContext 的 Console 中执行:
javascript
(() => {
if (typeof App === 'undefined' || typeof App.GetCurrentUser !== 'function') {
console.log('当前上下文没有 App.GetCurrentUser,先核对目标或等待界面就绪。');
return;
}
const u = App.GetCurrentUser();
console.table([{
有有效用户ID: !!(u && u.strSteamID && String(u.strSteamID) !== '0'),
当前登录名: u?.strAccountName ?? '',
离线模式: u ? !!u.bIsOfflineMode : null,
原始登录状态: typeof loginStore !== 'undefined' ? loginStore.m_eLoginState : null
}]);
})();
这一步会打印自己的登录名,便于你与 Steam 界面中的目标身份核对。截图时遮挡该列。判断在线成功要同时考虑:有有效用户ID、身份是预期账号、离线模式为 false。本构建曾观察到原始状态5,但不要把5当成所有版本都成立的常量。
备用方式的命令如下,仍在系列目录执行:
powershell
python .\scripts\query_steam.py status
返回 ready: false 时先等待客户端就绪,不继续判定。validUserId / accountName / offline / loginState 依次对应有效ID、当前登录名、离线模式和原始状态,判断标准与 Console 相同。截图同样要遮住 accountName。
#mermaid-svg-HsaH6Squ0bZ2EBHs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HsaH6Squ0bZ2EBHs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HsaH6Squ0bZ2EBHs .error-icon{fill:#552222;}#mermaid-svg-HsaH6Squ0bZ2EBHs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HsaH6Squ0bZ2EBHs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HsaH6Squ0bZ2EBHs .marker.cross{stroke:#333333;}#mermaid-svg-HsaH6Squ0bZ2EBHs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HsaH6Squ0bZ2EBHs p{margin:0;}#mermaid-svg-HsaH6Squ0bZ2EBHs .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HsaH6Squ0bZ2EBHs .cluster-label text{fill:#333;}#mermaid-svg-HsaH6Squ0bZ2EBHs .cluster-label span{color:#333;}#mermaid-svg-HsaH6Squ0bZ2EBHs .cluster-label span p{background-color:transparent;}#mermaid-svg-HsaH6Squ0bZ2EBHs .label text,#mermaid-svg-HsaH6Squ0bZ2EBHs span{fill:#333;color:#333;}#mermaid-svg-HsaH6Squ0bZ2EBHs .node rect,#mermaid-svg-HsaH6Squ0bZ2EBHs .node circle,#mermaid-svg-HsaH6Squ0bZ2EBHs .node ellipse,#mermaid-svg-HsaH6Squ0bZ2EBHs .node polygon,#mermaid-svg-HsaH6Squ0bZ2EBHs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HsaH6Squ0bZ2EBHs .rough-node .label text,#mermaid-svg-HsaH6Squ0bZ2EBHs .node .label text,#mermaid-svg-HsaH6Squ0bZ2EBHs .image-shape .label,#mermaid-svg-HsaH6Squ0bZ2EBHs .icon-shape .label{text-anchor:middle;}#mermaid-svg-HsaH6Squ0bZ2EBHs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HsaH6Squ0bZ2EBHs .rough-node .label,#mermaid-svg-HsaH6Squ0bZ2EBHs .node .label,#mermaid-svg-HsaH6Squ0bZ2EBHs .image-shape .label,#mermaid-svg-HsaH6Squ0bZ2EBHs .icon-shape .label{text-align:center;}#mermaid-svg-HsaH6Squ0bZ2EBHs .node.clickable{cursor:pointer;}#mermaid-svg-HsaH6Squ0bZ2EBHs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HsaH6Squ0bZ2EBHs .arrowheadPath{fill:#333333;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HsaH6Squ0bZ2EBHs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HsaH6Squ0bZ2EBHs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HsaH6Squ0bZ2EBHs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HsaH6Squ0bZ2EBHs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HsaH6Squ0bZ2EBHs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HsaH6Squ0bZ2EBHs .cluster text{fill:#333;}#mermaid-svg-HsaH6Squ0bZ2EBHs .cluster span{color:#333;}#mermaid-svg-HsaH6Squ0bZ2EBHs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HsaH6Squ0bZ2EBHs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HsaH6Squ0bZ2EBHs rect.text{fill:none;stroke-width:0;}#mermaid-svg-HsaH6Squ0bZ2EBHs .icon-shape,#mermaid-svg-HsaH6Squ0bZ2EBHs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HsaH6Squ0bZ2EBHs .icon-shape p,#mermaid-svg-HsaH6Squ0bZ2EBHs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HsaH6Squ0bZ2EBHs .icon-shape .label rect,#mermaid-svg-HsaH6Squ0bZ2EBHs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HsaH6Squ0bZ2EBHs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HsaH6Squ0bZ2EBHs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HsaH6Squ0bZ2EBHs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
否
是
查询实时用户
有有效用户ID?
尚不能确认已登录
身份是目标账号?
登录了其他账号
离线模式为 false?
不是已确认的在线会话
本次在线结果成立
🛬 文章小结
现在你能从客户端查询候选账号和实时会话。下一篇追问一个更具体的问题:03-点击切号后代码去了哪里?


