mcpywrap 0.3.3__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.
Files changed (125) hide show
  1. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/CHANGELOG.md +15 -0
  2. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/MANIFEST.in +5 -0
  3. {mcpywrap-0.3.3/mcpywrap.egg-info → mcpywrap-0.3.5}/PKG-INFO +22 -5
  4. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/README.md +21 -4
  5. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/docs/development.md +1 -0
  6. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/docs/engine-discovery.md +1 -1
  7. mcpywrap-0.3.5/docs/mcs-auth.md +45 -0
  8. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/docs/releasing.md +6 -0
  9. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/__init__.py +1 -1
  10. mcpywrap-0.3.5/mcpywrap/__main__.py +18 -0
  11. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/command_context.py +11 -2
  12. mcpywrap-0.3.5/mcpywrap/commands/connect_cmd.py +22 -0
  13. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/doctor_cmd.py +10 -1
  14. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/run_cmd.py +32 -15
  15. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/ui_cmd.py +3 -2
  16. mcpywrap-0.3.5/mcpywrap/mcstudio/auth_interaction.py +86 -0
  17. mcpywrap-0.3.5/mcpywrap/mcstudio/bridge_assets.py +84 -0
  18. mcpywrap-0.3.5/mcpywrap/mcstudio/bridge_payload/Injector.exe +0 -0
  19. mcpywrap-0.3.5/mcpywrap/mcstudio/bridge_payload/Loader.dll +0 -0
  20. mcpywrap-0.3.5/mcpywrap/mcstudio/bridge_payload/McpyMcsAuth.dll +0 -0
  21. mcpywrap-0.3.5/mcpywrap/mcstudio/bridge_payload/manifest.json +19 -0
  22. mcpywrap-0.3.5/mcpywrap/mcstudio/bridge_payload/publisher.cer +0 -0
  23. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/file_logs.py +3 -2
  24. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/game.py +5 -2
  25. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/log_protocol.py +22 -0
  26. mcpywrap-0.3.5/mcpywrap/mcstudio/mcs_auth.py +196 -0
  27. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/network.py +72 -56
  28. mcpywrap-0.3.5/mcpywrap/mcstudio/private_logs.py +70 -0
  29. mcpywrap-0.3.5/mcpywrap/mcstudio/session_worker.py +91 -0
  30. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/sessions.py +37 -4
  31. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/ui/project_ui.py +32 -9
  32. {mcpywrap-0.3.3 → mcpywrap-0.3.5/mcpywrap.egg-info}/PKG-INFO +22 -5
  33. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap.egg-info/SOURCES.txt +23 -1
  34. mcpywrap-0.3.5/native/mcs_auth/Bridge.cs +97 -0
  35. mcpywrap-0.3.5/native/mcs_auth/Injector.cs +92 -0
  36. mcpywrap-0.3.5/native/mcs_auth/Loader.c +63 -0
  37. mcpywrap-0.3.5/native/mcs_auth/publisher.cer +0 -0
  38. mcpywrap-0.3.5/native/mcs_auth/publisher.json +6 -0
  39. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/pyproject.toml +4 -1
  40. mcpywrap-0.3.5/scripts/build_mcs_auth_bridge.py +89 -0
  41. mcpywrap-0.3.5/scripts/check_mcs_auth_bridge.py +31 -0
  42. mcpywrap-0.3.5/skills/mcpywrap/SKILL.md +98 -0
  43. mcpywrap-0.3.5/skills/mcpywrap/references/troubleshooting.md +49 -0
  44. mcpywrap-0.3.5/skills/mcpywrap/scripts/bootstrap.ps1 +107 -0
  45. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/skills/mcpywrap/scripts/game_window.py +4 -4
  46. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/skills/mcpywrap/scripts/smoke.py +36 -19
  47. mcpywrap-0.3.5/tests/test_auth_interaction.py +171 -0
  48. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/tests/test_engine_discovery.py +2 -1
  49. mcpywrap-0.3.5/tests/test_local_auth_session.py +107 -0
  50. mcpywrap-0.3.5/tests/test_mcs_auth.py +173 -0
  51. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/tests/test_network.py +32 -56
  52. mcpywrap-0.3.5/tests/test_network_sessions.py +101 -0
  53. mcpywrap-0.3.5/tests/test_skill_workflows.py +245 -0
  54. mcpywrap-0.3.3/mcpywrap/__main__.py +0 -13
  55. mcpywrap-0.3.3/mcpywrap/commands/connect_cmd.py +0 -18
  56. mcpywrap-0.3.3/mcpywrap/mcstudio/session_worker.py +0 -55
  57. mcpywrap-0.3.3/skills/mcpywrap/SKILL.md +0 -106
  58. mcpywrap-0.3.3/skills/mcpywrap/references/troubleshooting.md +0 -28
  59. mcpywrap-0.3.3/skills/mcpywrap/scripts/bootstrap.ps1 +0 -65
  60. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/LICENSE +0 -0
  61. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/docs/local-dependencies.md +0 -0
  62. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/builders/AddonsPack.py +0 -0
  63. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/builders/MapPack.py +0 -0
  64. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/builders/__init__.py +0 -0
  65. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/builders/assembly.py +0 -0
  66. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/builders/dependency_manager.py +0 -0
  67. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/builders/file_merge.py +0 -0
  68. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/builders/project_builder.py +0 -0
  69. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/builders/watcher.py +0 -0
  70. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/cli.py +0 -0
  71. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/__init__.py +0 -0
  72. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/add_cmd.py +0 -0
  73. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/build_cmd.py +0 -0
  74. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/default_cmd.py +0 -0
  75. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/dependency_prompt.py +0 -0
  76. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/dev_cmd.py +0 -0
  77. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/edit_cmd.py +0 -0
  78. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/init_cmd.py +0 -0
  79. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/mod_cmd.py +0 -0
  80. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/modsdk_cmd.py +0 -0
  81. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/package_cmd.py +0 -0
  82. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/publish_cmd.py +0 -0
  83. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/remove_cmd.py +0 -0
  84. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/session_cmd.py +0 -0
  85. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/commands/sync_cmd.py +0 -0
  86. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/config.py +0 -0
  87. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/custom_packaging.py +0 -0
  88. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/dependencies.py +0 -0
  89. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/SimpleMonitor.py +0 -0
  90. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/__init__.py +0 -0
  91. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/discovery.py +0 -0
  92. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/editor.py +0 -0
  93. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/mcs.py +0 -0
  94. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/processes.py +0 -0
  95. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/runtime_cppconfig.py +0 -0
  96. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/studio_server.py +0 -0
  97. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/studio_server_ui.py +0 -0
  98. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/symlink_helper_global.py +0 -0
  99. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/symlink_helper_map.py +0 -0
  100. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/mcstudio/symlinks.py +0 -0
  101. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/minecraft/__init__.py +0 -0
  102. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/minecraft/addons.py +0 -0
  103. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/minecraft/level_dat.py +0 -0
  104. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/minecraft/map.py +0 -0
  105. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/minecraft/netease_modsdk.py +0 -0
  106. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/minecraft/template/generate_mod_files.py +0 -0
  107. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/minecraft/template/mod_template.py +0 -0
  108. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/project_init.py +0 -0
  109. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/ui/__init__.py +0 -0
  110. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/utils/__init__.py +0 -0
  111. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/utils/pip_error_parser.py +0 -0
  112. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/utils/print_guide.py +0 -0
  113. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/utils/project_setup.py +0 -0
  114. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap/utils/utils.py +0 -0
  115. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap.egg-info/dependency_links.txt +0 -0
  116. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap.egg-info/entry_points.txt +0 -0
  117. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap.egg-info/requires.txt +0 -0
  118. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/mcpywrap.egg-info/top_level.txt +0 -0
  119. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/setup.cfg +0 -0
  120. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/skills/mcpywrap/agents/openai.yaml +0 -0
  121. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/tests/manual_integration.py +0 -0
  122. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/tests/test_automation.py +0 -0
  123. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/tests/test_game_window_skill.py +0 -0
  124. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/tests/test_local_dependencies.py +0 -0
  125. {mcpywrap-0.3.3 → mcpywrap-0.3.5}/tests/test_package.py +0 -0
