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:

TypeDefault sizeNotes
int4 bytesSigned integer. Override to 1/2/8 for other widths.
float8 bytesdouble by default; pass 4 for float32.
bool1 byteC 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)
Parameters:
  • address (int) – target memory address.

  • pytype (Type) – one of bool, int, float, str, bytes.

  • bufflength (int) – value size in bytes. Optional for numeric types; required for str / bytes.

Returns:

the decoded value.

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 for str / bytes. For str / bytes an explicit value is a maximum that truncates (str counts characters, bytes counts bytes) and never pads. Since it is optional, pass value by 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_*:

ShortcutReads / writesBytes
read_char / read_uchar8-bit integer β€” signed / unsigned1
read_short / read_ushort16-bit integer β€” signed / unsigned2
read_int / read_uint32-bit integer β€” signed / unsigned4
read_long / read_ulong32-bit integer β€” signed / unsigned4
read_longlong / read_ulonglong64-bit integer β€” signed / unsigned8
read_float32-bit floating point4
read_double64-bit floating point8
read_boolboolean1
read_string / read_bytestext / raw bytesyou 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 in try/except OSError.

  • PermissionError β€” the handle was opened without write access (Windows read-only handle). See Opening a process.

  • ValueError β€” bufflength was omitted for a str or bytes read (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