Skip to content

v3.0.0

Choose a tag to compare

@JeanExtreme002 JeanExtreme002 released this 08 Sep 23:23
ce0eda9

⚠️ Breaking change

read_process_memory(addr, int, N) with N in 3, 5, 6 or 7 and the value's
top bit set now returns a different number — on every platform, with no
exception raised.

bytes in memory: FF FF FF   (a 24-bit unsigned field at its maximum)

2.2.1  ->  16777215
3.0.0  ->  -1

Cheat Engine offers a "3 Bytes" type because games cap money and score in
24-bit fields, so this is reachable by following an ordinary tutorial. An
existing script, unchanged:

money = process.read_process_memory(addr, int, 3)
if money < 1000:
    process.write_process_memory(addr, int, 3, 16777215)   # "top up"
value read money < 1000 result
2.2.1 16777215 False does nothing — correct
3.0.0 −1 True tops up a field already at maximum

The guard inverts, and the branch that flips is the one that writes. Half
the value space of each affected width changes sign. Widths 1, 2, 4 and 8 are
untouched, and so is the int default of 4.

Why: int is signed everywhere else in this library — every C type it uses
is signed, the unsigned reads have their own family, and a scan already
compares signed on both of its sides. Widths 3/5/6/7 read unsigned only because
the padding byte happened to be zero, so a scan could match -1 at an address
that every read then reported as 16777215.

If you want the old value:

raw = process.read_process_memory(addr, bytes, 3)
value = int.from_bytes(raw, sys.byteorder, signed=False)

Other behaviour changes

These reject what used to be accepted. Nothing that worked depends on them —
the previous behaviour was memory-unsafe or meaningless:

  • A width wider than the type's largest C representation now raises
    ValueError.
    get_c_type_of(bool, 8) sized a 1-byte buffer for an 8-byte
    read, overflowing the calling process's memory; int at 16 overflowed 8.
  • float accepts only 4 and 8. IEEE-754 has no form between them, so other
    widths returned a plausible-looking number decoded from bytes nobody read
    (5.5e-318).
  • Linux reads and writes the width you asked for, not sizeof(buffer). A
    3-byte write used to touch 4 bytes, destroying a neighbouring byte, and the
    same read answered differently per platform.
  • search_by_addresses returns values at unusual int widths instead of
    None, and raises for a width the type cannot represent instead of reporting
    the address as unreadable.

str and bytes are unchanged.

New: MCP server

An optional Model Context Protocol server,
so an AI assistant can run the scan / refine / read loop itself.

pip install "PyMemoryEditor[mcp]"
claude mcp add pymemoryeditor -- pymemoryeditor-mcp

Fourteen tools cover the workflow, with results kept server-side behind a
handle so a 40 000-hit first scan costs a few tokens rather than a context
window. You approve every attach and your client confirms every write; OS and
credential processes are refused outright. The core library stays
dependency-free — only the protocol layer needs the SDK.

See the MCP guide.

Also

  • iter_processes() — a dependency-free counterpart to psutil.process_iter().
  • decode_scan_target and make_predicate promoted from private, so anything
    that must agree with a scan can compare the way a scan does.