@@ -1,5 +1,20 @@
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
+
10
+ ## 0.3.4
11
+
12
+ - 新增显式 `--mcs-auth`:网络连接、单人测试和单人项目 GUI 可按次使用已登录的 MC Studio 身份;默认仍不读取身份,配置文件不能自动启用。
13
+ - 发布包包含自签名桥接、公钥证书和校验清单,无需用户编译;先正常尝试启用,仅在适用的证书信任错误下,经人工确认安装当前用户证书并重试一次。
14
+ - MCS 未运行、取消授权、系统阻止和重试失败均给出用户可理解的提示;Skill 的非交互/JSON 模式全程不弹窗、不安装证书,返回错误代码和处理提示。
15
+ - 单人会话通过管道传递身份,临时配置退出即清理;SDK 和引擎日志在落盘前脱敏,长期世界配置不保存凭据。
16
+ - 已完成真实 MCS 身份读取、网络服手动进服和单人 Mod 双端加载验证。token 自动刷新与运行期认证 RPC 尚未接入;自签名信任不能覆盖 Smart App Control 或组织策略。
17
+
3
18
  ## 0.3.3
4
19
 
5
20
  - 新增实验性 `mcpy connect HOST --port PORT`,无需项目配置或登录身份即可尝试连接服务器。
@@ -1,8 +1,13 @@
1
1
  include LICENSE README.md CHANGELOG.md
2
2
  include docs/releasing.md
3
3
  include docs/local-dependencies.md docs/engine-discovery.md docs/development.md
4
+ include docs/mcs-auth.md
4
5
  recursive-include tests *.py
5
6
  recursive-include skills *.md *.yaml *.ps1 *.py
7
+ include scripts/build_mcs_auth_bridge.py
8
+ include scripts/check_mcs_auth_bridge.py
9
+ recursive-include native/mcs_auth *.cs *.c *.cer *.json
10
+ recursive-include mcpywrap/mcstudio/bridge_payload *.exe *.dll *.cer *.json
6
11
  prune test
7
12
  prune .git
8
13
  global-exclude __pycache__ *.py[cod] *.cppconfig *.log
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcpywrap
3
- Version: 0.3.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,26 @@ host = "192.168.31.101"
140
140
  port = 19132
