Reconcile spec with Phase 2 review outcome; ignore *.log

Record the D1 deviation: InProcessReader reads the current process through
ReadProcessMemory/WriteProcessMemory on a self-handle, not unsafe direct
deref. Rationale: .NET cannot catch AccessViolationException, so a raw deref
of a bad address terminates the host with no soft-failure path. Update the
memory-access spec (new fail-soft scenario), design D1, and task 2.9.

Log task 2.10: ReadString null-terminator scan is not code-unit aligned, so
UTF-16/UTF-32 can match a misaligned multi-byte null or miss one split
across a chunk boundary (harmless for ASCII/UTF-8, the WoW case).

Ignore *.log (testrun.log).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
kbe
2026-07-21 19:28:28 +02:00
co-authored by Claude Opus 4.8
parent 8374650aac
commit 991387198d
4 changed files with 11 additions and 3 deletions
+1
View File
@@ -9,3 +9,4 @@ reference/
# Scratch
*.tmp
*.log
@@ -36,7 +36,7 @@ WhiteMagic is a **new, additive** .NET 8 library that unifies the four. It reuse
`MemoryBase` defines abstract `ReadBytes`/`WriteBytes`/`Read<T>`/`Write<T>`, relative/absolute addressing, and hosts the `PatchManager`. Two concrete readers:
- `ExternalReader : MemoryBase``ReadProcessMemory`/`WriteProcessMemory` over a `SafeMemoryHandle`. Owns allocation, injection, and the remote-thread + main-thread executors.
- `InProcessReader : MemoryBase``unsafe` direct pointer deref; owns the `DetourManager` and `InProcessInvoker`.
- `InProcessReader : MemoryBase`reads the current process via `ReadProcessMemory`/`WriteProcessMemory` on a self-handle; owns the `DetourManager` and `InProcessInvoker`. **(Revised from `unsafe` direct deref during Phase 2: .NET cannot catch `AccessViolationException`, so a bad deref kills the host with no soft-failure path; RPM-on-self fails soft. The in-process speed win moves to the delegate-call/detour paths, not the reader. See `specs/memory-access`.)**
**Why**: GreyMagic proved this abstraction lets the same higher-level code (pattern scan, patch, high-level API) run in either mode. External is the primary path for a bot host; in-process is the fast/crash-free path once injected.
@@ -2,7 +2,13 @@
### Requirement: Abstract memory base with two readers
WhiteMagic SHALL expose an abstract `MemoryBase` type defining `ReadBytes`, `WriteBytes`, generic `Read<T>`/`Write<T>`, array read/write, and string read/write, with two concrete implementations: `ExternalReader` (out-of-process via ReadProcessMemory/WriteProcessMemory) and `InProcessReader` (in-process via direct pointer dereference).
WhiteMagic SHALL expose an abstract `MemoryBase` type defining `ReadBytes`, `WriteBytes`, generic `Read<T>`/`Write<T>`, array read/write, and string read/write, with two concrete implementations: `ExternalReader` (out-of-process via ReadProcessMemory/WriteProcessMemory) and `InProcessReader` (in-process, reading the current process through ReadProcessMemory/WriteProcessMemory on a self-handle).
> **Deviation from design D1.** D1 originally specified `InProcessReader` as `unsafe` direct pointer dereference (the "fast/crash-free" path). Implementation revised it to `ReadProcessMemory`/`WriteProcessMemory` on a handle to the current process, because .NET (Core) cannot catch `AccessViolationException` (`HandleProcessCorruptedStateExceptions` is removed), so a raw deref of a bad address terminates the host process with no soft-failure path. RPM on a self-handle fails soft (returns empty) like `ExternalReader`. The in-process performance win therefore moves to the delegate-call and detour paths (`InProcessInvoker`, `DetourManager`), not the reader.
#### Scenario: in-process read fails soft on an invalid address
- **WHEN** an `InProcessReader` reads an unmapped or protected address
- **THEN** it MUST return empty/`default` rather than crash the host process
#### Scenario: external read round-trip
- **WHEN** an `ExternalReader` opens a target process and writes a value with `Write<int>(addr, 0x1234)` then reads it back with `Read<int>(addr)`
@@ -16,7 +16,8 @@
- [x] 2.6 Implement `ReadString`/`WriteString` on `MemoryBase` to pass 2.5
- [x] 2.7 Add tests for relative/absolute addressing (`GetAbsolute`/`GetRelative`, `isRelative` flag)
- [x] 2.8 Implement addressing helpers to pass 2.7
- [x] 2.9 Add tests + `unsafe` implementation for `InProcessReader` (direct deref against own process); verify shared `MemoryBase` API works for both readers
- [x] 2.9 Add tests + implementation for `InProcessReader` (RPM/WPM on a self-handle — see D1 deviation note; direct deref rejected because .NET cannot catch `AccessViolationException`); verify shared `MemoryBase` API works for both readers
- [ ] 2.10 Follow-up (found in review): `ReadString` scans for the null terminator byte-by-byte, so for UTF-16/UTF-32 it can match a **misaligned** multi-byte null across a char boundary (e.g. `"A"`+U+4200 = `41 00 00 42` matches `{00,00}` at offset 1) and can miss a terminator split across the 64-byte chunk boundary. Harmless for ASCII/UTF-8 (the WoW case). Fix: align the scan to the encoding's code-unit width and carry the last `(nullLen-1)` bytes across chunks. Add a UTF-16 test.
## 3. Managed Assembler (spec: managed-assembler)