Repository navigation
fix: correct four defects in Memory and verify them in CI - #11
Merged
Merged
Conversation
MemoryXTest was a demo hard-coded to a specific game's process and addresses, so it could compile but never run. Rewrite it as a self-verifying harness: it commits a page inside its own process, opens that process through the library, round-trips every read and write overload, and exits non-zero on any failed check. That needs no external target and no elevation, so the build workflow now runs it and CI checks behaviour rather than only compilation. The defects it covers: - WriteMemory(long, byte) forwarded to BitConverter.GetBytes, which has no byte overload. The argument was promoted to short, so writing one byte clobbered the following byte. The README example was worse: 0xba is an int literal, so it bound to the int overload and wrote four bytes. Build the single-byte buffer directly, and correct the README to cast. - ReadSingle requested 8 bytes for a 4-byte value, so a float near the end of a committed region failed to read and silently returned 0. Request 4, as ReadInt32 already does. The harness places a float in the last 4 bytes of a one-page allocation, which fails without this fix. - Both GetProcessHandle overloads returned true whenever no exception was thrown, including when OpenProcess returned NULL for access denied. Callers then saw every read return zeros. Return whether the handle is non-null. - GetBaseAddress bound an exception variable it never used (CS0168). Release builds of MemoryXTest also now set Prefer32Bit=false. Memory declares lpBaseAddress as a 64-bit long, which only matches the native LPVOID parameter in a 64-bit process; an AnyCPU executable otherwise defaults to running as 32-bit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SKZ2gugmpaYSHPS7LR5Xjg
ayuthmang
marked this pull request as ready for review
September 2, 2026 18:24
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes four defects in
Memory.csand turnsMemoryXTestinto a harness that exercises the library on every build.CI is green on
7435c68: all 17 checks pass in both Debug and Release.The harness
MemoryXTestwas a demo hard-coded toTutorial-x86_64and fixed addresses — it compiled but could never run, so CI only ever proved the code parsed.It now commits one page inside its own process, opens that process through the library, round-trips every read and write overload against the page, and exits non-zero on any failed check. No external target process, no elevation, so the build workflow runs it directly. Nothing is lost from the old file: the README already carries every usage example it demonstrated.
The defects
1.
WriteMemory(long, byte)never wrote one byteBitConverter.GetByteshas nobyteoverload. The argument was promoted toshort(C# prefers a signed conversion target over unsigned), so writing one byte clobbered the following byte.The README example was worse still —
0xbais anintliteral, so it bound to theintoverload and wrote four bytes. Fixed by building the single-byte buffer directly; the README now casts.2.
ReadSinglerequested 8 bytes for a 4-byte valueReadInt32correctly asks for 4;ReadSingleasked for 8. When the extra 4 bytes crossed into unreadable memory the whole read failed and the method silently returned0f.3.
GetProcessHandlereported success on failureBoth overloads returned
truewhenever no exception was thrown — including whenOpenProcessreturnedNULLfor access denied, which is exactly the case the README warns about. Callers gottrue, then every read returned zeros. They now return whether the handle is non-null.4.
GetBaseAddressbound an unused exception variablecatch (Exception ex)— theCS0168warning present in every build log.What the harness actually covers
Worth being precise, since a passing suite is only as good as its ability to fail:
0xFF, writes one byte, asserts the other three survive. The old code wrote{0xBA, 0x00}, soafter[1]would be0x00and the check fails.GetBytesWrittenwould also report 2.ReadSingleover-readVirtualAllocreserves at 64 KB granularity, so the next page is free; an 8-byte read spans into it, fails outright, and returns0f.OpenProcessfailure needs a process the runner cannot open, which is not reliable in CI. This fix rests on review, not on a test.CS0168Also
MemoryXTestRelease builds now setPrefer32Bit=false.MemorydeclareslpBaseAddressas a 64-bitlong, which only matches the nativeLPVOIDparameter in a 64-bit process; an AnyCPU executable otherwise defaults to running 32-bit, where these signatures are wrong. Debug already set this — Release did not.Behaviour change
Fix 1 changes what a published method does: one byte written where two or four went before. It is what the method signature and the README both promise, and writing past the requested address is memory corruption in the target process. Say the word if you would rather keep the old overload and add a separate
WriteByte.Deliberately not included
Two items from the review need a maintainer decision rather than a silent change:
ReadMemoryPointerInt,WriteMemoryPointerandGetPointerAddresswalk chains withReadInt32, truncating any pointer above 4 GB — yet the README's worked example targets a 64-bit process. Switching to 64-bit reads unconditionally would break 32-bit targets, so this needs a design decision (bitness detection, or explicit 32/64 variants).WriteMemory(string)encodes ASCII whileReadStringdecodes UTF-8, andReadStringdoes not stop at a NUL.🤖 Generated with Claude Code
https://claude.ai/code/session_01SKZ2gugmpaYSHPS7LR5Xjg