141
141
  ```
142
142
 
143
- 空目录只需上述配置,无需执行 `mcpy init`;已有 Addon 项目也可添加此表。当前使用未认证连接,不读取登录身份或 token,不保证服务器允许进服;项目依赖仍会校验,但暂不装配本地 Mod。
143
+ 空目录只需上述配置,无需执行 `mcpy init`;已有 Addon 项目也可添加此表。默认使用未认证连接,不读取登录身份或 token,不保证服务器允许进服;项目依赖仍会校验,但暂不装配本地 Mod。
144
144
 
145
- 网络模式前台输出日志并显示日志文件路径,按 Ctrl+C 结束本次游戏。暂不支持 Map 项目、GUI、`--detach`、`--new` 或本地世界实例 ID。
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;进程启动不等于成功进服。
155
+
156
+ ### 使用 MC Studio 登录身份(可选)
157
+
158
+ 单人测试用 `mcpy run --mcs-auth`,网络服用 `mcpy connect <地址> --mcs-auth`;图形管理页可用 `mcpy ui --mcs-auth`。必须先打开并登录 MC Studio,且每次显式加参数,配置文件不会自动启用登录。
159
+
160
+ 发布包自带签名桥接组件,无需编译。程序先尝试启用;若确认为证书信任问题,人工交互模式会说明影响,并在你同意后安装当前用户证书、重试一次。安装证书不保证 Smart App Control 或组织策略放行。`--non-interactive`/`--json` 全程无弹窗、不安装证书,失败返回原因与处理提示。
161
+
162
+ 身份按次读取,日志脱敏,单人世界的长期配置不保存凭据。当前不支持 token 自动刷新和运行期认证代理。信任范围、移除方法及开发构建见[登录组件说明](docs/mcs-auth.md)。
146
163
 
147
164
  ## AI Agent 使用
148
165
 
@@ -155,8 +172,8 @@ port = 19132
155
172
 
156
173
  也可以下载仓库 ZIP,将完整的 `skills/mcpywrap` 文件夹复制到对应 Agent 的技能目录。
157
174
  无需从源码安装 Python 项目;CLI 和 Skill 分别安装,`pip/uv install` 不会自动注册 Skill。
158
- Skill 中的 `scripts/bootstrap.ps1` 可使用已有 uv 安装 CLI,并检查是否具备所需命令能力。
159
- 请使用 mcpywrap 0.3.3 或更高版本;需要固定组合时,可从 `v0.3.3` 标签安装对应 Skill。
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`,实际能力以脚本检测为准。
160
177
 
161
178
  安装后可直接描述任务:
162
179
 
@@ -113,9 +113,26 @@ host = "192.168.31.101"
113
113
  port = 19132
114
114
  ```
115
115
 
116
- 空目录只需上述配置,无需执行 `mcpy init`;已有 Addon 项目也可添加此表。当前使用未认证连接,不读取登录身份或 token,不保证服务器允许进服;项目依赖仍会校验,但暂不装配本地 Mod。
116
+ 空目录只需上述配置,无需执行 `mcpy init`;已有 Addon 项目也可添加此表。默认使用未认证连接,不读取登录身份或 token,不保证服务器允许进服;项目依赖仍会校验,但暂不装配本地 Mod。
117
117
 
118
- 网络模式前台输出日志并显示日志文件路径,按 Ctrl+C 结束本次游戏。暂不支持 Map 项目、GUI、`--detach`、`--new` 或本地世界实例 ID。
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;进程启动不等于成功进服。
128
+
129
+ ### 使用 MC Studio 登录身份(可选)
130
+
131
+ 单人测试用 `mcpy run --mcs-auth`,网络服用 `mcpy connect <地址> --mcs-auth`;图形管理页可用 `mcpy ui --mcs-auth`。必须先打开并登录 MC Studio,且每次显式加参数,配置文件不会自动启用登录。
132
+
133
+ 发布包自带签名桥接组件,无需编译。程序先尝试启用;若确认为证书信任问题,人工交互模式会说明影响,并在你同意后安装当前用户证书、重试一次。安装证书不保证 Smart App Control 或组织策略放行。`--non-interactive`/`--json` 全程无弹窗、不安装证书,失败返回原因与处理提示。
134
+
135
+ 身份按次读取,日志脱敏,单人世界的长期配置不保存凭据。当前不支持 token 自动刷新和运行期认证代理。信任范围、移除方法及开发构建见[登录组件说明](docs/mcs-auth.md)。
119
136
 
120
137
  ## AI Agent 使用
121
138
 
@@ -128,8 +145,8 @@ port = 19132
128
145
 
129
146
  也可以下载仓库 ZIP,将完整的 `skills/mcpywrap` 文件夹复制到对应 Agent 的技能目录。
130
147
  无需从源码安装 Python 项目;CLI 和 Skill 分别安装,`pip/uv install` 不会自动注册 Skill。
131
- Skill 中的 `scripts/bootstrap.ps1` 可使用已有 uv 安装 CLI,并检查是否具备所需命令能力。
132
- 请使用 mcpywrap 0.3.3 或更高版本;需要固定组合时,可从 `v0.3.3` 标签安装对应 Skill。
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`,实际能力以脚本检测为准。
133
150
 
