locum 0.1.1

Captures everything a Direct3D 11 game draws in a frame, to look at, query, change and replay: d3d11.dll and dxgi.dll beside the game that watch every call on its way to the real runtime.
Documentation

locum

crates.io: locum crates.io: locum-capture docs.rs CI release

A frame capture and replay tool for Direct3D 11 and Direct3D 12 games on Windows. It runs as stand-in system DLLs in the game's folder and writes each captured frame to a SQLite database, which a command-line tool and a local web viewer read.

Features

  • Records every draw, dispatch, clear, copy, update and map of a frame in GPU order, with each draw's shaders, state and bindings
  • Records the constant-buffer bytes each draw read, with constants named from the shaders' DXBC or DXIL reflection
  • Stores each used resource's contents as the frame began and the render and depth targets at the end of each pass
  • Captures up to 64 consecutive frames from one request
  • Keeps resource contents in a blob store shared by all captures of a game, keyed by XXH3-128 and LZ4-compressed
  • Lists, filters, exports (DDS, PNG, raw) and queries captures with locum-cli, including plain SQL
  • Traces a target's output lane back to the texture channels, constants and vertex attributes it came from
  • Replays a draw or a whole frame offline on the GPU or on WARP and verifies every target against the game's
  • Changes draws with rules (skip, solid colour, replacement shader, constant), live in Direct3D 11 games or in replay
  • Names textures, buffers and shader programs after the game's own files through indexes of DDS files, shaders and key lists
  • Diffs two captures draw by draw
  • Writes a process memory dump at capture time and searches it for the values a draw saw (Direct3D 11)
  • Reads, sets and calls game values from a table.txt to set up a scene before a capture
  • Browses captures in a local web viewer with timeline, draw, resource, shader and SQL pages
  • Compares the output of shaders whose results depend on GPU thread order by records rather than bytes

Getting Started

Requirements

  • Windows, and a 64-bit game that uses Direct3D 11 or Direct3D 12
  • Rust 1.99.0 (pinned in rust-toolchain.toml) with the MSVC build tools; the DLL shims are linked with lib.exe
  • Bun for the viewer
  • dxcompiler.dll for DXIL tools and DXIL replay rules: found on the DLL search path, in the Windows SDK, or at LOCUM_DXCOMPILER

Do not use locum on games with anti-cheat. Live override rules, memory dumps and the skip/max_mb capture options are implemented for Direct3D 11 only.

Installation

Each release has locum-cli for Windows (x86_64), with SHA256SUMS; cargo install locum-cli builds it too. The libraries are on crates.io (cargo add locum-capture). The DLLs that go beside a game are built from the workspace:

cargo build --release
Package What it is
locum locum.dll: wraps the Direct3D and DXGI objects and records captures
locum-shim-d3d11, locum-shim-dxgi, locum-shim-d3d12 d3d11.dll, dxgi.dll, d3d12.dll: forward every export to locum.dll
locum-shim-nvapi64 nvapi64.dll: an empty NVAPI for Direct3D 12 games
locum-capture The capture format, blob store, formats, reflection and rule language
locum-d3d11 Direct3D 11 readback shared by capture and replay
locum-cli Reads, replays and verifies captures
viewer Bun and Preact web viewer for captures
locum-smoke, locum-smoke-d3d12, xtask Test programs and the cargo xtask build tasks

Copy the DLLs from target/release/ next to the game's executable (for Unreal games, the one under Binaries/Win64):

  • Direct3D 11: locum.dll, d3d11.dll, dxgi.dll
  • Direct3D 12: locum.dll, d3d12.dll, dxgi.dll, nvapi64.dll

Remove them again when you are done.

Usage

locum writes its log, shaders and captures to %LOCALAPPDATA%\locum (or LOCUM_DIR). With the game running, create an empty file named capture there. locum removes the file and writes the next frame to captures\frame-<n>\.

New-Item "$env:LOCALAPPDATA\locum\capture"

locum-cli info last                          # what the capture holds and what it is missing
locum-cli draws last --ps 9f2c               # draws that used a pixel shader
locum-cli draw last 120                      # one draw: state, bindings, named constants, targets after it
locum-cli target last 120 rt0 -o rt0.png     # a render target read back after its pass
locum-cli verify last                        # replay the frame and compare every target with the game's

To open the capture in the viewer, which prints its address (http://127.0.0.1:5170/...):

cd viewer
bun install
bun run viewer --db "$env:LOCALAPPDATA\locum\captures\frame-<n>"

Other commands:

  • locum-cli shader last 9f2c -d: a shader's constant buffers, bindings and disassembly (--wgsl for WGSL)
  • locum-cli trace last 120 rt1.z: what one lane of a target is made of
  • locum-cli export last 812 -o tex.dds: a resource's contents as the frame began
  • locum-cli replay last 120 --rule "skip ps=9f2c": a draw replayed with a rule, before and after
  • locum-cli diff before after: two captures compared draw by draw
  • locum-cli --index files.db join last: what each resource was made from
  • locum-cli sql last "SELECT ...": any query on the capture

locum-cli --help lists every command. settings.txt, overrides.txt, table.txt and the capture request options are described in docs/README.md.

cargo xtask smoke runs the Direct3D 11 test programs through locum and verifies their captures on the GPU and WARP. cargo xtask smoke --api d3d12 does the same for Direct3D 12, and bun test runs the viewer's tests. docs/README.md is the full reference, and viewer/README.md covers the viewer. locum is licensed under GPL-3.0-or-later.

Releasing

A release is a commit that changes a version. Once every CI job has passed on main, CI's release job (.github/release.py) publishes each crate whose version crates.io does not have yet, in dependency order (trusted publishing: no token is stored anywhere), tags the commit v<version> (<package>-v<version> for a package with a version of its own) and creates the GitHub Release with the commits since the previous release as its notes and the binaries attached (<bin>-<version>-<target>.zip / .tar.gz with SHA256SUMS). A commit that changes no version only runs CI.

  1. Bump the version when a change is worth releasing: a patch (0.x.Y) for fixes and data or manifest additions, a minor (0.X.0) for new API or any breaking change before 1.0. Run cargo check so Cargo.lock follows.
  2. Push to main. Never publish by hand; a new crate's first release is the one exception (the token holder makes it, then the crate gets its trusted-publishing config).