Reading and writing memoryο
read_process_memory and write_process_memory are the two building blocks of
PyMemoryEditor. Once you know an address, you read or write it like any other
Python variable.
Supported typesο
PyMemoryEditor supports the five primitive Python types typically found in process memory:
| Type | Default size | Notes |
|---|---|---|
int | 4 bytes | Signed integer. Override to 1/2/8 for other widths. |
float | 8 bytes | double by default; pass 4 for float32. |
bool | 1 byte | C bool. |
str | β (required) | UTF-8 decoded with errors="replace". |
bytes | β (required) | Raw, no decoding. |
For numeric types, you can pass bufflength=None (or just omit it) to use the
default. For str and bytes, the size is required when reading (the
library needs to know how many bytes to pull back) but optional when
writing β a write simply stores the value you gave it.
Tip
Prefer the typed shortcuts below (read_int, write_float, read_stringβ¦)
if you donβt want to think about sizes at all β the width is baked into the
method name.
Reading a valueο
from PyMemoryEditor import OpenProcess
with OpenProcess(process_name="notepad.exe") as process:
address = 0x0005000C
# Integers β 4 bytes by default
score = process.read_process_memory(address, int)
# 1-byte integer
flag = process.read_process_memory(address, int, 1)
# 8-byte float (double)
speed = process.read_process_memory(address, float)
# 32 bytes interpreted as a UTF-8 string
name = process.read_process_memory(address, str, 32)
# Raw bytes (no decoding)
raw = process.read_process_memory(address, bytes, 16)
Method signatureο
- read_process_memory(address, pytype, bufflength=None)
String decoding
When pytype=str the raw bytes are decoded with errors="replace" β invalid
UTF-8 becomes the replacement character U+FFFD instead of raising.
If you need the bytes verbatim, pass pytype=bytes.
Writing a valueο
with OpenProcess(process_name="notepad.exe") as process:
address = 0x0005000C
# Write an int β bufflength is optional, so pass value by keyword.
process.write_process_memory(address, int, value=9999)
# Write a 2-byte int explicitly (positional bufflength still works).
process.write_process_memory(address, int, 2, 42)
# Write a string β no size needed; your text is stored as-is.
process.write_process_memory(address, str, value="Hello!")
# Write raw bytes
process.write_process_memory(address, bytes, value=b"\xDE\xAD\xBE\xEF")
Writing text? Count characters, not bytes.
For str writes, bufflength is a maximum number of characters β the
value is truncated to that many characters and then encoded, so you never have
to do UTF-8 byte math. write_process_memory(addr, str, 2, "Γ³Γ³lΓ‘") writes just
"Γ³Γ³" (4 bytes), and write_process_memory(addr, str, 3, "olΓ‘") keeps all of
"olΓ‘" whole. A shorter value is written as-is (no padding); pass None to
write the whole string. For bytes, the cap counts bytes instead.
Method signatureο
- write_process_memory(address, pytype, bufflength=None, value=...)
- Parameters:
address (int) β target memory address.
pytype (Type) β one of
bool,int,float,str,bytes.bufflength (int) β value size in bytes. Optional β defaults to
None, which uses the default width for numeric types and writes the whole value forstr/bytes. Forstr/bytesan explicit value is a maximum that truncates (strcounts characters,bytescounts bytes) and never pads. Since it is optional, passvalueby keyword when you omit it:write_process_memory(addr, int, value=9999).value β the value to write.
- Returns:
the written value.
Typed shortcutsο
Donβt want to remember that an Int32 is 4 bytes or that unsigned needs
special handling? Use the typed shortcuts. Each one is a read_* /
write_* pair with the size and signedness baked into the name:
with OpenProcess(process_name="game.exe") as process:
hp = process.read_int(0x7FF40010) # signed, 4 bytes
gold = process.read_uint(0x7FF40014) # unsigned, 4 bytes
speed = process.read_float(0x7FF40018) # 32-bit float
process.write_int(0x7FF40010, hp + 100) # heal up
process.write_bool(0x7FF4001C, True) # toggle a flag
No bufflength, no pytype β just the address (and the value, when writing).
Hereβs the full set; every read_* has a matching write_*:
| Shortcut | Reads / writes | Bytes |
|---|---|---|
read_char / read_uchar | 8-bit integer β signed / unsigned | 1 |
read_short / read_ushort | 16-bit integer β signed / unsigned | 2 |
read_int / read_uint | 32-bit integer β signed / unsigned | 4 |
read_long / read_ulong | 32-bit integer β signed / unsigned | 4 |
read_longlong / read_ulonglong | 64-bit integer β signed / unsigned | 8 |
read_float | 32-bit floating point | 4 |
read_double | 64-bit floating point | 8 |
read_bool | boolean | 1 |
read_string / read_bytes | text / raw bytes | you choose |
Note
Widths are fixed and the same on every OS β long is always 4 bytes here,
longlong always 8 β so your code reads the same number of bytes on Windows,
Linux and macOS.
Working with textο
read_string and write_string are the friendly way to handle text β no byte
counting, no manual decoding:
with OpenProcess(process_name="game.exe") as process:
# Write your text β UTF-8 encoding (accents, emojiβ¦) is handled for you.
process.write_string(0x7FF40020, "Pedro")
# Read it back: read a 32-byte field, stop at the first NUL terminator.
name = process.read_string(0x7FF40020, 32) # -> "Pedro"
read_string reads exactly the size you pass β that many bytes must be
readable or it raises OSError β and returns everything before the first
\0, so a generous field width like 32 gives you the real string without
the trailing padding. write_string writes exactly your text β pass
null_terminator=True if youβre overwriting a longer value and want a clean
cut-off:
process.write_string(0x7FF40020, "Ann", null_terminator=True)
# read_string now stops right after "Ann", even if "Pedro" was there before.
See also
Need the raw bytes with zero interpretation? Use read_bytes(address, length)
and write_bytes(address, data).
Common errorsο
OSErrorβ the address may have been freed between scan and write, or the page might not be writable. Wrap one-off writes intry/except OSError.PermissionErrorβ the handle was opened without write access (Windows read-only handle). See Opening a process.ValueErrorβbufflengthwas omitted for astrorbytesread (a write doesnβt need it β it sizes itself to your value).
Reading many addresses efficientlyο
When you have a list of addresses to read, do not loop over
read_process_memory β each call performs one syscall.
Use search_by_addresses instead, which reads each memory page only once and
extracts every requested address from it:
addresses = [0x10000, 0x10010, 0x10020, ...]
for address, value in process.search_by_addresses(int, 4, addresses):
print(f"0x{address:X} -> {value}")
On long address lists this is orders of magnitude faster.
See also
Searching memory β find addresses by value.
Pointers β follow multi-level pointer chains.