134
151
  安装后可直接描述任务:
135
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
- 本功能不包含第三方网络服 IP/端口连接。
55
+ 以上示例是本地世界会话。网络连接可用 `mcpy connect <地址> --detach --json`,或配置服务器后执行 `run --detach --json`;同样使用返回的项目路径、会话 ID 读取日志、截图、输入和停止。临时 connect 不读取项目引擎配置,使用 CLI 参数或环境变量覆盖。
@@ -0,0 +1,45 @@
1
+ # MC Studio 登录身份
2
+
3
+ 登录始终由 MC Studio 完成。`--mcs-auth` 只在本次启动读取已登录身份,不代填密码,不默认启用,也不会在失败时偷偷改用匿名身份。
4
+
5
+ ```powershell
6
+ mcpy run --mcs-auth
7
+ mcpy connect 192.168.31.101 --port 19132 --mcs-auth
8
+ mcpy ui --mcs-auth
9
+ # Agent:后台单人测试,任何失败都返回 JSON,不弹窗或安装证书
10
+ mcpy --project D:\mods\demo --non-interactive run --mcs-auth --detach --json
11
+ ```
12
+
13
+ 本地与网络均支持 `--detach --json`,共用会话查询、日志和停止。GUI 登录支持单人项目,网络目标使用 CLI。
14
+
15
+ `mcpy doctor --mcs-auth --json` 可只读检查组件文件,不读取身份、不写缓存、不修改证书。组件具备不表示已登录或系统策略允许启用。
16
+
17
+ ## 用户会看到什么
18
+
19
+ - MCS 未运行或未完成登录:提示先打开并登录;普通测试可去掉 `--mcs-auth`。
20
+ - 登录组件可正常启用:直接继续,无需安装证书。
21
+ - 组件被阻止且其完整签名只缺少根信任:人工交互时提供“信任此发布者并重试”,默认选择取消。确认前展示用途、信任范围、发布者、指纹与有效期。
22
+ - 安装后仍失败:停止重试,说明系统或组织策略仍可能阻止组件;不关闭 Windows 安全保护。
23
+ - 纯 CLI/Skill:`--non-interactive` 或 `--json` 不显示对话框、不读取确认输入、不安装证书。JSON 包含 `ok`、`error`、`hint`,身份失败另有 `code`,例如 `studio_unavailable`、`component_blocked`。
24
+
25
+ ## 证书与隐私
26
+
27
+ 组件使用项目自签名证书,不是公共 CA 认证的发布者。证书只在用户明确同意后加入 **当前用户**的“受信任的根证书颁发机构”,不会写入整机证书库。同一证书签署的其他代码也可能因此获得信任;这不是一次性的单文件例外,也不能覆盖 Smart App Control 或企业应用控制。
28
+
29
+ 发布包只包含公共证书和签名文件,不包含私钥。证书指纹与文件 SHA-256 在 `mcpywrap/mcstudio/bridge_payload/manifest.json` 中;加载和证书提示前都会检查组件完整性。
30
+
31
+ 桥接通过当前用户专用的一次性命名管道返回登录信息,不把 token 放入命令行或会话记录。游戏需要的身份仅写入本次运行配置,退出后删除;日志在写入磁盘前脱敏。MCS 必须保持运行;重新登录后应重新启动测试。
32
+
33
+ 停止使用时,去掉 `--mcs-auth` 即可。若曾安装证书,可打开 `certmgr.msc`,在当前用户的“受信任的根证书颁发机构 → 证书”中找到 `mcpywrap Project Code Signing`,核对上述指纹后移除。不要移除其他证书。已加载的托管桥接程序集会随 MC Studio 退出而卸载。
34
+
35
+ ## 开发与发布
36
+
37
+ 源代码位于 `native/mcs_auth`。CI 使用 x86 MSVC 和系统自带的 .NET Framework C# 编译器;本机开发也可使用 x86 TinyCC:
38
+
39
+ ```powershell
40
+ python scripts/build_mcs_auth_bridge.py --cc <x86-tcc.exe> --output <输出目录>
41
+ ```
42
+
43
+ 发布时由 CI 从 GitHub Actions secrets 取得加密 PFX 与密码,在临时 runner 中签署组件并清理私钥。仓库不保存私钥或编译后的组件,只保存源码与公钥校验信息。`scripts/check_mcs_auth_bridge.py` 检查源代码、产物、架构及发布者是否匹配;详见[发布流程](releasing.md)。
44
+
45
+ `MCPY_MCS_BRIDGE_DIR` 可指定开发组件目录;程序不会为任意外部组件提供证书信任安装。发布包的组件会缓存到用户目录的 `.local/share/mcpywrap/mcs-auth-bridge/<版本摘要>`,避免应用目录重定向造成外部终端找不到文件。
@@ -2,6 +2,12 @@
2
2
 
3
3
  主分支和 PR 运行 Windows Python 3.9、3.12、3.14 的自动化测试。通过后构建 wheel/sdist、运行严格元数据检查、检查产物内容,并在独立环境安装 wheel 验证 CLI 和模块导入。
4
4
 
