- Five archives (
Data1-5) have RSA-encrypted indexes and some AES-encrypted byte ranges. Files are found by a hash of their path, and a community dictionary names all 13,619. data Data0.bdtis the regulation: every game rule as 103 param tables, AES-256 encrypted. data- Inside are binders (BND4, or BXF4 split into header and data), usually DCX-compressed (zlib). Most asset types are a binder of FromSoft formats and Havok 2014 packfiles. data
- We read all of it with Python tools we wrote ourselves, so we don't need UXM (the community's unpacker) or Windows. The DLC's data is in the base archives too.
1The install
Steam app 374320, build 10167187. Everything sits in the install's Game/ folder:
| File | Size | What |
|---|---|---|
DarkSoulsIII.exe | 89 MB | The game: x64, image base 0x140000000, with MSVC RTTI and Havok's reflection text. YEBIS's 3,534 post-processing shaders are embedded in it. |
Data0.bdt | 0.8 MB | The regulation: all params |
Data1-4.bdt | 1-2.6 GB each | Characters, objects, effects, menus, scripts, shaders, sound projects |
Data5.bdt | 13.9 GB | The maps, by far the largest |
sound/ | FMOD Ex banks (.fsb) loose on disk: music, ambience, voices (Sound) | |
movie/ | 0.9 GB | Bink videos: fdp_opening.bik, the credits |
fmod_event64.dll, fmodex64.dll | FMOD Ex 4.44.61 with FromSoft's changes. The Vorbis setup headers the banks leave out are compiled in here. |
fdp is DS3's internal project name. It prefixes the sound banks, the videos and many file names.
2The sealed archives (BHD5)
How they're sealed
- Each
.bhdheader is RSA-encrypted with a public key per archive. Decrypting is a raw modular exponentiation of 256-byte blocks into 255 bytes. data - The header lists buckets of entries: a 32-bit hash of the path, offset and size in the
.bdt, and for some files an AES key and the byte ranges it encrypts, decrypted with AES-ECB. data - The hash is
h = h × 37 + cover the lower-cased path, so/map/m30_00_00_00/h30_00_00_00.hkxbhdbecomes a number. Names can only be recovered by guessing paths. UXM's dictionary names all 13,619 entries of this install.
How we open them
Our unpacker, a Python script, decrypts the headers, files and ranges itself, using the same public RSA keys the community tools use. It writes each file out under its real name, but only the files the importer needs. It doesn't depend on UXM or Windows.
Every entry decrypts and every name resolves. This is the same scheme the community tools use, re-implemented in Python.
3Binders inside binders
Almost nothing in the archives is a single asset. Each is a container of containers:
DataN.bdt
└─ c0000.anibnd.dcx DCX: a compressed wrapper ("DFLT" = zlib)
└─ BND4 binder names and ids for each entry, one file
├─ Skeleton.HKX a Havok 2014 packfile
├─ a000_000000.hkx one spline-compressed animation each …
└─ c0000.tae the animation events
map/m40_00_00_00/h40_00_00_00.hkxbhd + .hkxbdt
└─ BXF4 the same, split: header (.bhd) and data (.bdt) files
The extension says what a binder holds. We wrote a Python reader for each of these formats, apart from the Lua scripts, which the community's DSLuaDecompiler turns back into source. The ones this codex meets:
| Container | Holds | Chapter |
|---|---|---|
.msb | A map's layout: models, parts, regions, events | Levels |
.mapbnd | One map-piece FLVER | Levels |
.hkxbhd/.hkxbdt | Collision: one Havok packfile per collision model | Levels |
.tpf, .tpfbhd | Textures: DDS files, plus a float block on the lightmaps | Characters |
.chrbnd | A character: FLVER, ragdoll HKX, cloth HKX | Characters |
.anibnd | A skeleton, clips, TAE events | Characters |
.behbnd | The Havok Behavior graph: events to states to clips | Movement |
.partsbnd | One piece of equipment: FLVER and TPF | Characters |
.objbnd | An object: model, collision, skeleton and clips | Levels |
.ffxbnd | Effects (FXR3), their textures and models | Effects |
.hks / .luabnd | Havok Script (Lua 5.1) state machines / Lua 5.0 AI | Enemies |
.emevd | Event scripts: bosses, doors, bonfires | Enemies |
.talkesdbnd | Talk scripts (ESD state machines): bonfires, NPCs | Levels |
.gparam | Draw params: lights, fog, tone map, shadows per area | Renderer |
.mtdbnd, .shaderbdlebnd | Materials (MTD) and compiled DXBC shaders | Renderer |
.btl .btab .btpb | Map lights, lightmap atlas UVs, SH light probes | Renderer |
.fmg | Text: names, menus, prompts | |
.fev / .fsb | FMOD event projects / sample banks | Sound |
4The regulation: every rule as a table
Most of DS3's design numbers aren't in code. They're in params: typed tables of rows, each a fixed struct.
The whole set ships as the regulation, Data0.bdt. It is AES-256-CBC encrypted with a fixed key, then
DCX-compressed, then a BND4 of 103 .param files data. The save file carries a copy too.
A param file has rows (an id and an offset each) pointing at fixed-size structs. The layouts aren't in the file.
Field names and types come from the community's definitions (Smithbox's DS3 defs, Paramdex's row names), which
our param reader uses. DS3's files have format byte 0x2D = 0x85: the type name at an offset, and 64-bit row headers.
The params this codex relies on, and what they decide:
| Param | Decides |
|---|---|
NpcParam | An enemy: HP, capsule, turn speed, weight, which clips and graph it borrows, display masks, poise, stamina regen |
NpcThinkParam | Its AI: which Lua battle goal, sight range and angles, smell, how far it chases |
EquipParamWeapon / Protector / Goods | Weapons (damage, scaling, motion set, where it's held), armour (absorption, poise rate), items (the Estus Flask) |
BehaviorParam_PC / BehaviorParam | An attack: stamina cost, and which AtkParam or Bullet it uses |
AtkParam_Pc / AtkParam_Npc | Hit shapes on dummy polys, radii, damage corrections, hit-stop, damage level, knockback |
SpEffectParam | Status effects. One of them switches Gundyr into his second phase. |
CalcCorrectGraph | Curves: VIG → HP, attunement → FP, fall height → damage, stat scaling |
HitMtrlParam, FootSfxParam, SeMaterialConvertParam | Floor materials: footstep sounds, splashes, wading |
LockCamParam, LodParam, KnockBackParam, ToughnessParam, Bullet, ActionButtonParam, BonfireWarpParam, ModelSfxParam, ObjActParam, HitEffectSeParam | Camera, levels of detail, knockback timing, poise windows, projectiles, interaction prompts, bonfires, object effects, doors, hit sounds |
5Our side: one importer, neutral formats
The engine never sees a DS3 file. The unpacker extracts what's needed, the readers parse it, and the importer writes flat little-endian files that the engine uses in place after one read. Each has a header (a four-character magic and a version), arrays aligned to 16 bytes, one string table, and right-handed Y-up space in metres. When a format changes, its version goes up and the importer runs again: there are no compatibility layers.
| File | From DS3 |
|---|---|
.mesh v9 | FLVER: one interleaved 52-byte vertex, LOD face sets, per-submesh boxes, materials with 15 texture slots |
.scene v16 | MSB + gparam + btl + btab + btpb: placements, groups, light sets, fog, probes, lightmaps, enemies, effects, bonfires, sound regions, objects |
.col v2, .objcol | Havok compressed meshes: triangles with material and slide class, per-body room records |
.skel .anim .chr | Havok skeletons, decoded clips at 30 Hz with root motion and TAE events, character assembly |
.ragdoll .cloth | Havok ragdoll bodies and joints, Havok Cloth operators |
.fx | FXR3 effects reduced to emitters, models and lights |
.sbank | FMOD events and Vorbis samples |
.params | The params the engine uses, as typed tables |
Handedness. DS3 is left-handed, Y up, in metres. The importer negates Z on every position, normal and tangent,
and that alone makes the winding counter-clockwise (it agrees with the vertex normals on 99.4 % of a map piece's triangles).
Quaternions become (−x, −y, z, w) and root yaw is negated. A play-camera screenshot compared with the real game
settled which side is which data.
The formats are ours on purpose: a later importer for another game writes the same files. Everything DS3-specific stays on the importer's side of the line.