PyMemoryEditor 2.0.0__py3-none-any.whl → 2.0.1__py3-none-any.whl

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.
@@ -8,7 +8,7 @@ Supported platforms: Windows, Linux and macOS (32-bit and 64-bit).
8
8
  """
9
9
 
10
10
  __author__ = "Jean Loui Bernard Silva de Jesus"
11
- __version__ = "2.0.0"
11
+ __version__ = "2.0.1"
12
12
 
13
13
  import logging
14
14
  import sys
@@ -30,6 +30,7 @@ from ..process.scanning import (
30
30
  from ..process.thread_info import ThreadInfo
31
31
  from ..util import (
32
32
  _validate_pytype,
33
+ as_writable_c_buffer,
33
34
  get_c_type_of,
34
35
  values_to_bytes,
35
36
  )
@@ -187,13 +188,13 @@ def get_memory_regions(pid: int) -> Generator["MemoryRegion", None, None]:
187
188
 
188
189
  with mapping_file:
189
190
  for line in mapping_file:
190
- region_information = line.split()
191
+ region_information = line.split(maxsplit=5)
191
192
 
192
193
  try:
193
194
  addressing_range, privileges, offset, device, inode = (
194
195
  region_information[0:5]
195
196
  )
196
- path = region_information[5] if len(region_information) >= 6 else ""
197
+ path = region_information[5].strip() if len(region_information) >= 6 else ""
197
198
 
198
199
  start_address, end_address = [
199
200
  int(addr, 16) for addr in addressing_range.split("-")
@@ -378,6 +379,18 @@ def read_process_memory(pid: int, address: int, pytype: Type[T], bufflength: int
378
379
  return data.value
379
380
 
380
381
 
382
+ def read_process_memory_into(pid: int, address: int, buffer) -> int:
383
+ """
384
+ Read ``len(buffer)`` bytes from ``address`` directly into the writable
385
+ ``buffer``, with no intermediate allocation. Returns the number of bytes
386
+ read (always the buffer's byte length on success; a short read raises
387
+ ``_LinuxPartialIOError``).
388
+ """
389
+ c_buffer = as_writable_c_buffer(buffer)
390
+ size = len(c_buffer)
391
+ return _process_vm_readv(pid, addressof(c_buffer), address, size)
392
+
393
+
381
394
  def search_addresses_by_value(
382
395
  pid: int,
383
396
  pytype: Type[T],
@@ -21,6 +21,7 @@ from .functions import (
21
21
  get_modules,
22
22
  get_threads,
23
23
  read_process_memory,
24
+ read_process_memory_into,
24
25
  search_addresses_by_pattern,
25
26
  search_addresses_by_value,
26
27
  search_values_by_addresses,
@@ -117,6 +118,10 @@ class LinuxProcess(AbstractProcess):
117
118
  self.pid, address, pytype, resolve_bufflength(pytype, bufflength)
118
119
  )
119
120
 
121
+ def read_process_memory_into(self, address: int, buffer: object) -> int:
122
+ self.__require_open()
123
+ return read_process_memory_into(self.pid, address, buffer)
124
+
120
125
  def search_by_addresses(
121
126
  self,
122
127
  pytype: Type[T],
@@ -28,6 +28,7 @@ from ..process.scanning import (
28
28
  from ..process.thread_info import ThreadInfo
29
29
  from ..util import (
30
30
  _validate_pytype,
31
+ as_writable_c_buffer,
31
32
  get_c_type_of,
32
33
  values_to_bytes,
33
34
  )
@@ -545,6 +546,18 @@ def read_process_memory(
545
546
  return data.value
546
547
 
547
548
 
549
+ def read_process_memory_into(task: int, address: int, buffer) -> int:
550
+ """
551
+ Read ``len(buffer)`` bytes from ``address`` directly into the writable
552
+ ``buffer``, with no intermediate allocation. Returns the number of bytes
553
+ read (always the buffer's byte length on success; a short read raises
554
+ ``MachPartialReadError``).
555
+ """
556
+ c_buffer = as_writable_c_buffer(buffer)
557
+ size = len(c_buffer)
558
+ return _mach_read(task, address, ctypes.addressof(c_buffer), size)
559
+
560
+
548
561
  def write_process_memory(
549
562
  task: int,
550
563
  address: int,
@@ -25,6 +25,7 @@ from .functions import (
25
25
  get_task_for_pid,
26
26
  get_threads,
27
27
  read_process_memory,
28
+ read_process_memory_into,
28
29
  release_task,
29
30
  search_addresses_by_pattern,
30
31
  search_addresses_by_value,
@@ -292,6 +293,10 @@ class MacProcess(AbstractProcess):
292
293
  self.__task, address, pytype, resolve_bufflength(pytype, bufflength)
293
294
  )
294
295
 
296
+ def read_process_memory_into(self, address: int, buffer: object) -> int:
297
+ self.__require_open()
298
+ return read_process_memory_into(self.__task, address, buffer)
299
+
295
300
  def write_process_memory(
296
301
  self,
297
302
  address: int,
@@ -449,6 +449,49 @@ class AbstractProcess(ABC):
449
449
  """
