mcpywrap-core 0.6.2__tar.gz → 0.6.3__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_core-0.6.2 → mcpywrap_core-0.6.3}/PKG-INFO +1 -1
- mcpywrap_core-0.6.3/mcpywrap_core/_version.py +1 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/command_context.py +1 -1
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/run_cmd.py +9 -3
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/windows.py +49 -6
- mcpywrap_core-0.6.3/mcpywrap_core/mcstudio/lan_locator.py +94 -0
- mcpywrap_core-0.6.3/mcpywrap_core/mcstudio/lan_memory.py +142 -0
- mcpywrap_core-0.6.3/mcpywrap_core/mcstudio/lan_window.py +35 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/runtime_debug.py +7 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/sessions.py +2 -2
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mobile_host/client.py +2 -2
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mobile_host/intervention.py +2 -2
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/groups.py +49 -9
- mcpywrap_core-0.6.3/mcpywrap_core/runtime/lan_menu.py +72 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/profiles.py +1 -1
- mcpywrap_core-0.6.3/mcpywrap_core/runtime/windows_join.py +193 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/SKILL.md +1 -1
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/application-host.md +10 -6
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/dual-clients.md +12 -4
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/troubleshooting.md +3 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/scripts/smoke.py +1 -1
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/project_ui.py +1 -1
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/session_views.py +2 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core.egg-info/PKG-INFO +1 -1
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core.egg-info/SOURCES.txt +5 -0
- mcpywrap_core-0.6.2/mcpywrap_core/_version.py +0 -1
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/LICENSE +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/MANIFEST.in +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/README.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/THIRD_PARTY_NOTICES.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/__main__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/_process_identity.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/_script_worker.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/api.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/builders/AddonsPack.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/builders/MapPack.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/builders/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/builders/assembly.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/builders/dependency_manager.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/builders/file_merge.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/builders/project_builder.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/builders/watcher.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/cli.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/code_libraries.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/add_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/build_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/connect_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/default_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/dependency_prompt.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/deployment_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/deployment_output.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/desktop_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/dev_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/doctor_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/edit_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/engine_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/host_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/init_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/mod_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/modsdk_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/package_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/publish_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/record_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/remove_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/runtime_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/runtime_input_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/runtime_player_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/runtime_ui_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/script_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/serve_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/session_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/skill_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/sync_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/commands/ui_cmd.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/config.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/consumer_prepare.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/core-contract.json +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/custom_packaging.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/dependencies.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/android_icon.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/apk_profile.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/backend.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/host.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/install.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/macos.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/macos_icons.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/macos_network.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/official.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/preferences.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/engines/tui.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/framework_presets.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/frameworks.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/git_cache.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/git_projects.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/host.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/host_assets.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/input_executor.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/input_plan.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/SimpleMonitor.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/auth_interaction.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/bridge_assets.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/deployment_links.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/diagnostics.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/discovery.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/editor.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/file_logs.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/game.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/host_input.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/hot_reload.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/image.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/input_sequence.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/launcher_control.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/launcher_python.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/log_colorizer.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/log_protocol.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/log_query.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/log_stream.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/mcs.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/mcs_auth.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/network.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/private_logs.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/processes.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/recording_worker.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/recordings.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/runtime_cppconfig.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/runtime_input_payload.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/runtime_key_timeline_payload.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/runtime_player_payload.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/runtime_ui.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/runtime_ui_outline.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/runtime_ui_payload.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/screenshot.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/session_capabilities.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/session_worker.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/studio_server.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/studio_server_ui.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/symlink_helper_global.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/symlink_helper_map.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/symlinks.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/unified_input.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/window.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mcstudio/window_activation.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/minecraft/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/minecraft/addons.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/minecraft/level_dat.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/minecraft/map.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/minecraft/modsdk_environment.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/minecraft/modsdk_versions.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/minecraft/netease_modsdk.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/minecraft/template/generate_mod_files.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/minecraft/template/mod_template.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mobile_host/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mobile_host/options.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mobile_host/preparation.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mobile_host/profiles.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mobile_host/receipts.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mobile_host/routing.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/mobile_host/streams.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/operations/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/operations/build.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/operations/input_snapshot.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/operations/package.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/operations/project.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/operations/sync.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/prepare_environment.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/project_dependencies.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/project_init.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/project_scripts.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/recording_io.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/remote/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/remote/client.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/remote/http_server.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/remote/protocol.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/remote/service.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/deployment.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/deployment_inspection.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/deployment_service.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/diagnostics.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/instances.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/native.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/occupancy.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/preparation.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/storage.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/targets.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/watch.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/window_policy.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/runtime/windows.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/agents/openai.yaml +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/framework/behavior_pack/CatalogRuntime/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/framework/behavior_pack/manifest.json +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/example.toml +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/prepare.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/framework/build_tools/schema.json +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/framework/pyproject.toml +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/game/.gitignore +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/game/behavior_pack/manifest.json +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/game/data/items.json +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/game/pyproject.toml +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/assets/consumer-prepare/game/resource_pack/manifest.json +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/capture.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/consumer-prepare-author.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/consumer-prepare.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/input-sequence.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/input-writing.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/instance-world-settings.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/local-development.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/macos-local.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/network-sessions.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/project-dependencies.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/project-scripts.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/project-workflow.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/remote-testing.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/runtime-debug.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/runtime-input.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/runtime-key-timeline.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/runtime-player.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/runtime-ui.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/script-example.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/references/setup.md +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/scripts/bootstrap.ps1 +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/scripts/bootstrap.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/skill/scripts/game_window.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/source_files.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/timeline.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/application.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/assets/mcpy.icns +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/assets/mcpy.icon/Assets/01-cube.svg +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/assets/mcpy.icon/Assets/02-prompt.svg +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/assets/mcpy.icon/icon.json +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/assets/mcpy.png +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/engine_setup.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/log_view.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/log_window.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/run_session.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/session_controller.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/session_tools.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/ui/world_config.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/utils/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/utils/console.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/utils/link_snapshot.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/utils/locks.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/utils/paths.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/utils/pip_error_parser.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/utils/print_guide.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/utils/project_setup.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core/utils/utils.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core.egg-info/dependency_links.txt +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core.egg-info/requires.txt +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_core.egg-info/top_level.txt +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_host/__init__.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/mcpywrap_host/store.py +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/pyproject.toml +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/setup.cfg +0 -0
- {mcpywrap_core-0.6.2 → mcpywrap_core-0.6.3}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.6.3'
|
|
@@ -88,7 +88,7 @@ def require_project():
|
|
|
88
88
|
|
|
89
89
|
def restart_handoff(result, previous):
|
|
90
90
|
if not isinstance(result,dict) or not previous: return result
|
|
91
|
-
current=result.get('session')
|
|
91
|
+
current=result.get('replacement_session',result.get('session'))
|
|
92
92
|
if current and current!=previous:
|
|
93
93
|
result=dict(result,previous_session=previous,session_changed=True)
|
|
94
94
|
message='会话已更换;后续日志、截图和调试请使用 --session '+current+'。旧会话记录仍保留。'
|
|
@@ -69,10 +69,12 @@ def take_world_options(arguments, cppconfig=None):
|
|
|
69
69
|
@click.option('--force', '-f', is_flag=True, help='强制删除,不提示确认')
|
|
70
70
|
@click.option('--clean-all', is_flag=True, help='清空所有游戏实例')
|
|
71
71
|
@click.option('--clients', type=click.IntRange(1, 2), default=1, show_default=True,
|
|
72
|
-
help='
|
|
72
|
+
help='本机玩家客户端数量;Windows 本地双客户端默认自动加入目标主机')
|
|
73
|
+
@click.option('--join-mode', type=click.Choice(['auto','manual']), default=None,
|
|
74
|
+
help='Windows 本地双客户端加入方式;auto 自动加入,manual 保留手动选择局域网卡片')
|
|
73
75
|
@click.option('--launch-timeout', cls=HostOption, type=click.IntRange(5,120), default=None, help='App 宿主进程/画中画确认等待秒数;不含世界加载')
|
|
74
76
|
@click.argument('instance_prefix', required=False)
|
|
75
|
-
def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach, mcs_auth=False, cppconfig=None, clients=1, background=None, launch_timeout=None, **engine_overrides):
|
|
77
|
+
def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach, mcs_auth=False, cppconfig=None, clients=1, background=None, launch_timeout=None, join_mode=None, **engine_overrides):
|
|
76
78
|
"""游戏实例运行与管理
|
|
77
79
|
|
|
78
80
|
可直接运行 'mcpy run' 启动最新实例,或使用选项管理实例。
|
|
@@ -89,6 +91,8 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
|
|
|
89
91
|
raise click.UsageError('run --json 需要 --detach')
|
|
90
92
|
if clients != 1 and (list or delete or clean_all):
|
|
91
93
|
raise click.UsageError('--clients 仅用于启动游戏')
|
|
94
|
+
if join_mode is not None and clients != 2:
|
|
95
|
+
raise click.UsageError('--join-mode 仅用于 --clients 2 的本地联机')
|
|
92
96
|
world_config = take_world_options(engine_overrides, cppconfig)
|
|
93
97
|
if world_config is not None and (not new or instance_prefix or list or delete or clean_all):
|
|
94
98
|
raise click.UsageError('世界创建参数仅用于 --new,不覆盖已有实例')
|
|
@@ -121,13 +125,15 @@ def run_cmd(new, list, delete, force, clean_all, instance_prefix, no_gui, detach
|
|
|
121
125
|
from ..mcstudio.network import configured_target
|
|
122
126
|
target = configured_target(read_project(base_dir))
|
|
123
127
|
if target:
|
|
128
|
+
if join_mode is not None:
|
|
129
|
+
raise click.UsageError('--join-mode 不用于外部服务器连接')
|
|
124
130
|
if new or instance_prefix or world_config is not None:
|
|
125
131
|
raise click.UsageError('网络联调不支持世界创建参数')
|
|
126
132
|
if mcs_auth:
|
|
127
133
|
raise click.UsageError('双客户端进服暂不支持双账号认证')
|
|
128
134
|
return groups.start_network(base_dir, backend, target, engine_overrides,
|
|
129
135
|
background=options['background'])
|
|
130
|
-
return groups.start_local(base_dir, backend, options)
|
|
136
|
+
return groups.start_local(base_dir, backend, options, join_mode=join_mode)
|
|
131
137
|
recovered = False
|
|
132
138
|
try:
|
|
133
139
|
try:
|
|
@@ -8,7 +8,7 @@ class WindowsBackend(GameBackend):
|
|
|
8
8
|
capabilities = ('local-worlds', 'network-sessions', 'status', 'logs', 'stop', 'py',
|
|
9
9
|
'runtime', 'runtime-ui', 'runtime-player', 'reload', 'watch',
|
|
10
10
|
'screenshot', 'key', 'mouse', 'input-sequence', 'mcs-auth', 'serve', 'project-ui', 'editor',
|
|
11
|
-
'local-dual-client-manual')
|
|
11
|
+
'local-dual-client-manual', 'local-dual-client-auto')
|
|
12
12
|
setup_description = '请在 MC Studio 下载游戏引擎;使用 mcpy doctor 检查资源,可用 --engine-version 选择版本。'
|
|
13
13
|
|
|
14
14
|
def debug_channel(self, data, write_log):
|
|
@@ -16,9 +16,25 @@ class WindowsBackend(GameBackend):
|
|
|
16
16
|
self._world_ready = False
|
|
17
17
|
self._ready_log_cursors = {}
|
|
18
18
|
data['world_ready'] = False
|
|
19
|
-
|
|
19
|
+
channel = SafaiaChannel(write_log, ready=lambda: self._world_ready)
|
|
20
|
+
self._joiner = None
|
|
21
|
+
if (data.get('network') or {}).get('join') == 'auto':
|
|
22
|
+
from ..runtime.windows_join import AutoJoin
|
|
23
|
+
self._joiner = channel.local_join = AutoJoin(data, channel).start()
|
|
24
|
+
return channel
|
|
20
25
|
|
|
21
26
|
def refresh(self, data, channel):
|
|
27
|
+
changed = self._refresh_world(data, channel)
|
|
28
|
+
joiner = getattr(self, '_joiner', None)
|
|
29
|
+
if joiner:
|
|
30
|
+
state = joiner.status()
|
|
31
|
+
ready = state['state'] == 'joined'
|
|
32
|
+
if data.get('local_join') != state or data.get('world_ready') != ready:
|
|
33
|
+
data.update(local_join=state, world_ready=ready)
|
|
34
|
+
changed = True
|
|
35
|
+
return changed
|
|
36
|
+
|
|
37
|
+
def _refresh_world(self, data, channel):
|
|
22
38
|
if self._world_ready: return False
|
|
23
39
|
from pathlib import Path
|
|
24
40
|
import re
|
|
@@ -74,7 +90,7 @@ class WindowsBackend(GameBackend):
|
|
|
74
90
|
def prepare_guest(self, host):
|
|
75
91
|
return None
|
|
76
92
|
|
|
77
|
-
def start_guest(self, host, *, project_dir, session_id, group, role, player, background=False):
|
|
93
|
+
def start_guest(self, host, *, project_dir, session_id, group, role, player, background=False, join_mode='manual'):
|
|
78
94
|
from ..mcstudio import sessions
|
|
79
95
|
from ..mcstudio.discovery import require_resources, Engine
|
|
80
96
|
from ..runtime.deployment import Deployment
|
|
@@ -91,9 +107,15 @@ class WindowsBackend(GameBackend):
|
|
|
91
107
|
version = record.get('fingerprint') if isinstance(record, dict) else None
|
|
92
108
|
if not version:
|
|
93
109
|
raise EngineError('无法确认主机加载的版本,请重启联调组。', 'deployment_unavailable')
|
|
110
|
+
lan_target = None
|
|
111
|
+
if join_mode == 'auto':
|
|
112
|
+
from ..mcstudio.lan_window import helper
|
|
113
|
+
from ..runtime.windows_join import target
|
|
114
|
+
helper()
|
|
115
|
+
lan_target = target(host)
|
|
94
116
|
data = sessions.start(project_dir, session_id=session_id, group=group, role=role, background=background,
|
|
95
117
|
network={'target': None, 'engine': engine, 'player': player,
|
|
96
|
-
'local_host': host['session'], 'join': '
|
|
118
|
+
'local_host': host['session'], 'join': join_mode, 'lan_target': lan_target,
|
|
97
119
|
'deployment_fingerprint': version})
|
|
98
120
|
return sessions.handoff(data)
|
|
99
121
|
|
|
@@ -125,15 +147,36 @@ class WindowsBackend(GameBackend):
|
|
|
125
147
|
sessions.require_launch_requested(data['project'], data['session'])
|
|
126
148
|
data['phase'] = 'prepared'
|
|
127
149
|
sessions.save(directory/'session.json', data)
|
|
128
|
-
if data['network'].get('join')
|
|
150
|
+
if data['network'].get('join') in ('manual','auto'):
|
|
129
151
|
from ..runtime.groups import is_local_guest
|
|
130
152
|
from ..mcstudio.game import open_game
|
|
131
153
|
if not is_local_guest(data['project'], data):
|
|
132
154
|
raise ValueError('本地主机已退出或联调成员已变更,取消客机启动')
|
|
133
|
-
|
|
155
|
+
launch_config = None
|
|
156
|
+
if data['network']['join'] == 'auto':
|
|
157
|
+
from ..runtime.windows_join import bootstrap
|
|
158
|
+
launch_config = str(bootstrap(data, engine, directory))
|
|
159
|
+
sessions.save(directory/'session.json', data)
|
|
160
|
+
process = open_game(launch_config, engine=engine, logging_ip='127.0.0.1',
|
|
134
161
|
logging_port=receiver.port, wait=False, use_system_color=False,
|
|
135
162
|
capture_output=True, environment=environment,
|
|
136
163
|
background=data.get('background', False))
|
|
164
|
+
if process and data['network']['join'] == 'auto':
|
|
165
|
+
from ..mcstudio.processes import identity
|
|
166
|
+
from ..mcstudio.lan_memory import adapt
|
|
167
|
+
data['game'] = identity(process.pid)
|
|
168
|
+
sessions.save(directory/'session.json', data)
|
|
169
|
+
try:
|
|
170
|
+
data['network']['lan_adapter'] = adapt(data['game'], data['network']['lan_target']['port'],
|
|
171
|
+
cancelled=lambda: (directory/'stop').exists())
|
|
172
|
+
except BaseException as error:
|
|
173
|
+
process.terminate()
|
|
174
|
+
process.wait(timeout=10)
|
|
175
|
+
if isinstance(error, Exception):
|
|
176
|
+
from .host import EngineError
|
|
177
|
+
raise EngineError('Windows LAN 自动适配未完成:'+str(error), 'lan_adapter_unavailable',
|
|
178
|
+
'保留会话日志;不会使用其他版本地址重试。可停止客机后选择 --join-mode manual。') from error
|
|
179
|
+
raise
|
|
137
180
|
else:
|
|
138
181
|
from ..mcstudio.network import launch_network, ServerTarget
|
|
139
182
|
process = launch_network(engine, ServerTarget(**data['network']['target']),
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""识别 NetherNet TransportConfiguration 的默认 LAN 端口,不保存版本地址表。"""
|
|
2
|
+
from bisect import bisect_right
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import re
|
|
5
|
+
import struct
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LocatorError(ValueError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class PortSite:
|
|
14
|
+
instruction_rva: int
|
|
15
|
+
operand_rva: int
|
|
16
|
+
function_rva: int
|
|
17
|
+
expected: bytes
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def locate_port(image):
|
|
21
|
+
"""image 提供 sections、functions 和 read(rva, size),可用于静态夹具和活动映像。
|
|
22
|
+
|
|
23
|
+
从 Windows x64 展开表确定指令边界,再识别端口、广播周期及协议 magic
|
|
24
|
+
的同一栈对象初始化。必须恰好找到一处;不把 CALL 位移或数据中的 7551 当指令。
|
|
25
|
+
"""
|
|
26
|
+
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
|
|
27
|
+
from capstone.x86_const import X86_OP_IMM, X86_OP_MEM, X86_OP_REG
|
|
28
|
+
decoder = Cs(CS_ARCH_X86, CS_MODE_64)
|
|
29
|
+
decoder.detail = True
|
|
30
|
+
functions = sorted(image.functions)
|
|
31
|
+
starts = [start for start, end in functions]
|
|
32
|
+
seen, candidates = set(), []
|
|
33
|
+
for rva, size, executable in image.sections:
|
|
34
|
+
if not executable:
|
|
35
|
+
continue
|
|
36
|
+
code = image.read(rva, size)
|
|
37
|
+
for match in re.finditer(re.escape(struct.pack('<I', 7551)), code):
|
|
38
|
+
position = rva + match.start()
|
|
39
|
+
index = bisect_right(starts, position) - 1
|
|
40
|
+
if index < 0:
|
|
41
|
+
continue
|
|
42
|
+
start, end = functions[index]
|
|
43
|
+
if not start <= position < end or end-start > 32768 or start in seen:
|
|
44
|
+
continue
|
|
45
|
+
seen.add(start)
|
|
46
|
+
instructions = list(decoder.disasm(image.read(start, end-start), start))
|
|
47
|
+
if not instructions or instructions[-1].address + instructions[-1].size != end:
|
|
48
|
+
continue
|
|
49
|
+
for i, instruction in enumerate(instructions[:-1]):
|
|
50
|
+
operands = instruction.operands
|
|
51
|
+
if (instruction.mnemonic != 'mov' or len(operands) != 2 or
|
|
52
|
+
operands[0].type != X86_OP_REG or operands[0].size != 4 or
|
|
53
|
+
operands[1].type != X86_OP_IMM or operands[1].imm != 7551 or
|
|
54
|
+
instruction.imm_size != 4):
|
|
55
|
+
continue
|
|
56
|
+
store = instructions[i+1]
|
|
57
|
+
if (store.mnemonic != 'mov' or len(store.operands) != 2 or
|
|
58
|
+
store.operands[0].type != X86_OP_MEM or store.operands[0].size != 2 or
|
|
59
|
+
store.operands[1].type != X86_OP_REG):
|
|
60
|
+
continue
|
|
61
|
+
# 本组配置的 port 在 magic 后 8 字节,interval 紧接 port 后 4 字节。
|
|
62
|
+
port = store.operands[0].mem
|
|
63
|
+
register = instruction.reg_name(operands[0].reg)
|
|
64
|
+
low_register = store.reg_name(store.operands[1].reg)
|
|
65
|
+
aliases = {'eax': 'ax', 'ebx': 'bx', 'ecx': 'cx', 'edx': 'dx',
|
|
66
|
+
'esi': 'si', 'edi': 'di', **{'r%sd' % n: 'r%sw' % n for n in range(8,16)}}
|
|
67
|
+
if aliases.get(register) != low_register or store.reg_name(port.base) not in ('rsp','rbp') or port.index:
|
|
68
|
+
continue
|
|
69
|
+
interval, magic, passed = False, False, False
|
|
70
|
+
magic_register = None
|
|
71
|
+
for following in instructions[i+2:i+35]:
|
|
72
|
+
ops = following.operands
|
|
73
|
+
if following.mnemonic == 'mov' and len(ops) == 2:
|
|
74
|
+
if (ops[0].type == X86_OP_MEM and ops[0].mem.base == port.base and
|
|
75
|
+
ops[0].mem.disp == port.disp+4 and ops[1].type == X86_OP_IMM and ops[1].imm == 2000):
|
|
76
|
+
interval = True
|
|
77
|
+
if ops[0].type == X86_OP_REG and ops[1].type == X86_OP_IMM and ops[1].imm == 0xdeadbeef:
|
|
78
|
+
magic_register = following.reg_name(ops[0].reg)
|
|
79
|
+
if (ops[0].type == X86_OP_MEM and ops[0].mem.base == port.base and
|
|
80
|
+
ops[0].mem.disp == port.disp-8 and ops[1].type == X86_OP_REG and
|
|
81
|
+
magic_register and following.reg_name(ops[1].reg) in
|
|
82
|
+
(magic_register, magic_register[:-1] if magic_register.startswith('r') and magic_register.endswith('d') else 'r'+magic_register[1:])):
|
|
83
|
+
magic = True
|
|
84
|
+
if (following.mnemonic == 'lea' and len(ops) == 2 and
|
|
85
|
+
ops[0].type == X86_OP_REG and following.reg_name(ops[0].reg) == 'rcx' and
|
|
86
|
+
ops[1].type == X86_OP_MEM and ops[1].mem.base == port.base and
|
|
87
|
+
ops[1].mem.disp == port.disp-8):
|
|
88
|
+
passed = True
|
|
89
|
+
if interval and magic and passed:
|
|
90
|
+
candidates.append(PortSite(instruction.address, instruction.address+instruction.imm_offset,
|
|
91
|
+
start, bytes(instruction.bytes)))
|
|
92
|
+
if len(candidates) != 1:
|
|
93
|
+
raise LocatorError('无法唯一识别 NetherNet LAN 配置初始化(候选数:%s)。' % len(candidates))
|
|
94
|
+
return candidates[0]
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Windows 本地客机的进程内 LAN 默认值适配;不修改磁盘引擎。"""
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
import ctypes as C
|
|
4
|
+
from ctypes import wintypes as W
|
|
5
|
+
import struct
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from .processes import checked_process
|
|
9
|
+
from .lan_locator import LocatorError, locate_port
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ProcessImage:
|
|
13
|
+
def __init__(self, identity):
|
|
14
|
+
import pefile
|
|
15
|
+
self.identity = identity
|
|
16
|
+
if not checked_process(identity):
|
|
17
|
+
raise LocatorError('游戏进程已退出。')
|
|
18
|
+
self.kernel = C.WinDLL('kernel32', use_last_error=True)
|
|
19
|
+
self.psapi = C.WinDLL('psapi', use_last_error=True)
|
|
20
|
+
k = self.kernel
|
|
21
|
+
k.OpenProcess.argtypes = [W.DWORD, W.BOOL, W.DWORD]
|
|
22
|
+
k.OpenProcess.restype = W.HANDLE
|
|
23
|
+
k.CloseHandle.argtypes = [W.HANDLE]
|
|
24
|
+
k.ReadProcessMemory.argtypes = [W.HANDLE, C.c_void_p, C.c_void_p, C.c_size_t, C.POINTER(C.c_size_t)]
|
|
25
|
+
k.WriteProcessMemory.argtypes = k.ReadProcessMemory.argtypes
|
|
26
|
+
k.VirtualProtectEx.argtypes = [W.HANDLE, C.c_void_p, C.c_size_t, W.DWORD, C.POINTER(W.DWORD)]
|
|
27
|
+
k.FlushInstructionCache.argtypes = [W.HANDLE, C.c_void_p, C.c_size_t]
|
|
28
|
+
self.psapi.EnumProcessModulesEx.argtypes = [W.HANDLE, C.POINTER(C.c_void_p), W.DWORD, C.POINTER(W.DWORD), W.DWORD]
|
|
29
|
+
self.handle = k.OpenProcess(0x438 | 0x800, False, identity['pid'])
|
|
30
|
+
if not self.handle:
|
|
31
|
+
raise C.WinError(C.get_last_error())
|
|
32
|
+
try:
|
|
33
|
+
modules = (C.c_void_p * 1024)()
|
|
34
|
+
needed = W.DWORD()
|
|
35
|
+
if not self.psapi.EnumProcessModulesEx(self.handle, modules, C.sizeof(modules), C.byref(needed), 3):
|
|
36
|
+
raise C.WinError(C.get_last_error())
|
|
37
|
+
self.base = modules[0]
|
|
38
|
+
self.limit = 65536
|
|
39
|
+
pe = pefile.PE(data=self.read(0, 65536), fast_load=True)
|
|
40
|
+
if pe.FILE_HEADER.Machine != 0x8664 or not 65536 <= pe.OPTIONAL_HEADER.SizeOfImage <= 1024**3:
|
|
41
|
+
raise LocatorError('自动联机仅支持可识别的 Windows x64 引擎映像。')
|
|
42
|
+
self.limit = pe.OPTIONAL_HEADER.SizeOfImage
|
|
43
|
+
# 加壳入口和可写数据不是引擎代码候选;只取映像主体的可执行节。
|
|
44
|
+
self.sections = [(s.VirtualAddress, s.Misc_VirtualSize, bool(s.Characteristics & 0x20000000))
|
|
45
|
+
for s in pe.sections if s.Name.rstrip(b'\0') not in (b'.boot', b'.winlice', b'.reloc')]
|
|
46
|
+
directory = pe.OPTIONAL_HEADER.DATA_DIRECTORY[3]
|
|
47
|
+
if not directory.Size or directory.Size % 12 or directory.Size > 32*1024**2:
|
|
48
|
+
raise LocatorError('引擎缺少有效的 x64 展开表。')
|
|
49
|
+
self.functions = [(start, end) for start, end, unwind in struct.iter_unpack(
|
|
50
|
+
'<III', self.read(directory.VirtualAddress, directory.Size)) if 0 < start < end < self.limit]
|
|
51
|
+
if not self.functions:
|
|
52
|
+
raise LocatorError('引擎代码尚未解包或展开表不可识别。')
|
|
53
|
+
except BaseException:
|
|
54
|
+
self.close()
|
|
55
|
+
raise
|
|
56
|
+
|
|
57
|
+
def read(self, rva, size):
|
|
58
|
+
if rva < 0 or size < 0 or rva+size > self.limit:
|
|
59
|
+
raise LocatorError('引擎映像地址超出边界。')
|
|
60
|
+
buffer = C.create_string_buffer(size)
|
|
61
|
+
count = C.c_size_t()
|
|
62
|
+
if not self.kernel.ReadProcessMemory(self.handle, self.base+rva, buffer, size, C.byref(count)) or count.value != size:
|
|
63
|
+
raise LocatorError('引擎映像尚不可完整读取。')
|
|
64
|
+
return buffer.raw
|
|
65
|
+
|
|
66
|
+
@contextmanager
|
|
67
|
+
def suspended(self):
|
|
68
|
+
ntdll = C.WinDLL('ntdll')
|
|
69
|
+
ntdll.NtSuspendProcess.argtypes = [W.HANDLE]
|
|
70
|
+
ntdll.NtResumeProcess.argtypes = [W.HANDLE]
|
|
71
|
+
if not checked_process(self.identity) or ntdll.NtSuspendProcess(self.handle) < 0:
|
|
72
|
+
raise LocatorError('无法暂停本次游戏以原子更新 LAN 配置。')
|
|
73
|
+
try:
|
|
74
|
+
yield
|
|
75
|
+
finally:
|
|
76
|
+
if ntdll.NtResumeProcess(self.handle) < 0:
|
|
77
|
+
raise LocatorError('游戏恢复执行失败,需要停止本次客机。')
|
|
78
|
+
|
|
79
|
+
def patch(self, site, port):
|
|
80
|
+
if type(port) is not int or not 1 <= port <= 65535:
|
|
81
|
+
raise ValueError('无效的 LAN 端口')
|
|
82
|
+
with self.suspended():
|
|
83
|
+
if self.read(site.instruction_rva, len(site.expected)) != site.expected:
|
|
84
|
+
raise LocatorError('目标指令已变化,未写入。')
|
|
85
|
+
address = self.base+site.operand_rva
|
|
86
|
+
original = self.read(site.operand_rva, 4)
|
|
87
|
+
previous = W.DWORD()
|
|
88
|
+
if not self.kernel.VirtualProtectEx(self.handle, address, 4, 0x40, C.byref(previous)):
|
|
89
|
+
raise C.WinError(C.get_last_error())
|
|
90
|
+
try:
|
|
91
|
+
count = C.c_size_t()
|
|
92
|
+
replacement = struct.pack('<I', port)
|
|
93
|
+
if not self.kernel.WriteProcessMemory(self.handle, address, replacement, 4, C.byref(count)) or count.value != 4:
|
|
94
|
+
raise LocatorError('LAN 端口写入失败。')
|
|
95
|
+
self.kernel.FlushInstructionCache(self.handle, address, 4)
|
|
96
|
+
if self.read(site.operand_rva, 4) != replacement:
|
|
97
|
+
raise LocatorError('LAN 端口读回校验失败。')
|
|
98
|
+
except BaseException:
|
|
99
|
+
self.kernel.WriteProcessMemory(self.handle, address, original, 4, C.byref(count))
|
|
100
|
+
self.kernel.FlushInstructionCache(self.handle, address, 4)
|
|
101
|
+
raise
|
|
102
|
+
finally:
|
|
103
|
+
ignored = W.DWORD()
|
|
104
|
+
if not self.kernel.VirtualProtectEx(self.handle, address, 4, previous.value, C.byref(ignored)):
|
|
105
|
+
raise LocatorError('无法恢复引擎内存页属性。')
|
|
106
|
+
|
|
107
|
+
def close(self):
|
|
108
|
+
if self.handle:
|
|
109
|
+
self.kernel.CloseHandle(self.handle)
|
|
110
|
+
self.handle = None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def adapt(identity, port, cancelled=lambda: False, timeout=35):
|
|
114
|
+
"""等待解包后按结构定位;失败由调用者结束自己刚创建的客机。"""
|
|
115
|
+
if port == 7551:
|
|
116
|
+
return {'strategy': 'engine-default', 'port': port, 'modified': False}
|
|
117
|
+
deadline = time.monotonic()+timeout
|
|
118
|
+
error = None
|
|
119
|
+
while time.monotonic() < deadline:
|
|
120
|
+
if cancelled() or not checked_process(identity):
|
|
121
|
+
raise LocatorError('客机启动已取消或进程退出。')
|
|
122
|
+
image = None
|
|
123
|
+
try:
|
|
124
|
+
image = ProcessImage(identity)
|
|
125
|
+
site = locate_port(image)
|
|
126
|
+
except (LocatorError, OSError) as exc:
|
|
127
|
+
error = exc
|
|
128
|
+
else:
|
|
129
|
+
# 一旦找到唯一候选,写入错误不重试,不允许重复变更未知状态。
|
|
130
|
+
try:
|
|
131
|
+
if cancelled():
|
|
132
|
+
raise LocatorError('客机启动已取消,未写入。')
|
|
133
|
+
image.patch(site, port)
|
|
134
|
+
return {'strategy': 'nethernet-configuration-v1', 'port': port, 'modified': True,
|
|
135
|
+
'instruction_rva': site.instruction_rva, 'function_rva': site.function_rva}
|
|
136
|
+
finally:
|
|
137
|
+
image.close()
|
|
138
|
+
finally:
|
|
139
|
+
if image:
|
|
140
|
+
image.close()
|
|
141
|
+
time.sleep(.1)
|
|
142
|
+
raise LocatorError('自动定位 LAN 配置失败:' + str(error))
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""后台采集菜单文字;不移动系统指针、不激活窗口。"""
|
|
2
|
+
import hashlib
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
|
|
6
|
+
from .window import GameWindow
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def helper():
|
|
10
|
+
from ..host_assets import asset_directory
|
|
11
|
+
root = asset_directory('window_capture')
|
|
12
|
+
path = root/'mcpy-window-capture.exe'
|
|
13
|
+
manifest = json.loads((root/'manifest.json').read_text(encoding='utf-8'))
|
|
14
|
+
if manifest.get('ocr_protocol') != 1 or hashlib.sha256(path.read_bytes()).hexdigest() != manifest.get('executable_sha256'):
|
|
15
|
+
raise ValueError('自动加入需要新版 Windows 菜单识别组件;请更新完整 mcpywrap,或使用 --join-mode manual。')
|
|
16
|
+
return path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def observe(game):
|
|
20
|
+
window = GameWindow(game)
|
|
21
|
+
try:
|
|
22
|
+
result = subprocess.run([str(helper()), '--hwnd',str(window.hwnd),'--ocr'],
|
|
23
|
+
stdin=subprocess.DEVNULL,stdout=subprocess.PIPE,stderr=subprocess.PIPE,
|
|
24
|
+
creationflags=subprocess.CREATE_NO_WINDOW,timeout=12)
|
|
25
|
+
window.validate_process()
|
|
26
|
+
if result.returncode:
|
|
27
|
+
raise ValueError('菜单识别不可用:'+result.stderr.decode('utf-8','replace'))
|
|
28
|
+
if len(result.stdout)>512*1024:
|
|
29
|
+
raise ValueError('菜单识别结果过大')
|
|
30
|
+
data=json.loads(result.stdout)
|
|
31
|
+
if data.get('protocol') != 1:
|
|
32
|
+
raise ValueError('菜单识别协议不兼容')
|
|
33
|
+
return data
|
|
34
|
+
finally:
|
|
35
|
+
window.close()
|
|
@@ -310,6 +310,9 @@ class SafaiaChannel:
|
|
|
310
310
|
self.serial.release()
|
|
311
311
|
|
|
312
312
|
def close(self):
|
|
313
|
+
joiner = getattr(self, 'local_join', None)
|
|
314
|
+
if joiner:
|
|
315
|
+
joiner.stop.set()
|
|
313
316
|
self.stop_event.set()
|
|
314
317
|
with self.changed:
|
|
315
318
|
if self.client:
|
|
@@ -321,6 +324,8 @@ class SafaiaChannel:
|
|
|
321
324
|
for thread in (self.thread, self.discovery):
|
|
322
325
|
if thread.is_alive():
|
|
323
326
|
thread.join(timeout=1)
|
|
327
|
+
if joiner:
|
|
328
|
+
joiner.close()
|
|
324
329
|
|
|
325
330
|
|
|
326
331
|
class _ControlHandler(socketserver.BaseRequestHandler):
|
|
@@ -345,6 +350,8 @@ class _ControlHandler(socketserver.BaseRequestHandler):
|
|
|
345
350
|
group = membership(self.server.project, self.server.session)
|
|
346
351
|
if not group or not group.get('connection_verified') or request.get('kind') != 'python':
|
|
347
352
|
raise ValueError('客机尚未确认加入指定主机,或请求了未支持的资源热更')
|
|
353
|
+
if getattr(self.server.channel, 'joining', False) is True:
|
|
354
|
+
raise ValueError('客机正在自动加入,请等待连接校验完成后再执行调试操作。')
|
|
348
355
|
if request.get('action') == 'execute':
|
|
349
356
|
options = {'condition': request['condition']} if request.get('condition') is not None else {}
|
|
350
357
|
if options and not hasattr(self.server.channel, 'submit'):
|
|
@@ -200,8 +200,8 @@ def handoff(data):
|
|
|
200
200
|
'已有世界继续使用存档中的设置。')
|
|
201
201
|
from ..engines.backend import get_backend
|
|
202
202
|
result.update(get_backend(data.get('backend', 'windows')).handoff(data))
|
|
203
|
-
if data.get('network') and data['network'].get('join')
|
|
204
|
-
result.update(join='
|
|
203
|
+
if data.get('network') and data['network'].get('join') in ('manual','auto'):
|
|
204
|
+
result.update(join=data['network']['join'], local_host=data['network']['local_host'],
|
|
205
205
|
connection_verified=False, addons_assembled=False,
|
|
206
206
|
engine_version=data['network']['engine']['version'])
|
|
207
207
|
elif data.get('network'):
|
|
@@ -43,7 +43,7 @@ class Client:
|
|
|
43
43
|
except (URLError, TimeoutError, OSError) as error:
|
|
44
44
|
if isinstance(error, RemoteError): raise
|
|
45
45
|
failure = RemoteError('无法连接 mcpy App;连接失败不足以区分服务关闭、进程退出或端口错误', 'host_unavailable',
|
|
46
|
-
hint='
|
|
46
|
+
hint='请用户手动恢复开发连接(mcpy://service/start);不要自动调用唤起命令。主动暂停的服务需在“开发”页重新开启,已有操作先查询原任务。')
|
|
47
47
|
failure.details = {'endpoint': self.endpoint, 'recovery_uri': 'mcpy://service/start',
|
|
48
48
|
'last_known_service': profiles.cached_service(self.profile), 'live_state': 'unavailable'}
|
|
49
49
|
require_user(failure)
|
|
@@ -216,7 +216,7 @@ def pair(url, code, workspace, label='mcpy CLI', timeout=900):
|
|
|
216
216
|
saved['expires'] = time.time()+900
|
|
217
217
|
if resumable: profiles.write_private(receipt, saved)
|
|
218
218
|
import sys
|
|
219
|
-
print('等待你允许连接:可直接点击 mcpy 弹出的确认卡或通知中的「允许连接」。没有提示时再打开 mcpy
|
|
219
|
+
print('等待你允许连接:可直接点击 mcpy 弹出的确认卡或通知中的「允许连接」。没有提示时再打开 mcpy 的“开发”页。', file=sys.stderr)
|
|
220
220
|
end = time.monotonic()+timeout
|
|
221
221
|
while time.monotonic() < end:
|
|
222
222
|
result = request(base+'/result', credentials)
|
|
@@ -13,14 +13,14 @@ def require_user(error, operation_id=None):
|
|
|
13
13
|
if details.get('operation_state')!='waiting_user' and getattr(error,'code',None) not in USER_ACTION_CODES:
|
|
14
14
|
return False
|
|
15
15
|
offline=getattr(error,'code',None)=='host_unavailable'
|
|
16
|
-
message=('请在手机上手动打开 mcpy
|
|
16
|
+
message=('请在手机上手动打开 mcpy,在“开发”页确认开发连接已开启,连接恢复后可返回当前工具,再告诉我。若切换应用后又断连,请在该页打开省电设置,小米选择“无限制”。'
|
|
17
17
|
if offline else
|
|
18
18
|
'请在手机上手动切回 mcpy,继续这次启动或恢复游戏画面;完成后告诉我。')
|
|
19
19
|
if not offline:
|
|
20
20
|
message+='如果希望以后直接从开发工具启动,请在系统应用权限中允许 mcpy 的“后台弹出界面”,并检查画中画权限;各品牌入口可能不同。'
|
|
21
21
|
if getattr(error,'code',None)=='device_locked': message='请先解锁手机。'+message
|
|
22
22
|
instruction=('立即停止自动尝试,把 user_message 告诉用户并等待用户明确确认。'
|
|
23
|
-
'不要自动 run、restart
|
|
23
|
+
'不要自动 run、restart、重新构建、新建世界、循环轮询或执行 android-open、am start、恢复 URI 或其他唤起/权限绕过方式。'
|
|
24
24
|
'收到用户确认后,只查询或恢复原任务;不要把用户未回复当作已处理。')
|
|
25
25
|
details.update(needs_user_action=True,next_action='ask_user',retryable=False,
|
|
26
26
|
retry_policy='after_user_confirmation',user_message=message,agent_instruction=instruction)
|