Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions PyMemoryEditor/linux/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
from ..enums import ScanTypesEnum
from ..process import AbstractProcess
from ..process.errors import ClosedProcess
from ..util import prepare_write, resolve_bufflength
from ..util import (
UNSET,
prepare_write,
resolve_bufflength,
resolve_bufflength_for_value,
)
from ..process.module_info import ModuleInfo
from ..process.region import MemoryRegion
from ..process.thread_info import ThreadInfo
Expand Down Expand Up @@ -110,13 +115,15 @@ def read_process_memory(
def search_by_addresses(
self,
pytype: Type[T],
bufflength: Optional[int],
addresses: Sequence[int],
bufflength: Optional[int] = None,
addresses: Sequence[int] = UNSET,
*,
raise_error: bool = False,
memory_regions: Optional[Sequence[MemoryRegion]] = None,
) -> Generator[Tuple[int, Optional[T]], None, None]:
self.__require_open()
if addresses is UNSET:
raise TypeError("addresses is required.")
return search_values_by_addresses(
self.pid,
pytype,
Expand All @@ -129,8 +136,8 @@ def search_by_addresses(
def search_by_value(
self,
pytype: Type[T],
bufflength: Optional[int],
value: Union[bool, int, float, str, bytes],
bufflength: Optional[int] = None,
value: Union[bool, int, float, str, bytes] = UNSET,
scan_type: ScanTypesEnum = ScanTypesEnum.EXACT_VALUE,
*,
progress_information: bool = False,
Expand All @@ -147,7 +154,7 @@ def search_by_value(
return search_addresses_by_value(
self.pid,
pytype,
resolve_bufflength(pytype, bufflength),
resolve_bufflength_for_value(pytype, bufflength, value),
value,
scan_type,
progress_information,
Expand Down Expand Up @@ -175,9 +182,9 @@ def search_by_pattern(
def search_by_value_between(
self,
pytype: Type[T],
bufflength: Optional[int],
start: Union[bool, int, float, str, bytes],
end: Union[bool, int, float, str, bytes],
bufflength: Optional[int] = None,
start: Union[bool, int, float, str, bytes] = UNSET,
end: Union[bool, int, float, str, bytes] = UNSET,
*,
not_between: bool = False,
progress_information: bool = False,
Expand All @@ -194,7 +201,7 @@ def search_by_value_between(
return search_addresses_by_value(
self.pid,
pytype,
resolve_bufflength(pytype, bufflength),
resolve_bufflength_for_value(pytype, bufflength, start, end),
(start, end),
scan_type,
progress_information,
Expand All @@ -206,8 +213,8 @@ def write_process_memory(
self,
address: int,
pytype: Type[T],
bufflength: Optional[int],
value: Union[bool, int, float, str, bytes],
bufflength: Optional[int] = None,
value: Union[bool, int, float, str, bytes] = UNSET,
) -> Union[bool, int, float, str, bytes]:
self.__require_open()
w_pytype, w_length, w_value = prepare_write(pytype, bufflength, value)
Expand Down
39 changes: 24 additions & 15 deletions PyMemoryEditor/macos/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
from ..process.module_info import ModuleInfo
from ..process.region import MemoryRegion
from ..process.thread_info import ThreadInfo
from ..util import prepare_write, resolve_bufflength
from ..util import (
UNSET,
prepare_write,
resolve_bufflength,
resolve_bufflength_for_value,
)

from .functions import (
allocate_memory,
Expand Down Expand Up @@ -177,13 +182,15 @@ def _static_image_ranges(self):
def search_by_addresses(
self,
pytype: Type[T],
bufflength: Optional[int],
addresses: Sequence[int],
bufflength: Optional[int] = None,
addresses: Sequence[int] = UNSET,
*,
raise_error: bool = False,
memory_regions: Optional[Sequence[MemoryRegion]] = None,
) -> Generator[Tuple[int, Optional[T]], None, None]:
self.__require_open()
if addresses is UNSET:
raise TypeError("addresses is required.")
return search_values_by_addresses(
self.__task,
pytype,
Expand All @@ -196,8 +203,8 @@ def search_by_addresses(
def search_by_value(
self,
pytype: Type[T],
bufflength: Optional[int],
value: Union[bool, int, float, str, bytes],
bufflength: Optional[int] = None,
value: Union[bool, int, float, str, bytes] = UNSET,
scan_type: ScanTypesEnum = ScanTypesEnum.EXACT_VALUE,
*,
progress_information: bool = False,
Expand All @@ -214,7 +221,7 @@ def search_by_value(
return search_addresses_by_value(
self.__task,
pytype,
resolve_bufflength(pytype, bufflength),
resolve_bufflength_for_value(pytype, bufflength, value),
value,
scan_type,
progress_information,
Expand Down Expand Up @@ -242,9 +249,9 @@ def search_by_pattern(
def search_by_value_between(
self,
pytype: Type[T],
bufflength: Optional[int],
start: Union[bool, int, float, str, bytes],
end: Union[bool, int, float, str, bytes],
bufflength: Optional[int] = None,
start: Union[bool, int, float, str, bytes] = UNSET,
end: Union[bool, int, float, str, bytes] = UNSET,
*,
not_between: bool = False,
progress_information: bool = False,
Expand All @@ -261,7 +268,7 @@ def search_by_value_between(
return search_addresses_by_value(
self.__task,
pytype,
resolve_bufflength(pytype, bufflength),
resolve_bufflength_for_value(pytype, bufflength, start, end),
(start, end),
scan_type,
progress_information,
Expand All @@ -284,8 +291,8 @@ def write_process_memory(
self,
address: int,
pytype: Type[T],
bufflength: Optional[int],
value: Union[bool, int, float, str, bytes],
bufflength: Optional[int] = None,
value: Union[bool, int, float, str, bytes] = UNSET,
) -> Union[bool, int, float, str, bytes]:
"""
Write a value to a memory address.
Expand All @@ -303,9 +310,11 @@ def write_process_memory(

:param address: target memory address.
:param pytype: type of value to be written (bool, int, float, str, bytes).
:param bufflength: value size in bytes. ``None`` uses the default for
numeric types (int→4, float→8, bool→1); ``str``/``bytes`` require
an explicit size.
:param bufflength: value size in bytes. Optional — defaults to ``None``,
which uses the default width for numeric types (int→4, float→8,
bool→1) and writes the exact encoded length for ``str`` / ``bytes``.
Since it is optional, pass ``value`` by keyword when omitting it
(``write_process_memory(addr, str, value="hi")``).
:param value: value to be written.
"""
self.__require_open()
Expand Down
61 changes: 42 additions & 19 deletions PyMemoryEditor/process/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
)

from ..enums import ScanTypesEnum
from ..util import UNSET
from .info import ProcessInfo
from .module_info import ModuleInfo
from .region import MemoryRegion, MemoryRegionSnapshot
Expand Down Expand Up @@ -224,8 +225,8 @@ def snapshot_memory_regions(self) -> MemoryRegionSnapshot:
def search_by_addresses(
self,
pytype: Type[T],
bufflength: Optional[int],
addresses: Sequence[int],
bufflength: Optional[int] = None,
addresses: Sequence[int] = UNSET,
*,
raise_error: bool = False,
memory_regions: Optional[Sequence[MemoryRegion]] = None,
Expand All @@ -234,6 +235,14 @@ def search_by_addresses(
Search the whole memory space, accessible to the process,
for the provided list of addresses, returning their values.

:param bufflength: value size in bytes. Optional — defaults to ``None``,
which uses the default width for numeric types (int→4, float→8,
bool→1). ``str`` / ``bytes`` still require an explicit size here:
unlike a search by value, there is no value to infer the width
from — only addresses to read. Since ``bufflength`` is optional,
pass ``addresses`` by keyword when omitting it:
``search_by_addresses(int, addresses=[0x1000, 0x1004])``.
:param addresses: the addresses to read. Required.
:param memory_regions: optional snapshot returned by `snapshot_memory_regions()`.
Pass it to skip the region enumeration on hot iterative workflows.
"""
Expand All @@ -243,8 +252,8 @@ def search_by_addresses(
def search_by_value(
self,
pytype: Type[T],
bufflength: Optional[int],
value: Union[bool, int, float, str, bytes],
bufflength: Optional[int] = None,
value: Union[bool, int, float, str, bytes] = UNSET,
scan_type: ScanTypesEnum = ScanTypesEnum.EXACT_VALUE,
*,
progress_information: bool = False,
Expand All @@ -256,10 +265,13 @@ def search_by_value(
for the provided value, returning the found addresses.

:param pytype: type of value to be queried (bool, int, float, str or bytes).
:param bufflength: value size in bytes (1, 2, 4, 8). For numeric types
(int, float, bool) you may pass None to use the default
(int→4, float→8, bool→1). str and bytes require an explicit value.
:param bufflength: value size in bytes (1, 2, 4, 8). Optional — defaults
to ``None``: numeric types (int, float, bool) use their default
width (int→4, float→8, bool→1) and ``str`` / ``bytes`` infer it from
the encoded length of ``value``. Since it is optional, pass ``value``
by keyword when omitting it: ``search_by_value(int, value=100)``.
:param value: value to be queried (bool, int, float, str or bytes).
Required.
:param scan_type: the way to compare the values.
:param progress_information: if True, a dictionary with the progress information will be returned.
:param writeable_only: if True, search only at writeable memory regions.
Expand Down Expand Up @@ -300,9 +312,9 @@ def search_by_pattern(
def search_by_value_between(
self,
pytype: Type[T],
bufflength: Optional[int],
start: Union[bool, int, float, str, bytes],
end: Union[bool, int, float, str, bytes],
bufflength: Optional[int] = None,
start: Union[bool, int, float, str, bytes] = UNSET,
end: Union[bool, int, float, str, bytes] = UNSET,
*,
not_between: bool = False,
progress_information: bool = False,
Expand All @@ -313,7 +325,11 @@ def search_by_value_between(
Search the whole memory space, accessible to the process,
for a value within the provided range, returning the found addresses.

See `search_by_value` for parameter semantics.
See `search_by_value` for parameter semantics. ``bufflength`` is
likewise optional (defaults to ``None``): for ``str`` / ``bytes`` it is
inferred from the longest of ``start`` / ``end`` (the shorter endpoint
is NUL-padded to that width). Pass ``start`` / ``end`` by keyword when
omitting ``bufflength``: ``search_by_value_between(int, start=10, end=20)``.
"""
raise NotImplementedError()

Expand Down Expand Up @@ -348,28 +364,35 @@ def write_process_memory(
self,
address: int,
pytype: Type[T],
bufflength: Optional[int],
value: Union[bool, int, float, str, bytes],
bufflength: Optional[int] = None,
value: Union[bool, int, float, str, bytes] = UNSET,
) -> Union[bool, int, float, str, bytes]:
"""
Write a value to a memory address.

:param address: target memory address (ex: 0x006A9EC0).
:param pytype: type of value to be written into memory (bool, int, float, str or bytes).
:param bufflength: value size in bytes.
:param bufflength: value size in bytes. Optional — defaults to ``None``.

* For numeric types (int, float, bool) it is the exact write width;
pass ``None`` to use the default — int→4, float→8, bool→1.
leave it as ``None`` to use the default — int→4, float→8, bool→1.
* For ``str`` / ``bytes`` it is a *minimum* field width, not a hard
cap. The whole value is always written: if its encoded form is
longer than ``bufflength`` every byte is still written (so
``write(addr, str, 3, "olá")`` writes all 4 UTF-8 bytes instead
of raising — you may count characters, not bytes). If it is
shorter, the field is NUL-padded up to ``bufflength`` (handy to
clear a fixed-size buffer). ``None`` writes exactly the encoded
length. ``str`` is encoded as UTF-8; no NUL terminator is
appended.
:param value: value to be written.
clear a fixed-size buffer). ``None`` (the default) writes exactly
the encoded length. ``str`` is encoded as UTF-8; no NUL terminator
is appended.
:param value: value to be written. Required — since ``bufflength`` is
now optional, pass it by keyword when omitting ``bufflength``::

write_process_memory(address, str, value="hi")
write_process_memory(address, int, value=99)

Positional calls keep working unchanged
(``write_process_memory(address, int, 4, 99)``).
:return: the original ``value`` passed in.
"""
raise NotImplementedError()
Expand Down
2 changes: 2 additions & 0 deletions PyMemoryEditor/util/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-

from .convert import (
UNSET,
_validate_pytype,
convert_from_byte_array,
get_c_type_of,
prepare_write,
resolve_bufflength,
resolve_bufflength_for_value,
value_to_bytes,
values_to_bytes,
)
Expand Down
Loading
Loading