450
450
  raise NotImplementedError()
451
451
 
452
+ @abstractmethod
453
+ def read_process_memory_into(self, address: int, buffer: object) -> int:
454
+ """
455
+ Read ``len(buffer)`` bytes from ``address`` directly into a
456
+ pre-allocated, writable ``buffer`` and return the number of bytes read.
457
+
458
+ This is the zero-copy counterpart of :meth:`read_process_memory`: where
459
+ that method allocates a fresh object on every call, this one fills a
460
+ buffer you own and reuse. In a tight polling / recording loop that reads
461
+ the same-sized region over and over, reusing one buffer keeps memory use
462
+ constant instead of producing a stream of short-lived objects for the
463
+ garbage collector to reclaim.
464
+
465
+ :param address: target memory address (ex: 0x006A9EC0).
466
+ :param buffer: any writable, contiguous buffer-protocol object — a
467
+ ``bytearray``, a ``ctypes`` array, a writable ``memoryview``, a
468
+ ``numpy`` array, etc. Its byte length determines how many bytes are
469
+ read; an element-typed buffer (e.g. a ``numpy`` ``int32`` array) is
470
+ sized in **bytes**, not elements. The bytes are written in place; no
471
+ decoding is performed (this is the raw-``bytes`` read path). Decode
472
+ or reinterpret them yourself afterwards
473
+ (``int.from_bytes`` / ``struct.unpack`` / ``numpy`` views / ...).
474
+ :return: the number of bytes read — always equal to the buffer's byte
475
+ length on success (a short read raises instead, mirroring
476
+ :meth:`read_process_memory`).
477
+
478
+ :raises TypeError: if ``buffer`` is not a writable buffer (e.g. an
479
+ immutable ``bytes`` object).
480
+ :raises ValueError: if ``buffer`` is empty or not contiguous.
481
+ :raises OSError: if the read fails, or returns fewer bytes than
482
+ requested (e.g. the range crosses an unreadable/freed page).
483
+
484
+ Example
485
+ -------
486
+ ::
487
+
488
+ buffer = bytearray(16)
489
+ while recording:
490
+ process.read_process_memory_into(addr, buffer)
491
+ handle(buffer) # same buffer reused every iteration
492
+ """
493
+ raise NotImplementedError()
494
+
452
495
  @abstractmethod