5
+ Windows CI 先用 MSVC 从 `native/mcs_auth` 编译 x86 登录桥接,再签名并通过构建产物交给测试和 Python 打包任务;编译后的 EXE/DLL 不提交到 Git。仓库只保留发布者公钥证书和指纹。
6
+
7
+ 仓库 Actions secrets:`MCS_BRIDGE_SIGNING_PFX` 保存 Base64 编码的加密 PFX,`MCS_BRIDGE_SIGNING_PASSWORD` 保存独立的强密码。只有 main/发布标签的签名步骤可读取它们,PR 只构建未签名测试产物。CI 将证书导入临时 runner 的个人证书库,签名后清理,不安装根信任。私钥、PFX 和密码均不进入日志或 artifact。
8
+
9
+ 轮换签名身份时,需要同时更新这两个 secrets 和 `native/mcs_auth/publisher.cer`、`publisher.json`。脚本会拒绝与仓库固定公钥不匹配的签名证书;未签名产物不能进入 PyPI 发布任务。
10
+
5
11
  发布凭据使用 PyPI Trusted Publishing,不在仓库或工作流中保存长期 API token。在 PyPI 项目 `mcpywrap` 的 Publishing 设置添加 GitHub publisher:
6
12
 
7
13
  | 字段 | 值 |
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """mcpywrap - 将 Python 3 代码转换为 Python 2 代码的工具"""
3
3
 
4
- __version__ = '0.3.3'
4
+ __version__ = '0.3.5'
@@ -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()
@@ -37,6 +37,11 @@ def json_output():
37
37
  return bool(current and current.json_output)
38
38
 
39
39
 
40
+ def human_interaction():
41
+ current = _context.get()
42
+ return current is not None and not non_interactive() and not json_output()
43
+
44
+
40
45
  @contextlib.contextmanager
41
46
  def project_scope(path):
42
47
  previous = _context.get()
@@ -89,7 +94,10 @@ class OperationGroup(click.Group):
89
94
  result = {'ok': False, 'error': '操作已中断', 'hint': None}
90
95
  except (ValueError, OSError) as exc:
91
96
  code = 1
92
- result = {'ok': False, 'error': str(exc), 'hint': '请核对项目配置和相关文件路径。'}
97
+ result = {'ok': False, 'error': str(exc),
98
+ 'hint': getattr(exc, 'hint', '请核对项目配置和相关文件路径。')}
99
+ if getattr(exc, 'code', None):
100
+ result['code'] = exc.code
93
101
  except Exception as exc:
94
102
  code = 1
95
103
  with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', prefix='mcpy-error-',
@@ -103,7 +111,8 @@ class OperationGroup(click.Group):
103
111
  data = {'ok': code == 0, 'error': None, 'hint': None}
104
112
  if isinstance(result, dict):
105
113
  data.update(result)
106
- click.echo(json.dumps(data, ensure_ascii=False), file=output)
114
+ # ASCII JSON survives both GBK consoles and UTF-8 subprocess pipes losslessly.
115
+ click.echo(json.dumps(data, ensure_ascii=True), file=output)
107
116
  elif code:
108
117
  click.echo('错误: ' + str((result or {}).get('error') or '操作失败'), err=True)
109
118
  if (result or {}).get('hint'):
@@ -0,0 +1,22 @@
1
+ """无项目的临时网络连接,可显式使用 MCS 当前身份。"""
2
+ import click
3
+
4
+ from ..command_context import OperationCommand
5
+ from ..mcstudio.discovery import engine_options
6
+ from ..mcstudio.network import ServerTarget, run_network
7
+
8
+
9
+ @click.command(cls=OperationCommand)
10
+ @engine_options
11
+ @click.argument('host')
12
+ @click.option('--port', type=click.IntRange(1, 65535), default=19132, show_default=True)
13
+ @click.option('--mcs-auth', is_flag=True, help='本次连接使用已登录的 MC Studio 身份')
14
+ @click.option('--detach', is_flag=True, help='后台连接并返回游戏会话;JSON 必须指定')
15
+ def connect_cmd(host, port, mcs_auth=False, detach=False, **engine_overrides):
16
+ """尝试连接服务器;前台采集日志,Ctrl+C 结束本次游戏。
17
+
18
+ 默认不读取登录身份;--mcs-auth 要求 MCS 已登录及本机桥接可用。
19
+ 不读取项目配置,不装配本地 Mod,不保证服务器接受连接。
20
+ """
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
- def doctor_cmd(**overrides):
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
@@ -125,7 +125,8 @@ def _build_dependency_tree(node, tree_node):
125
125
 
126
126
 
