PyMemoryEditor 3.3.3__tar.gz → 3.4.1__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.
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PKG-INFO +7 -4
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/__init__.py +1 -1
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/linux/process.py +3 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/macos/process.py +4 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/mcp/toolset.py +4 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/abstract.py +38 -8
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/process.py +4 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/conf.py +1 -1
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/pyproject.toml +11 -1
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/test_parsing.py +20 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/test_toolset.py +2 -2
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_write_safety.py +62 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/.flake8 +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/.gitignore +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/.readthedocs.yaml +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/CODE_OF_CONDUCT.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/CONTRIBUTING.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/LICENSE +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/Makefile +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/__main__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/_auto_refresh_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/_gc.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/_icon.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/_widgets.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/application.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/assets/icon.svg +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/cheat_entry.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/cheat_poll_worker.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/cheat_table.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/log_console_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/main_window.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/memory_map_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/memory_viewer_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/modules_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/open_process_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/pointer_chain_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/pointer_scan_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/results_view.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/scan_types.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/scan_worker.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/scanner_panel.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/threads_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/app/value_types.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/enums.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/linux/functions.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/linux/libc.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/linux/types.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/macos/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/macos/functions.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/macos/libsystem.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/macos/types.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/mcp/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/mcp/__main__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/mcp/config.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/mcp/policy.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/mcp/procinfo.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/mcp/server.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/mcp/session.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/_guard.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/errors.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/info.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/module_info.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/pointer_scan.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/region.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/remote_pointer.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/scanning.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/thread_info.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/process/util.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/py.typed +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/util/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/util/convert.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/util/pattern.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/util/scan.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/util/scan_numpy.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/memory_allocation_states.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/memory_protections.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/memory_types.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/process_operations.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/standard_access_rights.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/functions.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/types.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/README.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/SECURITY.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/assets/screenshots/app.png +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/assets/social-preview.html +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/assets/social-preview.png +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/_static/.gitkeep +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/_static/custom.css +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/_templates/layout.html +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/enums.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/errors.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/index.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/memory-region.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/module-info.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/openprocess.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/pointer-path.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/remote-pointer.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/thread-info.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/api/utilities.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/app.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/contributing.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/funding.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/glossary.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/allocate-free.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/logging.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/memory-regions.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/modules-threads.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/opening-process.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/pattern-scan.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/pointer-scan.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/pointers.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/read-write.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/guide/searching.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/index.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/installation.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/license.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/mcp.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/platform-notes.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/quickstart.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/requirements.txt +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/troubleshooting.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/docs/why.md +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/conftest.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_app_cheat_entry.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_app_gc.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_app_pointer_parsing.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_app_scan_request.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_app_smoke.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_app_value_types.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_app_widgets.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_auto_refresh_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_cheat_poll_worker.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_main_window_lifecycle.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_open_process_dialog.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_refine_scan_worker.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/app/test_results_model.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/conftest.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/core/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/core/test_abstract_process.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/core/test_errors.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/core/test_logger.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/conftest.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/test_argument_fuzz.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/test_fake_fidelity.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/test_live.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/test_policy.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/test_registry.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/test_server.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/mcp_server/test_session.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_allocate_free.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_bufflength_inference.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_editor.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_narrow_width_consistency.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_partial_io.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_read_into.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_search_argument_errors.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_str_boundary.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_str_decode_consistency.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_typed_accessors.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_unaligned_scan.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_write_span.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/memory/test_write_str_bytes_width.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_bitness.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_bitness_fallback_warns.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_closed_generators.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_linux_error_hints.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_linux_pid_reuse.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_linux_proc_decoding.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_linux_snapshot_passthrough.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_linux_types.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_macos_close.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_macos_load_commands.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_macos_protect.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_macos_region_walk.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_macos_snapshot.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_macos_threads.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_macos_transient_reads.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_macos_types.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_win32_backend_guards.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_win32_permissions.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_win32_region_walk.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_win32_toolhelp.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/platforms/test_win32_wow64.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/pointer/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/pointer/test_pointer_chain.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/pointer/test_pointer_scan.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/pointer/test_remote_pointer.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/process/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/process/test_foreign_process.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/process/test_memory_region.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/process/test_module_enumeration.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/process/test_partial_name_match.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/process/test_process_enumeration.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/process/test_process_lookup.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/process/test_thread_enumeration.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/__init__.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/_fakes.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/test_chunking_integration.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/test_pattern_compile.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/test_pattern_scan.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/test_region_snapshot.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/test_scan.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/test_scan_numpy.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/test_scan_properties.py +0 -0
- {pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/tests/scan/test_scanning_helper.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: PyMemoryEditor
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.1
|
|
4
4
|
Summary: Read, write and scan process memory in a few lines of Python — Cheat Engine-style scans, pointer chains and AOB search on Windows, Linux and macOS.
|
|
5
5
|
Project-URL: Homepage, https://github.com/JeanExtreme002/PyMemoryEditor
|
|
6
6
|
Project-URL: Documentation, https://pymemoryeditor.readthedocs.io
|
|
@@ -38,7 +38,8 @@ Classifier: Typing :: Typed
|
|
|
38
38
|
Requires-Python: >=3.10
|
|
39
39
|
Provides-Extra: app
|
|
40
40
|
Requires-Dist: psutil<8,>=5.9; extra == 'app'
|
|
41
|
-
Requires-Dist: pyside6<
|
|
41
|
+
Requires-Dist: pyside6<6.12,>=6.5; (python_version < '3.12') and extra == 'app'
|
|
42
|
+
Requires-Dist: pyside6<7,>=6.5; (python_version >= '3.12') and extra == 'app'
|
|
42
43
|
Provides-Extra: dev
|
|
43
44
|
Requires-Dist: build; extra == 'dev'
|
|
44
45
|
Requires-Dist: flake8; extra == 'dev'
|
|
@@ -46,7 +47,8 @@ Requires-Dist: hypothesis; extra == 'dev'
|
|
|
46
47
|
Requires-Dist: mcp<3,>=2; extra == 'dev'
|
|
47
48
|
Requires-Dist: mypy; extra == 'dev'
|
|
48
49
|
Requires-Dist: psutil<8,>=5.9; extra == 'dev'
|
|
49
|
-
Requires-Dist: pyside6<
|
|
50
|
+
Requires-Dist: pyside6<6.12,>=6.5; (python_version < '3.12') and extra == 'dev'
|
|
51
|
+
Requires-Dist: pyside6<7,>=6.5; (python_version >= '3.12') and extra == 'dev'
|
|
50
52
|
Requires-Dist: pytest; extra == 'dev'
|
|
51
53
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
52
54
|
Requires-Dist: pytest-qt; extra == 'dev'
|
|
@@ -60,7 +62,8 @@ Provides-Extra: tests
|
|
|
60
62
|
Requires-Dist: hypothesis; extra == 'tests'
|
|
61
63
|
Requires-Dist: mcp<3,>=2; extra == 'tests'
|
|
62
64
|
Requires-Dist: psutil<8,>=5.9; extra == 'tests'
|
|
63
|
-
Requires-Dist: pyside6<
|
|
65
|
+
Requires-Dist: pyside6<6.12,>=6.5; (python_version < '3.12') and extra == 'tests'
|
|
66
|
+
Requires-Dist: pyside6<7,>=6.5; (python_version >= '3.12') and extra == 'tests'
|
|
64
67
|
Requires-Dist: pytest; extra == 'tests'
|
|
65
68
|
Requires-Dist: pytest-cov; extra == 'tests'
|
|
66
69
|
Requires-Dist: pytest-qt; extra == 'tests'
|
|
@@ -196,12 +196,14 @@ class LinuxProcess(AbstractProcess):
|
|
|
196
196
|
bufflength: Optional[int] = None,
|
|
197
197
|
) -> T:
|
|
198
198
|
self.__require_target(hot_path=True)
|
|
199
|
+
self._require_address(address)
|
|
199
200
|
return read_process_memory(
|
|
200
201
|
self.pid, address, pytype, resolve_bufflength(pytype, bufflength)
|
|
201
202
|
)
|
|
202
203
|
|
|
203
204
|
def read_process_memory_into(self, address: int, buffer: object) -> int:
|
|
204
205
|
self.__require_target(hot_path=True)
|
|
206
|
+
self._require_address(address)
|
|
205
207
|
return read_process_memory_into(self.pid, address, buffer)
|
|
206
208
|
|
|
207
209
|
def _scan_addresses(
|
|
@@ -270,6 +272,7 @@ class LinuxProcess(AbstractProcess):
|
|
|
270
272
|
value: Union[bool, int, float, str, bytes] = UNSET,
|
|
271
273
|
) -> Union[bool, int, float, str, bytes]:
|
|
272
274
|
self.__require_target()
|
|
275
|
+
self._require_address(address)
|
|
273
276
|
w_pytype, w_length, w_value = prepare_write(pytype, bufflength, value)
|
|
274
277
|
write_process_memory(self.pid, address, w_pytype, w_length, w_value)
|
|
275
278
|
return value
|
|
@@ -255,12 +255,14 @@ class MacProcess(AbstractProcess):
|
|
|
255
255
|
bufflength: Optional[int] = None,
|
|
256
256
|
) -> T:
|
|
257
257
|
self._require_open()
|
|
258
|
+
self._require_address(address)
|
|
258
259
|
return read_process_memory(
|
|
259
260
|
self.__task, address, pytype, resolve_bufflength(pytype, bufflength)
|
|
260
261
|
)
|
|
261
262
|
|
|
262
263
|
def read_process_memory_into(self, address: int, buffer: object) -> int:
|
|
263
264
|
self._require_open()
|
|
265
|
+
self._require_address(address)
|
|
264
266
|
return read_process_memory_into(self.__task, address, buffer)
|
|
265
267
|
|
|
266
268
|
def write_process_memory(
|
|
@@ -296,6 +298,7 @@ class MacProcess(AbstractProcess):
|
|
|
296
298
|
:param value: value to be written.
|
|
297
299
|
"""
|
|
298
300
|
self._require_open()
|
|
301
|
+
self._require_address(address)
|
|
299
302
|
w_pytype, w_length, w_value = prepare_write(pytype, bufflength, value)
|
|
300
303
|
write_process_memory(self.__task, address, w_pytype, w_length, w_value)
|
|
301
304
|
return value
|
|
@@ -308,6 +311,7 @@ class MacProcess(AbstractProcess):
|
|
|
308
311
|
|
|
309
312
|
def free_memory(self, address: int, size: int = 0) -> bool:
|
|
310
313
|
self._require_open()
|
|
314
|
+
self._require_address(address)
|
|
311
315
|
# mach_vm_deallocate needs the exact size. Reuse the tracked size when
|
|
312
316
|
# the caller doesn't supply one.
|
|
313
317
|
actual_size = size or self.__allocations.get(address, 0)
|
|
@@ -424,6 +424,10 @@ def _parse_signed_int(raw: Any, field: str) -> int:
|
|
|
424
424
|
negative = text.startswith("-")
|
|
425
425
|
body = text[1:] if negative else text
|
|
426
426
|
try:
|
|
427
|
+
# int() accepts a sign of its own, so "--5" would strip to "-5" and
|
|
428
|
+
# the two minuses cancel: a write of "--5" stored +5. One sign only.
|
|
429
|
+
if negative and body.lstrip()[:1] in ("-", "+"):
|
|
430
|
+
raise ValueError(body)
|
|
427
431
|
value = int(body, 16) if body[:2].lower() == "0x" else int(body, 10)
|
|
428
432
|
except (ValueError, IndexError):
|
|
429
433
|
raise ToolError(
|
|
@@ -53,15 +53,22 @@ _POINTER_SCAN_FORMAT = "pymemoryeditor-pointerscan"
|
|
|
53
53
|
_POINTER_SCAN_VERSION = 1
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
class
|
|
57
|
-
"""
|
|
56
|
+
class _AddressOutOfRangeError(ValueError, OSError):
|
|
57
|
+
"""An address (or a pointer chain step) lies outside the address space.
|
|
58
58
|
|
|
59
59
|
A ``ValueError`` as documented, and also an ``OSError`` because the failed
|
|
60
60
|
read of the wrapped address it replaces raised one: callers that guard a
|
|
61
|
-
stale chain with ``except OSError`` keep working.
|
|
61
|
+
stale chain or a bad address with ``except OSError`` keep working.
|
|
62
62
|
"""
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
# One past the highest address this host can pass to the OS. ctypes masks an
|
|
66
|
+
# address argument to the width of the C type it is converted to instead of
|
|
67
|
+
# rejecting it, so a 32-bit Python would turn 0x1_0000_1000 into 0x1000 just as
|
|
68
|
+
# a 64-bit one turns 2**64 + 0x1000 into 0x1000.
|
|
69
|
+
_HOST_ADDRESS_END = min(ADDRESS_SPACE_END, 1 << (8 * ctypes.sizeof(ctypes.c_void_p)))
|
|
70
|
+
|
|
71
|
+
|
|
65
72
|
class AbstractProcess(ABC):
|
|
66
73
|
"""
|
|
67
74
|
Abstract class to represent a process.
|
|
@@ -205,6 +212,23 @@ class AbstractProcess(ABC):
|
|
|
205
212
|
if not self._target_alive():
|
|
206
213
|
raise ProcessIDNotExistsError(self.pid)
|
|
207
214
|
|
|
215
|
+
@staticmethod
|
|
216
|
+
def _require_address(address: int) -> None:
|
|
217
|
+
"""
|
|
218
|
+
Refuse an address outside ``[0, host address space)`` before it
|
|
219
|
+
reaches ctypes, which would mask it to the C type's width and read,
|
|
220
|
+
write or free a *different*, wrapped address instead of failing.
|
|
221
|
+
"""
|
|
222
|
+
if not 0 <= address < _HOST_ADDRESS_END:
|
|
223
|
+
raise _AddressOutOfRangeError(
|
|
224
|
+
"address %s is outside the %d-bit address space; it would "
|
|
225
|
+
"wrap around to a different address."
|
|
226
|
+
% (
|
|
227
|
+
("-0x%X" % -address) if address < 0 else ("0x%X" % address),
|
|
228
|
+
_HOST_ADDRESS_END.bit_length() - 1,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
208
232
|
@abstractmethod
|
|
209
233
|
def _detect_is_64bit(self) -> Optional[bool]:
|
|
210
234
|
"""
|
|
@@ -736,7 +760,9 @@ class AbstractProcess(ABC):
|
|
|
736
760
|
and ``convert_from_byte_array``. Callers that need the original
|
|
737
761
|
bytes verbatim (no decoding) should pass ``pytype=bytes``.
|
|
738
762
|
|
|
739
|
-
:raises ValueError: if ``pytype`` is unsupported
|
|
763
|
+
:raises ValueError: if ``pytype`` is unsupported, or ``address`` is
|
|
764
|
+
negative or past the end of the address space (also an
|
|
765
|
+
``OSError``, so ``except OSError`` still catches it).
|
|
740
766
|
:raises OSError: if the read fails, or returns fewer bytes than
|
|
741
767
|
requested (e.g. the range crosses an unreadable/freed page).
|
|
742
768
|
"""
|
|
@@ -770,7 +796,9 @@ class AbstractProcess(ABC):
|
|
|
770
796
|
|
|
771
797
|
:raises TypeError: if ``buffer`` is not a writable buffer (e.g. an
|
|
772
798
|
immutable ``bytes`` object).
|
|
773
|
-
:raises ValueError: if ``buffer`` is empty or not contiguous
|
|
799
|
+
:raises ValueError: if ``buffer`` is empty or not contiguous, or
|
|
800
|
+
``address`` is outside the address space (as in
|
|
801
|
+
:meth:`read_process_memory`).
|
|
774
802
|
:raises OSError: if the read fails, or returns fewer bytes than
|
|
775
803
|
requested (e.g. the range crosses an unreadable/freed page).
|
|
776
804
|
|
|
@@ -821,7 +849,8 @@ class AbstractProcess(ABC):
|
|
|
821
849
|
(``write_process_memory(address, int, 4, 99)``).
|
|
822
850
|
:raises ValueError: if ``pytype`` is unsupported, or if an ``int``
|
|
823
851
|
``value`` does not fit in ``bufflength`` bytes (rejected up front
|
|
824
|
-
rather than silently truncated)
|
|
852
|
+
rather than silently truncated), or if ``address`` is outside the
|
|
853
|
+
address space (as in :meth:`read_process_memory`).
|
|
825
854
|
:raises OSError: if the write fails, or fewer bytes than requested are
|
|
826
855
|
written (e.g. the range crosses an unwritable/freed page).
|
|
827
856
|
:return: the original ``value`` passed in.
|
|
@@ -1080,7 +1109,8 @@ class AbstractProcess(ABC):
|
|
|
1080
1109
|
:return: ``True`` on success.
|
|
1081
1110
|
|
|
1082
1111
|
:raises ValueError: on macOS and Windows, for a region this object did
|
|
1083
|
-
not allocate when no ``size`` is given
|
|
1112
|
+
not allocate when no ``size`` is given, or for an ``address``
|
|
1113
|
+
outside the address space.
|
|
1084
1114
|
:raises NotImplementedError: on Linux (see :meth:`allocate_memory`).
|
|
1085
1115
|
"""
|
|
1086
1116
|
raise NotImplementedError()
|
|
@@ -1190,7 +1220,7 @@ class AbstractProcess(ABC):
|
|
|
1190
1220
|
# it, so `ptr + offset` below zero or past 2**64 would silently
|
|
1191
1221
|
# read a *different*, wrapped address. Refuse it by name instead.
|
|
1192
1222
|
if not 0 <= addr < ADDRESS_SPACE_END:
|
|
1193
|
-
raise
|
|
1223
|
+
raise _AddressOutOfRangeError(
|
|
1194
1224
|
"pointer chain step produced address %s, outside the "
|
|
1195
1225
|
"64-bit address space; the chain is stale (a hop read "
|
|
1196
1226
|
"NULL or garbage) or an offset is wrong."
|
|
@@ -303,6 +303,7 @@ class WindowsProcess(AbstractProcess):
|
|
|
303
303
|
) -> T:
|
|
304
304
|
self._require_open()
|
|
305
305
|
self.__require_read()
|
|
306
|
+
self._require_address(address)
|
|
306
307
|
return ReadProcessMemory(
|
|
307
308
|
self.__process_handle,
|
|
308
309
|
address,
|
|
@@ -313,6 +314,7 @@ class WindowsProcess(AbstractProcess):
|
|
|
313
314
|
def read_process_memory_into(self, address: int, buffer: object) -> int:
|
|
314
315
|
self._require_open()
|
|
315
316
|
self.__require_read()
|
|
317
|
+
self._require_address(address)
|
|
316
318
|
return ReadProcessMemoryInto(self.__process_handle, address, buffer)
|
|
317
319
|
|
|
318
320
|
def write_process_memory(
|
|
@@ -324,6 +326,7 @@ class WindowsProcess(AbstractProcess):
|
|
|
324
326
|
) -> Union[bool, int, float, str, bytes]:
|
|
325
327
|
self._require_open()
|
|
326
328
|
self.__require_write()
|
|
329
|
+
self._require_address(address)
|
|
327
330
|
w_pytype, w_length, w_value = prepare_write(pytype, bufflength, value)
|
|
328
331
|
WriteProcessMemory(
|
|
329
332
|
self.__process_handle,
|
|
@@ -344,6 +347,7 @@ class WindowsProcess(AbstractProcess):
|
|
|
344
347
|
def free_memory(self, address: int, size: int = 0) -> bool:
|
|
345
348
|
self._require_open()
|
|
346
349
|
self.__require_operation()
|
|
350
|
+
self._require_address(address)
|
|
347
351
|
# MEM_RELEASE tears down whatever VirtualAlloc'd block starts at
|
|
348
352
|
# `address` -- a heap segment or a thread stack just as readily as one
|
|
349
353
|
# of ours -- so an address this object did not allocate is refused
|
|
@@ -23,7 +23,7 @@ copyright = f"{datetime.now().year}, {author}"
|
|
|
23
23
|
try:
|
|
24
24
|
from PyMemoryEditor import __version__ as release
|
|
25
25
|
except Exception: # pragma: no cover - docs can build without the package installed
|
|
26
|
-
release = "3.
|
|
26
|
+
release = "3.4.1"
|
|
27
27
|
|
|
28
28
|
version = ".".join(release.split(".")[:2])
|
|
29
29
|
|
|
@@ -87,8 +87,18 @@ tests = [
|
|
|
87
87
|
# psutil 7 removed nothing the app uses (Process.name/create_time,
|
|
88
88
|
# pid_exists, process_iter with pid/name/username, and the three exceptions);
|
|
89
89
|
# the app suite passes on 7.2.2.
|
|
90
|
+
#
|
|
91
|
+
# PySide6 6.12.0 is excluded below Python 3.12: its Linux wheels drop a
|
|
92
|
+
# reference to ``None`` on ordinary void Qt calls (upstream PYSIDE-3474), so
|
|
93
|
+
# a long enough session -- a CI worker that has run the app suite -- aborts
|
|
94
|
+
# with ``Fatal Python error: none_dealloc: deallocating None``. On 3.12+ the
|
|
95
|
+
# singletons are immortal (PEP 683) and the leak is harmless, which is why
|
|
96
|
+
# only the 3.10/3.11 cells of the matrix were exposed. The crash took the
|
|
97
|
+
# xdist worker down, and the session then hung until the job's 20-minute
|
|
98
|
+
# timeout. Lift the cap once a 6.12.x release passes the app suite on 3.11.
|
|
90
99
|
app = [
|
|
91
|
-
"PySide6>=6.5,<
|
|
100
|
+
"PySide6>=6.5,<6.12; python_version < '3.12'",
|
|
101
|
+
"PySide6>=6.5,<7; python_version >= '3.12'",
|
|
92
102
|
"psutil>=5.9,<8",
|
|
93
103
|
]
|
|
94
104
|
# The Model Context Protocol server (PyMemoryEditor.mcp), which lets an AI
|
|
@@ -158,6 +158,15 @@ class TestParseOffset:
|
|
|
158
158
|
assert parse_offset(hex(MAX_OFFSET_MAGNITUDE)) == MAX_OFFSET_MAGNITUDE
|
|
159
159
|
assert parse_offset(-MAX_OFFSET_MAGNITUDE) == -MAX_OFFSET_MAGNITUDE
|
|
160
160
|
|
|
161
|
+
@pytest.mark.parametrize("raw", ["--8", "-+8", "- -8"])
|
|
162
|
+
def test_a_doubled_sign_is_refused_not_cancelled(self, raw):
|
|
163
|
+
"""``"--8"`` used to strip one minus and let ``int()`` read the other,
|
|
164
|
+
so the two cancelled and the hop walked forwards instead of back."""
|
|
165
|
+
from PyMemoryEditor.mcp.toolset import parse_offset
|
|
166
|
+
|
|
167
|
+
with pytest.raises(ToolError, match="Could not parse"):
|
|
168
|
+
parse_offset(raw)
|
|
169
|
+
|
|
161
170
|
|
|
162
171
|
class TestNumericArgumentsReachTheModelAsToolError:
|
|
163
172
|
"""Only ``ToolError`` text reaches the model.
|
|
@@ -253,6 +262,17 @@ class TestParseValue:
|
|
|
253
262
|
with pytest.raises(ToolError, match="hex"):
|
|
254
263
|
parse_value(bytes, raw)
|
|
255
264
|
|
|
265
|
+
@pytest.mark.parametrize("raw", ["--5", "-+5", "- -5", "--0x5"])
|
|
266
|
+
def test_int_refuses_a_doubled_sign_instead_of_cancelling_it(self, raw):
|
|
267
|
+
"""``"--5"`` parsed as +5, so a ``write_value`` of it stored the
|
|
268
|
+
opposite sign of what was sent. A typo must fail, not flip."""
|
|
269
|
+
with pytest.raises(ToolError, match="Could not parse"):
|
|
270
|
+
parse_value(int, raw)
|
|
271
|
+
|
|
272
|
+
@pytest.mark.parametrize("raw, expected", [("+5", 5), ("- 5", -5)])
|
|
273
|
+
def test_a_single_sign_still_parses(self, raw, expected):
|
|
274
|
+
assert parse_value(int, raw) == expected
|
|
275
|
+
|
|
256
276
|
def test_int_rejects_junk_with_actionable_message(self):
|
|
257
277
|
with pytest.raises(ToolError, match="0x"):
|
|
258
278
|
parse_value(int, "one hundred")
|
|
@@ -672,10 +672,10 @@ class TestPointers:
|
|
|
672
672
|
):
|
|
673
673
|
# The library's range error is both a ValueError and an OSError; it
|
|
674
674
|
# must read as a stale chain, not as a malformed argument.
|
|
675
|
-
from PyMemoryEditor.process.abstract import
|
|
675
|
+
from PyMemoryEditor.process.abstract import _AddressOutOfRangeError
|
|
676
676
|
|
|
677
677
|
def resolve(*args, **kwargs):
|
|
678
|
-
raise
|
|
678
|
+
raise _AddressOutOfRangeError("pointer chain step produced address -0x100")
|
|
679
679
|
|
|
680
680
|
monkeypatch.setattr(fake_process, "resolve_pointer_chain", resolve)
|
|
681
681
|
with pytest.raises(ToolError) as caught:
|
|
@@ -208,3 +208,65 @@ def test_write_to_unmapped_address_raises():
|
|
|
208
208
|
process.write_int(0x1, 7)
|
|
209
209
|
finally:
|
|
210
210
|
process.close()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# --------------------------------------------------------------------------- #
|
|
214
|
+
# (3) an address outside the address space must raise, not wrap.
|
|
215
|
+
# ctypes masks an address argument to 64 bits (32 on a 32-bit Python), so
|
|
216
|
+
# ``2**64 + addr`` used to read and write ``addr`` itself, with no error.
|
|
217
|
+
# --------------------------------------------------------------------------- #
|
|
218
|
+
|
|
219
|
+
_ADDRESS_WIDTH = 8 * ctypes.sizeof(ctypes.c_void_p)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _wrapping_addresses(address):
|
|
223
|
+
"""Addresses that ctypes would mask onto ``address``."""
|
|
224
|
+
return [address + (1 << _ADDRESS_WIDTH), address - (1 << _ADDRESS_WIDTH)]
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@pytest.fixture
|
|
228
|
+
def own_process():
|
|
229
|
+
process = OpenProcess(pid=os.getpid())
|
|
230
|
+
try:
|
|
231
|
+
yield process
|
|
232
|
+
finally:
|
|
233
|
+
process.close()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@pytest.mark.parametrize("which", [0, 1], ids=["past-the-end", "negative"])
|
|
237
|
+
def test_write_to_a_wrapping_address_raises_and_leaves_memory_untouched(own_process, which):
|
|
238
|
+
target = ctypes.c_int32(1234)
|
|
239
|
+
wrapped = _wrapping_addresses(ctypes.addressof(target))[which]
|
|
240
|
+
|
|
241
|
+
with pytest.raises(ValueError, match="outside the %d-bit address space" % _ADDRESS_WIDTH):
|
|
242
|
+
own_process.write_process_memory(wrapped, int, 4, 4321)
|
|
243
|
+
with pytest.raises(ValueError, match="outside"):
|
|
244
|
+
own_process.write_int(wrapped, 4321)
|
|
245
|
+
|
|
246
|
+
assert target.value == 1234
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@pytest.mark.parametrize("which", [0, 1], ids=["past-the-end", "negative"])
|
|
250
|
+
def test_read_of_a_wrapping_address_raises(own_process, which):
|
|
251
|
+
target = ctypes.c_int32(1234)
|
|
252
|
+
wrapped = _wrapping_addresses(ctypes.addressof(target))[which]
|
|
253
|
+
|
|
254
|
+
with pytest.raises(ValueError, match="outside"):
|
|
255
|
+
own_process.read_process_memory(wrapped, int, 4)
|
|
256
|
+
with pytest.raises(ValueError, match="outside"):
|
|
257
|
+
own_process.read_process_memory_into(wrapped, bytearray(4))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def test_out_of_range_address_is_still_an_os_error(own_process):
|
|
261
|
+
"""A negative address used to fail inside the OS read with ``OSError``;
|
|
262
|
+
callers guarding a bad address with ``except OSError`` must still catch it."""
|
|
263
|
+
with pytest.raises(OSError, match="outside"):
|
|
264
|
+
own_process.read_process_memory(-1, int, 4)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_the_last_address_of_the_space_is_not_refused_by_the_range_check(own_process):
|
|
268
|
+
"""The top address is in range: whatever happens there is the OS's call
|
|
269
|
+
(unmapped, so ``OSError``), not the range check's ``ValueError``."""
|
|
270
|
+
with pytest.raises(OSError) as error:
|
|
271
|
+
own_process.read_process_memory((1 << _ADDRESS_WIDTH) - 1, bytes, 1)
|
|
272
|
+
assert not isinstance(error.value, ValueError)
|
|
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
|
|
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
|
{pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/memory_allocation_states.py
RENAMED
|
File without changes
|
{pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/memory_protections.py
RENAMED
|
File without changes
|
|
File without changes
|
{pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/process_operations.py
RENAMED
|
File without changes
|
{pymemoryeditor-3.3.3 → pymemoryeditor-3.4.1}/PyMemoryEditor/win32/enums/standard_access_rights.py
RENAMED
|
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
|