Run an unmodified Windows memory-editing tool, such as Cheat Engine, under Wine on Linux, with its memory reads and writes redirected to a separate running Windows VM via memflow.
The tool sees a local Windows process. The bytes come from the guest VM, read out-of-band by the hypervisor. The interposed tool API is passive introspection: it reads and writes existing guest memory from the outside. Explicit guest DLL injection is a separate decant-cli guest-inject path that accepts DLL bytes, maps them into a selected guest process, and invokes the payload through the configured guest execution method.
$ decant-cli read 2980 0x00007ff756d00000 16
0x00007ff756d00000 4d 5a 90 00 03 00 00 00 04 00 00 00 ff ff 00 00 MZ..............
# bytes from the VM's explorer.exe, served to a Wine-hosted tool
How it works
┌──────────────────────────────────────────────┐
│ Windows guest VM (QEMU/KVM) │
│ target.exe, unmodified │
└──────────────────▲───────────────────────────┘
│ physical RAM read out-of-band (memflow)
┌──────────────────┴───────────────────────────┐
│ Linux host │
│ │
│ decant-daemon (the cellar) │
│ owns the MemoryBackend, dispatches reqs │
│ ▲ │
│ │ TCP 127.0.0.1 │
│ │ length-prefixed bincode │
│ │ (decant-protocol) │
│ Wine process │ │
│ [ unmodified tool ] │
│ + decant-interpose.dll (the carafe) │
│ intercepts Win32/NT memory exports │
└──────────────────────────────────────────────┘
Every Win32/NT memory and introspection API a tool can call (ReadProcessMemory,
NtReadVirtualMemory, VirtualQueryEx, CreateToolhelp32Snapshot, EnumProcessModules,
GetProcAddress, and the rest) reduces to nine primitives, the
MemoryBackend trait:
;
;
;
;
;
;
;
// + process_by_name, module_by_name
Translating these once covers every Win32 API above them.
Backends
MockBackend: scriptable mock guest, runs offline.MemflowBackend(--features memflow): reads guest physical RAM.
See docs/ARCHITECTURE.md.
Library
Use Decant as a crate. Embed a backend in your own program, or connect to a daemon.
use *;
// In-process backend, the way memflow is used (MemflowBackend needs --features memflow):
let backend = connect?;
let proc = backend.process_by_name?;
let hits = scan?;
let addr = resolve?;
let bytes = backend.read?;
// Or talk to a running daemon over the network:
let mut client = new;
let procs = client.processes?;
client.write?;
Quickstart (offline, no VM)
For the Wine and cross-compile path:
# cross-compiles a Rust cdylib (hello-dll), loads it from a PE32+ exe under an
# isolated repo-local WINEPREFIX, calls the exported `add`, prints 5
xtask subcommands: setup, build-native, build-dll, test, test-live, wine-smoke, guest-inject-fixture, inject-test, e2e.
Master runner
scripts/decant.sh is the tracked operator entry point. It builds the required artifacts and keeps runtime VM and Wine actions behind subcommands. Fixture and regression harnesses live in scripts/decant-test.sh.
# Build host and Wine artifacts.
# Run a Wine-hosted tool through the launcher. The target can be Cheat Engine or any PE exe.
# Start the memflow daemon in the foreground.
MEMFLOW_PLUGIN_PATH=/opt/memflow
MEMFLOW_PLUGIN_PATH=/opt/memflow
# Inject DLL bytes into a guest process through a running daemon.
# Reproduce the tracked VM fixture. Copy target/decant-run/guest-inject-target.exe
# into the VM, start it there, then run:
MEMFLOW_PLUGIN_PATH=/opt/memflow
The KVM connector normally needs root access for the daemon. If running guest-fixture from a non-interactive shell, run sudo -v first so the script can start the daemon without a password prompt.
CLI
Point decant-cli at a running daemon. The commands are the same for mock and VM backends.
$ decant-cli processes
4 System
2980 explorer.exe
3120 target.exe
...
$ decant-cli modules 2980
ntdll.dll 0x00007ffb8e2c0000 0x1f0000
KERNEL32.DLL 0x00007ffb8d910000 0x0c1000
...
$ decant-cli read 2980 0x00007ff756d00000 16
0x00007ff756d00000 4d 5a 90 00 03 00 00 00 ... MZ..............
$ decant-cli write 3120 0x00007ff700401000 deadbeef
wrote 4 bytes
$ decant-cli read 3120 0x00007ff700401000 4
0x00007ff700401000 de ad be ef ....
$ decant-cli scan 3120 "DE CA ?? EF" # AOB: hex bytes, ?? or ? wildcards
0x00007ff7004012a0
0x00007ff700401dd8
$ decant-cli resolve 3120 0x140010200 0x10 # pointer chain: base plus offsets
0x0000000000140010290 -> u64=0x539 (1337)
$ decant-cli diagnostics
connector: memflow:kvm reads: 42 writes: 3 unsupported_ops: 0
Full set: processes, modules <pid>, exports <pid> <module>, read <pid> <addr> <len>, write <pid> <addr> <hexbytes>, memory-map <pid>, scan <pid> "<AOB>", resolve <pid> <base> <off>..., diagnostics. Add --json for machine-readable output.
Running the daemon
Mock backend (no VM, default; develop the whole stack against a mock guest):
VM backend (memflow; see the memflow backend section of docs/ARCHITECTURE.md). Build it once:
Choose one memflow connector path and keep its argument shape with it. The QEMU connector
reads the qemu process directly: no kernel module, and no root once the binary has ptrace
capability. Its arg is the VM name from qemu -name guest=<name>; leave it empty to
auto-detect a single VM.
# one-time, instead of sudo:
MEMFLOW_PLUGIN_PATH=/path/to/plugins DECANT_CONNECTOR_ARGS=<vm-name> \
# decant-daemon listening on 127.0.0.1:7878 (backend: memflow:qemu)
The KVM connector reads through the memflow.ko kernel module: lower overhead, needs
root and the qemu PID as its arg. Do not pass the VM name to this connector.
QEMU_PID=
If the QEMU connector starts, finds the qemu process, then exits with unable to find dtb,
the daemon never binds its TCP port and the CLI will report connection refused. That is a
connector/Windows-OS-layer startup failure, not evidence that the target process or DLL is
wrong. Use the KVM connector path if that plugin and kernel module are available, or pass
memflow-win32 OS hints such as dtb/kernel_hint once those are known:
MEMFLOW_PLUGIN_PATH=/path/to/plugins \
DECANT_CONNECTOR_ARGS=<vm-name> \
DECANT_OS_ARGS=':arch=x64,dtb=<hex-dtb-without-0x>,kernel_hint=<hex-va-without-0x>' \
Usage notes:
- Connector arg: the qemu connector takes the VM name (or empty to auto-detect); the kvm connector takes the qemu PID. Both are memflow's bare default arg; a
pid=named arg fails withError(Connector, ArgValidation). - OS arg:
DECANT_OS_ARGSis passed to memflow-win32. Use a leading:when only passing key/value hints, for example:arch=x64,dtb=1aa000. MEMFLOW_PLUGIN_PATHpoints at the directory withlibmemflow_{qemu,kvm,win32}.so. The plugin ABI is the integerMEMFLOW_PLUGIN_VERSION, not the crate version; a 0.2.4 core loads 0.2.1 plugins.- The backend connects before binding the socket, so a failure exits with a message instead of leaving a partial server.
- Write to stable memory (zero padding), not active heap; a hot slot can be reclaimed by the guest between operations.
Running a tool under the interposer
wine-env/run.sh runs any unmodified Windows tool under the isolated prefix with the
carafe injected and pointed at a daemon. It co-locates decant-launcher.exe and
decant_interpose.dll next to the target, starts it suspended, injects the carafe, and
connects to DECANT_ENDPOINT (default 127.0.0.1:7878).
DECANT_ENDPOINT=127.0.0.1:7878
The tool sees the guest: its process list (served from NtQuerySystemInformation),
scans, and reads and writes all route to the daemon. A full-region scan reads the guest's
committed memory one request per caller read; the backend reuses the resolved process and
the daemon disables Nagle, so region scans run at interactive speed (see the memflow backend section of the architecture doc). Install
a GUI tool into the prefix first with WINEPREFIX="$PWD/wine-env/prefix" wine installer.exe,
then point run.sh at its executable. If a window fails to map after an interrupted run,
reset the prefix with WINEPREFIX="$PWD/wine-env/prefix" wineserver -k before relaunching.
Injection configuration
decant-launcher.exe reads an optional TOML file from DECANT_CONFIG. If the file is
absent, the launcher uses the standard method and a 5000 ms ready-token timeout. A malformed
file fails before the target is resumed.
[]
= "standard" # standard | manual-map | thread-hijack | plugin | external
= 5000
= "my_injector.dll"
= ["my_inject.exe", "--flag"]
Methods:
standard: public-exportCreateRemoteThreadatkernel32!LoadLibraryA; this is the default and keeps the version-agnostic guarantee.manual-map: mapsdecant_interpose.dllfrom its image bytes, applies relocations, resolves imports, runs TLS callbacks, and calls the DLL entry point. It reportsLoaderInternalsportability and is verified only by the carafe ready signal.thread-hijack: rewrites the suspended main thread context to run a small loader stub, waits for the ready signal, then releases the stub so it restores registers and jumps to the original instruction pointer. It reportsPrologueBytesportability.plugin: loads a PE cdylib fromplugin_pathand calls itsdecant_injectexport through the versioned ABI.external: startsexternal_command, writes one protocol frame containing the target PID, carafe path, carafe bytes, and ready-token name, then waits for the same ready signal.
Bring-your-own injectors must run PE-side in the same Wine prefix as the launcher. The process
handle is a wineserver handle, so an arbitrary Linux program cannot use it directly. A plugin
exports decant_inject_abi() -> u32 and decant_inject(*mut DecantInjectRequest) -> i32; return
0 after starting the load, and let the harness wait on the ready token. Low-level plugin code can
reuse decant_inject::sdk for remote allocation, read/write, protection changes, remote
LoadLibraryA, remote GetProcAddress, and remote-thread start/wait without reaching into Wine
internals.
Guest-side DLL mapping is a separate injection domain. The daemon selects the guest process at
injection time, so config can target a PID directly or a process name plus an optional byte
pattern that disambiguates matching processes. Patterns are hex bytes with ?/?? wildcards.
[]
= "guest"
= "manual-map"
= 10000
[]
= "target.exe" # or: pid = 1234
= "44 45 ?? 41"
= "44 45 43 41 4E 54 3A 3A 53 54 41 47 45 30 30"
= "44 45 43 41 4E 54 3A 3A 52 45 53 55 4C 54 30 34"
= "payload.dll"
= "virtual-alloc"
= "require-loaded" # require-loaded | load-with-guest-loader
= "callbacks-only" # callbacks-only | skip | require-static
= "section" # section | rwx
= "reject-unsupported" # reject-unsupported | best-effort | allow-unsupported
= "native" # native | registered-unwind
= "standard" # standard | write-through-final
= "existing-thread" # existing-thread | require-module-backed
= "private" # private | sec-image
= "off" # off | vad-image-map
= "kernel32.dll"
= "Sleep"
[]
= "iat-hook"
= 10000
The memflow guest injection backend maps PE32+ x64 DLL bytes, applies DIR64 relocations,
resolves normal and delay imports by name or ordinal, follows forwarded exports, materializes
newly allocated pages through the configured IAT hook, writes the image with read-after-write
checks, applies section-derived page protections by default, calls TLS callbacks according to
tls, and calls DllMain. final_protections = "section" allocates RW memory, writes the
mapped image, then applies PE-derived permissions before payload code runs; rwx is available as
an explicit compatibility mode. dependency_policy = "require-loaded" means every imported
module must already be present in the target process;
load-with-guest-loader calls the target's LoadLibraryA and GetProcAddress for missing
dependencies. Full loader-managed static TLS registration remains unsupported. By default,
loader_metadata = "reject-unsupported" rejects payloads that need static TLS slots, unwind
registration, or load-config processing. loader_metadata = "best-effort" registers x64 unwind
metadata through guest RtlAddFunctionTable and seeds the load-config security cookie when the
mapped image exposes the default cookie slot; loader-private state such as VAD/section-object
state, full LDR ownership, and per-thread TLS template propagation is still not
synthesized. When loader_entries = "synthesized", Decant allocates a static TLS
slot via TlsAlloc, patches the index into the image buffer, copies the TLS
template, and calls TlsSetValue for the current helper/target thread. That is
only valid for code running on the same thread; other existing threads are not
covered, and the remote-thread DllMain thread does not inherit that template.
For payloads with load-config metadata, loader_metadata = "best-effort"
and loader_entries = "synthesized" also request a CFG valid-call-target mark
for the entry point and exported function RVAs via SetProcessValidCallTargets;
broader CFG/load-config metadata is still not synthesized.
allow-unsupported skips the guards only for payloads that do not rely on the corresponding
loader registration. call_stack = "registered-unwind" registers x64 unwind metadata for the
IAT-hook stub and uses a single framed stack allocation so stack walking can unwind through the
stub; it does not, by itself, spoof caller frames or shape the stack to impersonate another call
path. permission_transitions = "write-through-final" allocates with final-ish image
permissions, materializes pages by read-touch when the initial protection is not writable, writes
the mapped image through memflow, and skips final VirtualProtect calls that already match the
initial protection; the allocation/write/protect sequence is still observable.
thread_starts = "require-module-backed" keeps the IAT-hook path on an existing target thread
and rejects runs unless the IAT slot, original import target, and staging cave are inside loaded
module ranges; it verifies module-backed hook plumbing but does not inspect payload entrypoints
or helper calls. For remote-thread, require-module-backed requires a payload-image executable
code cave for the ThreadProc thunk and refuses to fall back to a temporary thread start.
image_backing = "sec-image" stages the payload as a guest temp file and maps it
through CreateFileMappingW(SEC_IMAGE) +
MapViewOfFile(FILE_MAP_COPY), so the executable region starts as a real kernel-created
image-file section instead of private committed memory. Decant then applies relocations, imports,
delay imports, the load-config security cookie, TLS callbacks, and DllMain on top of that view.
The section object and image-file VAD backing are produced by the NT memory manager through
public guest exports, not forged. Pages Decant patches (imports, security cookie, IAT) become
copy-on-write private pages, while unpatched pages remain file-backed, so the
section object is real but the modified view is not identical to the on-disk image. sec-image
requires allocation = "virtual-alloc" for helper buffers and
final_protections = "section", because an image-file-backed mapping uses PE-derived page
protections rather than a single RWX region.
guest.execution.method = "iat-hook" is the default execution path. Decant snapshots the
selected thunk, stage bytes, and writable result slot, patches them, lets one target thread run
the requested call, reads the return value, and restores the IAT-hook transaction (IAT slot,
stub bytes, result block) on every exit path. The sec-image trampoline and
registered-unwind metadata are restored or left in place separately from this transaction.
The result slot is execution scratch for this call path, not a payload success marker. For
repeatable runs, set guest.stage_pattern or guest.stage_base to executable staging bytes you
control, and set guest.result_pattern or guest.result_base to a writable scratch slot;
otherwise Decant only auto-selects memory that passes those permission checks.
guest.execution.method = "remote-thread" creates a kernel-tracked guest thread by first
entering the target through the IAT-hook trampoline, then calling CreateThread from inside the
target process. The thread starts at a ThreadProc thunk that calls
DllMain(hinst, DLL_PROCESS_ATTACH, NULL) with the proper x64 calling convention. If the mapped
payload image has a large enough executable code cave, Decant places that thunk there so the
kernel-recorded start address is inside the mapped image; otherwise it uses a temporary helper
allocation. thread_starts = "require-module-backed" makes the payload-image code cave
mandatory and fails rather than using the temporary fallback. The ThreadProc thunk stores
DllMain's return value and a completion marker in its scratch block; the host polls those bytes
through the backend instead of requiring another target import call after CreateThread.
Remote-thread launch helpers use native helper-call stack handling even when
stack_shaping = "spoofed" is selected; DllMain itself runs on the new thread through the thunk.
thread-hijack,
apc, and package/session selectors are parsed but not implemented by this backend.
Guest injection results include artifact audit: notes for the observable properties of the
selected path: private or SEC_IMAGE-backed image memory, loader/module metadata state,
section-derived versus explicit-RWX permissions, registered or unregistered unwind/load-config
metadata, call-stack policy, permission-transition policy, thread-start policy, image-backing
policy, and the selected execution path. When requested, Decant can synthesize partial,
transient PEB loader-list entries; it does not create VADs or section objects. With
image_backing = "sec-image" the section object and image-file VAD backing are real
kernel-created state produced through public guest exports, not forged. stack_shaping = "spoofed" is limited to writing a synthetic return address for selected helper/payload calls; it
does not synthesize a full caller chain or normalize arbitrary stacks. Decant does not hide all
allocation/write/protect observability. It does not synthesize or rewrite kernel thread-start
metadata. vad_spoof = "vad-image-map" is parsed as an explicit experimental request, but the
memflow backend currently returns unsupported rather than mutating undocumented Windows VAD
fields. Use image_backing = "sec-image" for the supported path that obtains real image-file VAD
backing from the guest memory manager.
For UWP/AppContainer loader-style injection, the relevant extra requirement is DLL file access:
the AppContainer SID such as S-1-15-2-1 must be granted read/execute access before
LoadLibraryW can open the file. Private guest byte manual-map does not use a guest-visible DLL
path; image_backing = "sec-image" stages a temporary guest file to obtain a real image section,
and loader-style methods use guest-visible paths as well.
Limits
The interposed Win32 memory API exposes inspection and editing to the Wine-hosted tool. It does
not turn the tool's arbitrary process-control calls into guest execution. Explicit
no-guest-software DLL mapping is provided by decant-cli guest-inject through the separate
guest injection domain above. Decant returns a structured error and increments a diagnostics
counter for any operation it cannot perform, and never returns a false success.
| Supported | Unsupported (returns an error) |
|---|---|
| Read and write existing memory | Tool-initiated VirtualAllocEx and new guest allocations |
| AOB scan | CreateRemoteThread and remote threads |
| Pointer-chain resolution | DLL injection into the target |
| Module and export resolution | SetWindowsHookEx |
| In-place byte patching | Calling a guest function |
Explicit guest DLL injection via decant-cli guest-inject (manual-map method implemented) |
|
VirtualProtectEx/NtProtectVirtualMemory (success; reports the page's prior protection) |
|
VirtualQueryEx/NtQueryVirtualMemory (State/Type/Protect) |
Notes:
- Hooks are event-driven; Decant polls. It cannot deliver a
SetWindowsHookEx-style callback. - A paged-out guest page reads as not-present (a
ReadFailed, not truncated bytes). - Freezing a fast-changing or per-frame value is racy by construction. Decant reads and writes guest memory out of band; it cannot install a hook in the guest or perform an atomic read-modify-write across the boundary, so a freeze loop can lose races against the game's own writes. Slow-changing values freeze reliably.
- Cheat Engine and any other tool that resolves the memory APIs at runtime route the same as one that imports them. Such a tool does not import
ReadProcessMemory; it looks the address up withGetProcAddressat runtime, and it lists processes throughNtQuerySystemInformationrather than toolhelp. The carafe patchesGetProcAddress's own import slot, so every runtime lookup of an interposed memory API returns the carafe's hook, and it synthesizesNtQuerySystemInformationfor the process list, along withNtOpenProcess,NtGetNextProcess,Toolhelp32ReadProcessMemory, and theNtQueryInformationProcessimage classes. A tool that imports the APIs directly (the bundledsample-tool) routes through the import-table patch instead. This is general, not a Cheat-Engine special case; either way the binding stays on public Win32/NT exports.cargo xtask dynamicexercises the runtime-resolution path with a tool that resolves every memory API only throughGetProcAddressand enumerates only throughNtQuerySystemInformation. What stays unsupported through the hooked tool API is arbitrary guest code execution (see the table above); explicit guest DLL injection goes throughdecant-cli guest-inject. - The synthetic process handle services the full handle tail:
OpenProcess,ReadProcessMemory,WriteProcessMemory,CloseHandleandNtClose,DuplicateHandle,WaitForSingleObject/WaitForSingleObjectEx/NtWaitForSingleObject,GetHandleInformation/SetHandleInformation,GetProcessId,GetExitCodeProcess,GetPriorityClass,GetProcessTimes,IsWow64Process,QueryFullProcessImageName,GetProcessImageFileName, theNtQueryInformationProcessbasic, wow64, and image classes,VirtualQueryExandNtQueryVirtualMemory, andVirtualProtectExandNtProtectVirtualMemory. The protection-change pair returns success and reports the page's prior protection without altering it: memflow writes guest physical memory and is not bound by virtual page protection, so a write to a page the tool sees as read-only lands without a real protection change.NtQueryInformationProcess(ProcessBasicInformation)returns the pid with a PEB base of 0, since memflow's generic ABI does not expose it, so guest PEB-walking features are unavailable. The execution and process-control exports (memory allocation, remote threads,TerminateProcess,NtSuspendProcess/NtResumeProcess) refuse. VirtualQueryExandNtQueryVirtualMemoryreportState,Type, andProtectderived from the guest page tables and module list: a region overlapping a loaded module reportsMEM_IMAGE, othersMEM_PRIVATE.MEM_MAPPEDis not distinguished, reserved uncommitted memory is not enumerated, and copy-on-write and guard sub-flags are not reported. Default scans over all types are unaffected; aType-filtered orProtect-filtered scan may differ from native.
See docs/ARCHITECTURE.md section 3.
Version-agnosticism
The carafe binds only to the public Win32/NT export ABI and the PE format (IAT patching plus
GetProcAddress hooking), never Wine internals (__wine_unix_call, the wineserver protocol,
syscall-dispatch thunks). That
surface is the most stable part of Wine, so the DLL runs on any Wine version without a recompile
tied to Wine's layout.
- Delivery: launcher-driven remote-thread injection (
decant-launcher). Suspended-create, thenLoadLibraryviaCreateRemoteThread, thenDllMaininstalls the IAT hooks. The target stays unmodified. - Alternative delivery:
manual-mapconsumes the carafe image bytes and does not register the DLL in the loader module list; the same ready-token verification confirms hook installation. - Limitation: a tool that issues a raw
syscallinstruction, never calling the namedNt*export, bypasses export-level interception. Catching it would need Wine-internal syscall-dispatch hooking, which Decant avoids to keep portability.
See the injection and interception, and version-agnosticism sections of docs/ARCHITECTURE.md.
Crate layout
Mixed-target Cargo workspace. Host crates are default-members; the Windows crates build only
with --target x86_64-pc-windows-gnu. x86_64 throughout.
| Path | Target | Role |
|---|---|---|
crates/decant-vmi |
host | Library facade: re-exports backends, scanner and resolver, and the client |
crates/decant-protocol |
host + win-gnu | Wire contract and shared domain types; write_msg/read_msg framing |
crates/decant-client |
host + win-gnu | Daemon client over decant-protocol |
crates/decant-backend |
host | MemoryBackend trait, MockBackend, MockGuest |
crates/decant-memflow |
host | MemflowBackend (VM, feature-gated) |
crates/decant-analysis |
host | AOB scanner and pointer-chain resolver |
crates/decant-daemon |
host | TCP server and dispatch (the cellar) |
crates/decant-cli |
host | user CLI |
crates/decant-wine-harness |
host | launches exes under Wine for cargo test |
crates/decant-interpose |
win-gnu (cdylib) | interposer DLL (the carafe), IAT patching |
crates/decant-inject |
host + win-gnu | injection trait, config, ABI, SDK, and shipped injectors |
testbins/guest-target |
win-gnu | sample target for VM tests |
testbins/sample-tool |
win-gnu | stand-in tool for harness tests |
testbins/decant-launcher |
win-gnu | suspended-create injection harness |
testbins/decant-plugin-standard |
win-gnu (cdylib) | example plugin wrapping the standard injector |
testbins/decant-external-standard |
win-gnu | example external command wrapping the standard injector |
testbins/dll-smoke |
win-gnu | loads hello-dll, checks the toolchain under Wine |
testbins/hello-dll |
win-gnu (cdylib) | minimal PE32+ DLL exporting add |
xtask |
host | build and test orchestration |
Status
Decant reads and writes guest memory, runs AOB scans, resolves pointer chains, and
provides an interposer that redirects an unmodified tool's Win32 calls. The memflow
backend is validated against a Windows 10 guest. Guest injection with the manual-map method is
also exercised against that VM through the tracked fixture: the daemon allocates guest memory,
materializes the pages, writes a relocated DLL image from bytes, and calls DllMain. The fixture
payload updates its own marker so the test can assert that the payload ran; normal
guest-inject does not add a marker or require the target to report success.
The regular test set runs offline with no VM.
Testing
Two modes behind one trait: a mock backend offline, and memflow against a VM.
Writes are verified by read-back, not by the return value. Unsupported operations return a structured error, asserted in tests so they cannot become silent corruption.
The architecture and internals are documented in docs/ARCHITECTURE.md.
License
Dual-licensed under MIT OR Apache-2.0.