453
496
  def write_process_memory(
454
497
  self,
@@ -4,6 +4,7 @@ from .convert import (
4
4
  UNSET,
5
5
  _check_int_fits,
6
6
  _validate_pytype,
7
+ as_writable_c_buffer,
7
8
  convert_from_byte_array,
8
9
  get_c_type_of,
9
10
  prepare_write,
@@ -209,6 +209,54 @@ def prepare_write(
209
209
  return pytype, length, value
210
210
 
211
211
 
212
+ def as_writable_c_buffer(buffer: Any) -> "ctypes.Array":
213
+ """
214
+ Wrap a writable buffer-protocol object as a ``ctypes`` ``c_char`` array
215
+ that shares the same underlying storage, so a backend can read process
216
+ memory *directly into the caller's buffer* with no intermediate
217
+ allocation. Returning the ``ctypes`` array (rather than a bare address)
218
+ keeps a reference to the source alive for as long as the caller holds it,
219
+ which is what makes the address safe to pass to the OS read call.
220
+
221
+ Accepts anything exposing a writable, contiguous buffer — ``bytearray``,
222
+ a ``ctypes`` array, a writable ``memoryview``, a ``numpy`` array, etc. The
223
+ array's byte length is taken from the buffer itself (``memoryview.nbytes``),
224
+ so element-typed buffers like a ``numpy`` ``int32`` array are sized in
225
+ bytes, not elements.
226
+
227
+ :raises TypeError: if ``buffer`` does not support the buffer protocol or is
228
+ read-only (e.g. ``bytes`` — use ``bytearray`` instead).
229
+ :raises ValueError: if ``buffer`` is empty or not contiguous.
230
+ """
231
+ try:
232
+ view = memoryview(buffer)
233
+ except TypeError:
234
+ raise TypeError(
235
+ "buffer must support the writable buffer protocol "
236
+ "(e.g. bytearray, a ctypes array, or a numpy array), got %s."
237
+ % type(buffer).__name__
238
+ )
239
+
240
+ try:
241
+ if view.readonly:
242
+ raise TypeError(
243
+ "buffer must be writable; got a read-only buffer "
244
+ "(e.g. bytes). Use bytearray or another writable buffer."
245
+ )
246
+ if not view.contiguous:
247
+ raise ValueError("buffer must be contiguous.")
248
+ nbytes = view.nbytes
249
+ finally:
250
+ # Release the inspection view promptly so it never lingers as an extra
251
+ # export on the source object (e.g. blocking a later bytearray resize).
252
+ view.release()
253
+
254
+ if nbytes == 0:
255
+ raise ValueError("buffer must have a non-zero length.")
256
+
257
+ return (ctypes.c_char * nbytes).from_buffer(buffer)
258
+
259
+
212
260
  def convert_from_byte_array(
213
261
  byte_array: ctypes.Array, pytype: Type[T], length: int
214
262
  ) -> T:
@@ -27,6 +27,7 @@ from ..process.scanning import (
27
27
  from ..process.thread_info import ThreadInfo
28
28
  from ..util import (
29
29
  _validate_pytype,
30
+ as_writable_c_buffer,
30
31
  get_c_type_of,
31
32
  values_to_bytes,
32
33
  )
@@ -501,6 +502,40 @@ def ReadProcessMemory(
501
502
  return data.value
502
503
 
503
504
 
505
+ def ReadProcessMemoryInto(process_handle: int, address: int, buffer) -> int:
506
+ """
507
+ Read ``len(buffer)`` bytes from ``address`` directly into the writable
508
+ ``buffer``, with no intermediate allocation. Returns the number of bytes
509
+ read.
510
+
511
+ Raises OSError if the read fails or returns fewer bytes than requested
512
+ (same partial-read guard as :func:`ReadProcessMemory`).
513
+ """
514
+ c_buffer = as_writable_c_buffer(buffer)
515
+ size = len(c_buffer)
516
+ bytes_read = ctypes.c_size_t(0)
517
+
518
+ ctypes.set_last_error(0)
519
+ success = kernel32.ReadProcessMemory(
520
+ process_handle,
521
+ ctypes.c_void_p(address),
522
+ ctypes.byref(c_buffer),
523
+ size,
524
+ ctypes.byref(bytes_read),
525
+ )
526
+
527
+ if not success:
528
+ _raise_last_error("ReadProcessMemory")
529
+
530
+ if bytes_read.value != size:
531
+ raise OSError(
532
+ "ReadProcessMemory partial read at 0x%X: %d of %d bytes read."
533
+ % (address, bytes_read.value, size)
534
+ )
535
+
536
+ return bytes_read.value
537
+
538
+
504
539
  def _read_region(process_handle: int, address: int, size: int):
505
540
  """Read a memory region; returns the byte buffer or None on failure."""
506
541
  region_data = (ctypes.c_byte * size)()
@@ -27,6 +27,7 @@ from .functions import (
27
27
  GetProcessHandle,
28
28
  GetThreads,
29
29
  ReadProcessMemory,
30
+ ReadProcessMemoryInto,
30
31
  SearchAddressesByPattern,
31
32
  SearchAddressesByValue,
32
33
  SearchValuesByAddresses,
@@ -313,6 +314,11 @@ class WindowsProcess(AbstractProcess):
313
314
  resolve_bufflength(pytype, bufflength),
314
315
  )
315
316
 
317
+ def read_process_memory_into(self, address: int, buffer: object) -> int:
318
+ self.__require_open()
319
+ self.__require_read()
320
+ return ReadProcessMemoryInto(self.__process_handle, address, buffer)
321
+
316
322
  def write_process_memory(
317
323
  self,
318
324
  address: int,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyMemoryEditor
3
- Version: 2.0.0
3
+ Version: 2.0.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
@@ -1,4 +1,4 @@
1
- PyMemoryEditor/__init__.py,sha256=V3znWAe0ZF3a0Y_LxPTHwWkPqyelQSPzv7M9BUT8aWU,3234
1
+ PyMemoryEditor/__init__.py,sha256=t0Ni85aNdANRY5CtAi4ULjQbERLIDnZjbo0rAGI-9JQ,3234
2
2
  PyMemoryEditor/__main__.py,sha256=Xon0BV6VbHEIGM1L0RfMJRHIgfSCrI7uxbKsWdp8XbQ,87
3
3
  PyMemoryEditor/enums.py,sha256=q_SWQYgh4F7yyAa7sZvNo2Sw91TSm3aTqPfTA7OqldM,322
4
4
  PyMemoryEditor/app/__init__.py,sha256=raiaXlRdMUZLMlWIpLHZvDmFf3pIUnbhuMKWQUYmo3Y,226
@@ -23,17 +23,17 @@ PyMemoryEditor/app/scan_worker.py,sha256=Tf_87Ad__OimeUDzTnBSKYMnlrVxomGOGQh6NnD
23
23
  PyMemoryEditor/app/scanner_panel.py,sha256=Z4BcN4WYL8_LVZ_7lUGZcqsCSCcTgAR3PNo-FNw8wjQ,18381
24
24
  PyMemoryEditor/app/threads_dialog.py,sha256=rZMrxNZ8-uJrBwK8zvkubNsigoBrN36buSyWVhiXOUU,6469
25
25
  PyMemoryEditor/app/value_types.py,sha256=3Yq5ms7mb31nLKsvVO8VyyxpEdp6pTdsK1j3ul8ayek,10621
26
- PyMemoryEditor/linux/functions.py,sha256=jhP9N_eOOkAkVH2_Alx_Cz4yqtnsRoooPu7LAsC0ea8,21154
26
+ PyMemoryEditor/linux/functions.py,sha256=raBILyavPlliEzM4r3z6qEcOWG_G7GUd_d1aWHF9K5U,21681
27
27
  PyMemoryEditor/linux/libc.py,sha256=U4yOpHJ48OAaDxcVk_s57GGXlvmyakInwM2liegf3OA,1431
28
- PyMemoryEditor/linux/process.py,sha256=13RSUUMLCLblFvzfaaqVKlqU6dwxJtsicrEsvj72SdE,8431
28
+ PyMemoryEditor/linux/process.py,sha256=_PafbkQHAcwsQlbwcUFd6ERHN_c4wQ3Qcgyt82Gz2Zw,8636
29
29
  PyMemoryEditor/linux/types.py,sha256=sOgO-s9oqKrKKCHMG4N-Pe7sNw49V06PeMENSNnNFl8,1924
30
30
  PyMemoryEditor/macos/__init__.py,sha256=85mOA4U5i9EW2r1ggKqRJkqRq0e9bV3I2HdfHIvox10,72
31
- PyMemoryEditor/macos/functions.py,sha256=MsudOJK85s_eVrLqQZTexuDwjoAPaihyozRFTTVNqug,41768
31
+ PyMemoryEditor/macos/functions.py,sha256=C9_Cg4njd_1pCz-6cDNosnLN1DSRIeF4AK58WNyyNNc,42279
32
32
  PyMemoryEditor/macos/libsystem.py,sha256=H9p72IqO0C5CrMFCAjnY_iQCzi_AQgw6ZN6CXsopceo,8683
33
- PyMemoryEditor/macos/process.py,sha256=2yhFneMQAbBZm-Mhxvs5XpDjENS6wogSJ__bxk6CTzQ,13051
33
+ PyMemoryEditor/macos/process.py,sha256=06zgLaTKIyc1DQZFgS-9lqT2imB_IYFTOs4xEfSgftg,13259
34
34
  PyMemoryEditor/macos/types.py,sha256=gttewVsWwwQKzhOhMkkjUTg0_ipH3kKkkQhxmv7oiUE,4855
35
35
  PyMemoryEditor/process/__init__.py,sha256=Xh5jngBqZyNIA9y3uwE49LxK8uH8YYsu6qotx1mP_WE,213
36
- PyMemoryEditor/process/abstract.py,sha256=JU0df3YM17ST2YjSzRX5rkecZEzr5orw5GZZcJFVSGM,51023
36
+ PyMemoryEditor/process/abstract.py,sha256=Q-6_nDssx4zNX0xYiww5K9TX8HFOb35I3U3DQrG8TVE,53205
37
37
  PyMemoryEditor/process/errors.py,sha256=485SXkidg9_E_KjuhuF6AsQNuFZlY-Ag-sG1C6NsGMw,2054
38
38
  PyMemoryEditor/process/info.py,sha256=p1LXc6tMdf2KAjlHY8d1wNqx1Zo3yqwa3LscWGT2RQg,1317
39
39
  PyMemoryEditor/process/module_info.py,sha256=D7qAoBymhe6vrL0IYc2R6ZhWE-OPmYQzf6hSp_f2KmA,2365
@@ -43,13 +43,13 @@ PyMemoryEditor/process/remote_pointer.py,sha256=y2OiBrocmCnDi2Sv1BF7N6UX_AdQKHPA
43
43
  PyMemoryEditor/process/scanning.py,sha256=iMzrxrRJf7bc1PXACiiZq0DX4DsSx7IFAgJrNSGHHI4,17384
44
44
  PyMemoryEditor/process/thread_info.py,sha256=GeIpfL9TD0YVV75vlBlCUlseCDUNsTDKmxzmCjizjyc,2209
45
45
  PyMemoryEditor/process/util.py,sha256=qwh3agrj8B_EDu5LsfMxKFhFSp9YDafXkoUuOUV7owI,3160
46
- PyMemoryEditor/util/__init__.py,sha256=7j0RoeqBzUD9TtnhglaCBCqIK9qZtse1ETATIABjjm4,486
47
- PyMemoryEditor/util/convert.py,sha256=41aWtlc3rg1ZUXLFWDeiq7vTCPi0-5AInN2I0iAD15U,12377
46
+ PyMemoryEditor/util/__init__.py,sha256=Lux1_4twAhkzdWDV4xt4INmshKXZAHje_NU2WuwQhoE,512
47
+ PyMemoryEditor/util/convert.py,sha256=CnnJ0wTc8oN5kliAaH4IZJtxI1J0_hS7YKFyVuNyMWU,14399
48
48
  PyMemoryEditor/util/pattern.py,sha256=d8PLbTCMwpPiO1HF0tJ0WNXHQ0VG33dJvqAHLGMExf0,4321
49
49
  PyMemoryEditor/util/scan.py,sha256=ZCQP2g_ANFdHNmhXf2CayV8zB44IAqHsG2lA7REMKF0,19811
50
50
  PyMemoryEditor/util/scan_numpy.py,sha256=osqZh7hfu9meKGXJPROLqzQ-UdSrCduqkv7EzUfVDp4,5889
51
- PyMemoryEditor/win32/functions.py,sha256=U_eubUCHLwWvv4oiJLDTrAjWhCs71UBjP9QUu8EF-XA,33177
52
- PyMemoryEditor/win32/process.py,sha256=EzsACFRuDrObc06mjmZD8QO8Jwq9Pfj38n_nbG6JjfE,12464
51
+ PyMemoryEditor/win32/functions.py,sha256=v-OAHBJZEcmLTCbRuqPpprik_CuvYVEuyVH-POAXlM0,34204
52
+ PyMemoryEditor/win32/process.py,sha256=jpBuzMAdOqiwD9peGbQ3d7xZU7Gpp9IDhWrfTmMnXzw,12706
53
53
  PyMemoryEditor/win32/types.py,sha256=yzSO2Ojzn7f10G_mS3sJRQO8co3CBYtlVCiKYRrCD5Y,4632
54
54
  PyMemoryEditor/win32/enums/__init__.py,sha256=RZXhXb-yCGHVDEi0OxV27w0g2l6b68BIbKhHXyTM3cw,301
55
55
  PyMemoryEditor/win32/enums/memory_allocation_states.py,sha256=NTwC9XkUqlCjoSalwykEG2OZUiqXVylcLP2nYSd39zI,953
@@ -59,8 +59,8 @@ PyMemoryEditor/win32/enums/process_operations.py,sha256=wn-aMl01VVg-2rFavR9szBNR
59
59
  PyMemoryEditor/win32/enums/standard_access_rights.py,sha256=VGWlTagW4hvbm2AjZ4NK56ZrQwRmEl9Kz9GSCG8o-HI,738
60
60
  PyMemoryEditor/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
61
  PyMemoryEditor/app/assets/icon.svg,sha256=T7OqR7ldqC_dK2Ib2ntsH2rWO-Z5RzNK4WdD1wFwgM4,7132
62
- pymemoryeditor-2.0.0.dist-info/METADATA,sha256=lM1_4LbJbTQFx_mdmohkxQBemqLYe5euavMws6smLUo,9622
63
- pymemoryeditor-2.0.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
64
- pymemoryeditor-2.0.0.dist-info/entry_points.txt,sha256=DHldxZSlpukln94QwxXYiJIuSzVjltYA0MAk3vRkWQk,71
65
- pymemoryeditor-2.0.0.dist-info/licenses/LICENSE,sha256=YkOx6Nvkavq36bgiAc3eF85SBG0Qu14KMAvxmv6Ssjc,1089
66
- pymemoryeditor-2.0.0.dist-info/RECORD,,
62
+ pymemoryeditor-2.0.1.dist-info/METADATA,sha256=dY5Py_y0FnxH9sB9kweRMxrTs7Reeu5SquvHsZs1oUg,9622
63
+ pymemoryeditor-2.0.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
64
+ pymemoryeditor-2.0.1.dist-info/entry_points.txt,sha256=DHldxZSlpukln94QwxXYiJIuSzVjltYA0MAk3vRkWQk,71
65
+ pymemoryeditor-2.0.1.dist-info/licenses/LICENSE,sha256=YkOx6Nvkavq36bgiAc3eF85SBG0Qu14KMAvxmv6Ssjc,1089
66
+ pymemoryeditor-2.0.1.dist-info/RECORD,,