Memory regions
A process’s address space is split into regions — contiguous blocks of memory, each with its own size, permissions and origin. Understanding the region map is the foundation of memory editing: every read, write or scan ultimately touches a region.
Listing the regions
get_memory_regions() is a generator that yields one
MemoryRegion per region:
with OpenProcess(process_name="game.exe") as process:
for region in process.get_memory_regions():
print(
hex(region.address),
region.size,
"R" if region.is_readable else "-",
"W" if region.is_writable else "-",
"X" if region.is_executable else "-",
"shared" if region.is_shared else "",
)
The MemoryRegion dataclass
Each region is an instance of MemoryRegion — an immutable
@dataclass(frozen=True) with the following fields:
| Field | Type | Meaning |
|---|---|---|
address | int | Base address of the region. |
size | int | Region size in bytes. |
is_readable | bool | True if the region can be read. |
is_writable | bool | True if the region can be written. |
is_executable | bool | True if the region contains executable code. |
is_shared | bool | True if the region is a shared/file-backed mapping. |
path | str | File backing the region (Linux only — empty on Windows/macOS). |
struct | platform-specific | Raw platform descriptor (see below). |
Immutability
MemoryRegion is frozen, so any region you obtain from
get_memory_regions() or snapshot_memory_regions() is safe to share across
threads or pass through functions without defensive copies.
The platform-specific struct
The struct attribute carries the underlying OS-specific descriptor. You
usually won’t need it — the portable booleans above are enough. When you do:
Windows —
MEMORY_BASIC_INFORMATION_{32,64}withProtect(PAGE_* bitmask) andType(MEM_PRIVATE/MEM_IMAGE/MEM_MAPPED).Linux — a small struct with
Privileges(bytes likerwxp/rwxs).macOS — a struct with
Protection(VM_PROT_*bitmask) andShared.
Snapshotting for refine workflows
For iterative scans (the Cheat Engine “First Scan → Next Scan” loop),
snapshot_memory_regions() materializes the region list once so subsequent
calls can reuse it:
regions = process.snapshot_memory_regions()
# Pass the same snapshot to as many scans as you want.
candidates = list(process.search_by_value(int, value=100, memory_regions=regions))
refined = list(process.search_by_addresses(int, addresses=candidates, memory_regions=regions))
The return type is MemoryRegionSnapshot — a thin list subclass that
behaves exactly like list[MemoryRegion]. Internally, the scan helpers
detect via isinstance(memory_regions, MemoryRegionSnapshot) that the list
is already address-sorted and skip the per-call sorted(...) step.
Filtered snapshots are not pre-sorted anymore
When you build a new list from a snapshot ([r for r in snap if r.is_writable]),
the result is a plain list, not a MemoryRegionSnapshot. The scan helpers
will re-sort it defensively — safe but slightly slower for huge region maps.
Pass the original snapshot when you can.
See Searching memory for the full recipe.
Filtering regions yourself
Once you have the snapshot, you can filter it however you like before handing it to a scan:
# Only writable regions (skip read-only static data — much faster).
writable = [r for r in regions if r.is_writable]
for address in process.search_by_value(int, value=target, memory_regions=writable):
...
A complete region map
A small script that prints a textual memory map (similar to the GUI app’s Memory Map dialog):
from PyMemoryEditor import OpenProcess
with OpenProcess(process_name="game.exe") as process:
print(f"{'ADDRESS':<18}{'SIZE':>14} RWX Source")
for region in process.get_memory_regions():
rwx = "".join([
"R" if region.is_readable else "-",
"W" if region.is_writable else "-",
"X" if region.is_executable else "-",
])
print(
f"0x{region.address:016X} "
f"{region.size:>12,} {rwx} "
f"{region.path or ''}"
)
See also
Modules & threads — list loaded DLLs/SOs/dylibs and threads.
Searching memory — the high-level scan API.