v2.2.0
What's changed
Two issues: a byte-array scan that refused to run, and a macOS scan that gave up part-way through. Both turned out to be one wrong assumption repeated in several places, so the fixes reach further than the reports did.
Fixed
- Byte Array scans no longer need you to count. Searching for a byte array required keeping the Length field in sync with the bytes you typed, and got both failure modes wrong: a value wider than Length died with
ValueError: byte string too long— the default is 4, so any 5-byte value hit it — and a value narrower than Length was NUL-padded, silently turning the scan into "these bytes followed by zeros", which finds nothing and never says why. The width now comes from the value, as it always did for String (UTF-8) and as the library itself does inresolve_bufflength_for_value(#79, #87) - Everything that acts on the rows on screen uses the width they were read at. Fixing the above exposed the same mistake in five more places, all reading the Length field — which follows the Value box, and you can retype that between scans. Promoting to the cheat table created entries at the wrong width; Increased / Decreased / Changed / Unchanged re-read at a width the baseline was never recorded at, so every address reported as changed; Update Values re-read at whatever was in the box, overwriting stored values with truncated reads, and aborted outright if the box was empty; the length readout ignored a range's upper bound while the scan sized by
max(lo, hi); and Increased/Decreased Value By sized the re-read from the delta text. All of them now use the width of the scan that produced the rows, adopted when that scan actually lands — so one that errors, is cancelled, or is rejected leaves nothing behind (#87) - macOS: a scan no longer gives up on the first page the kernel won't hand over.
mach_vm_read_overwritereturningKERN_MEMORY_ERROR(kr=10) aborted the whole scan, discarding the rest of the address space and leaving a half-populated result list next to an error dialog. The kernel header documents that code as "temporary", in explicit contrast with the permanentKERN_MEMORY_FAILUREbeside it, so it belongs with the three page-vanished codes already skipped. It comes from file-backed read-only mappings — code segments, dylibs, the dyld shared cache — which is why pattern scans hit it first: they walk those deliberately, since an AOB signature normally lives in code. Unchecking Writable regions only reproduced it with any value type (#88) - A cheat-table cell that shows hex now writes hex. Giving AOB entries a real width made their Value cell editable for the first time, and it was wired backwards: the cell formatted what it read as hex but parsed an edit as a pattern, so typing
00wrote ASCII0x30 0x30into the target and reported success (#87)
Added
- IDA patterns work as cheat-table entries, and
?patches. A pattern is a way to find an address, so its parse answers the scanner's question and hands back the pattern text — not something any address can hold. Specs answer both questions now, and in a value cell a?keeps the byte that is already there: over48 8B 12 34 00, editing to48 8B ? ? 90writes48 8B 12 34 90. You name the bytes you mean to change and leave the operands alone, the way Cheat Engine does (#87) - Regex is offered where a value is read at a known address. Both pointer dialogs filtered their "Read value as" list by "is this a pattern?", which lumps together two specs that behave nothing alike once the address is known. An IDA pattern genuinely can't be sized there; a regex can, and it renders bytes as text up to the first NUL, which
String (UTF-8)does not — readingPlayer42\0\xff\xfe\x01restshowsPlayer42rather than the tail of garbage (#87)
Changed
- The Length field is a read-only readout for String and Byte Array, showing what the value sizes to, and
— (set by the value)before anything is typed. Regex keeps the only editable width in the app: itsbyte_length, the maximum a match can consume (#87) - Values that used to be dropped in silence are refused with a message: one wider than the entry that holds it —
prepare_writetreats that width as a hard truncating cap — an empty text value, which sized to a 1-byte NUL buffer and matched every zeroed byte in the target, and Increased/Decreased Value By on String or Byte Array, where the comparison concatenates rather than adds and could never be true (#87) - Changing a frozen row's type releases the freeze instead of leaving the box ticked over a target that no longer means anything (#87)
- ~900 lines of new app tests. The macOS memory and scan suites run for the first time on that platform — they were failing on
main, all fifteen with kr=10, which CI could not see since macOS is not in the matrix
Install
pip install --upgrade PyMemoryEditor📖 Full documentation: pymemoryeditor.readthedocs.io
Full Changelog: v2.1.0...v2.2.0