127
127
  def _run_game_with_instance(config_path, level_id, all_packs, wait=True, log_callback=None,
128
- engine_overrides=None, no_gui=False, logging_port=None, output_path=None):
128
+ engine_overrides=None, no_gui=False, logging_port=None, output_path=None,
129
+ auth_context=None, auth_config_path=None, capture_output=False):
129
130
  """使用指定的实例运行游戏
130
131
 
131
132
  Args:
@@ -141,6 +142,8 @@ def _run_game_with_instance(config_path, level_id, all_packs, wait=True, log_cal
141
142
  from pathlib import Path
142
143
  from ..dependencies import read_project
143
144
  project_dir = str(Path(config_path).resolve().parent.parent)
145
+ if auth_context is not None and (not auth_config_path or not no_gui or wait):
146
+ raise ValueError('带身份的单人测试必须通过会话 worker 启动')
144
147
  config = read_project(project_dir)
145
148
  if 'server' in config.get('tool', {}).get('mcpywrap', {}):
146
149
  # GUI/旧会话也不能将网络目标误启动成本地世界。
@@ -259,13 +262,20 @@ def _run_game_with_instance(config_path, level_id, all_packs, wait=True, log_cal
259
262
  log_message(f"✓ 已创建world_resource_packs.json,包含{len(resource_packs_config)}个资源包", "success")
260
263
 
261
264
  # 启动游戏
265
+ launch_config_path = config_path
266
+ if auth_context is not None:
267
+ auth_context.apply(runtime_config)
268
+ launch_config_path = auth_config_path
269
+ with open(launch_config_path, 'w', encoding='utf-8') as stream:
270
+ json.dump(runtime_config, stream, ensure_ascii=False)
262
271
  logging_port = logging_port if logging_port is not None else _gen_random_port()
263
272
 
264
273
  log_message(f"🚀 正在启动游戏实例: {level_id[:8]}...", "bright_blue")
265
274
 
266
275
  with console.status("启动游戏中...", spinner="dots"):
267
- game_process = open_game(config_path, logging_port=logging_port, wait=False, engine=engine,
268
- **({'output_path': output_path} if output_path else {}))
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 or capture_output else
278
+ {'output_path': output_path} if output_path else {}))
269
279
 
270
280
  if not game_process:
271
281
  log_message("❌ 游戏启动失败", "error")
@@ -331,20 +341,22 @@ def _gen_random_port():
331
341
  @engine_options
332
342
  @click.option("--no-gui", is_flag=True, help="只显示游戏,不打开辅助 GUI")
333
343
  @click.option("--detach", is_flag=True, help="后台运行并返回游戏会话")
344
+ @click.option('--mcs-auth', is_flag=True, help='本次单人测试或网络连接使用已登录的 MC Studio 身份')
334
345
  @click.option('--new', '-n', is_flag=True, help='创建新的游戏实例')
335
346
  @click.option('--list', '-l', is_flag=True, help='列出所有可用的游戏实例')
336
347
  @click.option('--delete', '-d', help='删除指定的游戏实例 (输入实例ID前缀)')
337
348
  @click.option('--force', '-f', is_flag=True, help='强制删除,不提示确认')
338
349
  @click.option('--clean-all', is_flag=True, help='清空所有游戏实例')
339
350
  @click.argument('instance_prefix', required=False)
340
- def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach, **engine_overrides):
351
+ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach, mcs_auth=False, **engine_overrides):
341
352
  """游戏实例运行与管理
342
353
 
343
354
  可直接运行 'mcpy run' 启动最新实例,或使用选项管理实例。
344
355
 
345
- 配置 [tool.mcpywrap.server] host/port 时改为未认证网络连接,
356
+ 配置 [tool.mcpywrap.server] host/port 时改为网络连接;默认未认证,
357
+ 可用 --mcs-auth 显式读取 MCS 身份,单人测试同样支持此选项。
346
358
  不装配本地 Mod,不保证服务器接受连接。网络模式前台采集日志,
