Tools for working with Darktide resource bundles. Provides a Rust library for parsing and
extracting .bundle files, a CLI (dtex) for interactive use, and a C-compatible FFI
library for integration into other projects. Supports Linux and Windows.
Darktide bundles are Oodle (Kraken) compressed archives that store file names and extensions
as MurmurHash64A hashes rather than plaintext. dtex handles decompression, hash resolution,
and file extraction.
Every file in a bundle is extractable. Bundles contain dozens of asset types (about 50 known
extensions), including lua, texture, package, bones, particles, material,
shader, wwise_bank, wwise_stream, and more.
How files are named on extraction depends on the type:
- Lua bytecode — recovered exactly from the bytecode debug info (chunkname) via
--lua-chunknames. This is the only file type whose original path is fully recoverable. - Other types — named by resolving the
name_hashthrough a dictionary built withscan, or fall back to the hex hash under the extension directory. - All types —
--rawwrites files as<name_hash>.<ext_hash>with no resolution.
name_hashis content-addressed, not derived from the file path. Dictionary-based resolution has 0% coverage forname_hashfields, so non-Lua files generally cannot be recovered to their original paths by scanning bundle content. The dictionary workflow remains useful for other hash lookups.- Plaintext Lua source (a small number of entries) is not compiled bytecode; it is passed through unchanged and standard LuaJIT tooling skips it.
- Decompression requires the proprietary Oodle library at runtime (see Installation).
- Bundle classification is based on magic bytes: Only the first 8 bytes are checked, so non-bundle files that coincidentally match the magic will be opened and fail during parsing.
- Directory extraction only processes top-level files: Subdirectories are not recursed.
This matches the Darktide bundle directory layout where all bundles are flat and non-bundle
files are either
.streamfiles or indata/subdirectories.
See docs/bundle-format.md for the binary format details and
docs/luajit-bytecode-format.md for Lua-specific handling.
dtex dynamically loads the Oodle shared library at runtime. The library is searched in the following order:
--oodle-libcommand-line flagDTEX_OODLE_LIBenvironment variable- Windows only:
<game-dir>/binaries/oo2core_9_win64.dll(if--game-diris provided or auto-discovered) - Next to the
dtexbinary (exe-dir) - Current working directory
- System library search path
Place the platform-appropriate file in one of these locations (or use --oodle-lib):
- Linux:
liboo2corelinux64.so.9(Oodle 2.9.14) - Windows:
oo2core_9_win64.dll(Oodle 2.9.10)
See docs/oodle-library.md for how to obtain the library from
Epic's CDN.
Download the latest release for your platform from the
Releases page. Unzip it, drop
the Oodle library next to the dtex binary, then make dtex available on your PATH.
Linux / macOS:
mkdir -p ~/.local/bin
cp dtex ~/.local/bin/
cp liboo2corelinux64.so.9 ~/.local/bin/
# Ensure ~/.local/bin is on your PATH (add to ~/.bashrc / ~/.zshrc if not):
export PATH="$HOME/.local/bin:$PATH"
dtex --versionWindows (PowerShell):
$dest = "$env:LOCALAPPDATA\dtex"
New-Item -ItemType Directory -Force -Path $dest | Out-Null
Copy-Item dtex.exe, oo2core_9_win64.dll $dest
# Add to PATH for the current user (run once):
[Environment]::SetEnvironmentVariable("Path", "$dest;" + [Environment]::GetEnvironmentVariable("Path", "User"), "User")
dtex --versionIf the Oodle library ships with the Darktide game in your Steam installation, you can use
--game-dir <path> to auto-discover the DLL on Windows (e.g., --game-dir "C:\Games\Darktide").
Requires the Rust toolchain.
cargo build --releaseThe binary is at target/release/dtex (Linux) or target/release/dtex.exe (Windows). Copy
it (and the Oodle library) onto your PATH as in Option 1.
dtex list -i <bundle_file> [extension]# All files from a single bundle
dtex extract -i <bundle_file> -o <output_dir>
# Filter by extension
dtex extract -i <bundle_file> -o <output_dir> lua
# Extract all bundles from a directory (e.g., the game's bundle/ folder)
dtex extract -i <bundle_dir> -o <output_dir>
# Lua files named by their chunkname (original source paths)
dtex extract -i <bundle_dir> -o <output_dir> --lua-chunknames lua
# Raw mode: <name_hash>.<ext_hash>, no resolution
dtex extract -i <bundle_file> -o <output_dir> --raw
# Dictionary-based name resolution
dtex extract -i <bundle_file> -o <output_dir> --dictionary dictionary.txtWhen extracting from a directory (e.g., the game's bundle/ folder), dtex processes
all top-level bundle files. Non-bundle files (like .stream files and data/ subdirectories)
are skipped automatically.
dtex extract -i /path/to/darktide/bundle -o extracted_filesWhen multiple bundles contain files that would write to the same output path, you can control
the behavior with --on-collision:
overwrite(default): Write the file from each bundle, recording collisions in the summary.skip: Keep the first occurrence, skip subsequent writes.error: Abort when a collision is detected.
Collisions are reported to stderr when they occur.
dtex extract -i <bundle_dir> -o output --on-collision skipBy default, errors during directory extraction are recorded and processing continues. Use
--strict to abort on the first error.
dtex extract -i <bundle_dir> -o output --strictTo track the provenance of all extracted files, use --manifest to write a TSV file:
dtex extract -i <bundle_dir> -o output --manifest manifest.tsvThe manifest contains: output_path<TAB>source_bundle<TAB>name_hash<TAB>ext for every extracted file.
--quiet: Suppress progress and summary output (errors still print).--json: Output machine-readable JSON to stdout (human-readable summary still goes to stderr unless--quiet).--verbose: Enable verbose output (including Oodle debug logging).
# Find all files with a given extension
dtex find lua -i <bundle_dir>
# Without an extension, print a per-extension count summary
dtex find -i <bundle_dir>
# Output as JSON
dtex find lua -i <bundle_dir> --json# Check classification of files in a directory
dtex validate -i <bundle_dir>
# Output as JSON
dtex validate -i <bundle_dir> --json# Dump all extension/name hashes from a bundle index
dtex dump-hashes -i <bundle_file>
# Scan bundle content for path strings to build a dictionary
dtex scan -i <bundle_file> [-i <bundle_file> ...] -o dictionary.txt
dtex scan -i <bundle_file> -o dictionary.txt --merge existing_dictionary.txt
# Check dictionary coverage against bundle index hashes
dtex coverage -d dictionary.txt -i <bundle_file> [-i <bundle_file> ...]Lua files are written as standard LuaJIT bytecode (the Fatshark wrapper is stripped and the magic/version bytes restored automatically).
| Option | Description | Default |
|---|---|---|
--oodle-lib <path> |
Path to the Oodle shared library | Platform default (see Installation) |
--game-dir <path> |
Darktide game directory (for Steam auto-discovery and Windows Oodle DLL) | Auto-discovered or none |
--verbose |
Enable verbose output (including Oodle debug output) | false |
--quiet |
Suppress stderr progress/summary (errors still print) | false |
--json |
Output machine-readable JSON for data commands | false |
| Option | Description |
|---|---|
--raw |
Write files as <name_hash>.<ext_hash> without name resolution |
--dictionary <path> |
Resolve name_hash via a dictionary file |
--lua-chunknames |
Name Lua files using the chunkname from bytecode debug info |
--on-collision <mode> |
How to handle output path collisions: overwrite, skip, or error (default: overwrite) |
--strict |
Abort on first error (instead of continuing) |
--manifest <path> |
Write manifest TSV of extracted files |
Lua source can be fully recovered in two stages: dtex extracts Darktide's
custom-wrapped LuaJIT bytecode into standard LuaJIT bytecode, then a LuaJIT
decompiler turns it back into readable Lua.
# 1. Extract Lua into standard LuaJIT bytecode
dtex extract -i <bundle_dir> -o <bytecode_dir> --lua-chunknames lua
# 2. Decompile the bytecode into Lua source
luadejit <bytecode_dir> -o <source_dir>luadejit is a LuaJIT 2.x decompiler (luadejit --help lists options such as
--strip-dir-prefix to trim a common leading path from chunkname-derived
filenames). See docs/luajit-bytecode-format.md
for the wrapper format and normalization details.
For non-Lua types, build a MurmurHash64A reverse-lookup dictionary by scanning decompressed bundle content for path strings, then use it to name extracted files.
# 1. Build a dictionary
dtex scan -i bundle1.bundle -i bundle2.bundle -o dictionary.txt
# 2. Check coverage
dtex coverage -d dictionary.txt -i bundle1.bundle -i bundle2.bundle
# 3. Extract with resolved names (unresolved files fall back to <ext>/<name_hash>)
dtex extract -i bundle1.bundle -o output --dictionary dictionary.txtFiles resolved by the dictionary are written to their original paths; unresolved files land under their extension directory by hex hash. To grow the dictionary incrementally:
dtex scan -i new_bundle.bundle -o dictionary.txt --merge dictionary.txtAdd darktide-bundle to your Cargo.toml:
[dependencies]
darktide-bundle = { path = "crates/darktide-bundle" }use darktide_bundle::{Bundle, Oodle};
let oodle = Oodle::load("liboo2corelinux64.so.9")?; // Linux
// let oodle = Oodle::load("oo2core_9_win64.dll")?; // Windows
let mut bundle = Bundle::open("data.bundle")?;
let index = bundle.read_index()?;
let files = bundle.extract_files(&oodle)?;
for file in &files {
println!("hash: 0x{:016x}", file.name);
}Key types:
Bundle— opens and parses a.bundlefile; providesread_index()andextract_files().Oodle— loads the Oodle shared library at runtime vialibloading.FileEntry— a decompressed file with name hash, extension hash, variant data, and content.IndexEntry— one entry from the bundle index (extension hash, name hash, mode).Dictionary— MurmurHash64A reverse-lookup dictionary for name resolution.normalize_luajit/denormalize_luajit/is_darktide_wrapped— Lua bytecode normalization (lossless, reversible).scan_strings— scan binary data for path-like strings.murmur_hash64/lookup_extension— hashing and extension lookup.
The darktide-ffi crate compiles to a shared library (cdylib) and static library
(staticlib) with a C-compatible API. It wraps darktide-bundle using opaque handles for
use from other languages.
Pre-built FFI artifacts ship with each Release: the shared library (libdarktide_ffi.so / darktide_ffi.dll), static library (libdarktide_ffi.a / darktide_ffi.lib), and darktide.h are bundled in the platform archive alongside the dtex binary.
A ready-to-use C header is included at crates/darktide-ffi/darktide.h; C and C++ consumers can #include it directly. The function list below is authoritative if you need to hand-transcribe signatures.
Exported functions:
darktide_oodle_load(path)/darktide_oodle_free(oodle)darktide_bundle_open(path)/darktide_bundle_free(bundle)darktide_bundle_read_index(bundle)/darktide_bundle_index_free(index)darktide_bundle_index_count(index)/darktide_bundle_index_entry(bundle, idx, out)darktide_bundle_extract_all(bundle, oodle)/darktide_bundle_files_free(files)darktide_bundle_files_count(files)/darktide_bundle_file_entry(files, idx, out)darktide_bundle_file_data(files, idx, out_buf, out_len)darktide_murmur_hash64(data, len)/darktide_lookup_extension(hash)
All functions taking raw pointers are unsafe extern "C"; see the # Safety docs on each.
darktide_bundle_file_data copies min(data_len, out_len) bytes — the caller must size the
buffer using data_len from darktide_bundle_file_entry.
cargo build --releaseArtifacts:
- CLI:
target/release/dtex(.exeon Windows) - FFI shared:
target/release/libdarktide_ffi.so(Linux) /darktide_ffi.dll(Windows) - FFI static:
target/release/libdarktide_ffi.a
- Root crate (
darktide-extractor-cli) — CLI binarydtex(list,extract,dump-hashes,scan,coverage,find,validate); also the Cargo workspace root. crates/darktide-bundle— core library (bundle parsing, Oodle decompression, MurmurHash64A, Lua normalization, dictionary).crates/darktide-ffi— C ABI wrapper (cdylib+staticlib).docs/— technical specifications.
The root CLI crate and darktide-ffi both depend on darktide-bundle, which has
no internal crate dependencies. All workspace crates share a single version that
is bumped together on each release.
docs/bundle-format.md— the.bundlebinary layout.docs/luajit-bytecode-format.md— Darktide's custom LuaJIT wrapper and normalization.docs/oodle-library.md— obtaining the Oodle library from Epic's CDN.