DCX envelopes
Most files inside the archives are wrapped in a DCX envelope: a fixed
76-byte header naming the codec and the sizes, then the compressed payload. A file’s name says so with a .dcx
suffix (m11_01_00_00.msb.dcx); the envelope holds exactly one file, often a BND4 binder of several. Proven
The header is three tagged blocks, DCS (sizes), DCP (codec) and DCA (payload), all big-endian. Proven
Big-endian. The payload follows at 76, then at most 15 zero bytes of alignment.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | char[4] = DCX\0 | magic | Proven | |
| 0x4 | 4 | u32 | format version | Sourced | 0x11000 (or 0x10000 in older DFLT files); SoulsFormats' variant key |
| 0x8 | 4 | u32 = 0x18 | DCS offset | Inferred | always 0x18, where DCS starts |
| 0xC | 4 | u32 = 0x24 | DCP offset | Inferred | always 0x24, where DCP starts |
| 0x10 | 4 | u32 | unknown | Unknown | 0x44, or 0x24 in older DFLT files |
| 0x14 | 4 | u32 | unknown | Unknown | 0x4C when the field at 16 is 0x44, else 0x2C |
| 0x18 | 4 | char[4] = DCS\0 | DCS tag | Proven | |
| 0x1C | 4 | u32 | decoded size | Proven | |
| 0x20 | 4 | u32 | payload size | Proven | |
| 0x24 | 4 | char[4] = DCP\0 | DCP tag | Proven | |
| 0x28 | 4 | char[4] | codec | Proven | KRAK, ZSTD or DFLT |
| 0x2C | 4 | u32 = 0x20 | DCP block size | Inferred | always 0x20: from 36 to the DCA tag at 68 |
| 0x30 | 1 | u8 | compression level | Sourced | SoulsFormats' name; 6 in every KRAK envelope, 9 in every DFLT envelope of the archives |
| 0x31 | 3 | u8[3] | reserved | Proven | always zero |
| 0x34 | 4 | u32 | reserved | Proven | always zero |
| 0x38 | 1 | u8 | unknown | Unknown | 0 in every archive envelope; 15 in the DFLT envelopes of Elden Ring's older regulation files |
| 0x39 | 3 | u8[3] | reserved | Proven | always zero |
| 0x3C | 4 | u32 | reserved | Proven | always zero |
| 0x40 | 4 | u32 = 0x10100 | unknown | Unknown | always 0x10100 |
| 0x44 | 4 | char[4] = DCA\0 | DCA tag | Proven | |
| 0x48 | 4 | u32 = 8 | DCA header size | Sourced | SoulsFormats' compressed header length |
Only the combinations of version, the field at 16, level and the byte at 56 listed here occur; every envelope in both installs is one of them. Proven
| Codec | Version | Field at 16 | Level | Byte at 56 | Sekiro | Elden Ring |
|---|---|---|---|---|---|---|
KRAK |
0x11000 |
0x44 |
6 | 0 | 27,074: 7,830 top-level, 19,244 inside binders | 117,121: 71,312 top-level, 45,809 inside binders |
DFLT |
0x10000 |
0x24 |
9 | 0 | 12 top-level | 19 top-level |
DFLT |
0x10000 |
0x44 |
9 | 0 | 8 top-level, 1 inside a binder | 1 top-level |
DFLT |
0x11000 |
0x44 |
9 | 0 | 1 top-level | none |
DFLT |
0x11000 |
0x44 |
9 | 15 | none | the 16 regulation files up to 1.10 |
ZSTD |
0x11000 |
0x44 |
21 | 0 | none | the 13 regulation files from 1.12.1 to 1.17.3 |
The archive rows count every envelope at any depth in the archives of both installs (Sekiro’s five pairs, Elden Ring’s
six; Elden Ring’s DLC pair is not installed). Proven · both archive sets, Elden Ring without the DLC pair The
regulation rows count the 28 published Elden Ring regulation files and the installed one, once decrypted.
Observed · Elden Ring No KRAK envelope has level 9 in either install. Proven · both archive sets
Codecs
Section titled “Codecs”| Codec | What it is | Where | Confidence |
|---|---|---|---|
KRAK |
Oodle Kraken. The games load it from their own oo2core_6_win64.dll |
the archives of both games | Proven |
ZSTD |
One Zstandard frame, with KRAK’s header layout |
Elden Ring’s regulation.bin once decrypted, from 1.12.1 on |
Proven · Elden Ring |
DFLT |
zlib | 22 Sekiro and 20 Elden Ring archive envelopes, and Elden Ring’s regulation files up to 1.10 | Proven |
In Sekiro’s Data5, the entry with path hash a28b84c8 declares a larger compressed size in its KRAK header than
its stored extent holds: the header asks for 1,342,263 bytes and the entry is 1,114,112 bytes long. The entries of the
pair are contiguous, so the next entry starts where this one ends and the stored payload simply stops short of the
declared size. No other envelope of either install is short; this is the only one of Sekiro’s 27,096 and Elden Ring’s 117,141.
Proven · every envelope of both archive sets
Kraken payload framing
Section titled “Kraken payload framing”A KRAK payload is a run of blocks, one per 262,144 decoded bytes (the last holds what is left). Each block starts
with a two-byte header; the decoder type in it sets the framing that follows. Proven · 27,073 Sekiro and 117,121 Elden Ring payloads walked to their last byte
One per 262,144 decoded bytes, at the start of each block.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 1 | u8 | flags | Sourced | low nibble 0xC and bits 4-5 clear in every block; bit 6 set: the block is stored uncompressed; bit 7 set: the decoder restarts here |
| 0x1 | 1 | u8 | decoder | Sourced | bits 0-6: decoder type; bit 7: checksums follow (never set) |
| Decoder type | Name | Block framing | Sekiro payloads | Elden Ring payloads |
|---|---|---|---|---|
| 6 | Kraken | one 3-byte big-endian quantum header per block (compressed size minus one in the low 18 bits; 0x3FFFF marks a memset quantum followed by one fill byte) |
26,139 | 116,161 |
| 2 | LZB16 | 16 quanta of 16,384 decoded bytes per block, each with a 2-byte big-endian header (compressed size minus one in the low 14 bits) | 934 | 960 |
Decoder type 2 is LZB16, not LZNIB: compressing with each compressor of oo2core versions 5, 6 and 9, only the LZB16
compressor writes blocks beginning 8C 02, and the LZNIB compressor writes 8C 01. Observed oozextract names its
type-2 decoder Lznib; the framing it reads is the one above, and the name is the only disagreement. Sourced · oozextract
Every block of every payload has the restart bit set: the first blocks begin 8C 06, 8C 02 or (stored) CC 06, and
the 303,703 later blocks of Elden Ring and 70,897 of Sekiro begin 8C 06 or CC 06. No block has the checksum bit,
and no payload uses another decoder type. Of the quantum kinds beyond a compressed run, only the memset one occurs (67
quanta in Sekiro, 115 in Elden Ring); there is no whole-match quantum and no stored small quantum. Walking the headers this
way consumes every payload exactly: no payload has bytes left after its last quantum. Proven · both archive sets
Sekiro depends on that bit. A rewritten object file of eight Kraken blocks in which only the first block restarts makes the game exit while loading it, and the same file with every block restarting loads and plays. Observed · Sekiro · two loads of each
A stored block (header CC 06: 1,180 first and 1,292 later blocks in Sekiro, 4,420 and 3,339 in Elden Ring) is the
block’s decoded bytes raw, with no quantum header: a walk steps over as many bytes as the block decodes to (what is left,
for the last block). The walk needs only the payload and the envelope’s decoded size, never decoding, and ends at the
payload’s last byte on all 27,073 readable Sekiro and 117,121 Elden Ring payloads. Proven · both archive sets, Elden Ring without the DLC pair
Oodle’s own decode does not report how many input bytes it used, so a payload with bytes left after its last quantum is
not noticed by decoding it. oo2core_6 returns the original bytes from a stream of its own making with one byte
appended: 9 sizes from 1 to 600,000 bytes, for each of the compressors that make decoder types 2, 6, 10, 11 and 12 (it
returns nothing for the LZNA, type 5, streams of the same sizes, with or without the extra byte). Observed
Only the walk of the framing finds trailing bytes. The framing of the decoder types the games do not use (5, 10, 11, 12) and the escapes of a
16 KiB quantum header (low 14 bits 0x3FFF: bits 14-15 0 whole match, 1 memset, 2 stored) are oozextract’s Sourced · oozextract.
No shipped payload has one. Streams oo2core_6 makes for each of the decoder types 2, 5, 6, 10, 11 and 12 (9 sizes each, 1 to
600,000 bytes) walk to exactly their last byte under the framing above, and a stream with a byte added or removed does
not. Observed
All 934 Sekiro and 960 Elden Ring decoder-type-2 payloads are one quantum long: they decode to 264 to 2,044 bytes in
Sekiro (one is 2,044) and 270 to 2,040 in Elden Ring. In Elden Ring 278 are whole top-level files and 682 are the
.hkx.dcx members of the map collision binders (.hkxbhd); in Sekiro 50 are top-level and 884 are members of
.edgebhd (383), .tpfbhd (434), .hkxbhd (51) and .wallbhd (16) binders. Proven · both archive sets, Elden Ring without the DLC pair
The game’s Oodle library and oozextract decode the Kraken payloads to the same bytes on all 27,073 readable Sekiro payloads and all 117,121 Elden Ring ones, so a reader does not need the game’s library where oozextract is available. Proven · both archive sets, Elden Ring without the DLC pair
A Kraken quantum can code a run of bytes as a Huffman chunk with a one-symbol alphabet: the eight bytes
40 0A 68 00 03 00 40 80 decode to 667 bytes of 0x02, and one quantum of a Sekiro payload holds such a chunk.
Observed · Sekiro The released oozextract 0.5.6 refuses that chunk, because its one-symbol path returns the
number of input bytes it read where the chunk’s decoded size is expected, as the C decoder ooz it follows does. With
only that return value corrected, it decodes a sample of 256 Sekiro KRAK envelopes to the same bytes as
oo2core_6. Observed · Sekiro · 256 Sekiro envelopes A reader that relies on oozextract instead of the game’s
library has to handle that case.
A writer that compresses KRAK payloads with the games’ oo2core_6 must size its output buffer with that version’s
OodleLZ_GetCompressedBufferSizeNeeded, which takes the raw size alone: 65,536 bytes gives 65,810. Oodle 2.9
(oo2core_9) takes the compressor and the raw size (Kraken, 65,536 gives 65,560). Observed Called with the two-argument
form, the older library reads the compressor id (8 for Kraken) as the raw size and returns the bound for 8 bytes, so the
output buffer is too small and the compressor writes past it. Observed The one-argument signature for versions 5
and 6 is also SoulsFormats’. Sourced · SoulsFormats
The decoded size in the header is the allocation a reader must make before decoding, and it can be large: Elden Ring’s
shader binder /shader/shaderbdle.shaderbdlebnd.dcx decodes to about 676 MB.
Observed · Elden Ring
Writing an envelope
Section titled “Writing an envelope”The 76-byte header is a function of the codec tag, the version, the field at 16, the level, the byte at 56 and the two
sizes: writing it from those fields reproduces the header of every envelope of both archive sets and of the 29
regulation files. Proven · both archive sets, Elden Ring without the DLC pair The field at 20 is 0x2C when the field
at 16 is 0x24 and 0x4C otherwise, and the payload starts at 76 in every variant.
What a writer cannot reproduce is the payload, because the shipped compressors are not the ones a writer has.
| Codec | Compressing the decoded bytes again | Confidence |
|---|---|---|
DFLT |
The payloads are zlib streams (78 DA). zlib 1.3.1 at level 9 reproduces 48 of the 58 shipped payloads byte for byte: 13 of Sekiro’s 22, 19 of Elden Ring’s 20 and all 16 regulation files up to 1.10. The other 10 were made by another deflate encoder: for the 9 of Sekiro’s tried, no zlib level or memory level from 1 to 9 gives their bytes, and their sizes are both below and above level 9’s. Rust’s miniz_oxide reproduces none of the 58 and zlib-rs 9. |
Observed |
KRAK |
oo2core_6 Kraken at levels 0 to 9 reproduces the shipped payload of none of 40 sampled Sekiro envelopes except the 3 or 4 whose payload is the data stored raw; the sizes at level 6 (the header’s level byte) and at level 4 come within about 4 % of the shipped ones, above and below. |
Observed · Sekiro |
ZSTD |
Every shipped frame (13 files) has the descriptor byte 0x00 (no content size, no checksum, no dictionary, not single-segment) and the window descriptor 0x80 (a 64 MiB window, the window log of level 21). libzstd 1.5.7 at level 21, streaming without a stated size, makes the same first six bytes for the installed 1.17.3 regulation’s 54,111,776 bytes and 1,739,983 bytes after them where the shipped frame has 2,045,636. |
Observed · Elden Ring |
A decoder that checks sizes exactly refuses what an envelope with a wrong size word makes of these streams, except a
stream that ends in a memset quantum (below). For 190 sampled Sekiro and 708 sampled Elden Ring Kraken payloads,
oo2core_6 refuses every one decoded to one byte more than the header says, and every one decoded to one byte less
except those whose last block is stored (14 and 45), which it decodes to the shorter prefix; it refuses every payload
one byte short and accepts every payload with one byte added. Observed
Walking the framing (Kraken payload framing) refuses the two it accepts: a payload one byte
over, and an envelope whose decoded size is one less where the last block is stored, both leave bytes after the last block.
Observed
A memset quantum states no length: it is its header and one fill byte, and it decodes to as many copies of the byte as
the quantum covers, which only the envelope’s decoded size sets. A payload whose last quantum is a memset is therefore
consistent with the decoded size one less or one more, as long as the last quantum stays within its quantum size, and
oo2core_6, oozextract and the walk of the framing all decode it to its bytes one shorter, or with one more fill byte.
A one-block payload 8C 06 07 FF FF xx decodes through oo2core_6 to as many bytes of xx, from 1 to 262,144, as its
decoded size says, and is refused at 0 and at 262,145, where a second block would need a header. Observed Over every
Kraken payload of both installs, with each size word one less and one more and the payload one byte short and one byte
over (six variants of each payload), the walk of the framing followed by oo2core_6 refuses every variant but decoded
sizes one off of a payload ending in a memset quantum: 8 of Elden Ring’s 702,726 variants and 2 of Sekiro’s 162,438,
each decoding to one byte fewer or one more fill byte. Proven · both archive sets, Elden Ring without the DLC pair
With the DLC pair installed the same check makes 922,230 Elden Ring variants and accepts the same 8: none of the
expansion’s payloads adds one. Proven · Elden Ring · every archive pair, the DLC pair included
Elden Ring’s /asset/aet/aet220/aet220_816.tpf.dcx, 11,884,253 bytes ending in three memset blocks (8C 06 07 FF FF 00),
is one. Observed · Elden Ring So no reader can check the decoded-size word of such an envelope against its
payload; only the decoded file’s own sizes can.
A whole-match quantum (a 16 KiB quantum header’s escape 0) states a distance, not a length, as oozextract reads it, so
the decoded size one off may decode the same way. No shipped payload has a whole-match quantum.