347
- Ctrl+C 结束本次游戏,暂不支持 --detach。
359
+ Ctrl+C 结束本次网络游戏;--detach 返回可查询、操作和停止的会话。
348
360
  """
349
361
  base_dir = str(current_project())
350
362
  if (delete or clean_all) and non_interactive() and not force:
@@ -352,6 +364,8 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
352
364
  # 检查项目是否已初始化
353
365
  if not config_exists():
354
366
  require_project()
367
+ if mcs_auth and (list or delete or clean_all):
368
+ raise click.UsageError('--mcs-auth 仅用于网络连接,不用于存档管理')
355
369
 
356
370
  # 在存档选择、配置同步和目录创建之前分流;管理选项仍只管理本地存档。
357
371
  if not (list or delete or clean_all):
@@ -360,13 +374,14 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
360
374
  config = read_project(base_dir)
361
375
  target = configured_target(config)
362
376
  if target is not None:
377
+ if mcs_auth:
378
+ from dataclasses import replace
379
+ target = replace(target, auth='mcs')
363
380
  if new or instance_prefix:
364
381
  raise click.UsageError('网络模式不支持 --new 或本地世界实例 ID')
365
- if detach:
366
- raise click.UsageError('网络模式暂不支持 --detach;请在终端会话中前台运行')
367
382
  packs = prepare_project(base_dir, config)
368
383
  return run_network(target, project_dir=base_dir, packs=packs,
369
- engine_overrides=engine_overrides)
384
+ engine_overrides=engine_overrides, detach=detach)
370
385
 
371
386
  # 确保 map 项目的 setuptools 配置同步
372
387
  ensure_map_setuptools_sync(interactive=False)
@@ -395,6 +410,10 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
395
410
  all_packs = _setup_dependencies(project_name, base_dir)
396
411
  if all_packs is None:
397
412
  raise click.ClickException('依赖校验失败')
413
+ auth_context = None
414
+ if mcs_auth:
415
+ from ..mcstudio.mcs_auth import acquire_identity
416
+ auth_context = acquire_identity()
398
417
 
399
418
  # 确定要使用的实例
400
419
  config_path = None
@@ -444,14 +463,12 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
444
463
  from ..command_context import json_output
445
464
  if json_output() and not detach:
446
465
  raise click.UsageError('run --json 需要 --detach,避免等待游戏退出')
447
- if no_gui or detach or non_interactive():
466
+ if no_gui or detach or non_interactive() or mcs_auth:
448
467
  from ..mcstudio import sessions
449
468
  import time
450
- data = sessions.start(base_dir, config_path, level_id, engine_overrides)
451
- result = {'application': 'game', 'project': base_dir, 'session': data['session'],
452
- 'state': data['state'], **data['game'], 'log_path': data['log_path'],
453
- 'engine_log_path': data.get('engine_log_path'),
454
- 'window_title_hint': 'Minecraft', 'window_verified': False}
469
+ data = sessions.start(base_dir, config_path, level_id, engine_overrides,
470
+ **({'auth_context': auth_context} if auth_context is not None else {}))
471
+ result = sessions.handoff(data)
455
472
  if detach:
456
473
  return result
457
474
  click.echo('会话: ' + data['session'] + ';日志: ' + data['log_path'])
@@ -10,7 +10,8 @@ console = Console()
10
10
 
11
11
 
12
12
  @click.command(cls=OperationCommand)
13
- def ui_cmd():
13
+ @click.option('--mcs-auth', is_flag=True, help='本次 GUI 单人测试使用已登录的 MC Studio 身份')
14
+ def ui_cmd(mcs_auth=False):
14
15
  """启动图形界面"""
15
16
  from ..command_context import json_output
16
17
  if non_interactive() or json_output():
@@ -25,5 +26,5 @@ def ui_cmd():
25
26
 
26
27
  from ..ui.project_ui import show_run_ui
27
28
  # 显示图形界面
28
- show_run_ui(str(current_project()))
29
+ show_run_ui(str(current_project()), mcs_auth=mcs_auth)
29
30
 
@@ -0,0 +1,86 @@
1
+ """Human-only recovery for the optional identity feature; no silent certificate trust."""
2
+ import json
3
+ import os
4
+ from pathlib import Path
5
+ import subprocess
6
+
7
+
8
+ def _powershell_json(script, directory):
9
+ environment = dict(os.environ, MCPY_BRIDGE_CHECK=str(directory))
10
+ environment = {k: v for k, v in environment.items() if k.casefold() != 'psmodulepath'}
11
+ powershell = str(Path(os.environ.get('WINDIR', r'C:\Windows'))/'System32/WindowsPowerShell/v1.0/powershell.exe')
12
+ result = subprocess.run([powershell, '-NoProfile', '-Command', script],
13
+ env=environment, capture_output=True, timeout=20,
14
+ creationflags=subprocess.CREATE_NO_WINDOW)
15
+ if result.returncode:
16
+ return None
17
+ try:
18
+ return json.loads(result.stdout.decode('utf-8-sig'))
19
+ except (ValueError, UnicodeError):
20
+ return None
21
+
22
+
23
+ def certificate_can_help(directory, certificate):
24
+ """Offer recovery only for intact signatures terminating at an untrusted root."""
25
+ script = r'''
26
+ [Console]::OutputEncoding=[Text.UTF8Encoding]::new($false)
27
+ $items=@()
28
+ foreach($name in @('Injector.exe','Loader.dll','McpyMcsAuth.dll')) {
29
+ $sig=Get-AuthenticodeSignature -LiteralPath (Join-Path $env:MCPY_BRIDGE_CHECK $name)
30
+ $untrusted=$false
31
+ if($sig.SignerCertificate) {
32
+ $chain=New-Object Security.Cryptography.X509Certificates.X509Chain
33
+ $chain.ChainPolicy.RevocationMode='NoCheck'
34
+ $valid=$chain.Build($sig.SignerCertificate)
35
+ $states=@($chain.ChainStatus | ForEach-Object {$_.Status.ToString()})
36
+ $untrusted=(!$valid -and $states.Count -eq 1 -and $states[0] -eq 'UntrustedRoot')
37
+ }
38
+ $items+=@{status=$sig.Status.ToString();thumbprint=$sig.SignerCertificate.Thumbprint;untrusted_root=$untrusted}
39
+ }
40
+ ConvertTo-Json -InputObject $items -Compress
41
+ '''
42
+ try:
43
+ result = _powershell_json(script, directory)
44
+ except (OSError, subprocess.TimeoutExpired):
45
+ return False
46
+ return isinstance(result, list) and len(result) == 3 and all(
47
+ item.get('thumbprint') == certificate['thumbprint'] and item.get('untrusted_root') and
48
+ item.get('status') in ('UnknownError', 'NotTrusted') for item in result)
49
+
50
+
51
+ def confirm_certificate(certificate):
52
+ from PyQt5.QtWidgets import QApplication, QMessageBox
53
+ app = QApplication.instance() or QApplication([])
54
+ dialog = QMessageBox()
55
+ dialog.setWindowTitle('启用 MC Studio 登录身份')
56
+ dialog.setIcon(QMessageBox.Warning)
57
+ dialog.setText('Windows 尚未信任 mcpywrap 登录组件的发布者。')
58
+ dialog.setInformativeText(
59
+ '此组件会读取你在 MC Studio 中已登录的身份,用于启动本次游戏。\n\n'
60
+ '你可以将发布者证书加入当前 Windows 用户的信任列表,再试一次。'
61
+ '这也会信任同一证书签署的其他程序,并非只允许本次运行。\n\n'
62
+ '部分电脑仍会受智能应用控制或组织策略限制,安装证书不保证能够启用。'
63
+ '取消后仍可去掉 --mcs-auth 进行普通测试。')
64
+ dialog.setDetailedText('发布者:'+certificate['subject']+'\n证书指纹:'+certificate['thumbprint']+
65
+ '\n有效期至:'+certificate['expires']+
66
+ '\n仅安装公钥证书,不安装私钥,不关闭 Windows 安全保护。')
67
+ accept = dialog.addButton('信任此发布者并重试', QMessageBox.AcceptRole)
68
+ cancel = dialog.addButton('暂不启用', QMessageBox.RejectRole)
69
+ dialog.setDefaultButton(cancel)
70
+ dialog.exec_()
71
+ return dialog.clickedButton() is accept
72
+
73
+
74
+ def install_certificate(certificate):
75
+ # Called only after the explicit consent above. No system-wide store or elevation.
76
+ command = str(Path(os.environ.get('WINDIR', r'C:\Windows'))/'System32/certutil.exe')
77
+ result = subprocess.run([command, '-user', '-addstore', 'Root', certificate['path']],
78
+ capture_output=True, timeout=30, creationflags=subprocess.CREATE_NO_WINDOW)
79
+ if result.returncode:
80
+ raise ValueError('证书未能安装。可能缺少权限或被组织策略限制;你仍可不带 --mcs-auth 进行普通测试。')
81
+
82
+
83
+ def show_failure(message):
84
+ from PyQt5.QtWidgets import QApplication, QMessageBox
85
+ app = QApplication.instance() or QApplication([])
86
+ QMessageBox.warning(None, '暂时无法使用 MC Studio 登录身份', message)
@@ -0,0 +1,84 @@
1
+ """Install only the bridge payload shipped with this package, without changing trust."""
2
+ import hashlib
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ import shutil
7
+
8
+ BINARIES = ('Injector.exe', 'Loader.dll', 'McpyMcsAuth.dll')
9
+ PAYLOAD = Path(__file__).with_name('bridge_payload')
10
+
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
+
36
+ def digest(path):
37
+ return hashlib.sha256(path.read_bytes()).hexdigest()
38
+
39
+
40
+ def payload_manifest():
41
+ manifest = json.loads((PAYLOAD/'manifest.json').read_text(encoding='utf-8'))
42
+ required = set(BINARIES) | {'publisher.cer'}
43
+ if set(manifest['files']) != required:
44
+ raise ValueError('登录组件包不完整,请重新安装 mcpywrap。')
45
+ for name, expected in manifest['files'].items():
46
+ if digest(PAYLOAD/name) != expected:
47
+ raise ValueError('登录组件校验失败,请从项目的正式发布重新安装 mcpywrap。')
48
+ return manifest
49
+
50
+
51
+ def bridge_directory():
52
+ override = os.environ.get('MCPY_MCS_BRIDGE_DIR')
53
+ if override:
54
+ directory = Path(override).expanduser().resolve()
55
+ elif (PAYLOAD/'manifest.json').is_file():
56
+ manifest = payload_manifest()
57
+ version = digest(PAYLOAD/'manifest.json')[:16]
58
+ directory = Path.home()/'.local/share/mcpywrap/mcs-auth-bridge'/version
59
+ directory.mkdir(parents=True, exist_ok=True)
60
+ for name, expected in manifest['files'].items():
61
+ target = directory/name
62
+ if not target.is_file():
63
+ temporary = target.with_suffix(target.suffix+'.tmp')
64
+ shutil.copyfile(PAYLOAD/name, temporary)
65
+ os.replace(temporary, target)
66
+ if digest(target) != expected:
67
+ raise ValueError('本机登录组件已被更改,请删除组件缓存后重新运行。')
68
+ else:
69
+ directory = Path.home()/'.local/share/mcpywrap/mcs-auth-bridge'
70
+ if not all((directory/name).is_file() for name in BINARIES):
71
+ raise ValueError('此安装缺少登录组件,请重新安装正式版;源码开发者可运行 scripts/build_mcs_auth_bridge.py。')
72
+ return directory
73
+
74
+
75
+ def trusted_certificate_candidate(directory):
76
+ """Never offer to trust certificates supplied by an arbitrary developer override."""
77
+ try:
78
+ manifest = payload_manifest()
79
+ if any(digest(directory/name) != expected for name, expected in manifest['files'].items()):
80
+ return None
81
+ return {'path': str(directory/'publisher.cer'), 'thumbprint': manifest['thumbprint'],
82
+ 'subject': manifest['subject'], 'expires': manifest['expires']}
83
+ except (OSError, ValueError, KeyError):
84
+ return None