Initial commit
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
## 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, 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)`
|
||||
- **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)`
|
||||
Reference in New Issue
Block a user