mcpywrap 0.5.2__tar.gz → 0.5.4__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.5.2 → mcpywrap-0.5.4}/CHANGELOG.md +10 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/PKG-INFO +1 -1
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/game-backends.md +9 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/__init__.py +1 -1
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/package_cmd.py +16 -1
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/script_cmd.py +2 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/macos.py +1 -1
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/bridge_payload/Injector.exe +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/bridge_payload/Loader.dll +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/bridge_payload/McpyMcsAuth.dll +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/bridge_payload/manifest.json +3 -3
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/window_capture/manifest.json +1 -1
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/window_capture/mcpy-window-capture.exe +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/project_scripts.py +11 -5
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/remote/service.py +1 -1
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/profiles.py +59 -5
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap.egg-info/PKG-INFO +1 -1
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/pyproject.toml +1 -1
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/dual-clients.md +2 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/project-scripts.md +19 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_macos_network.py +16 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_project_scripts.py +22 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_runtime_profiles.py +57 -1
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/CONTRIBUTING.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/LICENSE +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/MANIFEST.in +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/README.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/RELEASING.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/THIRD_PARTY_NOTICES.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/code-libraries.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/engine-discovery.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/git-dependencies.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/input-sequence.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/launcher-client-python.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/local-dependencies.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/macos-resource-reload.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/mcs-auth.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/project-scripts.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/qt6-development.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/qumod.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/runtime-debug.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/runtime-input.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/runtime-player.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/runtime-ui.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/docs/shared-log-ui.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/examples/consumer_prepare/README.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/__main__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/_script_worker.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/builders/AddonsPack.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/builders/MapPack.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/builders/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/builders/assembly.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/builders/dependency_manager.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/builders/file_merge.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/builders/project_builder.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/builders/watcher.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/cli.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/code_libraries.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/command_context.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/add_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/build_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/connect_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/default_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/dependency_prompt.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/desktop_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/dev_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/doctor_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/edit_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/engine_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/init_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/mod_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/modsdk_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/publish_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/record_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/remove_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/run_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/runtime_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/runtime_input_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/runtime_player_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/runtime_ui_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/serve_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/session_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/sync_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/commands/ui_cmd.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/config.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/consumer_prepare.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/custom_packaging.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/dependencies.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/android_icon.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/apk_profile.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/backend.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/host.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/install.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/macos_icons.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/macos_network.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/official.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/preferences.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/tui.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/engines/windows.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/framework_presets.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/frameworks.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/git_cache.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/git_projects.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/input_executor.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/input_plan.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/SimpleMonitor.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/auth_interaction.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/bridge_assets.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/bridge_payload/publisher.cer +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/deployment_links.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/diagnostics.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/discovery.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/editor.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/file_logs.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/game.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/host_input.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/hot_reload.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/image.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/input_sequence.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/launcher_control.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/launcher_python.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/log_colorizer.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/log_protocol.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/mcs.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/mcs_auth.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/network.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/private_logs.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/processes.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/recording_worker.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/recordings.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/runtime_cppconfig.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/runtime_debug.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/runtime_input_payload.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/runtime_key_timeline_payload.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/runtime_player_payload.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/runtime_ui.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/runtime_ui_outline.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/runtime_ui_payload.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/screenshot.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/session_capabilities.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/session_worker.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/sessions.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/studio_server.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/studio_server_ui.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/symlink_helper_global.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/symlink_helper_map.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/symlinks.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/unified_input.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/mcstudio/window.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/minecraft/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/minecraft/addons.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/minecraft/level_dat.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/minecraft/map.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/minecraft/netease_modsdk.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/minecraft/template/generate_mod_files.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/minecraft/template/mod_template.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/prepare_environment.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/project_dependencies.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/project_init.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/recording_io.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/remote/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/remote/client.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/remote/http_server.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/deployment.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/groups.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/instances.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/native.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/occupancy.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/preparation.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/storage.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/targets.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/watch.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/runtime/windows.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/source_files.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/timeline.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/application.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/assets/mcpy.icns +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/assets/mcpy.icon/Assets/01-cube.svg +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/assets/mcpy.icon/Assets/02-prompt.svg +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/assets/mcpy.icon/icon.json +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/assets/mcpy.png +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/engine_setup.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/log_view.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/log_window.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/project_ui.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/run_session.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/session_controller.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/session_tools.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/session_views.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/ui/world_config.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/utils/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/utils/locks.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/utils/paths.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/utils/pip_error_parser.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/utils/print_guide.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/utils/project_setup.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap/utils/utils.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap.egg-info/SOURCES.txt +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap.egg-info/dependency_links.txt +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap.egg-info/entry_points.txt +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap.egg-info/requires.txt +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/mcpywrap.egg-info/top_level.txt +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/mcs_auth/Bridge.cs +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/mcs_auth/Injector.cs +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/mcs_auth/Loader.c +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/mcs_auth/publisher.cer +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/mcs_auth/publisher.json +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/runtime_input/README.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/runtime_input/launcher-monotonic.patch +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/runtime_input/manifest-monotonic.patch +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/runtime_input/steady_clock.h +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/window_capture/video_capture.h +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/native/window_capture/window_capture.cpp +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/scripts/build_all_native.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/scripts/build_app_icon.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/scripts/build_mcs_auth_bridge.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/scripts/build_window_capture.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/scripts/check_mcs_auth_bridge.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/scripts/check_release.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/scripts/check_window_capture.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/setup.cfg +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/SKILL.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/agents/openai.yaml +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/framework/behavior_pack/CatalogRuntime/__init__.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/framework/behavior_pack/manifest.json +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/framework/build_tools/example.toml +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/framework/build_tools/prepare.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/framework/build_tools/schema.json +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/framework/pyproject.toml +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/game/.gitignore +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/game/behavior_pack/manifest.json +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/game/data/items.json +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/game/pyproject.toml +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/assets/consumer-prepare/game/resource_pack/manifest.json +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/capture.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/consumer-prepare-author.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/consumer-prepare.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/input-sequence.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/input-writing.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/instance-world-settings.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/local-development.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/macos-local.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/network-sessions.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/project-dependencies.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/project-workflow.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/remote-testing.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/runtime-debug.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/runtime-input.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/runtime-key-timeline.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/runtime-player.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/runtime-ui.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/script-example.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/setup.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/references/troubleshooting.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/scripts/bootstrap.ps1 +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/scripts/bootstrap.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/scripts/game_window.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/skills/mcpywrap/scripts/smoke.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/README.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/consumer_prepare_acceptance.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/lifecycle_dependency_audit.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/runtime_controls/README.md +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/runtime_controls/client_probe.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/runtime_controls/input_probe.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/runtime_controls/key_timeline_probe.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/runtime_controls/movement.json +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/runtime_controls/run.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/runtime_controls/server_fixture.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual/runtime_controls/support.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_dependency_boundaries.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_input_window.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_integration.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_native_recording.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_remote_recording.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_runtime_input.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_runtime_input_unified.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_runtime_key_timeline.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_runtime_player.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/manual_runtime_ui.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/runtime_support.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_application_icons.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_auth_interaction.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_automation.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_build_replace_retry.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_code_libraries.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_consumer_prepare.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_dependency_boundaries.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_engine_backends.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_engine_compatibility.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_engine_discovery.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_engine_install.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_game_window_skill.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_git_projects.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_host_input.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_input_sequence.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_instance_cppconfig.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_launch_encoding.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_launcher_python.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_lifecycle_audit.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_lifecycle_races.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_local_auth_session.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_local_dependencies.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_manual_runtime_controls.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_mcs_auth.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_mouse.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_native_input_sequence.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_native_preferences.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_native_recording.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_network.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_network_sessions.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_pack_uuid_conflicts.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_package.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_qt_sessions.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_qumod_workflow.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_recordings.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_remote.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_remote_scripts.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_resource_reload.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_runtime_debug.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_runtime_deployment.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_runtime_groups.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_runtime_input.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_runtime_key_timeline.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_runtime_player.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_runtime_ui.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_runtime_ui_outline.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_screenshot.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_session_capabilities.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_shared_logs.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_skill_workflows.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_unified_skill_contract.py +0 -0
- {mcpywrap-0.5.2 → mcpywrap-0.5.4}/tests/test_window_focus.py +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.5.4
|
|
4
|
+
|
|
5
|
+
- 修复 macOS 网络会话启动时的状态保存路径:在启动游戏前,将 `prepared` 状态写入所属会话目录,避免会话记录未正确更新。
|
|
6
|
+
|
|
7
|
+
## 0.5.3
|
|
8
|
+
|
|
9
|
+
- 修复 Windows 双客户端收到 Addon 后入口导入失败:为每个角色使用独立浅层游戏资料目录,兼容网易 3.9 下载包加载器。旧资料复制迁移并保留备份;保留归属、占用锁及缓存版本检查,迁移中断或资料缺失时拒绝静默回退。
|
|
10
|
+
- 新增 `before_archive` 打包阶段及 `MCPY_PACKAGE_ROOT`,允许在依赖组装完成后处理隔离的发布副本;不裁剪开发源码或运行目录。禁止改动包 manifest 和引入文件链接,处理失败时保留原 ZIP。
|
|
11
|
+
- 压缩前链接检查共用跨 Python 版本实现,Python 3.9 同样识别 Windows junction。
|
|
12
|
+
|
|
3
13
|
## 0.5.2
|
|
4
14
|
|
|
5
15
|
- Addon 的本地/Git 依赖可声明 `consumer_prepare`,使用同一依赖版本的工具处理消费项目素材;build、package 和实际本地新启动自动准备,支持传递范围、来源去重及显式停用,无需消费者重复绑定 hooks。
|
|
@@ -25,6 +25,15 @@ macOS 支持离线 Addon 世界;无认证服务器连接需 CLI 0.4.3+ 与运
|
|
|
25
25
|
|
|
26
26
|
## AI 与纯命令控制
|
|
27
27
|
|
|
28
|
+
### Windows 多客户端资料路径
|
|
29
|
+
|
|
30
|
+
Windows 联调成员的生命周期记录、角色归属及占用锁仍位于项目 `.runtime/groups`;游戏实际使用的独立 profile 位于项目所在磁盘的 `mcpy-profiles/<摘要>`。摘要包含项目角色路径及宿主用户目录,不复用用户真实游戏资料;会话的 `network.windows_profile` 与角色目录中的 `windows-profile.json` 记录实际位置。普通单客户端、macOS 不改变布局。
|
|
31
|
+
|
|
32
|
+
这是 ModPC 3.9 下载包加载器的路径兼容处理:标准 `.../AppData/Roaming/MinecraftPE_Netease/packcache/<包>/` 有固定层数,较深的项目目录会被拼进 Python 模块名。目录链接会被引擎解析为实际路径,不能修复问题;仅覆盖 APPDATA 也不能替代 Windows 按 USERPROFILE 查询目录。使用真实浅层 profile,不修改引擎、sys.path 或 Addon 入口。
|
|
33
|
+
|
|
34
|
+
首次迁移在角色占用锁内复制旧 AppData 到临时目录,完成归属标记后原子改名,原副本保留。后续启动复用新位置;目标缺失、归属不符或遗留未完成迁移时停止,不回退到旧身份。旧资料的长路径用宿主扩展路径形式复制,传给游戏的仍是标准短路径。需要项目所在磁盘根目录允许创建 `mcpy-profiles`;不支持 UNC 或被替换为目录链接的位置,不自动提权。用户搬迁项目时需保留组记录及其指向的 profile,不能只把外部目录当缓存删除。
|
|
35
|
+
|
|
36
|
+
|
|
28
37
|
Windows/macOS 共同基线是 `--local --project <目录> --non-interactive run --no-gui --detach --json`,后续统一 status/logs/runtime py/runtime reload/stop。控制端不打开 Qt、小窗或 TUI,游戏本身仍需要显示服务及原生窗口。完整操作顺序维护在 [技能的公共流程](../skills/mcpywrap/references/local-development.md)。
|
|
29
38
|
|
|
30
39
|
`runtime capabilities --session <id> --json` 返回 schema_version=1:control 描述控制端要求,python 提供执行端侧与 worker 队列声明,reload 按同一后端限制返回 available/unsupported/unavailable。查询不执行游戏代码、不返回控制凭据,available 只说明允许调用。旧 worker 缺少队列声明时返回 null,不能推断支持。Windows 已知 Shader 限制也由 WindowsBackend 提供,实际重载与查询没有两份规则。
|
|
@@ -10,6 +10,7 @@ from ..command_context import OperationCommand, project_dir as current_project,
|
|
|
10
10
|
from ..config import config_exists
|
|
11
11
|
from ..dependencies import DependencyError, read_project
|
|
12
12
|
from ..builders.project_builder import AddonProjectBuilder, MapProjectBuilder
|
|
13
|
+
from ..utils.paths import is_link
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
@click.command(cls=OperationCommand)
|
|
@@ -35,7 +36,7 @@ def package_cmd(merge):
|
|
|
35
36
|
if project_type not in ('addon', 'map'):
|
|
36
37
|
raise ValueError('当前仅支持 Addon 和 Map 项目的打包')
|
|
37
38
|
|
|
38
|
-
from ..project_scripts import before, configured, validate_build
|
|
39
|
+
from ..project_scripts import before, configured, validate_build, operation
|
|
39
40
|
from ..consumer_prepare import PreparationPlan
|
|
40
41
|
plan = PreparationPlan(source)
|
|
41
42
|
if configured(source):
|
|
@@ -52,6 +53,15 @@ def package_cmd(merge):
|
|
|
52
53
|
success, error = builder.build()
|
|
53
54
|
if not success:
|
|
54
55
|
raise click.ClickException(f'构建失败: {error}')
|
|
56
|
+
# 依赖组装完成后,仅将隔离副本提供给压缩前处理器。
|
|
57
|
+
identity = _package_manifests(output)
|
|
58
|
+
with operation(source, 'before_archive', lock_held=True, after_task=plan.check,
|
|
59
|
+
package_root=output) as finalizers:
|
|
60
|
+
scripts.extend(finalizers)
|
|
61
|
+
if identity != _package_manifests(output):
|
|
62
|
+
raise ValueError('压缩前处理不得修改包 manifest 或包身份')
|
|
63
|
+
if any(is_link(p) for p in output.rglob('*')):
|
|
64
|
+
raise ValueError('压缩前处理不能在产物中引入目录或文件链接')
|
|
55
65
|
archive = Path(temporary) / 'package.zip'
|
|
56
66
|
with zipfile.ZipFile(archive, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
57
67
|
if project_type == 'addon':
|
|
@@ -77,6 +87,11 @@ def package_cmd(merge):
|
|
|
77
87
|
return {'artifact': str(destination), **({'scripts': scripts} if scripts else {})}
|
|
78
88
|
|
|
79
89
|
|
|
90
|
+
def _package_manifests(root):
|
|
91
|
+
return {str(p.relative_to(root)): p.read_bytes() for p in root.rglob('*')
|
|
92
|
+
if p.is_file() and p.name in ('manifest.json', 'pack_manifest.json')}
|
|
93
|
+
|
|
94
|
+
|
|
80
95
|
def _zip_dir_keep_empty(zipf, src_dir, dst_root):
|
|
81
96
|
"""保留构建产物的空目录,ZIP 路径统一使用相对路径和正斜杠。"""
|
|
82
97
|
source = Path(src_dir)
|
|
@@ -27,6 +27,8 @@ def list_cmd(effective=False):
|
|
|
27
27
|
result['execution_order'] = {event: [{'kind': 'root', 'name': name} for name in hooks.get(event, [])] +
|
|
28
28
|
[{'kind': 'consumer_prepare', 'provider': p['id'], 'name': p['name']}
|
|
29
29
|
for p in plan.enabled] for event in EVENTS}
|
|
30
|
+
result['execution_order']['before_archive'] = [
|
|
31
|
+
{'kind': 'root', 'name': name} for name in hooks.get('before_archive', [])]
|
|
30
32
|
if json_output():
|
|
31
33
|
return result
|
|
32
34
|
for script in result['scripts']:
|
|
@@ -380,7 +380,7 @@ else:
|
|
|
380
380
|
install.home()/'preferences/macos').prepare()
|
|
381
381
|
sessions.require_launch_requested(data['project'], data['session'])
|
|
382
382
|
data['phase'] = 'prepared'
|
|
383
|
-
sessions.save(
|
|
383
|
+
sessions.save(sessions.session_path(data['project'], data['session'])/'session.json', data)
|
|
384
384
|
return LaunchedGame(launch_session(data), capture_output=False, guard=guard)
|
|
385
385
|
except BaseException:
|
|
386
386
|
guard.close()
|
|
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": "386c7bb1972dcba1e5d4eb86709bc7d0576d789266805057cbc9d498e62dac10",
|
|
15
|
+
"Loader.dll": "aac19d20010d3ecc2af156688a795be7337a21e5264b4308d66deba2a16b1725",
|
|
16
|
+
"McpyMcsAuth.dll": "caed744e904c86727de5b2176e9aef0bb28002334188877f6746cc882a83bdbd",
|
|
17
17
|
"publisher.cer": "e5aa2ac39df40804ed8b08fe8e43c4a2b8923f0d02a360708486b2db5b6303d7"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"executable_sha256": "
|
|
2
|
+
"executable_sha256": "b9caca51171c908f4dca623ebe498ec06ed345427e80e89cc8917d19f117adc8",
|
|
3
3
|
"source_sha256": "0c0b2a02bfecea68b824a486ac1d88cde6bd9fc8ee8e4c9a6939870bd8e710d1",
|
|
4
4
|
"sources": {
|
|
5
5
|
"video_capture.h": "8daee1b8c1c8273bc8d19d39f0559f82c30d7ff900894bcc2cc197d3881532d7",
|
|
Binary file
|
|
@@ -17,7 +17,7 @@ from .dependencies import read_project
|
|
|
17
17
|
from .runtime import storage
|
|
18
18
|
from .utils.locks import directory_lock
|
|
19
19
|
|
|
20
|
-
EVENTS = ('before_build', 'before_package', 'before_run')
|
|
20
|
+
EVENTS = ('before_build', 'before_package', 'before_run', 'before_archive')
|
|
21
21
|
CHAIN_ENV = '_MCPY_SCRIPT_CHAIN'
|
|
22
22
|
|
|
23
23
|
|
|
@@ -112,12 +112,14 @@ def _active_process(record):
|
|
|
112
112
|
|
|
113
113
|
@contextmanager
|
|
114
114
|
def operation(project, event=None, *, task=None, args=(), cancel=None, progress=None, lock_held=False,
|
|
115
|
-
after_task=None):
|
|
115
|
+
after_task=None, package_root=None):
|
|
116
116
|
"""Hold the cooperative lock through the caller's consumption of generated files."""
|
|
117
117
|
project = Path(project).resolve()
|
|
118
118
|
reject_recursion()
|
|
119
119
|
scripts, hooks = configuration(project)
|
|
120
120
|
names = [task] if task is not None else hooks.get(event, [])
|
|
121
|
+
if event == 'before_archive' and names and package_root is None:
|
|
122
|
+
raise ScriptError('压缩前处理必须由 package 提供临时目录。', 'script_context_missing')
|
|
121
123
|
if any(name not in scripts for name in names):
|
|
122
124
|
raise ScriptError('未定义脚本: ' + str(task), 'script_not_found')
|
|
123
125
|
directory = project/'.mcpy/scripts'
|
|
@@ -131,7 +133,7 @@ def operation(project, event=None, *, task=None, args=(), cancel=None, progress=
|
|
|
131
133
|
if cancel and cancel():
|
|
132
134
|
raise ScriptError('脚本执行已取消', 'script_cancelled', script=name, event=event)
|
|
133
135
|
results.append(_execute(project, name, scripts[name], event or 'manual', args,
|
|
134
|
-
cancel=cancel, progress=progress))
|
|
136
|
+
cancel=cancel, progress=progress, package_root=package_root))
|
|
135
137
|
if after_task:
|
|
136
138
|
after_task()
|
|
137
139
|
if cancel and cancel():
|
|
@@ -205,13 +207,17 @@ def startup_hooks(data, deployment):
|
|
|
205
207
|
deployment.preparation_plan = plan
|
|
206
208
|
|
|
207
209
|
|
|
208
|
-
def _execute(project, name, spec, event, extra, *, cancel=None, progress=None):
|
|
210
|
+
def _execute(project, name, spec, event, extra, *, cancel=None, progress=None, package_root=None):
|
|
209
211
|
env = dict(os.environ)
|
|
212
|
+
# 临时打包目录只能由本次 package 注入,不能沿用父进程上下文。
|
|
213
|
+
env.pop('MCPY_PACKAGE_ROOT', None)
|
|
210
214
|
env.update({key.upper() if os.name == 'nt' else key: value for key, value in spec['env'].items()})
|
|
211
215
|
env.update({CHAIN_ENV: '1', 'MCPY_PROJECT_ROOT': str(project), 'MCPY_EVENT': event,
|
|
212
216
|
'MCPY_OPERATION': {'before_build': 'build', 'before_package': 'package',
|
|
213
|
-
'before_run': 'run'}.get(event, 'script'),
|
|
217
|
+
'before_run': 'run', 'before_archive': 'package'}.get(event, 'script'),
|
|
214
218
|
'PYTHONIOENCODING': 'utf-8', 'PYTHONUNBUFFERED': '1'})
|
|
219
|
+
if package_root is not None:
|
|
220
|
+
env['MCPY_PACKAGE_ROOT'] = str(Path(package_root).resolve())
|
|
215
221
|
command = list(spec['command']) + list(extra)
|
|
216
222
|
if spec['python'] and not Path(command[2]).is_file():
|
|
217
223
|
raise ScriptError('脚本文件不存在: ' + command[2], 'script_not_found', script=name, event=event)
|
|
@@ -53,7 +53,7 @@ def capabilities():
|
|
|
53
53
|
from ..engines.host import describe
|
|
54
54
|
backend = get_backend()
|
|
55
55
|
result['host'] = describe()
|
|
56
|
-
result['local_capabilities'] = ['project-scripts', 'consumer-prepare']
|
|
56
|
+
result['local_capabilities'] = ['project-scripts', 'consumer-prepare', 'package-finalize']
|
|
57
57
|
result['capabilities'] = list(dict.fromkeys(result['capabilities'] + list(backend.capabilities)))
|
|
58
58
|
if backend.managed_install:
|
|
59
59
|
result['runtime'] = backend.diagnose()
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from ..utils.paths import is_link
|
|
4
4
|
import json
|
|
5
|
+
import hashlib
|
|
5
6
|
import os
|
|
6
7
|
import subprocess
|
|
7
8
|
import sys
|
|
@@ -64,12 +65,13 @@ class PlayerProfile:
|
|
|
64
65
|
self.data['network']['profile'] = str(self.root)
|
|
65
66
|
|
|
66
67
|
def windows_environment(self):
|
|
67
|
-
|
|
68
|
+
profile = self.windows_directory()
|
|
69
|
+
roaming, local = profile/'AppData/Roaming', profile/'AppData/Local'
|
|
68
70
|
for path in (roaming, local):
|
|
69
|
-
if not path.resolve().is_relative_to(
|
|
71
|
+
if not path.resolve().is_relative_to(profile.resolve()):
|
|
70
72
|
raise EngineError('客户端系统目录指向了外部位置。', 'profile_modified')
|
|
71
73
|
path.mkdir(parents=True, exist_ok=True)
|
|
72
|
-
environment = dict(os.environ, USERPROFILE=str(
|
|
74
|
+
environment = dict(os.environ, USERPROFILE=str(profile), APPDATA=str(roaming), LOCALAPPDATA=str(local))
|
|
73
75
|
from ..mcstudio.processes import background_options
|
|
74
76
|
try:
|
|
75
77
|
probe = subprocess.run([sys.executable, '-c', FOLDER_PROBE], env=environment,
|
|
@@ -87,7 +89,7 @@ class PlayerProfile:
|
|
|
87
89
|
# Seed only presentation/input preferences, never account or device IDs.
|
|
88
90
|
from ..engines.preferences import game_preference, read_properties, write_properties
|
|
89
91
|
target = roaming/'MinecraftPE_Netease/minecraftpe/options.txt'
|
|
90
|
-
if not target.parent.resolve().is_relative_to(
|
|
92
|
+
if not target.parent.resolve().is_relative_to(profile.resolve()):
|
|
91
93
|
raise EngineError('客户端游戏目录指向了外部位置。', 'profile_modified')
|
|
92
94
|
if not target.exists() and os.environ.get('APPDATA'):
|
|
93
95
|
source = Path(os.environ['APPDATA'])/'MinecraftPE_Netease/minecraftpe/options.txt'
|
|
@@ -102,6 +104,54 @@ class PlayerProfile:
|
|
|
102
104
|
write_properties(target, values, ':')
|
|
103
105
|
return environment
|
|
104
106
|
|
|
107
|
+
def windows_directory(self):
|
|
108
|
+
"""Keep real Windows profiles at the depth expected by ModPC 3.9.
|
|
109
|
+
|
|
110
|
+
The received-pack loader strips eight path components. Known folders
|
|
111
|
+
follow USERPROFILE, and the engine resolves junctions, so the profile
|
|
112
|
+
itself must be a real drive:/mcpy-profiles/<id> directory. Project-local
|
|
113
|
+
ownership/leases stay authoritative; previous data is copied once and
|
|
114
|
+
retained as a backup. No engine or Addon import hooks are changed.
|
|
115
|
+
"""
|
|
116
|
+
import shutil
|
|
117
|
+
from ..source_files import long_path
|
|
118
|
+
parent = Path(self.project.anchor)/'mcpy-profiles'
|
|
119
|
+
if not self.project.drive or str(parent).startswith('\\\\') or is_link(parent):
|
|
120
|
+
raise EngineError('客机需要本地磁盘上的独立资料目录。', 'profile_unavailable')
|
|
121
|
+
key = occupancy.path_key(self.root) + '\n' + os.environ.get('USERPROFILE', '')
|
|
122
|
+
target = parent/hashlib.sha256(key.encode('utf-8')).hexdigest()[:32]
|
|
123
|
+
receipt = self.root/'windows-profile.json'
|
|
124
|
+
expected = {'path': str(target), 'owner': self.owner}
|
|
125
|
+
previous = storage.read(receipt)
|
|
126
|
+
if previous is not None and (not isinstance(previous, dict) or
|
|
127
|
+
any(previous.get(k) != v for k, v in expected.items())):
|
|
128
|
+
raise EngineError('客机资料路径记录已变化,已保留原内容。', 'profile_modified')
|
|
129
|
+
marker = target/'.mcpy-owner.json'
|
|
130
|
+
if os.path.lexists(target):
|
|
131
|
+
if (previous is None or is_link(target) or not target.is_dir() or
|
|
132
|
+
is_link(marker) or storage.read(marker) != self.owner):
|
|
133
|
+
raise EngineError('客机资料目录归属不明:' + str(target), 'profile_conflict')
|
|
134
|
+
else:
|
|
135
|
+
if previous is not None and previous.get('ready'):
|
|
136
|
+
raise EngineError('已迁移的客机资料目录缺失,未回退到旧副本。', 'recovery_required')
|
|
137
|
+
staging = target.with_name(target.name + '.preparing')
|
|
138
|
+
if os.path.lexists(staging):
|
|
139
|
+
raise EngineError('上次客机资料迁移尚未完成:' + str(staging), 'recovery_required',
|
|
140
|
+
'保留原目录和迁移现场,请核对后恢复;不会覆盖或删除。')
|
|
141
|
+
legacy = self.root/'AppData'
|
|
142
|
+
if is_link(legacy) or any(is_link(p) for p in long_path(legacy).rglob('*')):
|
|
143
|
+
raise EngineError('原客机资料含目录链接,未复制。', 'profile_modified')
|
|
144
|
+
storage.write(receipt, expected)
|
|
145
|
+
staging.mkdir(parents=True)
|
|
146
|
+
if legacy.exists():
|
|
147
|
+
shutil.copytree(long_path(legacy), long_path(staging/'AppData'))
|
|
148
|
+
storage.write(staging/'.mcpy-owner.json', self.owner)
|
|
149
|
+
staging.rename(target)
|
|
150
|
+
storage.write(receipt, dict(expected, ready=True))
|
|
151
|
+
self._windows_root = target
|
|
152
|
+
self.data['network']['windows_profile'] = str(target)
|
|
153
|
+
return target
|
|
154
|
+
|
|
105
155
|
def prepare_pack_cache(self, game_root, fingerprint):
|
|
106
156
|
"""Invalidate only this role's received packs when its host changes."""
|
|
107
157
|
import re
|
|
@@ -111,7 +161,11 @@ class PlayerProfile:
|
|
|
111
161
|
if not isinstance(fingerprint, str) or not re.fullmatch(r'[0-9a-f]{64}', fingerprint):
|
|
112
162
|
raise EngineError('无法确认主机部署版本,未启动客机。', 'deployment_unavailable')
|
|
113
163
|
cache = Path(game_root)/'packcache'
|
|
114
|
-
|
|
164
|
+
roots = [self.root.resolve()]
|
|
165
|
+
windows = getattr(self, '_windows_root', None)
|
|
166
|
+
if windows is not None and storage.read(windows/'.mcpy-owner.json') == self.owner:
|
|
167
|
+
roots.append(windows.resolve())
|
|
168
|
+
if not any(cache.resolve().is_relative_to(root) for root in roots) or is_link(cache):
|
|
115
169
|
raise EngineError('客机包缓存指向了外部目录,已保留原内容。', 'profile_modified')
|
|
116
170
|
receipt = self.root/'received-packs.json'
|
|
117
171
|
try:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mcpywrap"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.4"
|
|
8
8
|
description = "A wrapper for Minecraft China Edition Addons management"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{name = "boybook", email = "boybook@easecation.net"}]
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
本次双客户端入口仅用于 `--local`。`--remote` 仍按远端单会话能力使用;不支持时不回退到本机。双客户端暂不提供两个真实账号登录,不反复复用同一 MCS 身份尝试绕过服务器登录限制。
|
|
14
14
|
|
|
15
|
+
Windows 0.5.3 起,角色的实际游戏资料保存在项目所在磁盘 `mcpy-profiles/<摘要>` 的独立目录,以兼容下载包脚本加载器;组记录与占用锁仍在项目内。旧资料自动复制迁移并保留备份,后续使用新目录。查看会话 `network.windows_profile` 或角色的 `windows-profile.json` 确认位置,不将此目录当作可随意删除的缓存。迁移未完成、归属冲突或已迁移目录缺失时先保留现场;不要删记录、回退旧身份或共享真实用户 AppData 来绕过。磁盘根目录不可写时会启动失败,不自动提权。
|
|
16
|
+
|
|
15
17
|
## 启动并确认
|
|
16
18
|
|
|
17
19
|
以下命令的全局参数都放在子命令之前:
|
|
@@ -83,3 +83,22 @@ Ctrl+C、任务 timeout 或本次 session 的 stop 请求会取消任务并回
|
|
|
83
83
|
**脚本对源码的写入不会自动回滚。** 脚本失败会阻止本次 builder/启动,但源码中的半成品可能被下一次构建或已开启 watcher 读取。批量生成时暂停 watcher;单文件可使用上述包外临时文件和原子替换。多个文件的替换不构成事务,工具不承诺“生成成功前绝不热更”。
|
|
84
84
|
|
|
85
85
|
不要让脚本写入 `.mcpy/runtime`、`.runtime`、依赖缓存、全局游戏包或世界数据库。普通 build/package 使用源码生成结果,受管运行产物始终由原部署服务发布。资源文件更新、重载请求投递与游戏中实际生效是不同结果;组资源热更仍遵守现有不支持的限制。
|
|
86
|
+
|
|
87
|
+
## 组装后、压缩前处理
|
|
88
|
+
|
|
89
|
+
`local_capabilities` 包含 `package-finalize` 时,根项目可使用 `before_archive`。它在依赖组装完成后、ZIP写入前执行,不会重复运行消费者准备,也不在 build、run 或热更中执行。
|
|
90
|
+
|
|
91
|
+
```toml
|
|
92
|
+
[tool.mcpywrap.scripts.trim-release]
|
|
93
|
+
python = "tools/prepare_release.py"
|
|
94
|
+
timeout = 180
|
|
95
|
+
|
|
96
|
+
[tool.mcpywrap.hooks]
|
|
97
|
+
before_archive = ["trim-release"]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
处理器从 `MCPY_PACKAGE_ROOT` 读取本次隔离组装目录;`MCPY_PROJECT_ROOT` 仍是源码根,工作目录也不变。只处理临时副本,不改依赖缓存或运行目录。入口应拒绝缺少该上下文的手动执行。
|
|
101
|
+
|
|
102
|
+
用于裁剪未使用依赖或准备发布文件时,优先使用依赖官方工具并保留许可证;区分静态引用与动态导入。处理后检查实际文件,不能仅凭退出码宣称裁剪成功。不得修改 manifest 或包身份、引入文件/目录链接。任务失败时停止打包,原有 ZIP 保留;成功后仍须用实际生成的包验证游戏加载与受影响功能。
|
|
103
|
+
|
|
104
|
+
`script list --effective --json` 的 `execution_order.before_archive` 会列出这个阶段。旧 CLI 遇到未知 hook 会拒绝,不能通过删除 hook 生成未经处理的发布包。
|
|
@@ -40,6 +40,22 @@ class MacOSNetworkTests(unittest.TestCase):
|
|
|
40
40
|
def call(self, *args):
|
|
41
41
|
return CliRunner().invoke(cli, ['--local', '--project', str(self.root), '--non-interactive', *args, '--json'])
|
|
42
42
|
|
|
43
|
+
def test_network_launch_persists_prepared_phase_before_starting_game(self):
|
|
44
|
+
data = {'session': 'a' * 32, 'project': str(self.root), 'mode': 'network',
|
|
45
|
+
'launch': {'data': str(self.root/'data')}}
|
|
46
|
+
path = sessions.session_path(self.root, data['session'])/'session.json'
|
|
47
|
+
|
|
48
|
+
def launched(record):
|
|
49
|
+
self.assertEqual(json.loads(path.read_text())['phase'], 'prepared')
|
|
50
|
+
self.assertEqual(record['session'], data['session'])
|
|
51
|
+
return Mock()
|
|
52
|
+
|
|
53
|
+
with patch('mcpywrap.engines.preferences.NativePreferences'), \
|
|
54
|
+
patch.object(sessions, 'require_launch_requested') as requested, \
|
|
55
|
+
patch('mcpywrap.engines.macos.launch_session', side_effect=launched):
|
|
56
|
+
self.backend.launch(data, None)
|
|
57
|
+
requested.assert_called_once_with(data['project'], data['session'])
|
|
58
|
+
|
|
43
59
|
def test_connect_does_not_read_project_or_create_world(self):
|
|
44
60
|
(self.root/'pyproject.toml').write_text('broken = [')
|
|
45
61
|
result = self.call('connect', 'localhost', '--port', '29132', '--detach')
|
|
@@ -77,6 +77,28 @@ class ScriptTests(unittest.TestCase):
|
|
|
77
77
|
self.assertEqual(z.read('中文 project_bp/generated.json'),b'{"fresh":true}')
|
|
78
78
|
self.assertFalse(any('tools/' in n for n in z.namelist()))
|
|
79
79
|
|
|
80
|
+
def test_archive_hook_changes_only_release_copy_and_keeps_previous_zip_on_failure(self):
|
|
81
|
+
import zipfile
|
|
82
|
+
unused = self.project/'behavior_pack/unused.py'
|
|
83
|
+
unused.write_text('unused = True')
|
|
84
|
+
self.configure("import os\nfrom pathlib import Path\np=Path(os.environ['MCPY_PACKAGE_ROOT'])\nnext(p.rglob('unused.py')).unlink()\n")
|
|
85
|
+
config = read_project(self.project)
|
|
86
|
+
config['tool']['mcpywrap']['hooks'] = {'before_archive': ['assets']}
|
|
87
|
+
write_project(self.project, config)
|
|
88
|
+
result = self.invoke('package', '--json')
|
|
89
|
+
self.assertEqual(result.exit_code, 0, result.output)
|
|
90
|
+
artifact = Path(json.loads(result.stdout)['artifact'])
|
|
91
|
+
original = artifact.read_bytes()
|
|
92
|
+
self.assertTrue(unused.exists())
|
|
93
|
+
with zipfile.ZipFile(artifact) as archive:
|
|
94
|
+
self.assertFalse(any(n.endswith('/unused.py') for n in archive.namelist()))
|
|
95
|
+
(self.project/'tools/generate.py').write_text(
|
|
96
|
+
"import os\nfrom pathlib import Path\np=next(Path(os.environ['MCPY_PACKAGE_ROOT']).rglob('manifest.json'))\np.write_bytes(p.read_bytes()+b' ')\n")
|
|
97
|
+
result = self.invoke('package', '--json')
|
|
98
|
+
self.assertNotEqual(result.exit_code, 0, result.output)
|
|
99
|
+
self.assertEqual(artifact.read_bytes(), original)
|
|
100
|
+
self.assertTrue(unused.exists())
|
|
101
|
+
|
|
80
102
|
def test_skill_generator_example_builds_and_preserves_output_on_invalid_input(self):
|
|
81
103
|
import re
|
|
82
104
|
import zipfile
|
|
@@ -3,6 +3,7 @@ from pathlib import Path
|
|
|
3
3
|
from tempfile import TemporaryDirectory
|
|
4
4
|
from unittest.mock import Mock, patch
|
|
5
5
|
import os
|
|
6
|
+
import shutil
|
|
6
7
|
import unittest
|
|
7
8
|
|
|
8
9
|
from mcpywrap.runtime import groups, storage
|
|
@@ -13,6 +14,15 @@ from mcpywrap.engines.host import EngineError
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class ProfileTests(unittest.TestCase):
|
|
17
|
+
def windows_profile(self, profile):
|
|
18
|
+
root = profile.windows_directory()
|
|
19
|
+
def cleanup():
|
|
20
|
+
if root.exists():
|
|
21
|
+
self.assertEqual(storage.read(root/'.mcpy-owner.json'), profile.owner)
|
|
22
|
+
shutil.rmtree(root)
|
|
23
|
+
self.addCleanup(cleanup)
|
|
24
|
+
return root
|
|
25
|
+
|
|
16
26
|
def setUp(self):
|
|
17
27
|
self.temp = TemporaryDirectory()
|
|
18
28
|
self.addCleanup(self.temp.cleanup)
|
|
@@ -119,6 +129,7 @@ class ProfileTests(unittest.TestCase):
|
|
|
119
129
|
def test_child_known_folders_are_private_and_parent_environment_is_unchanged(self):
|
|
120
130
|
profile = PlayerProfile(self.data(0))
|
|
121
131
|
profile.prepare()
|
|
132
|
+
windows = self.windows_profile(profile)
|
|
122
133
|
source = self.root/'source-appdata'
|
|
123
134
|
options = source/'MinecraftPE_Netease/minecraftpe/options.txt'
|
|
124
135
|
options.parent.mkdir(parents=True)
|
|
@@ -127,12 +138,57 @@ class ProfileTests(unittest.TestCase):
|
|
|
127
138
|
before = dict(os.environ)
|
|
128
139
|
environment = profile.windows_environment()
|
|
129
140
|
self.assertEqual(dict(os.environ), before)
|
|
130
|
-
self.assertEqual(environment['USERPROFILE'], str(
|
|
141
|
+
self.assertEqual(environment['USERPROFILE'], str(windows))
|
|
142
|
+
self.assertEqual(len(windows.parts), 3)
|
|
131
143
|
target = Path(environment['APPDATA'])/'MinecraftPE_Netease/minecraftpe/options.txt'
|
|
132
144
|
self.assertEqual(target.read_text(encoding='utf-8'), 'gfx_viewdistance:128\nkeyboard_type_0_key.forward:87\n')
|
|
133
145
|
self.assertIn('private', options.read_text(encoding='utf-8'))
|
|
134
146
|
self.assertFalse((target.parent/'clientId.txt').exists())
|
|
135
147
|
|
|
148
|
+
@unittest.skipUnless(os.name == 'nt', 'Windows profile layout')
|
|
149
|
+
def test_migration_preserves_identity_and_does_not_recopy_stale_data(self):
|
|
150
|
+
profile = PlayerProfile(self.data(0)); profile.prepare()
|
|
151
|
+
old = profile.root/'AppData/Roaming/MinecraftPE_Netease/minecraftpe/clientId.txt'
|
|
152
|
+
old.parent.mkdir(parents=True); old.write_bytes(b'original-player')
|
|
153
|
+
windows = self.windows_profile(profile)
|
|
154
|
+
new = windows/old.relative_to(profile.root)
|
|
155
|
+
self.assertEqual(new.read_bytes(), b'original-player')
|
|
156
|
+
self.assertEqual(old.read_bytes(), b'original-player')
|
|
157
|
+
new.write_bytes(b'updated-player')
|
|
158
|
+
self.assertEqual(profile.windows_directory(), windows)
|
|
159
|
+
self.assertEqual(new.read_bytes(), b'updated-player')
|
|
160
|
+
second = PlayerProfile(self.data(1)); second.prepare()
|
|
161
|
+
self.assertNotEqual(self.windows_profile(second), windows)
|
|
162
|
+
game = windows/'AppData/Roaming/MinecraftPE_Netease'
|
|
163
|
+
profile.prepare_pack_cache(game, 'a'*64)
|
|
164
|
+
cache = game/'packcache/old.py'; cache.parent.mkdir(); cache.write_text('old')
|
|
165
|
+
profile.prepare_pack_cache(game, 'b'*64)
|
|
166
|
+
self.assertFalse(cache.exists())
|
|
167
|
+
self.assertEqual(new.read_bytes(), b'updated-player')
|
|
168
|
+
|
|
169
|
+
@unittest.skipUnless(os.name == 'nt', 'Windows profile layout')
|
|
170
|
+
def test_migrated_profile_missing_or_replaced_never_resets_identity(self):
|
|
171
|
+
profile = PlayerProfile(self.data(0)); profile.prepare()
|
|
172
|
+
windows = self.windows_profile(profile)
|
|
173
|
+
backup = windows.with_name(windows.name+'.test-backup')
|
|
174
|
+
windows.rename(backup)
|
|
175
|
+
try:
|
|
176
|
+
with self.assertRaises(EngineError) as error:
|
|
177
|
+
profile.windows_directory()
|
|
178
|
+
self.assertEqual(error.exception.code, 'recovery_required')
|
|
179
|
+
self.assertFalse(windows.exists())
|
|
180
|
+
finally:
|
|
181
|
+
backup.rename(windows)
|
|
182
|
+
marker = windows/'.mcpy-owner.json'
|
|
183
|
+
storage.write(marker, {'owner': 'someone-else'})
|
|
184
|
+
try:
|
|
185
|
+
with self.assertRaises(EngineError) as error:
|
|
186
|
+
profile.windows_directory()
|
|
187
|
+
self.assertEqual(error.exception.code, 'profile_conflict')
|
|
188
|
+
self.assertEqual(storage.read(marker), {'owner': 'someone-else'})
|
|
189
|
+
finally:
|
|
190
|
+
storage.write(marker, profile.owner)
|
|
191
|
+
|
|
136
192
|
|
|
137
193
|
if __name__ == '__main__':
|
|
138
194
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|