BHD5 and BDT archives
Almost every file the games load lives in an archive pair: a .bhd index and a .bdt data file of the same name
in the game folder. The index lists each file by the hash of its path, with where its bytes sit in the data file, how
long it is, and which of its byte ranges are encrypted. File names themselves are not stored anywhere: a path is found
by hashing it and looking the hash up. Proven
Reading a file is four steps: unwrap the index with the pair’s RSA public key, find the path’s hash in it, read the
listed bytes from the .bdt, and decrypt the listed ranges with the file’s AES key. What comes out is usually a
DCX envelope around the real file. Proven
Every entry of both installs reads to the same bytes as the reference readers produce (SoulsFormats for Sekiro, SoulsFormatsNEXT for Elden Ring, both with UXM’s keys and dictionaries), and every index is accounted for byte by byte. Proven · Sekiro, Elden Ring · every entry
The archive pairs
Section titled “The archive pairs”| Game | Pairs | Confidence |
|---|---|---|
| Sekiro | Data1 to Data5 (there is no Data0) |
Proven · Sekiro |
| Elden Ring | Data0 to Data3; DLC when the expansion is installed; the sound archives sd/sd and sd/sd_dlc02 |
Proven · Elden Ring |
Elden Ring’s list, and which of its pairs may be absent, is the one UXM’s EldenRingGameInfo.xml gives.
Sourced · Elden Ring · UXM No path hash appears in two of Sekiro’s pairs, nor in two of Elden Ring’s pairs
without the expansion. Proven · Sekiro, Elden Ring · every index
With the expansion installed, the DLC index lists five of Data3’s hashes again, and no other hash appears in two
pairs: /chr/c5550.anibnd.dcx, c5550.behbnd.dcx, c5550.chrbnd.dcx, c5550_h.texbnd.dcx and c5550_l.texbnd.dcx.
Proven · Elden Ring · every index, the expansion installed The two copies of each hold the same bytes. Each is the
same DCX envelope byte for byte up to its end (the 76-byte header and the payload), and each decodes to the same bytes.
They differ only in padding: Data3 pads its copies with 6 to 14 zero bytes to a multiple of sixteen; the DLC copies
are unpadded. The records of all ten state a logical size of 0. Proven · Elden Ring · all five So whichever pair
the game reads them from, it gets the same file. Inferred · Elden Ring
The five repeats on the installed expansion hold the same bytes, so they cannot tell. Whether a later pair overrides an
earlier one, or the first listing wins, decides what the game reads only if two listings ever hold different bytes.
UXM’s unpacker and Smithbox’s archive file system both walk the pairs in the order Data0 to Data3, DLC, sd/sd,
sd/sd_dlc02 and keep the first copy of a path, skipping a later one; that is their choice, not a statement of the game’s.
Sekiro’s five indexes list 8,765 files, of which UXM’s path dictionary names 8,752. Proven · Sekiro
Elden Ring’s six installed indexes list 97,118 files (5,878 in Data0, 39,419 in Data1, 39,684 in Data2, 1,672 in
Data3, 8,650 in sd/sd and 1,815 in sd/sd_dlc02); hashing every line of three community lists with the game’s rule
and intersecting them with the six indexes shows that UXM’s Elden Ring dictionary (revision 9501be8) names 97,031 of them,
the newer copy of that list bundled with Smithbox names 97,045, and Smithbox’s own file dictionary (130,951 paths, no two
sharing a hash) names all 97,118. Proven · Elden Ring The 87 entries UXM 9501be8 leaves unnamed are 77 .dcx files
(54 parts/*.partsbnd, 8 chr/*.texbnd, 4 chr/*.anibnd, 4 chr/*.chrbnd, 3 asset geometry binders, 2 .gparam
and 2 .devpatch shader bundles), 5 .bnk and 5 .wem. Proven · Elden Ring
The expansion’s DLC index lists 33,755 files, five of them the repeats above. Proven · Elden Ring
Data5 holds the map files: its salt is NTC_map and its index lists 5,449 of them. Observed · Sekiro
The index is wrapped in RSA
Section titled “The index is wrapped in RSA”A .bhd file on disk is a run of 256-byte blocks. Each block is the raw RSA public-key operation of a 255-byte
plaintext block (not PKCS#1 encryption: a signature-style envelope opened with the public key), and the plaintexts
joined in order are the index. Proven Each archive pair has its own 2048-bit public key. Proven Both games’ public keys
are published by UXM (ArchiveKeys.cs). Sourced · UXM
After the index’s own length (the field at offset 12 below), the unwrapped bytes hold fewer than 255 zero bytes of padding from the last block. Proven
The index
Section titled “The index”The unwrapped index is a header, a table of hash buckets, the file records the buckets point at, and per-file hash and key tables. Every byte of it belongs to one of these. Proven
Offsets are from the start of the unwrapped index. Integers are little-endian when the flag at 4 is 0xFF.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | char[4] = BHD5 | magic | Proven | |
| 0x4 | 1 | u8 | endianness | Proven | 0xFF little-endian, 0 big-endian |
| 0x5 | 1 | u8 | unknown | Unknown | 0 or 1 |
| 0x6 | 2 | u8[2] | reserved | Proven | always zero |
| 0x8 | 4 | u32 = 1 | version | Proven | |
| 0xC | 4 | u32 | index length | Proven | bytes of index; what follows is RSA padding |
| 0x10 | 4 | u32 | bucket count | Proven | |
| 0x14 | 4 | u32 | bucket table offset | Proven | |
| 0x18 | 4 | u32 | salt length | Proven | |
| 0x1C | salt length | char[] | salt | Sourced | SoulsFormats' name; nothing in reading depends on it |
The bucket table holds bucket count of these. A path's bucket is its hash modulo the bucket count.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 | file count | Proven | |
| 0x4 | 4 | u32 | file records offset | Proven | file count records of 40 bytes each |
A path’s bucket is its hash modulo the bucket count: it holds for every record of every bucket, 8,765 in Sekiro’s five
indexes and 97,118 in Elden Ring’s six, so a lookup can read one bucket. Proven[every entry of both installs, Elden Ring
without the DLC pair] The bucket counts are 173, 23, 83, 199 and 787 in Sekiro’s Data1 to Data5, and 839, 5,639,
5,669, 239, 1,237 and 263 in Elden Ring’s Data0 to Data3, sd/sd and sd/sd_dlc02. Proven · every index
The file record
Section titled “The file record”Both games use a 40-byte record with the same fields, laid out differently: Elden Ring widens the hash to 64 bits and narrows the unpadded size to 32. Proven
Dark Souls III uses the same layout.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 | path hash | Proven | base 37, see below |
| 0x4 | 4 | u32 | padded size | Proven | bytes stored in the .bdt |
| 0x8 | 8 | u64 | data offset | Proven | into the .bdt |
| 0x10 | 8 | u64 | hash table offset | Proven | 0 when the file has none |
| 0x18 | 8 | u64 | key table offset | Proven | 0 when the file is not encrypted |
| 0x20 | 8 | u64 | unpadded size | Proven | never more than the padded size |
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 8 | u64 | path hash | Proven | base 133, see below |
| 0x8 | 4 | u32 | padded size | Proven | bytes stored in the .bdt |
| 0xC | 4 | u32 | unpadded size | Proven | never more than the padded size |
| 0x10 | 8 | u64 | data offset | Proven | into the .bdt |
| 0x18 | 8 | u64 | hash table offset | Proven | 0 when the file has none |
| 0x20 | 8 | u64 | key table offset | Proven | 0 when the file is not encrypted |
Within one index no hash appears twice. Proven
The unpadded size is zero for most entries: for 7,851 of Sekiro’s 8,765 and 71,332 of Elden Ring’s 97,118 (every entry of
Sekiro’s Data2 and Data4 and of Elden Ring’s Data1 and Data3, none of Elden Ring’s sd pairs). Where it is stored, the padded size is it rounded up to 16 bytes,
except for 82 Sekiro entries and 6 Elden Ring ones. Proven[every entry of both installs, Elden Ring without
the DLC pair] The padded size is therefore the size to read, and “never more than the padded size” is all the stored
value guarantees. Proven
Within a pair the entries tile the .bdt file: ordered by data offset, each starts where the previous one ends, with no
gap and no overlap, and the first starts at offset 0. Proven · all 11 pairs A .bdt has no header of its own: its first
bytes are its first entry’s bytes (a DCX envelope in Sekiro’s Data1 and Elden Ring’s Data2, ciphertext in the pairs
whose first entry is encrypted). Proven · all 11 pairs
Hash and key tables
Section titled “Hash and key tables”A file’s key table gives the AES key and the byte ranges it applies to; its hash table gives a digest and the ranges it covers. Both list ranges the same way. Proven
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 8 | i64 | start | Proven | |
| 0x8 | 8 | i64 | end | Proven | exclusive; (-1, -1) is a disabled range |
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 16 | u8[16] | AES key | Proven | AES-128 |
| 0x10 | 4 | u32 | range count | Proven | |
| 0x14 | 16 × range count | Range[] | ranges | Proven | ascending, not overlapping, multiples of 16 |
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 32 | u8[32] | digest | Sourced | a SHA-256 of the listed ranges per SoulsFormats; not checked |
| 0x20 | 4 | u32 | range count | Proven | |
| 0x24 | 16 × range count | Range[] | ranges | Proven | ascending, not overlapping |
A file is decrypted by running AES-128 in ECB mode over exactly the ranges of its key table, block by block, and leaving every other byte as stored. Proven Disabled ranges are skipped. Proven Bytes outside the listed ranges are stored in the clear, so decrypting a whole file corrupts it. Proven
Path hashes
Section titled “Path hashes”A path is normalised before hashing: lower-cased, backslashes turned into forward slashes, and a leading / added if
it has none. The hash runs over the path’s UTF-16 code units, starting from zero, with wrapping arithmetic. Proven
| Game | Hash | Confidence |
|---|---|---|
| Sekiro | h = h × 37 + unit, 32-bit |
Proven · Sekiro |
| Elden Ring | h = h × 133 + unit, 64-bit |
Proven · Elden Ring |
So /map/m11_01_00_00/m11_01_00_00.msb.dcx is found by hashing that string, never by a stored name. Names come from
dictionaries the community has assembled (UXM ships one per game); a hash no dictionary names is a file whose path is
not known, not a missing file. Proven Because the hash is lossy, a dictionary can map two paths to one hash; both are
then candidates for that file. Inferred
The community’s path lists
Section titled “The community’s path lists”Smithbox’s file dictionaries list, for each path, the archive it is said to be in and a flag that says whether the path was
validated. Sekiro’s lists 8,949 paths (no archive named; 8,752 flagged valid and in the index, 197 flagged invalid and not) that
name 8,752 of the 8,765 index entries. Proven · Sekiro Elden Ring’s lists 130,951 lines (130,946 distinct paths) and,
held to the indexes without the expansion, names all 97,118 index entries; 97,123 of its lines hash into the index (five paths twice). The archive it states is the pair
that holds the path for all but five paths (stated dlc, in Data3), but the flag is not exact: 10,467 paths flagged invalid are
in the index, and 33,750 flagged valid are in none; 33,757 of the lines state the archive dlc, which those indexes do not
list. Proven · Elden Ring The five paths stated dlc but held in Data3 are the five the expansion’s index
lists again. Proven · Elden Ring
It is 0 or 1. Neither reference reader gives it a meaning, and reading does not depend on it.