mcpywrap 0.3.4__tar.gz → 0.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/CHANGELOG.md +7 -0
- {mcpywrap-0.3.4/mcpywrap.egg-info → mcpywrap-0.3.5}/PKG-INFO +14 -5
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/README.md +13 -4
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/docs/development.md +1 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/docs/engine-discovery.md +1 -1
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/docs/mcs-auth.md +3 -1
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/__init__.py +1 -1
- mcpywrap-0.3.5/mcpywrap/__main__.py +18 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/command_context.py +2 -1
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/connect_cmd.py +4 -2
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/doctor_cmd.py +10 -1
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/run_cmd.py +5 -10
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/bridge_assets.py +24 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/bridge_payload/Injector.exe +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/bridge_payload/Loader.dll +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/bridge_payload/McpyMcsAuth.dll +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/bridge_payload/manifest.json +3 -3
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/log_protocol.py +22 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/network.py +58 -75
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/private_logs.py +6 -7
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/session_worker.py +25 -11
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/sessions.py +24 -2
- {mcpywrap-0.3.4 → mcpywrap-0.3.5/mcpywrap.egg-info}/PKG-INFO +14 -5
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap.egg-info/SOURCES.txt +3 -1
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/pyproject.toml +1 -1
- mcpywrap-0.3.5/skills/mcpywrap/SKILL.md +98 -0
- mcpywrap-0.3.5/skills/mcpywrap/references/troubleshooting.md +49 -0
- mcpywrap-0.3.5/skills/mcpywrap/scripts/bootstrap.ps1 +107 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/skills/mcpywrap/scripts/game_window.py +4 -4
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/skills/mcpywrap/scripts/smoke.py +36 -19
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/test_auth_interaction.py +1 -1
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/test_engine_discovery.py +2 -1
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/test_mcs_auth.py +24 -14
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/test_network.py +32 -56
- mcpywrap-0.3.5/tests/test_network_sessions.py +101 -0
- mcpywrap-0.3.5/tests/test_skill_workflows.py +245 -0
- mcpywrap-0.3.4/mcpywrap/__main__.py +0 -13
- mcpywrap-0.3.4/skills/mcpywrap/SKILL.md +0 -108
- mcpywrap-0.3.4/skills/mcpywrap/references/troubleshooting.md +0 -28
- mcpywrap-0.3.4/skills/mcpywrap/scripts/bootstrap.ps1 +0 -65
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/LICENSE +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/MANIFEST.in +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/docs/local-dependencies.md +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/docs/releasing.md +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/builders/AddonsPack.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/builders/MapPack.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/builders/__init__.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/builders/assembly.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/builders/dependency_manager.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/builders/file_merge.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/builders/project_builder.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/builders/watcher.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/cli.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/__init__.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/add_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/build_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/default_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/dependency_prompt.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/dev_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/edit_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/init_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/mod_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/modsdk_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/package_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/publish_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/remove_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/session_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/sync_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/commands/ui_cmd.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/config.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/custom_packaging.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/dependencies.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/SimpleMonitor.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/__init__.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/auth_interaction.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/bridge_payload/publisher.cer +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/discovery.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/editor.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/file_logs.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/game.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/mcs.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/mcs_auth.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/processes.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/runtime_cppconfig.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/studio_server.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/studio_server_ui.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/symlink_helper_global.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/symlink_helper_map.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/mcstudio/symlinks.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/minecraft/__init__.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/minecraft/addons.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/minecraft/level_dat.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/minecraft/map.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/minecraft/netease_modsdk.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/minecraft/template/generate_mod_files.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/minecraft/template/mod_template.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/project_init.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/ui/__init__.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/ui/project_ui.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/utils/__init__.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/utils/pip_error_parser.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/utils/print_guide.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/utils/project_setup.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap/utils/utils.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap.egg-info/dependency_links.txt +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap.egg-info/entry_points.txt +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap.egg-info/requires.txt +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/mcpywrap.egg-info/top_level.txt +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/native/mcs_auth/Bridge.cs +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/native/mcs_auth/Injector.cs +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/native/mcs_auth/Loader.c +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/native/mcs_auth/publisher.cer +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/native/mcs_auth/publisher.json +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/scripts/build_mcs_auth_bridge.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/scripts/check_mcs_auth_bridge.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/setup.cfg +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/skills/mcpywrap/agents/openai.yaml +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/manual_integration.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/test_automation.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/test_game_window_skill.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/test_local_auth_session.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/test_local_dependencies.py +0 -0
- {mcpywrap-0.3.4 → mcpywrap-0.3.5}/tests/test_package.py +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.3.5
|
|
4
|
+
|
|
5
|
+
- 网络连接接入统一游戏会话:`connect/run --detach --json` 返回进程身份与日志,可复用 status/logs/stop、Skill 截图与按键脚本;网络 JSON 启动现在要求 `--detach`。
|
|
6
|
+
- 新增 `doctor --mcs-auth` 只读组件检查;Skill 安装脚本分别报告 CLI 能力与组件是否具备,按任务显式要求可选能力。
|
|
7
|
+
- 冒烟脚本支持 `--mcs-auth` 与 `--connect`,共用日志标记验证和本次会话清理;精简 Skill 入口并补充编辑器、地图合并及网络指引。
|
|
8
|
+
- 修复 Windows PowerShell 5.1 中文脚本读取与 GBK 管道兼容;JSON 无损保留 Unicode,会话 SDK/引擎日志统一解码为 UTF-8 后脱敏。
|
|
9
|
+
|
|
3
10
|
## 0.3.4
|
|
4
11
|
|
|
5
12
|
- 新增显式 `--mcs-auth`:网络连接、单人测试和单人项目 GUI 可按次使用已登录的 MC Studio 身份;默认仍不读取身份,配置文件不能自动启用。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcpywrap
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: A wrapper for Minecraft China Edition Addons management
|
|
5
5
|
Author-email: boybook <boybook@easecation.net>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -140,9 +140,18 @@ host = "192.168.31.101"
|
|
|
140
140
|
port = 19132
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
空目录只需上述配置,无需执行 `mcpy init`;已有 Addon
|
|
143
|
+
空目录只需上述配置,无需执行 `mcpy init`;已有 Addon 项目也可添加此表。默认使用未认证连接,不读取登录身份或 token,不保证服务器允许进服;项目依赖仍会校验,但暂不装配本地 Mod。
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
网络模式默认前台输出日志,Ctrl+C 结束本次游戏。增加 `--detach --json` 可返回会话,与本地游戏共用 `status/logs/stop` 及 Skill 截图、按键脚本:
|
|
146
|
+
|
|
147
|
+
```powershell
|
|
148
|
+
mcpy --project D:\tests\server --non-interactive connect example.com --detach --json
|
|
149
|
+
mcpy --project D:\tests\server status --session <id> --json
|
|
150
|
+
mcpy --project D:\tests\server stop --session <id> --json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
目录须已存在;临时 `connect` 不读取项目配置,只在该目录保存会话。后续操作使用启动返回的 `project` 和 `session`。
|
|
154
|
+
网络模式暂不支持 Map、Qt GUI、`--new` 或本地世界实例 ID;进程启动不等于成功进服。
|
|
146
155
|
|
|
147
156
|
### 使用 MC Studio 登录身份(可选)
|
|
148
157
|
|
|
@@ -163,8 +172,8 @@ port = 19132
|
|
|
163
172
|
|
|
164
173
|
也可以下载仓库 ZIP,将完整的 `skills/mcpywrap` 文件夹复制到对应 Agent 的技能目录。
|
|
165
174
|
无需从源码安装 Python 项目;CLI 和 Skill 分别安装,`pip/uv install` 不会自动注册 Skill。
|
|
166
|
-
Skill 中的 `scripts/bootstrap.ps1`
|
|
167
|
-
|
|
175
|
+
Skill 中的 `scripts/bootstrap.ps1` 使用已有 uv 安装或复用 CLI,报告可选能力;`-RequireCapability network-sessions` 或 `mcs-auth` 仅在任务需要时检查。身份参数与桥接组件就绪分别报告,Git/可编辑安装可能缺少组件。
|
|
176
|
+
推荐使用 mcpywrap 0.3.5 或更高版本,可从 `v0.3.5` 标签安装对应 Skill。网络会话需 `connect --help` 包含 `--detach`,实际能力以脚本检测为准。
|
|
168
177
|
|
|
169
178
|
安装后可直接描述任务:
|
|
170
179
|
|
|
@@ -113,9 +113,18 @@ host = "192.168.31.101"
|
|
|
113
113
|
port = 19132
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
空目录只需上述配置,无需执行 `mcpy init`;已有 Addon
|
|
116
|
+
空目录只需上述配置,无需执行 `mcpy init`;已有 Addon 项目也可添加此表。默认使用未认证连接,不读取登录身份或 token,不保证服务器允许进服;项目依赖仍会校验,但暂不装配本地 Mod。
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
网络模式默认前台输出日志,Ctrl+C 结束本次游戏。增加 `--detach --json` 可返回会话,与本地游戏共用 `status/logs/stop` 及 Skill 截图、按键脚本:
|
|
119
|
+
|
|
120
|
+
```powershell
|
|
121
|
+
mcpy --project D:\tests\server --non-interactive connect example.com --detach --json
|
|
122
|
+
mcpy --project D:\tests\server status --session <id> --json
|
|
123
|
+
mcpy --project D:\tests\server stop --session <id> --json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
目录须已存在;临时 `connect` 不读取项目配置,只在该目录保存会话。后续操作使用启动返回的 `project` 和 `session`。
|
|
127
|
+
网络模式暂不支持 Map、Qt GUI、`--new` 或本地世界实例 ID;进程启动不等于成功进服。
|
|
119
128
|
|
|
120
129
|
### 使用 MC Studio 登录身份(可选)
|
|
121
130
|
|
|
@@ -136,8 +145,8 @@ port = 19132
|
|
|
136
145
|
|
|
137
146
|
也可以下载仓库 ZIP,将完整的 `skills/mcpywrap` 文件夹复制到对应 Agent 的技能目录。
|
|
138
147
|
无需从源码安装 Python 项目;CLI 和 Skill 分别安装,`pip/uv install` 不会自动注册 Skill。
|
|
139
|
-
Skill 中的 `scripts/bootstrap.ps1`
|
|
140
|
-
|
|
148
|
+
Skill 中的 `scripts/bootstrap.ps1` 使用已有 uv 安装或复用 CLI,报告可选能力;`-RequireCapability network-sessions` 或 `mcs-auth` 仅在任务需要时检查。身份参数与桥接组件就绪分别报告,Git/可编辑安装可能缺少组件。
|
|
149
|
+
推荐使用 mcpywrap 0.3.5 或更高版本,可从 `v0.3.5` 标签安装对应 Skill。网络会话需 `connect --help` 包含 `--detach`,实际能力以脚本检测为准。
|
|
141
150
|
|
|
142
151
|
安装后可直接描述任务:
|
|
143
152
|
|
|
@@ -19,6 +19,7 @@ python skills/mcpywrap/scripts/smoke.py --project D:\mods\test --game --expect-l
|
|
|
19
19
|
|
|
20
20
|
脚本结束后停止自己创建的会话;日志和产物留在项目中。
|
|
21
21
|
需要手动截图验证时直接运行 `run --no-gui --detach --json`,保存会话 ID,操作完后显式 stop。
|
|
22
|
+
网络验证使用 `smoke.py --project <已有目录> --connect <地址> --expect-log <约定标记>`,不打包项目。单人与网络测试都可显式加 `--mcs-auth`;脚本停止自己创建的会话。
|
|
22
23
|
历史验收记录仅用于追溯,不能代替当前版本测试。
|
|
23
24
|
|
|
24
25
|
发布见 [发布流程](releasing.md)。
|
|
@@ -52,4 +52,4 @@ mcpy --project D:\mods\demo stop --session <id> --json
|
|
|
52
52
|
|
|
53
53
|
只打开游戏窗口,日志写入返回的文件。`running` 表示进程存活,加载是否成功应查看项目日志或游戏画面。
|
|
54
54
|
Computer Use 只接手游戏画面;Qt 管理、模板及日志页留给人工操作。
|
|
55
|
-
|
|
55
|
+
以上示例是本地世界会话。网络连接可用 `mcpy connect <地址> --detach --json`,或配置服务器后执行 `run --detach --json`;同样使用返回的项目路径、会话 ID 读取日志、截图、输入和停止。临时 connect 不读取项目引擎配置,使用 CLI 参数或环境变量覆盖。
|
|
@@ -10,7 +10,9 @@ mcpy ui --mcs-auth
|
|
|
10
10
|
mcpy --project D:\mods\demo --non-interactive run --mcs-auth --detach --json
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
本地与网络均支持 `--detach --json`,共用会话查询、日志和停止。GUI 登录支持单人项目,网络目标使用 CLI。
|
|
14
|
+
|
|
15
|
+
`mcpy doctor --mcs-auth --json` 可只读检查组件文件,不读取身份、不写缓存、不修改证书。组件具备不表示已登录或系统策略允许启用。
|
|
14
16
|
|
|
15
17
|
## 用户会看到什么
|
|
16
18
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
mcpywrap 命令行工具的入口点
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .cli import cli
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
"""主函数,作为 CLI 入口点"""
|
|
10
|
+
import sys
|
|
11
|
+
# Keep the caller's text encoding; unsupported symbols must not abort an operation.
|
|
12
|
+
for stream in (sys.stdout, sys.stderr):
|
|
13
|
+
if hasattr(stream, 'reconfigure'):
|
|
14
|
+
stream.reconfigure(errors='backslashreplace')
|
|
15
|
+
cli()
|
|
16
|
+
|
|
17
|
+
if __name__ == "__main__":
|
|
18
|
+
main()
|
|
@@ -111,7 +111,8 @@ class OperationGroup(click.Group):
|
|
|
111
111
|
data = {'ok': code == 0, 'error': None, 'hint': None}
|
|
112
112
|
if isinstance(result, dict):
|
|
113
113
|
data.update(result)
|
|
114
|
-
|
|
114
|
+
# ASCII JSON survives both GBK consoles and UTF-8 subprocess pipes losslessly.
|
|
115
|
+
click.echo(json.dumps(data, ensure_ascii=True), file=output)
|
|
115
116
|
elif code:
|
|
116
117
|
click.echo('错误: ' + str((result or {}).get('error') or '操作失败'), err=True)
|
|
117
118
|
if (result or {}).get('hint'):
|
|
@@ -11,10 +11,12 @@ from ..mcstudio.network import ServerTarget, run_network
|
|
|
11
11
|
@click.argument('host')
|
|
12
12
|
@click.option('--port', type=click.IntRange(1, 65535), default=19132, show_default=True)
|
|
13
13
|
@click.option('--mcs-auth', is_flag=True, help='本次连接使用已登录的 MC Studio 身份')
|
|
14
|
-
|
|
14
|
+
@click.option('--detach', is_flag=True, help='后台连接并返回游戏会话;JSON 必须指定')
|
|
15
|
+
def connect_cmd(host, port, mcs_auth=False, detach=False, **engine_overrides):
|
|
15
16
|
"""尝试连接服务器;前台采集日志,Ctrl+C 结束本次游戏。
|
|
16
17
|
|
|
17
18
|
默认不读取登录身份;--mcs-auth 要求 MCS 已登录及本机桥接可用。
|
|
18
19
|
不读取项目配置,不装配本地 Mod,不保证服务器接受连接。
|
|
19
20
|
"""
|
|
20
|
-
return run_network(ServerTarget(host, port, 'mcs' if mcs_auth else 'none'),
|
|
21
|
+
return run_network(ServerTarget(host, port, 'mcs' if mcs_auth else 'none'),
|
|
22
|
+
engine_overrides=engine_overrides, detach=detach)
|
|
@@ -9,7 +9,8 @@ from ..mcstudio.discovery import (
|
|
|
9
9
|
|
|
10
10
|
@click.command(cls=OperationCommand)
|
|
11
11
|
@engine_options
|
|
12
|
-
|
|
12
|
+
@click.option('--mcs-auth', is_flag=True, help='同时只读检查登录组件;不读取身份或安装证书')
|
|
13
|
+
def doctor_cmd(mcs_auth=False, **overrides):
|
|
13
14
|
"""诊断游戏发现和运行资源(只读,无需初始化项目)"""
|
|
14
15
|
result = discover_engines(current_project(), overrides=overrides)
|
|
15
16
|
data = result.to_dict()
|
|
@@ -21,6 +22,11 @@ def doctor_cmd(**overrides):
|
|
|
21
22
|
_, issues = studio_installation(purpose)
|
|
22
23
|
data['resources'][purpose].extend(issues)
|
|
23
24
|
data['ok'] = bool(result.selected and not result.error and not data['resources']['run'])
|
|
25
|
+
if mcs_auth:
|
|
26
|
+
from ..mcstudio.bridge_assets import inspect_bridge
|
|
27
|
+
data['mcs_auth'] = inspect_bridge()
|
|
28
|
+
if not data['mcs_auth']['component_available']:
|
|
29
|
+
data.update(ok=False, error=data['mcs_auth']['error'], hint=data['mcs_auth']['hint'])
|
|
24
30
|
from ..command_context import json_output
|
|
25
31
|
if not json_output():
|
|
26
32
|
for candidate in result.candidates:
|
|
@@ -30,4 +36,7 @@ def doctor_cmd(**overrides):
|
|
|
30
36
|
for kind, issues in data['resources'].items():
|
|
31
37
|
for issue in issues:
|
|
32
38
|
click.echo(f'资源 [{kind}]: {issue}')
|
|
39
|
+
if mcs_auth:
|
|
40
|
+
click.echo('登录组件: ' + ('文件齐全,尚未验证登录和系统策略' if data['mcs_auth']['component_available']
|
|
41
|
+
else data['mcs_auth']['error']))
|
|
33
42
|
return data
|
|
@@ -126,7 +126,7 @@ def _build_dependency_tree(node, tree_node):
|
|
|
126
126
|
|
|
127
127
|
def _run_game_with_instance(config_path, level_id, all_packs, wait=True, log_callback=None,
|
|
128
128
|
engine_overrides=None, no_gui=False, logging_port=None, output_path=None,
|
|
129
|
-
auth_context=None, auth_config_path=None):
|
|
129
|
+
auth_context=None, auth_config_path=None, capture_output=False):
|
|
130
130
|
"""使用指定的实例运行游戏
|
|
131
131
|
|
|
132
132
|
Args:
|
|
@@ -274,7 +274,7 @@ def _run_game_with_instance(config_path, level_id, all_packs, wait=True, log_cal
|
|
|
274
274
|
|
|
275
275
|
with console.status("启动游戏中...", spinner="dots"):
|
|
276
276
|
game_process = open_game(launch_config_path, logging_port=logging_port, wait=False, engine=engine,
|
|
277
|
-
**({'capture_output': True} if auth_context is not None else
|
|
277
|
+
**({'capture_output': True} if auth_context is not None or capture_output else
|
|
278
278
|
{'output_path': output_path} if output_path else {}))
|
|
279
279
|
|
|
280
280
|
if not game_process:
|
|
@@ -356,7 +356,7 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
|
|
|
356
356
|
配置 [tool.mcpywrap.server] host/port 时改为网络连接;默认未认证,
|
|
357
357
|
可用 --mcs-auth 显式读取 MCS 身份,单人测试同样支持此选项。
|
|
358
358
|
不装配本地 Mod,不保证服务器接受连接。网络模式前台采集日志,
|
|
359
|
-
Ctrl+C
|
|
359
|
+
Ctrl+C 结束本次网络游戏;--detach 返回可查询、操作和停止的会话。
|
|
360
360
|
"""
|
|
361
361
|
base_dir = str(current_project())
|
|
362
362
|
if (delete or clean_all) and non_interactive() and not force:
|
|
@@ -379,11 +379,9 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
|
|
|
379
379
|
target = replace(target, auth='mcs')
|
|
380
380
|
if new or instance_prefix:
|
|
381
381
|
raise click.UsageError('网络模式不支持 --new 或本地世界实例 ID')
|
|
382
|
-
if detach:
|
|
383
|
-
raise click.UsageError('网络模式暂不支持 --detach;请在终端会话中前台运行')
|
|
384
382
|
packs = prepare_project(base_dir, config)
|
|
385
383
|
return run_network(target, project_dir=base_dir, packs=packs,
|
|
386
|
-
engine_overrides=engine_overrides)
|
|
384
|
+
engine_overrides=engine_overrides, detach=detach)
|
|
387
385
|
|
|
388
386
|
# 确保 map 项目的 setuptools 配置同步
|
|
389
387
|
ensure_map_setuptools_sync(interactive=False)
|
|
@@ -470,10 +468,7 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
|
|
|
470
468
|
import time
|
|
471
469
|
data = sessions.start(base_dir, config_path, level_id, engine_overrides,
|
|
472
470
|
**({'auth_context': auth_context} if auth_context is not None else {}))
|
|
473
|
-
result =
|
|
474
|
-
'state': data['state'], **data['game'], 'log_path': data['log_path'],
|
|
475
|
-
'engine_log_path': data.get('engine_log_path'),
|
|
476
|
-
'window_title_hint': 'Minecraft', 'window_verified': False}
|
|
471
|
+
result = sessions.handoff(data)
|
|
477
472
|
if detach:
|
|
478
473
|
return result
|
|
479
474
|
click.echo('会话: ' + data['session'] + ';日志: ' + data['log_path'])
|
|
@@ -9,6 +9,30 @@ BINARIES = ('Injector.exe', 'Loader.dll', 'McpyMcsAuth.dll')
|
|
|
9
9
|
PAYLOAD = Path(__file__).with_name('bridge_payload')
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def inspect_bridge():
|
|
13
|
+
"""只检查组件文件;不复制缓存、不读取身份、不改变证书信任。"""
|
|
14
|
+
override = os.environ.get('MCPY_MCS_BRIDGE_DIR')
|
|
15
|
+
bundled = not override and (PAYLOAD/'manifest.json').is_file()
|
|
16
|
+
directory = (Path(override).expanduser().resolve() if override else PAYLOAD if bundled
|
|
17
|
+
else Path.home()/'.local/share/mcpywrap/mcs-auth-bridge')
|
|
18
|
+
result = {'component_available': False, 'directory': str(directory),
|
|
19
|
+
'source': 'override' if override else 'package' if bundled else 'development',
|
|
20
|
+
'integrity_verified': False, 'trust_verified': False, 'login_verified': False,
|
|
21
|
+
'error': None, 'hint': None}
|
|
22
|
+
try:
|
|
23
|
+
if bundled:
|
|
24
|
+
payload_manifest()
|
|
25
|
+
result['integrity_verified'] = True
|
|
26
|
+
missing = [name for name in BINARIES if not (directory/name).is_file()]
|
|
27
|
+
if missing:
|
|
28
|
+
raise ValueError('缺少登录组件: ' + ', '.join(missing))
|
|
29
|
+
result['component_available'] = True
|
|
30
|
+
except (OSError, ValueError, KeyError, TypeError) as exc:
|
|
31
|
+
result.update(error=str(exc), hint='使用包含桥接组件的正式发布包;Git/可编辑安装需另行构建组件,'
|
|
32
|
+
'并通过 MCPY_MCS_BRIDGE_DIR 指定目录。组件存在不表示已登录或系统策略允许启用。')
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
|
|
12
36
|
def digest(path):
|
|
13
37
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
14
38
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"Loader.c": "f2f1f781ce82fc30c62a6ce22bb048d3b023f6ff500e6ec8228da28b4dbf1b4a"
|
|
12
12
|
},
|
|
13
13
|
"files": {
|
|
14
|
-
"Injector.exe": "
|
|
15
|
-
"Loader.dll": "
|
|
16
|
-
"McpyMcsAuth.dll": "
|
|
14
|
+
"Injector.exe": "61355aeeb02b23e2ff790f336c75486697028d98055f95bf5eb0c3a47538e43c",
|
|
15
|
+
"Loader.dll": "b8eb5e121e005b57e7720e3dfd3aac99f2393678bc4ce7d87194fa270e287db8",
|
|
16
|
+
"McpyMcsAuth.dll": "fe5c60e7d8266cf8ea843fef6dd517d6324397d3dc477f480830b807945f9abc",
|
|
17
17
|
"publisher.cer": "e5aa2ac39df40804ed8b08fe8e43c4a2b8923f0d02a360708486b2db5b6303d7"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -9,6 +9,28 @@ def decode_text(raw):
|
|
|
9
9
|
return raw.decode('gb18030', errors='replace')
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
class TextLogDecoder:
|
|
13
|
+
"""普通 stdout 按完整行解码,跨块保留字节;UTF-8 优先、GBK/GB18030 回退。"""
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.pending = bytearray()
|
|
16
|
+
|
|
17
|
+
def feed(self, data):
|
|
18
|
+
self.pending.extend(data)
|
|
19
|
+
output = []
|
|
20
|
+
while b'\n' in self.pending:
|
|
21
|
+
line, _, rest = self.pending.partition(b'\n')
|
|
22
|
+
output.append(decode_text(bytes(line)) + '\n')
|
|
23
|
+
self.pending = bytearray(rest)
|
|
24
|
+
if len(self.pending) > 1024 * 1024:
|
|
25
|
+
output.append(self.finish())
|
|
26
|
+
return ''.join(output)
|
|
27
|
+
|
|
28
|
+
def finish(self):
|
|
29
|
+
text = decode_text(bytes(self.pending))
|
|
30
|
+
self.pending.clear()
|
|
31
|
+
return text
|
|
32
|
+
|
|
33
|
+
|
|
12
34
|
class LogDecoder:
|
|
13
35
|
def __init__(self):
|
|
14
36
|
self.pending = bytearray()
|
|
@@ -3,17 +3,15 @@ import codecs
|
|
|
3
3
|
import ipaddress
|
|
4
4
|
import json
|
|
5
5
|
import re
|
|
6
|
-
import
|
|
7
|
-
import tempfile
|
|
6
|
+
import time
|
|
8
7
|
import uuid
|
|
9
|
-
from dataclasses import dataclass
|
|
8
|
+
from dataclasses import asdict, dataclass
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
|
|
12
11
|
import click
|
|
13
12
|
|
|
14
13
|
from ..dependencies import DependencyService, addon_directories, read_project
|
|
15
14
|
from .discovery import discover_engines, require_resources
|
|
16
|
-
from .file_logs import FileLogServer
|
|
17
15
|
from .game import open_game
|
|
18
16
|
from .mcs import is_windows
|
|
19
17
|
from .runtime_cppconfig import gen_runtime_config
|
|
@@ -90,97 +88,82 @@ def unauthenticated_config(engine, target):
|
|
|
90
88
|
return config
|
|
91
89
|
|
|
92
90
|
|
|
93
|
-
def
|
|
94
|
-
"""
|
|
91
|
+
def launch_network(engine, target, config_path, logging_port, auth_context=None):
|
|
92
|
+
"""由会话 worker 调用;监听、日志脱敏和进程回收由 worker 统一负责。"""
|
|
93
|
+
require_resources(engine)
|
|
94
|
+
config = unauthenticated_config(engine, target)
|
|
95
|
+
if target.auth == 'mcs' and auth_context is None:
|
|
96
|
+
raise ValueError('网络会话缺少本次请求的 MCS 身份,拒绝匿名回退')
|
|
97
|
+
if auth_context:
|
|
98
|
+
auth_context.apply(config)
|
|
99
|
+
game_id = str(uuid.uuid4())
|
|
100
|
+
config['misc']['game_id'] = game_id
|
|
101
|
+
config['room_info']['item_ids'] = [game_id]
|
|
102
|
+
config.update(vip_using_mod=[], isCloud=False, path=str(config_path))
|
|
103
|
+
config_path.write_text(json.dumps(config, ensure_ascii=False), encoding='utf-8')
|
|
104
|
+
return open_game(str(config_path), engine=engine, logging_ip='127.0.0.1',
|
|
105
|
+
logging_port=logging_port, wait=False, use_system_color=False,
|
|
106
|
+
capture_output=True)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def run_network(target, *, project_dir=None, packs=(), engine_overrides=None, detach=False):
|
|
110
|
+
"""预检后创建普通游戏会话;临时 connect 只用项目目录存会话,不读取其配置。"""
|
|
111
|
+
from ..command_context import project_dir as current_project, json_output
|
|
112
|
+
from . import sessions
|
|
113
|
+
if json_output() and not detach:
|
|
114
|
+
raise click.UsageError('网络运行 --json 需要 --detach;使用 status/logs/stop 管理返回的会话')
|
|
95
115
|
if not is_windows():
|
|
96
116
|
raise ValueError('网络游戏启动仅支持 Windows')
|
|
97
117
|
engine = discover_engines(project_dir, engine_overrides,
|
|
98
118
|
read_project_config=project_dir is not None).require_engine()
|
|
99
119
|
require_resources(engine)
|
|
100
|
-
config = unauthenticated_config(engine, target)
|
|
101
120
|
identity = None
|
|
102
|
-
secrets = []
|
|
103
121
|
if target.auth == 'mcs':
|
|
104
122
|
from .mcs_auth import acquire_identity
|
|
105
123
|
identity = acquire_identity()
|
|
106
|
-
identity.apply(config)
|
|
107
|
-
# Match MCS's generated ID relationship for third-party servers without a numeric game ID.
|
|
108
|
-
game_id = str(uuid.uuid4())
|
|
109
|
-
config['misc']['game_id'] = game_id
|
|
110
|
-
config['room_info']['item_ids'] = [game_id]
|
|
111
|
-
config.update(vip_using_mod=[], isCloud=False)
|
|
112
|
-
secrets = identity.secrets()
|
|
113
124
|
click.echo('已读取 MCS 当前身份;连接结果仍以服务器响应为准。')
|
|
114
125
|
else:
|
|
115
126
|
click.echo('未认证网络连接:不使用账号/token,服务器可能拒绝连接。')
|
|
116
127
|
click.echo(f'本次仅连接服务器,工具未装配本地 Addon(已解析 {len(packs)} 个)。')
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
root = Path(project_dir or current_project()).resolve()
|
|
129
|
+
data = sessions.start(root, auth_context=identity,
|
|
130
|
+
network={'target': asdict(target), 'engine': asdict(engine)})
|
|
131
|
+
result = sessions.handoff(data)
|
|
132
|
+
if detach:
|
|
133
|
+
return result
|
|
134
|
+
click.echo(f"会话: {data['session']};PID: {data['game']['pid']}(尚未确认连接)")
|
|
135
|
+
click.echo(f"日志: {data['log_path']}\n引擎输出: {data['engine_log_path']}\nCtrl+C 结束本次游戏。")
|
|
121
136
|
tails = []
|
|
122
|
-
|
|
123
137
|
def relay(final=False):
|
|
124
|
-
for
|
|
125
|
-
click.echo(decoder.decode(
|
|
126
|
-
|
|
138
|
+
for stream, decoder in tails:
|
|
139
|
+
click.echo(decoder.decode(stream.read(), final=final), nl=False)
|
|
127
140
|
try:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if identity:
|
|
132
|
-
from .private_logs import RedactedDecoder, EngineLogCapture
|
|
133
|
-
receiver = FileLogServer(log_path, decoder_factory=lambda: RedactedDecoder(secrets))
|
|
134
|
-
else:
|
|
135
|
-
receiver = FileLogServer(log_path)
|
|
136
|
-
receiver.start()
|
|
137
|
-
engine_log.touch()
|
|
138
|
-
for path in (log_path, engine_log):
|
|
141
|
+
for key in ('log_path', 'engine_log_path'):
|
|
142
|
+
path = Path(data[key])
|
|
143
|
+
path.touch(exist_ok=True)
|
|
139
144
|
tails.append((path.open('rb'), codecs.getincrementaldecoder('utf-8')(errors='replace')))
|
|
140
|
-
click.echo(f'目标: {target.host}:{target.port}\n日志: {log_path}\n引擎输出: {engine_log}')
|
|
141
|
-
process = open_game(str(config_path), engine=engine, logging_ip='127.0.0.1',
|
|
142
|
-
logging_port=receiver.port, wait=False, use_system_color=False,
|
|
143
|
-
**({'capture_output': True} if identity else {'output_path': str(engine_log)}))
|
|
144
|
-
if not process:
|
|
145
|
-
raise click.ClickException(f'游戏启动失败;日志目录: {directory}')
|
|
146
|
-
if identity:
|
|
147
|
-
engine_capture = EngineLogCapture(process.stdout, engine_log, secrets)
|
|
148
|
-
click.echo(f'游戏进程已创建,PID: {process.pid}(尚未确认连接);Ctrl+C 结束本次游戏。')
|
|
149
145
|
while True:
|
|
150
146
|
relay()
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
final = sessions.read(root, data['session'])
|
|
148
|
+
if final['state'] == 'failed':
|
|
149
|
+
sessions.stop(root, data['session'])
|
|
150
|
+
if final['state'] not in ('starting', 'running'):
|
|
151
|
+
# read() may observe the game exiting just before the worker flushes logs.
|
|
152
|
+
if final['state'] == 'exited' and 'exit_code' not in final:
|
|
153
|
+
time.sleep(0.2)
|
|
154
|
+
continue
|
|
153
155
|
break
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
time.sleep(0.2)
|
|
157
|
+
except BaseException:
|
|
158
|
+
sessions.stop(root, data['session'])
|
|
159
|
+
raise
|
|
156
160
|
finally:
|
|
157
161
|
try:
|
|
158
|
-
|
|
159
|
-
process.terminate()
|
|
160
|
-
try:
|
|
161
|
-
process.wait(timeout=5)
|
|
162
|
-
except subprocess.TimeoutExpired:
|
|
163
|
-
process.kill()
|
|
164
|
-
process.wait(timeout=5)
|
|
162
|
+
relay(final=True)
|
|
165
163
|
finally:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
receiver.close()
|
|
173
|
-
relay(final=True)
|
|
174
|
-
finally:
|
|
175
|
-
for tail, _ in tails:
|
|
176
|
-
tail.close()
|
|
177
|
-
config_path.unlink(missing_ok=True)
|
|
178
|
-
if code:
|
|
179
|
-
error = click.ClickException(f'游戏退出码 {code};日志: {log_path};引擎输出: {engine_log}')
|
|
180
|
-
error.exit_code = code if 0 < code < 256 else 1
|
|
181
|
-
raise error
|
|
182
|
-
return {'application': 'game', 'pid': process.pid, 'state': 'exited', 'exit_code': code,
|
|
183
|
-
'authenticated': False, 'connection_verified': False, 'addons_assembled': False,
|
|
184
|
-
'identity_source': target.auth, 'identity_provided': identity is not None,
|
|
185
|
-
'host': target.host, 'port': target.port, 'log_path': str(log_path),
|
|
186
|
-
'engine_log_path': str(engine_log), 'engine_version': engine.version}
|
|
164
|
+
for stream, _ in tails:
|
|
165
|
+
stream.close()
|
|
166
|
+
if final['state'] == 'failed' or final.get('exit_code', 0):
|
|
167
|
+
raise click.ClickException(final.get('error') or f"游戏退出码 {final['exit_code']};日志: {data['log_path']}")
|
|
168
|
+
result.update(state=final['state'], exit_code=final.get('exit_code', 0))
|
|
169
|
+
return result
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Streaming credential redaction, including secrets split across read boundaries."""
|
|
2
|
-
import codecs
|
|
3
2
|
import threading
|
|
4
3
|
|
|
5
|
-
from .log_protocol import LogDecoder
|
|
4
|
+
from .log_protocol import LogDecoder, TextLogDecoder
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class Redactor:
|
|
@@ -42,23 +41,23 @@ class RedactedDecoder:
|
|
|
42
41
|
|
|
43
42
|
|
|
44
43
|
class EngineLogCapture:
|
|
45
|
-
def __init__(self, stream, path, secrets):
|
|
44
|
+
def __init__(self, stream, path, secrets=()):
|
|
46
45
|
self.stream, self.path, self.secrets = stream, path, secrets
|
|
47
46
|
self.error = None
|
|
48
47
|
self.thread = threading.Thread(target=self._read, daemon=True)
|
|
49
48
|
self.thread.start()
|
|
50
49
|
|
|
51
50
|
def _read(self):
|
|
52
|
-
decoder =
|
|
51
|
+
decoder = TextLogDecoder()
|
|
53
52
|
redactor = Redactor(self.secrets)
|
|
54
53
|
try:
|
|
55
54
|
with open(self.path, 'w', encoding='utf-8', newline='') as output:
|
|
56
55
|
while True:
|
|
57
|
-
chunk = self.stream.read(4096)
|
|
56
|
+
chunk = getattr(self.stream, 'read1', self.stream.read)(4096)
|
|
58
57
|
if not chunk:
|
|
59
|
-
output.write(redactor.feed(decoder.
|
|
58
|
+
output.write(redactor.feed(decoder.finish(), final=True))
|
|
60
59
|
break
|
|
61
|
-
output.write(redactor.feed(decoder.
|
|
60
|
+
output.write(redactor.feed(decoder.feed(chunk)))
|
|
62
61
|
output.flush()
|
|
63
62
|
except (OSError, ValueError) as exc:
|
|
64
63
|
self.error = type(exc).__name__
|
|
@@ -24,7 +24,7 @@ def run(project, session):
|
|
|
24
24
|
auth_context = None
|
|
25
25
|
if data.get('mcs_auth'):
|
|
26
26
|
from .mcs_auth import AuthContext
|
|
27
|
-
from .private_logs import RedactedDecoder
|
|
27
|
+
from .private_logs import RedactedDecoder
|
|
28
28
|
try:
|
|
29
29
|
payload = json.loads(sys.stdin.buffer.read(65536).decode('utf-8'))
|
|
30
30
|
auth_context = AuthContext.parse(payload, data['mcs_pid'])
|
|
@@ -37,17 +37,27 @@ def run(project, session):
|
|
|
37
37
|
receiver.start()
|
|
38
38
|
if (directory / 'stop').exists():
|
|
39
39
|
raise ValueError('启动已取消')
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
if data.get('mode') == 'network':
|
|
41
|
+
from .network import launch_network, ServerTarget
|
|
42
|
+
from .discovery import Engine
|
|
43
|
+
process = launch_network(Engine(**data['network']['engine']),
|
|
44
|
+
ServerTarget(**data['network']['target']),
|
|
45
|
+
directory/'runtime.cppconfig', receiver.port, auth_context)
|
|
46
|
+
success = bool(process)
|
|
47
|
+
else:
|
|
48
|
+
from ..commands.run_cmd import _run_game_with_instance
|
|
49
|
+
with project_scope(project):
|
|
50
|
+
success, process = _run_game_with_instance(
|
|
51
|
+
data['config_path'], data['level_id'], [], wait=False,
|
|
52
|
+
engine_overrides=data['engine_overrides'], no_gui=True, logging_port=receiver.port,
|
|
53
|
+
output_path=str(directory / 'engine.log'), capture_output=True,
|
|
54
|
+
**({'auth_context': auth_context, 'auth_config_path': str(auth_path)} if auth_context else {}))
|
|
55
|
+
if success and process:
|
|
56
|
+
from .private_logs import EngineLogCapture
|
|
57
|
+
engine_capture = EngineLogCapture(process.stdout, directory/'engine.log',
|
|
58
|
+
auth_context.secrets() if auth_context else ())
|
|
47
59
|
if not success or not process or process.poll() is not None:
|
|
48
|
-
raise ValueError('游戏进程未成功启动,详见 worker.log')
|
|
49
|
-
if auth_context:
|
|
50
|
-
engine_capture = EngineLogCapture(process.stdout, directory/'engine.log', secrets)
|
|
60
|
+
raise ValueError('游戏进程未成功启动,详见 worker.log 和 engine.log')
|
|
51
61
|
data.update(state='running', game=identity(process.pid))
|
|
52
62
|
sessions.save(path, data)
|
|
53
63
|
while process.poll() is None:
|
|
@@ -66,10 +76,14 @@ def run(project, session):
|
|
|
66
76
|
try:
|
|
67
77
|
if engine_capture:
|
|
68
78
|
engine_capture.close()
|
|
79
|
+
except OSError as exc:
|
|
80
|
+
data.update(state='failed', error=str(exc))
|
|
69
81
|
finally:
|
|
70
82
|
if receiver:
|
|
71
83
|
receiver.close()
|
|
72
84
|
auth_path.unlink(missing_ok=True)
|
|
85
|
+
if data.get('mode') == 'network':
|
|
86
|
+
(directory/'runtime.cppconfig').unlink(missing_ok=True)
|
|
73
87
|
sessions.save(path, data)
|
|
74
88
|
|
|
75
89
|
|