Add whitemagic-foundation OpenSpec design; isolate reference libs

Design-only foundation for WhiteMagic, a .NET 8 x64 library unifying the
four studied process-manipulation libs. Adds proposal, design (7 decisions),
7 capability specs, and TDD task breakdown; all validate strict.

Move Blackmagic, Blackmagic-old, GreyMagic, MemorySharp, fasm into
reference/ (gitignored) — studied, not built here; each has its own
upstream repo and nested .git. Rewrite plan doc paths to reference/.

Corrects two factual defects found in review:
- current BlackMagic has no D3D EndScene hook; MainThreadPump is net-new
  built on DetourManager, not a port
- no BlackMagic.slnx exists; task 1.3 creates a fresh solution

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
kbe
2026-07-21 16:50:03 +02:00
co-authored by Claude Opus 4.8
commit 4405af15fd
44 changed files with 4495 additions and 0 deletions
@@ -0,0 +1,57 @@
## ADDED Requirements
### 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).
#### 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)`
- **THEN** the returned value MUST equal `0x1234`
#### Scenario: in-process read of own memory
- **WHEN** an `InProcessReader` reads a known address in its own process
- **THEN** the value MUST match a direct managed read of the same address
#### Scenario: shared API surface
- **WHEN** code is written against the `MemoryBase` abstract type
- **THEN** it MUST operate unchanged against both `ExternalReader` and `InProcessReader`
### Requirement: Typed read/write via marshal cache
`MemoryBase` SHALL support generic `Read<T>`/`Write<T>` for blittable and marshalled struct types, using a per-type `MarshalCache<T>` that caches size, type code, and marshalling requirements to avoid per-call reflection.
#### Scenario: blittable struct round-trip
- **WHEN** a blittable `[StructLayout(LayoutKind.Sequential)]` struct is written and read back
- **THEN** all fields MUST be preserved exactly
#### Scenario: marshal cache computed once
- **WHEN** `Read<T>` is invoked repeatedly for the same type `T`
- **THEN** `Marshal.SizeOf` and type inspection for `T` MUST be computed at most once and reused
#### Scenario: array read
- **WHEN** `Read<T>(addr, count)` is called
- **THEN** it MUST return an array of exactly `count` elements read contiguously from `addr`
### Requirement: String read and write with encoding
`MemoryBase` SHALL read and write strings with a caller-specified `Encoding` and a maximum length, terminating reads at a null terminator or the maximum length.
#### Scenario: ASCII write then read
- **WHEN** `WriteString(addr, "hello", Encoding.ASCII)` is called then `ReadString(addr, Encoding.ASCII)`
- **THEN** the result MUST equal `"hello"`
#### Scenario: read stops at null terminator
- **WHEN** a null-terminated string shorter than `maxLength` is read
- **THEN** the returned string MUST exclude the terminator and everything after it
### Requirement: Relative and absolute addressing
`MemoryBase` SHALL convert between addresses relative to the module image base and absolute addresses via `GetAbsolute` and `GetRelative`, and accept an `isRelative` flag on read/write operations.
#### Scenario: relative resolves against image base
- **WHEN** `GetAbsolute(relative)` is called with the process image base known
- **THEN** the result MUST equal `imageBase + relative`
#### Scenario: read with isRelative
- **WHEN** `Read<int>(offset, isRelative: true)` is called
- **THEN** the read MUST occur at `GetAbsolute(offset)`