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>
58 lines
3.3 KiB
Markdown
58 lines
3.3 KiB
Markdown
## ADDED Requirements
|
|
|
|
### Requirement: Three-tier execution model
|
|
|
|
WhiteMagic SHALL provide three execution strategies selected by payload safety: `RemoteThreadExecutor` (via `CreateRemoteThread`), `MainThreadPump` (work marshalled onto the target's own thread), and `InProcessInvoker` (direct native-delegate calls when injected in-process).
|
|
|
|
#### Scenario: strategies are distinct and selectable
|
|
- **WHEN** a caller chooses an execution strategy
|
|
- **THEN** each of remote-thread, main-thread-pump, and in-process MUST be individually invokable
|
|
|
|
#### Scenario: main-thread pump is the documented default for game state
|
|
- **WHEN** documentation or API guidance describes calling functions that touch single-thread-affinity process state
|
|
- **THEN** it MUST direct callers to the main-thread pump, not `CreateRemoteThread`
|
|
|
|
### Requirement: Remote-thread execution for thread-agnostic payloads
|
|
|
|
`RemoteThreadExecutor` SHALL create a remote thread at a target address using a calling-convention-aware stub, wait for completion, and return the typed exit value. Its documentation MUST state that it is safe only for thread-agnostic payloads.
|
|
|
|
#### Scenario: execute with parameters and convention
|
|
- **WHEN** `Execute<int>(addr, CallingConvention.Cdecl, arg1, arg2)` is called on a safe self-contained function
|
|
- **THEN** the target MUST be called with the arguments laid out per cdecl and the typed return value returned
|
|
|
|
#### Scenario: parameters marshalled and freed
|
|
- **WHEN** a `string` or struct parameter is passed to `Execute`
|
|
- **THEN** it MUST be allocated in the remote process, passed by pointer, and freed after the call completes
|
|
|
|
#### Scenario: no process open
|
|
- **WHEN** `Execute` is called with no process open
|
|
- **THEN** it MUST fail deterministically rather than crash
|
|
|
|
### Requirement: Crash-safe main-thread pump
|
|
|
|
`MainThreadPump` SHALL install a hook on a per-frame function in the target and, each time that function runs, drain a thread-safe queue of work items, executing each on the target's own thread and returning its result or exception to the requesting caller.
|
|
|
|
#### Scenario: work runs on the hooked thread
|
|
- **WHEN** a work item is queued and the hooked per-frame function next executes
|
|
- **THEN** the work item MUST run in the context of the thread that calls the per-frame function
|
|
|
|
#### Scenario: result returned to caller
|
|
- **WHEN** a caller queues a function returning a value and awaits its completion
|
|
- **THEN** the caller MUST receive the returned value
|
|
|
|
#### Scenario: exception propagated, pump survives
|
|
- **WHEN** a queued work item throws
|
|
- **THEN** the exception MUST be surfaced to the requesting caller AND subsequent queued items MUST still be processed
|
|
|
|
#### Scenario: uninstall restores the frame function
|
|
- **WHEN** the pump is disposed
|
|
- **THEN** the hooked per-frame function MUST be restored to its original bytes
|
|
|
|
### Requirement: In-process delegate invocation
|
|
|
|
`InProcessInvoker` SHALL convert a function address to a typed managed delegate and call it directly, without creating a thread or crossing a thread boundary.
|
|
|
|
#### Scenario: call as delegate
|
|
- **WHEN** `CreateFunction<TDelegate>(addr)` is called in-process and the returned delegate is invoked
|
|
- **THEN** the native function at `addr` MUST be called directly on the current thread with the delegate's marshalled arguments
|