PyMemoryEditor 3.2.0__tar.gz → 3.3.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.1/CONTRIBUTING.md +199 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/Makefile +37 -12
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PKG-INFO +12 -6
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/__init__.py +1 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/_auto_refresh_dialog.py +6 -7
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/_widgets.py +7 -7
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/application.py +5 -6
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/cheat_poll_worker.py +23 -17
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/cheat_table.py +78 -38
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/main_window.py +84 -30
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/memory_viewer_dialog.py +5 -5
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/open_process_dialog.py +1 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/pointer_chain_dialog.py +2 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/pointer_scan_dialog.py +85 -6
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/results_view.py +76 -13
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/scan_worker.py +10 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/scanner_panel.py +1 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/value_types.py +3 -3
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/linux/functions.py +58 -25
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/linux/libc.py +2 -3
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/linux/process.py +43 -88
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/functions.py +150 -56
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/process.py +60 -102
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/config.py +46 -21
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/policy.py +191 -24
- pymemoryeditor-3.3.1/PyMemoryEditor/mcp/procinfo.py +258 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/server.py +232 -25
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/session.py +74 -22
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/toolset.py +318 -100
- pymemoryeditor-3.3.1/PyMemoryEditor/process/_guard.py +43 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/abstract.py +273 -31
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/info.py +3 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/pointer_scan.py +134 -13
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/region.py +10 -2
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/scanning.py +60 -35
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/__init__.py +1 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/convert.py +13 -3
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/pattern.py +11 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/scan.py +53 -2
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/memory_protections.py +1 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/functions.py +115 -40
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/process.py +48 -100
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/SECURITY.md +25 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/errors.md +18 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/utilities.md +13 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/conf.py +1 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/allocate-free.md +12 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/modules-threads.md +10 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/pattern-scan.md +6 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/pointer-scan.md +4 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/pointers.md +9 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/read-write.md +10 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/searching.md +5 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/installation.md +2 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/mcp.md +70 -31
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/platform-notes.md +34 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/pyproject.toml +38 -24
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_cheat_entry.py +141 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_smoke.py +183 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_cheat_poll_worker.py +8 -3
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_main_window_lifecycle.py +128 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_results_model.py +61 -3
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/conftest.py +35 -0
- pymemoryeditor-3.3.1/tests/core/test_abstract_process.py +208 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_parsing.py +25 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_policy.py +211 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_server.py +257 -1
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_session.py +95 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_toolset.py +144 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_search_argument_errors.py +56 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_typed_accessors.py +43 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_bitness_fallback_warns.py +1 -1
- pymemoryeditor-3.3.1/tests/platforms/test_closed_generators.py +186 -0
- pymemoryeditor-3.3.1/tests/platforms/test_linux_proc_decoding.py +106 -0
- pymemoryeditor-3.3.1/tests/platforms/test_linux_snapshot_passthrough.py +109 -0
- pymemoryeditor-3.3.1/tests/platforms/test_macos_close.py +75 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_load_commands.py +63 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_protect.py +77 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_region_walk.py +76 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_snapshot.py +49 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_backend_guards.py +249 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/pointer/test_pointer_chain.py +29 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/pointer/test_pointer_scan.py +302 -2
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_process_lookup.py +12 -0
- pymemoryeditor-3.3.1/tests/scan/_fakes.py +65 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_chunking_integration.py +27 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_pattern_scan.py +101 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_region_snapshot.py +58 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_scan.py +51 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_scan_numpy.py +98 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_scanning_helper.py +141 -0
- pymemoryeditor-3.2.0/CONTRIBUTING.md +0 -113
- pymemoryeditor-3.2.0/tests/platforms/test_linux_snapshot_passthrough.py +0 -60
- pymemoryeditor-3.2.0/tests/pointer/test_pointer_review_fixes.py +0 -224
- pymemoryeditor-3.2.0/tests/scan/test_scan_review_fixes.py +0 -443
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/.flake8 +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/.gitignore +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/.readthedocs.yaml +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/CODE_OF_CONDUCT.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/LICENSE +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/__main__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/_icon.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/assets/icon.svg +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/cheat_entry.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/log_console_dialog.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/memory_map_dialog.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/modules_dialog.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/scan_types.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/app/threads_dialog.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/enums.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/linux/types.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/libsystem.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/macos/types.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/mcp/__main__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/errors.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/module_info.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/remote_pointer.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/thread_info.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/process/util.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/py.typed +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/util/scan_numpy.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/memory_allocation_states.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/memory_types.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/process_operations.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/enums/standard_access_rights.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/PyMemoryEditor/win32/types.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/README.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/assets/screenshots/app.png +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/assets/social-preview.html +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/assets/social-preview.png +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/_static/.gitkeep +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/_static/custom.css +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/_templates/layout.html +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/enums.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/index.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/memory-region.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/module-info.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/openprocess.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/pointer-path.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/remote-pointer.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/api/thread-info.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/app.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/contributing.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/funding.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/glossary.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/logging.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/memory-regions.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/guide/opening-process.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/index.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/license.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/quickstart.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/requirements.txt +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/troubleshooting.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/docs/why.md +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_pointer_parsing.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_scan_request.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_value_types.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_app_widgets.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_auto_refresh_dialog.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_open_process_dialog.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/app/test_refine_scan_worker.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/core/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/core/test_errors.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/core/test_logger.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/conftest.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_argument_fuzz.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_fake_fidelity.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_live.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/mcp_server/test_registry.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_allocate_free.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_bufflength_inference.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_editor.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_narrow_width_consistency.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_partial_io.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_read_into.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_str_boundary.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_str_decode_consistency.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_unaligned_scan.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_write_safety.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_write_span.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/memory/test_write_str_bytes_width.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_bitness.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_linux_error_hints.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_linux_pid_reuse.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_linux_types.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_threads.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_transient_reads.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_macos_types.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_permissions.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_region_walk.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_toolhelp.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/platforms/test_win32_wow64.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/pointer/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/pointer/test_remote_pointer.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_foreign_process.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_memory_region.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_module_enumeration.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_partial_name_match.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_process_enumeration.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/process/test_thread_enumeration.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/__init__.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_pattern_compile.py +0 -0
- {pymemoryeditor-3.2.0 → pymemoryeditor-3.3.1}/tests/scan/test_scan_properties.py +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Contributing to PyMemoryEditor
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing!
|
|
4
|
+
|
|
5
|
+
## Development setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python -m venv venv
|
|
9
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
10
|
+
make install-dev # pip install -e ".[dev]"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The `dev` extra installs the full toolchain and the GUI deps.
|
|
14
|
+
|
|
15
|
+
The `Makefile` is the single source of truth for the dev commands below — run
|
|
16
|
+
`make help` to see every target (docs, build, coverage, etc.). The raw command
|
|
17
|
+
each target wraps is shown in parentheses if you'd rather run it directly.
|
|
18
|
+
|
|
19
|
+
## Running the test suite
|
|
20
|
+
|
|
21
|
+
The tests read and write the memory of the test process itself; they should run
|
|
22
|
+
on any supported platform without elevated privileges.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
make test-fast # pytest tests -m "not slow" (~20 s: unit, GUI and MCP tests)
|
|
26
|
+
make test # pytest tests -v (everything, incl. live whole-process scans)
|
|
27
|
+
make test-mcp # pytest tests/mcp_server (the MCP server suite, ~10 s)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`test-fast` is the loop to iterate on; the `slow` and `scan_heavy` markers
|
|
31
|
+
(defined in `pyproject.toml`) hold the tests that scan the test process's whole
|
|
32
|
+
address space, which takes minutes on CI runners. The scan property tests
|
|
33
|
+
(`tests/scan/test_scan_properties.py`) are marked `slow` too, so run them
|
|
34
|
+
directly when you touch the scan kernels:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pytest tests/scan/test_scan_properties.py
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
On CI the Hypothesis tests run under a derandomized `ci` profile (selected
|
|
41
|
+
automatically when `CI` is set, or with `HYPOTHESIS_PROFILE=ci`), so a failure
|
|
42
|
+
reproduces on re-run; locally they keep exploring new examples.
|
|
43
|
+
|
|
44
|
+
### Testing one platform
|
|
45
|
+
|
|
46
|
+
The suite always exercises the backend of the OS it runs on (`win32/`,
|
|
47
|
+
`linux/` or `macos/`); tests written for another platform's backend skip
|
|
48
|
+
themselves.
|
|
49
|
+
So testing a platform means running the suite on it -- locally, in a VM, or
|
|
50
|
+
by opening a PR and reading that OS's CI cells. To mirror one CI cell on its
|
|
51
|
+
own OS:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
QT_QPA_PLATFORM=offscreen pytest tests --ignore=tests/mcp_server # build / build-macos
|
|
55
|
+
pytest tests/mcp_server -p no:pytest-qt # build-mcp (Linux)
|
|
56
|
+
pytest tests/mcp_server -p no:pytest-qt -m "not scan_heavy" # build-mcp (Windows, macOS)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`pytest tests/platforms` runs only the platform-specific regression tests;
|
|
60
|
+
add `-k linux` (or `win32`, `macos`, `bitness`) to narrow them further. The
|
|
61
|
+
`speed` extra (NumPy) switches the scans to the vectorized path, which is what
|
|
62
|
+
the `build-speed` and macOS jobs run; leave it out to test the pure-Python one.
|
|
63
|
+
|
|
64
|
+
`tests/process/test_foreign_process.py` attaches to a child process. On macOS
|
|
65
|
+
that needs the `com.apple.security.cs.debugger` entitlement (see below), which
|
|
66
|
+
CI runners do not have, so it skips there: run it locally on a signed Python
|
|
67
|
+
before changing how the macOS backend opens a foreign task.
|
|
68
|
+
|
|
69
|
+
## Linting
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
make lint # flake8 PyMemoryEditor tests
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Type checking
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
make type-check # mypy PyMemoryEditor
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Before you push
|
|
82
|
+
|
|
83
|
+
Run lint, type-check and the test suite in one go:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
make pre-commit # lint + type-check + test
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The CI pipeline runs the same lint, mypy and tests, and blocks merges on failure.
|
|
90
|
+
The library matrix runs Python 3.10–3.15 on Ubuntu and Windows plus Python
|
|
91
|
+
3.10, 3.12 and 3.14 on macOS, so all three platform backends are exercised on
|
|
92
|
+
every push. Dedicated jobs also run the suite with the `speed` extra (NumPy) to keep
|
|
93
|
+
the vectorized scan path covered, and the MCP server suite once per OS. Even so,
|
|
94
|
+
contributors touching a specific backend are encouraged to run `pytest tests`
|
|
95
|
+
locally on that platform before submitting. `make ci` runs the same lint,
|
|
96
|
+
type-check and coverage-gated test invocations the workflow does.
|
|
97
|
+
|
|
98
|
+
## Project layout
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
PyMemoryEditor/
|
|
102
|
+
├── __init__.py # Public API + platform dispatch
|
|
103
|
+
├── enums.py # ScanTypesEnum (cross-platform)
|
|
104
|
+
├── process/ # Abstract base, errors, process info, util
|
|
105
|
+
├── util/ # Cross-platform helpers: scan and type conversion
|
|
106
|
+
├── win32/ # Windows implementation (kernel32, user32)
|
|
107
|
+
├── linux/ # Linux implementation (process_vm_readv/writev, /proc/<pid>/maps)
|
|
108
|
+
├── macos/ # macOS implementation (task_for_pid, mach_vm_*)
|
|
109
|
+
├── mcp/ # Model Context Protocol server exposed as `pymemoryeditor-mcp`
|
|
110
|
+
└── app/ # PySide6 (Qt) demo app exposed as `pymemoryeditor` CLI
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The three platform packages implement `AbstractProcess` from `process/abstract.py`.
|
|
114
|
+
The public alias `OpenProcess` is chosen at import time in `__init__.py` based on
|
|
115
|
+
`sys.platform`.
|
|
116
|
+
|
|
117
|
+
### Platform-specific test notes
|
|
118
|
+
- **Linux**: requires `/proc/sys/kernel/yama/ptrace_scope=0` to attach to processes
|
|
119
|
+
not descended from the test runner. Self-process tests work without changes.
|
|
120
|
+
- **macOS**: opening another process requires the Python binary to be signed with
|
|
121
|
+
the `com.apple.security.cs.debugger` entitlement (or SIP off + root). Self-
|
|
122
|
+
process tests work without changes.
|
|
123
|
+
- **Windows**: no special privileges needed for self-process tests.
|
|
124
|
+
|
|
125
|
+
## Submitting changes
|
|
126
|
+
|
|
127
|
+
1. Open an issue first for bug reports or substantial features.
|
|
128
|
+
2. Branch from `main`. Keep commits focused.
|
|
129
|
+
3. Run `make pre-commit` (lint + type-check + tests) locally before pushing.
|
|
130
|
+
4. Open a PR describing the change and how it was tested.
|
|
131
|
+
5. Give the PR a [Conventional Commits](https://www.conventionalcommits.org/) title,
|
|
132
|
+
e.g. `fix: reject empty search targets` or `docs(mcp): document --allow-write`.
|
|
133
|
+
A required check lints it: the type must be one of `feat`, `fix`, `perf`,
|
|
134
|
+
`refactor`, `revert`, `docs`, `ci`, `build`, `chore`, `test`, `style`, and
|
|
135
|
+
the subject must start lowercase and not end with a period. PRs are
|
|
136
|
+
squash-merged, so the title becomes the commit message on `main`.
|
|
137
|
+
|
|
138
|
+
## Versioning
|
|
139
|
+
|
|
140
|
+
PyMemoryEditor follows [Semantic Versioning](https://semver.org/). The public
|
|
141
|
+
API is everything documented on Read the Docs, and its *behaviour* counts as
|
|
142
|
+
much as its signatures: a call that keeps its name and arguments but returns
|
|
143
|
+
a different value, accepts or rejects different input, or touches different
|
|
144
|
+
memory is a breaking change.
|
|
145
|
+
|
|
146
|
+
- **Major** (`4.0.0`): any breaking change, including a behaviour change to a
|
|
147
|
+
documented default -- even when the old behaviour was a bug that someone
|
|
148
|
+
could reasonably depend on (3.0.0's signed 3-byte reads are the model: the
|
|
149
|
+
release notes lead with it, show the before/after and the way back).
|
|
150
|
+
- **Minor** (`3.3.0`): new features, and behaviour changes that only reject
|
|
151
|
+
what was memory-unsafe or meaningless before. When a minor release does
|
|
152
|
+
change a default (an opt-out such as `aligned=False` must exist), its
|
|
153
|
+
release notes say **Behaviour change** in bold.
|
|
154
|
+
- **Patch** (`3.2.1`): fixes that make the code do what the docs already
|
|
155
|
+
said.
|
|
156
|
+
|
|
157
|
+
The changelog is the
|
|
158
|
+
[GitHub Releases](https://github.com/JeanExtreme002/PyMemoryEditor/releases)
|
|
159
|
+
page: describe every user-visible change in the PR so it can be carried into
|
|
160
|
+
the release notes.
|
|
161
|
+
|
|
162
|
+
## Release process
|
|
163
|
+
|
|
164
|
+
Releases are cut by publishing a GitHub Release; nothing is uploaded from a
|
|
165
|
+
laptop.
|
|
166
|
+
|
|
167
|
+
1. Open a `chore: bump version to X.Y.Z` PR that sets the version in five
|
|
168
|
+
places: `__version__` in `PyMemoryEditor/__init__.py`, the fallback
|
|
169
|
+
`release` in `docs/conf.py`, and in `server.json` both `version` fields
|
|
170
|
+
plus the `PyMemoryEditor[mcp]==X.Y.Z` pin in the package arguments.
|
|
171
|
+
`tests/mcp_server/test_registry.py` fails if the package and `server.json`
|
|
172
|
+
disagree.
|
|
173
|
+
2. Merge it and wait for the `Python Package` run on `main` to go green.
|
|
174
|
+
3. Publish a GitHub Release with the tag `vX.Y.Z` on that commit; its notes
|
|
175
|
+
are the changelog for the version.
|
|
176
|
+
`.github/workflows/publish.yml` then checks that the tag matches
|
|
177
|
+
`__version__` and that the commit has a green `Python Package` run (it
|
|
178
|
+
waits for one still in progress), builds and `twine check --strict`s the
|
|
179
|
+
distributions, uploads them to PyPI by trusted publishing (no token), and
|
|
180
|
+
lists the version on the MCP Registry.
|
|
181
|
+
|
|
182
|
+
If the CI gate fails because `main`'s run was cancelled by a later push, run
|
|
183
|
+
`gh workflow run python-package.yml --ref vX.Y.Z` and re-run the release
|
|
184
|
+
workflow once it is green. `make publish` is a manual fallback for when the
|
|
185
|
+
workflow itself is unavailable: it refuses to run outside a clean checkout of
|
|
186
|
+
the `v<__version__>` tag, but it uploads by API token and does not update the
|
|
187
|
+
MCP Registry.
|
|
188
|
+
|
|
189
|
+
## Reporting bugs
|
|
190
|
+
|
|
191
|
+
Please include:
|
|
192
|
+
- Operating system and architecture (e.g. Windows 11 x64, Ubuntu 22.04 x64).
|
|
193
|
+
- Python version (`python --version`).
|
|
194
|
+
- A minimal reproducer if possible.
|
|
195
|
+
- For Linux: whether `/proc/sys/kernel/yama/ptrace_scope` is `0` or `1`.
|
|
196
|
+
|
|
197
|
+
## Security
|
|
198
|
+
|
|
199
|
+
If you find a security issue, please see [`SECURITY.md`](https://github.com/JeanExtreme002/PyMemoryEditor/blob/main/SECURITY.md). **Do not** report via GitHub issues.
|
|
@@ -31,7 +31,7 @@ help:
|
|
|
31
31
|
@echo " $(YELLOW)run-app$(NC) - Run the PyMemoryEditor App"
|
|
32
32
|
@echo " $(YELLOW)install-dev$(NC) - Install development dependencies"
|
|
33
33
|
@echo " $(YELLOW)test$(NC) - Run the full test suite (incl. live whole-process scans)"
|
|
34
|
-
@echo " $(YELLOW)test-fast$(NC) - Run the fast lane only (-m \"not slow\", ~20 s)"
|
|
34
|
+
@echo " $(YELLOW)test-fast$(NC) - Run the fast lane only (-m \"not slow\", ~20 s; skips the scan property tests)"
|
|
35
35
|
@echo " $(YELLOW)test-mcp$(NC) - Run the MCP server suite"
|
|
36
36
|
@echo " $(YELLOW)test-verbose$(NC) - Run tests with verbose output"
|
|
37
37
|
@echo " $(YELLOW)test-coverage$(NC) - Run tests with coverage report"
|
|
@@ -42,7 +42,8 @@ help:
|
|
|
42
42
|
@echo " $(YELLOW)build-wheel$(NC) - Build wheel package"
|
|
43
43
|
@echo " $(YELLOW)build-sdist$(NC) - Build source distribution"
|
|
44
44
|
@echo " $(YELLOW)validate$(NC) - Validate package"
|
|
45
|
-
@echo " $(YELLOW)publish$(NC) -
|
|
45
|
+
@echo " $(YELLOW)publish$(NC) - MANUAL FALLBACK: upload to PyPI by token from a checkout of the release tag"
|
|
46
|
+
@echo " (releases go through GitHub Releases; see CONTRIBUTING.md)"
|
|
46
47
|
@echo " $(YELLOW)publish-test$(NC) - Publish to Test PyPI"
|
|
47
48
|
@echo " $(YELLOW)ci$(NC) - Mirror the CI workflow: lint, type-check, both coverage-gated test runs"
|
|
48
49
|
@echo " $(YELLOW)version$(NC) - Show current version"
|
|
@@ -56,6 +57,7 @@ help:
|
|
|
56
57
|
@echo " $(YELLOW)venv$(NC) - Create virtual environment"
|
|
57
58
|
@echo " $(YELLOW)venv-activate$(NC) - Show command to activate venv"
|
|
58
59
|
@echo " $(YELLOW)all$(NC) - Run full pipeline (install, lint, test, build)"
|
|
60
|
+
@echo " $(YELLOW)release$(NC) - MANUAL FALLBACK: pre-publish checks, then publish (same tag guard)"
|
|
59
61
|
|
|
60
62
|
# Create virtual environment
|
|
61
63
|
.PHONY: venv
|
|
@@ -113,8 +115,11 @@ test:
|
|
|
113
115
|
$(PYTHON) -m pytest $(TEST_DIR) -v
|
|
114
116
|
@echo "$(GREEN)Tests completed!$(NC)"
|
|
115
117
|
|
|
116
|
-
# Fast lane: unit,
|
|
117
|
-
#
|
|
118
|
+
# Fast lane: unit, GUI and MCP tests (~20 s: ~13 s for the library and app,
|
|
119
|
+
# ~5 s for the MCP suite). Everything marked `slow` is left out, which
|
|
120
|
+
# includes the scan property tests in tests/scan/test_scan_properties.py --
|
|
121
|
+
# run `make test` or `pytest tests/scan/test_scan_properties.py` for those.
|
|
122
|
+
# The markers are defined in pyproject.toml.
|
|
118
123
|
.PHONY: test-fast
|
|
119
124
|
test-fast:
|
|
120
125
|
@echo "$(GREEN)Running the fast test lane (-m \"not slow\")...$(NC)"
|
|
@@ -202,15 +207,34 @@ build-sdist: clean
|
|
|
202
207
|
.PHONY: validate
|
|
203
208
|
validate: build
|
|
204
209
|
@echo "$(GREEN)Validating package...$(NC)"
|
|
205
|
-
$(PYTHON) -m twine check $(DIST_DIR)/*
|
|
210
|
+
$(PYTHON) -m twine check --strict $(DIST_DIR)/*
|
|
206
211
|
@echo "$(GREEN)Package validation completed!$(NC)"
|
|
207
212
|
|
|
208
|
-
#
|
|
209
|
-
#
|
|
210
|
-
#
|
|
211
|
-
# the
|
|
213
|
+
# Refuse to upload anything but a clean checkout of the tag `v<__version__>`.
|
|
214
|
+
# publish.yml checks the tag against the version before its upload; this is
|
|
215
|
+
# the same guard for the manual path, plus a clean tree so the files built are
|
|
216
|
+
# the files tagged. It does not replace the workflow's other gate (a green
|
|
217
|
+
# python-package.yml run on the commit): check that by hand.
|
|
218
|
+
.PHONY: check-release-tag
|
|
219
|
+
check-release-tag:
|
|
220
|
+
@version=$$($(PYTHON) -c "import $(PACKAGE_NAME); print($(PACKAGE_NAME).__version__)") && \
|
|
221
|
+
tag="v$$version" ; \
|
|
222
|
+
found=$$(git tag --points-at HEAD | xargs) ; \
|
|
223
|
+
if ! git tag --points-at HEAD | grep -qx "$$tag"; then \
|
|
224
|
+
echo "$(RED)HEAD is not tagged $$tag (found: $${found:-no tag}). Check out the release tag first.$(NC)"; exit 1; \
|
|
225
|
+
fi; \
|
|
226
|
+
if [ -n "$$(git status --porcelain --untracked-files=no)" ]; then \
|
|
227
|
+
echo "$(RED)The working tree has uncommitted changes; publish from a clean checkout of $$tag.$(NC)"; exit 1; \
|
|
228
|
+
fi; \
|
|
229
|
+
echo "$(GREEN)HEAD is $$tag, matching __version__ $$version.$(NC)"
|
|
230
|
+
|
|
231
|
+
# Publish to PyPI by API token. MANUAL FALLBACK only: releases are cut by
|
|
232
|
+
# publishing a GitHub Release, which .github/workflows/publish.yml uploads to
|
|
233
|
+
# PyPI via trusted publishing (OIDC, no token) and then lists on the MCP
|
|
234
|
+
# Registry. Use this target only if that workflow is unavailable, and note it
|
|
235
|
+
# does not list the release on the MCP Registry.
|
|
212
236
|
.PHONY: publish
|
|
213
|
-
publish: validate
|
|
237
|
+
publish: check-release-tag validate
|
|
214
238
|
@echo "$(YELLOW)Are you sure you want to publish to PyPI? [y/N]$(NC)" && read ans && [ $${ans:-N} = y ]
|
|
215
239
|
@echo "$(GREEN)Publishing to PyPI...$(NC)"
|
|
216
240
|
$(PYTHON) -m twine upload $(DIST_DIR)/*
|
|
@@ -332,9 +356,10 @@ info:
|
|
|
332
356
|
@echo "$(GREEN)Installed packages:$(NC)"
|
|
333
357
|
@$(PIP) list | grep -E "($(PACKAGE_NAME)|pytest|flake8|mypy|twine|build)"
|
|
334
358
|
|
|
335
|
-
#
|
|
359
|
+
# Manual release (fallback for publish.yml; see `publish`). The tag guard runs
|
|
360
|
+
# first so a mismatched checkout fails before the full pre-publish pipeline.
|
|
336
361
|
.PHONY: release
|
|
337
|
-
release: pre-publish publish
|
|
362
|
+
release: check-release-tag pre-publish publish
|
|
338
363
|
@echo "$(GREEN)Release completed!$(NC)"
|
|
339
364
|
|
|
340
365
|
.PHONY: release-test
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: PyMemoryEditor
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.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
|
|
@@ -37,27 +37,33 @@ Classifier: Topic :: System :: Monitoring
|
|
|
37
37
|
Classifier: Typing :: Typed
|
|
38
38
|
Requires-Python: >=3.10
|
|
39
39
|
Provides-Extra: app
|
|
40
|
-
Requires-Dist: psutil<
|
|
41
|
-
Requires-Dist: pyside6
|
|
40
|
+
Requires-Dist: psutil<8,>=5.9; extra == 'app'
|
|
41
|
+
Requires-Dist: pyside6<7,>=6.5; extra == 'app'
|
|
42
42
|
Provides-Extra: dev
|
|
43
43
|
Requires-Dist: build; extra == 'dev'
|
|
44
44
|
Requires-Dist: flake8; extra == 'dev'
|
|
45
45
|
Requires-Dist: hypothesis; extra == 'dev'
|
|
46
46
|
Requires-Dist: mcp<3,>=2; extra == 'dev'
|
|
47
47
|
Requires-Dist: mypy; extra == 'dev'
|
|
48
|
-
Requires-Dist: psutil<
|
|
49
|
-
Requires-Dist: pyside6
|
|
48
|
+
Requires-Dist: psutil<8,>=5.9; extra == 'dev'
|
|
49
|
+
Requires-Dist: pyside6<7,>=6.5; extra == 'dev'
|
|
50
50
|
Requires-Dist: pytest; extra == 'dev'
|
|
51
51
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
52
52
|
Requires-Dist: pytest-qt; extra == 'dev'
|
|
53
53
|
Requires-Dist: pytest-xdist; extra == 'dev'
|
|
54
|
-
Requires-Dist: twine; extra == 'dev'
|
|
54
|
+
Requires-Dist: twine>=7; extra == 'dev'
|
|
55
55
|
Provides-Extra: mcp
|
|
56
56
|
Requires-Dist: mcp<3,>=2; extra == 'mcp'
|
|
57
57
|
Provides-Extra: speed
|
|
58
58
|
Requires-Dist: numpy>=1.24; extra == 'speed'
|
|
59
59
|
Provides-Extra: tests
|
|
60
|
+
Requires-Dist: hypothesis; extra == 'tests'
|
|
61
|
+
Requires-Dist: mcp<3,>=2; extra == 'tests'
|
|
62
|
+
Requires-Dist: psutil<8,>=5.9; extra == 'tests'
|
|
63
|
+
Requires-Dist: pyside6<7,>=6.5; extra == 'tests'
|
|
60
64
|
Requires-Dist: pytest; extra == 'tests'
|
|
65
|
+
Requires-Dist: pytest-cov; extra == 'tests'
|
|
66
|
+
Requires-Dist: pytest-qt; extra == 'tests'
|
|
61
67
|
Requires-Dist: pytest-xdist; extra == 'tests'
|
|
62
68
|
Description-Content-Type: text/markdown
|
|
63
69
|
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
Shared base for the read-only, auto-refreshing table dialogs (Memory Map,
|
|
4
4
|
Modules, Threads).
|
|
5
5
|
|
|
6
|
-
All three
|
|
6
|
+
All three do the same dance — run a one-shot read off the UI thread, repopulate
|
|
7
7
|
a sortable table when it returns, poll on a timer, and tear the worker down
|
|
8
|
-
safely on close
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
only what differs: how to fetch the data, and how to render it.
|
|
8
|
+
safely on close. This owns that lifecycle once, so the fiddly bits (the
|
|
9
|
+
in-flight guard that self-throttles the poll, the detach-on-shutdown safety)
|
|
10
|
+
live in one place; subclasses provide only what differs: how to fetch the
|
|
11
|
+
data, and how to render it.
|
|
13
12
|
|
|
14
13
|
It also owns *when* a failure is worth interrupting the user — see
|
|
15
14
|
:meth:`AutoRefreshTableDialog._handle_failed`.
|
|
@@ -182,7 +181,7 @@ class AutoRefreshTableDialog(TearsDownOnClose, QDialog):
|
|
|
182
181
|
Process) *every* tick fails, and ``_on_data_failed`` pops a **modal**
|
|
183
182
|
dialog. A modal runs a nested event loop, so the timer fires again while
|
|
184
183
|
it is up and the failures stack — hundreds of focus-stealing dialogs the
|
|
185
|
-
user could only escape by killing the app from another TTY
|
|
184
|
+
user could only escape by killing the app from another TTY.
|
|
186
185
|
|
|
187
186
|
Reporting is therefore tied to the two moments where a failure changes
|
|
188
187
|
what the user sees: the **first** fetch (empty window) and the one that
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
"""Small Qt widgets shared between dialogs.
|
|
4
4
|
|
|
5
|
-
Centralises tiny helpers (numeric sort items, hex address parsing)
|
|
6
|
-
|
|
5
|
+
Centralises tiny helpers (numeric sort items, hex address parsing) shared by
|
|
6
|
+
several dialog modules.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import threading
|
|
@@ -46,9 +46,9 @@ def shutdown_worker_thread(worker: Optional[QThread], wait_ms: int = 2000) -> No
|
|
|
46
46
|
cancel()
|
|
47
47
|
try:
|
|
48
48
|
# The four-argument static form on purpose: bare `worker.disconnect()`
|
|
49
|
-
# raises TypeError in PySide6 (
|
|
50
|
-
#
|
|
51
|
-
#
|
|
49
|
+
# raises TypeError in PySide6, and `worker.disconnect(worker)` only
|
|
50
|
+
# drops connections whose *receiver* is the worker — ours belong to the
|
|
51
|
+
# dialog.
|
|
52
52
|
QObject.disconnect(worker, None, None, None)
|
|
53
53
|
except (RuntimeError, TypeError):
|
|
54
54
|
pass
|
|
@@ -141,8 +141,8 @@ class TearsDownOnClose:
|
|
|
141
141
|
``QDialog`` delivers a ``QCloseEvent`` only for a real close. The app's own
|
|
142
142
|
"Close" buttons call ``accept()`` and Esc reaches ``reject()``; both land in
|
|
143
143
|
``done()``, which hides the dialog and emits ``finished`` *without* any
|
|
144
|
-
close event — so a teardown living in ``closeEvent`` never
|
|
145
|
-
dialog
|
|
144
|
+
close event — so a teardown living in ``closeEvent`` would never run, and
|
|
145
|
+
the dialog would keep polling, hidden and unreachable.
|
|
146
146
|
|
|
147
147
|
Subclasses implement :meth:`_teardown`; the mixin runs it exactly once,
|
|
148
148
|
whichever way the dialog is dismissed, and must precede ``QDialog`` in the
|
|
@@ -542,8 +542,7 @@ def main(argv=None):
|
|
|
542
542
|
|
|
543
543
|
``argv`` defaults to ``sys.argv`` so packaging tools (which call
|
|
544
544
|
``main()`` with no arguments) keep working. Tests and embedders can pass
|
|
545
|
-
an explicit list
|
|
546
|
-
ignored, which made the parameter meaningless.
|
|
545
|
+
an explicit list.
|
|
547
546
|
|
|
548
547
|
Terminal users want Ctrl+C to kill the app, which takes a process-wide
|
|
549
548
|
signal change — see :func:`main_cli`, the console-script entry point.
|
|
@@ -602,8 +601,8 @@ def main(argv=None):
|
|
|
602
601
|
# Change Process… released that one and attached the window
|
|
603
602
|
# to another.
|
|
604
603
|
window.process.close()
|
|
605
|
-
except Exception:
|
|
606
|
-
|
|
604
|
+
except Exception: # noqa: BLE001 — exiting anyway; keep the cause
|
|
605
|
+
_LOG.debug("Closing the process handle on exit failed", exc_info=True)
|
|
607
606
|
|
|
608
607
|
|
|
609
608
|
def main_cli(argv=None):
|
|
@@ -611,9 +610,9 @@ def main_cli(argv=None):
|
|
|
611
610
|
Entry point for the ``pymemoryeditor`` console script and ``python -m``.
|
|
612
611
|
|
|
613
612
|
Qt's event loop blocks inside C++, so Python's default SIGINT handler only
|
|
614
|
-
runs once the interpreter next regains control. In practice it
|
|
613
|
+
runs once the interpreter next regains control. In practice it raises
|
|
615
614
|
KeyboardInterrupt inside our own _PointerCursorFilter.eventFilter override,
|
|
616
|
-
where PySide6 swallows it and merely prints a traceback
|
|
615
|
+
where PySide6 swallows it and merely prints a traceback. Hand SIGINT
|
|
617
616
|
back to the OS for the duration of the run so Ctrl+C from a terminal
|
|
618
617
|
terminates the app immediately.
|
|
619
618
|
|
|
@@ -56,8 +56,8 @@ class _CheatPollWorker(QThread):
|
|
|
56
56
|
with the current ``{key: "ErrorType: message"}`` map. The signal fires only
|
|
57
57
|
when that set *changes* (a freeze starts or stops failing), so the UI gets a
|
|
58
58
|
persistent cue without 10 Hz spam, and the first failure of each entry is
|
|
59
|
-
logged once.
|
|
60
|
-
|
|
59
|
+
logged once. Swallowing the exception instead would make a freeze that
|
|
60
|
+
never landed look identical to one that did.
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
63
|
values_ready = Signal(object) # list[tuple[int, type, int, Any]]
|
|
@@ -96,9 +96,9 @@ class _CheatPollWorker(QThread):
|
|
|
96
96
|
) -> None:
|
|
97
97
|
"""Queue a one-shot manual write to be performed on the worker thread.
|
|
98
98
|
|
|
99
|
-
The cheat table's inline value edits
|
|
100
|
-
``write_process_memory`` directly on the UI thread
|
|
101
|
-
the target
|
|
99
|
+
The cheat table's inline value edits come through here: calling
|
|
100
|
+
``write_process_memory`` directly on the UI thread would freeze the UI
|
|
101
|
+
when the target is slow or the page faults. Routing them here keeps the
|
|
102
102
|
syscall off the UI thread; a write that fails comes back via the
|
|
103
103
|
``write_failed`` signal. Latency is at most one tick (~100 ms), which is
|
|
104
104
|
imperceptible for a manual edit.
|
|
@@ -115,20 +115,21 @@ class _CheatPollWorker(QThread):
|
|
|
115
115
|
with QMutexLocker(self._mutex):
|
|
116
116
|
self._stop = True
|
|
117
117
|
|
|
118
|
-
def
|
|
119
|
-
"""
|
|
118
|
+
def _take_pending_writes(self) -> List[Tuple[int, type, int, Any]]:
|
|
119
|
+
"""Detach the queued writes. The caller holds ``self._mutex``."""
|
|
120
|
+
pending = self._pending_writes
|
|
121
|
+
self._pending_writes = []
|
|
122
|
+
return pending
|
|
123
|
+
|
|
124
|
+
def _perform_writes(
|
|
125
|
+
self, pending: List[Tuple[int, type, int, Any]]
|
|
126
|
+
) -> List[Tuple[int, type, int, str]]:
|
|
127
|
+
"""Perform manual writes taken with :meth:`_take_pending_writes`.
|
|
120
128
|
|
|
121
129
|
Returns one ``(address, pytype, length, message)`` tuple per write that
|
|
122
|
-
failed, so ``run`` can surface it via ``write_failed``.
|
|
123
|
-
|
|
124
|
-
write never blocks a UI thread enqueuing the next edit.
|
|
130
|
+
failed, so ``run`` can surface it via ``write_failed``. Runs outside the
|
|
131
|
+
lock, so a slow write never blocks a UI thread enqueuing the next edit.
|
|
125
132
|
"""
|
|
126
|
-
with QMutexLocker(self._mutex):
|
|
127
|
-
if not self._pending_writes:
|
|
128
|
-
return []
|
|
129
|
-
pending = self._pending_writes
|
|
130
|
-
self._pending_writes = []
|
|
131
|
-
|
|
132
133
|
failures: List[Tuple[int, type, int, str]] = []
|
|
133
134
|
for address, pytype, length, value in pending:
|
|
134
135
|
if self._stopping():
|
|
@@ -146,10 +147,15 @@ class _CheatPollWorker(QThread):
|
|
|
146
147
|
|
|
147
148
|
def run(self) -> None:
|
|
148
149
|
while True:
|
|
150
|
+
# Snapshot and queued writes are taken together, under one lock:
|
|
151
|
+
# an inline edit on a frozen row publishes the new frozen value and
|
|
152
|
+
# queues its write back to back, and a snapshot copied before that
|
|
153
|
+
# pair would re-freeze the old value over the write just drained.
|
|
149
154
|
with QMutexLocker(self._mutex):
|
|
150
155
|
if self._stop:
|
|
151
156
|
return
|
|
152
157
|
snapshot = list(self._snapshot)
|
|
158
|
+
pending = self._take_pending_writes()
|
|
153
159
|
|
|
154
160
|
# Never let an exception escape the loop body: a QThread whose
|
|
155
161
|
# run() raises dies silently, and the table would then stop
|
|
@@ -158,7 +164,7 @@ class _CheatPollWorker(QThread):
|
|
|
158
164
|
try:
|
|
159
165
|
# Apply queued manual writes before reading, so a just-typed
|
|
160
166
|
# value lands and is read back as the new current value this tick.
|
|
161
|
-
for failure in self.
|
|
167
|
+
for failure in self._perform_writes(pending):
|
|
162
168
|
self.write_failed.emit(failure)
|
|
163
169
|
|
|
164
170
|
if snapshot:
|