BMD and BDL Model Format - LordNed/WindEditor GitHub Wiki

The BMD and BDL 3D model format is used in several Nintendo games, including The Legend of Zelda: The Wind Waker, Mario Kart: Double Dash!!, and Super Mario Sunshine. The format consists of a header followed by several "chunks." Each chunk is identified by a four-character magic and contributes to one aspect of the model, such as material data or joint data.

The chunks and many fields within the chunks are padded to a 32-byte boundary with the string This is padding data to align.

Table of Contents

Header


The format's header consists of two magics, the file size and a chunk count, and then a dummy chunk.

The first magic is a specifier for the file type ("J3D") and version number (1 or 2). The second magic is the model type, either 'BMD3' or 'BDL4', depending on the file type.

The dummy chunk has a magic of 'SVR3' and consists of an array of 12 bytes. These bytes are nearly always -1.

// 0x20/32 bytes long 
/*0x00*/ char[4] Magic_J3Dx;
/*0x04*/ char[4] ModelType; // BMD3 for BMD files, BDL4 for BDL files
/*0x08*/ int FileSize;
/*0x0C*/ int ChunkCount;
/*0x10*/ byte[16] Padding; //Unused tag "SVR3" + FF

Scenegraph (INF1)


The scenegraph defines a hierarchy of joints, materials, and geometry used during rendering.

It begins with a header, which includes the chunk's magic, the chunk's size, the number of geometry packets, the number of vertexes, and the offset to the actual hierarchy data.

Header

// 0x18/24 bytes long
/*0x00*/ char[4] Magic_INF1;
/*0x04*/ int ChunkSize; //Total bytes of chunk
/*0x06*/ short Unknown1; //0 for BDL, 01 for BMD
/*0x08*/ short Padding;
/*0x0C*/ int PacketCount;
/*0x10*/ int VertexCount; //Number of vertexes in VTX1 section
/*0x14*/ int HierarchyDataOffset; //Offset from start of INF1 Chunk

Immediately following the header is the hierarchy data, stored in an array of HierarchyData structs.

The specific number of structs is not specified in the format. To load the correct number of structs, you will have to load four bytes at a time until you hit one that has a Type value of 0.

An accurate approximation of the number of structs in the array cannot be made based on the chunk size alone due to the fact that chunks are padded to the nearest 32-bytes. Thus, the chunk size minus the size of the chunk's header does not necessarily reflect the size of the array in bytes.

Hierarchy Data

The struct containing the hierarchy data is composed of a type and an index into the relevant table.

// 0x4/4 bytes long
/*0x00*/ HierarchyDataTypes Type //See HierarchyDataTypes below
/*0x02*/ short Index //Index into Joint, Material, or Shape table.

The enum for HierarchyDataTypes is defined below.

Hierarchy Data Types

public enum HierarchyDataTypes : ushort
{
    Finish = 0x0, //Terminator
    NewNode = 0x01, // Hierarchy down (insert node), new child
    EndNode = 0x02, // Hierarchy up, close child
    Joint = 0x10,
    Material = 0x11,
    Shape = 0x12, // Batch
}

VTX1 Chunk


VTX1Header

// 0x40/64 bytes long
/*0x00*/ char[4] Magic_VTX1; //'VTX1'
/*0x04*/ int ChunkSize; //Total bytes of chunk
/*0x08*/ int VertexFormatOffset; //Offset to VertexFormats from VTX1 chunk start.
/*0x0C*/ int[13] VertexDataOffsets; //13 offsets to Vertex data. 0 if no data. Relative to VTX1Header start.

Positioned at the VertexFormatOffset is an array of VertexFormatOffsets. The count of these is not specified in the file (and determination via ChunkSize is not possible). Instead, load VertexFormats until one with an ArrayType of NullAttr (0xFF) is found.

VertexFormat

// 0x10/16 bytes long
/*0x00*/ int ArrayType; //Defines what type of VertexFormat this is (See below)
/*0x04*/ int ComponentCount; //Meaning depends on DataType (see gx.h: "CompCount")
/*0x08*/ int DataType; //What type of data is stored (See below)
/*0x0C*/ byte DecimalPoint; //Number of mantissa bits for fixed point numbers (position of decimal point)
/*0x0D*/ byte[3] Padding;

Below are possible values for the ArrayType variable.

public enum ArrayTypes
{
    PositionMatrixIndex, Tex0MatrixIndex, Tex1MatrixIndex, Tex2MatrixIndex, Tex3MatrixIndex,
    Tex4MatrixIndex, Tex5MatrixIndex, Tex6MatrixIndex, Tex7MatrixIndex,
    Position, Normal, Color0, Color1, Tex0, Tex1, Tex2, Tex3, Tex4, Tex5, Tex6,Tex7,
    PositionMatrixArray, NormalMatrixArray, TextureMatrixArray, LitMatrixArray, NormalBinormalTangent,
    MaxAttr, NullAttr = 0xFF,
}

Below are possible values for the DataType variable. Depending on the ArrayType value it can mean either a datatype from one or the other enums below. Example: Color types use 0x5 from DataTypesAlt (RGBA8) while Position can use Float32 from DataTypes.

public enum DataTypes
{
    Unsigned8 = 0x0,
    Signed8 = 0x1, 
    Unsigned16 = 0x2,
    Signed16 = 0x3,
    Float32 = 0x4,
}
public enum DataTypesAlt
{
    RGB565 = 0x0,
    RGB8 = 0x1,
    RGBX8 = 0x2
    RGBA4 = 0x3
    RGBA6 = 0x4
    RGBA8 = 0x5
}

For each ArrayFormat, the data is stored at corresponding VTX1Header.VertexDataOffsets offset. You don't need to know how many coords/normals/colors/texCoords are stored because these arrays are indexed by tristrips in the SHP1Header section.

Skinning Envelopes (EVP1)


EVP1Header

// 0x1C/28 bytes long
/*0x00*/ char[4] Magic_EVP1; // 'EVP1'
/*0x04*/ int chunkSize;
/*0x08*/ short numIndexes;
/*0x0A*/ short padding1;
/*0x0C*/ int indexCountOffset;
/*0x10*/ int indexDataOffset;
/*0x14*/ int weightOffset;
/*0x18*/ int boneMatrixOffset;

Index Count Array

// 0x01/1 bytes long
/*0x00*/ byte numBones;

This gives the number of bones that influence the desired vertex. The index of this count is the index of the vertex's data in the index data array. Therefore, to get the offset into the index data array of the indexes for the desired vertex, you must add up all of the previous counts and multiply the result by 2.

Index Data Array

//Variable size. Lowest is 2.

This holds the indexes of the bone matrices that the desired vertex belongs to.

Weights

// 0x4/4 bytes long
/*0x00*/ float weight;

The entries in this section are the scale values for a bone's influence on the vertex. It appears that each index in the vertex's index array takes a weight based on the index's own index. For instance, if a vertex has two bone matrix indexes with indexes into the index data array at 3 and 4, those bone matrices will be scaled by weights 3 and 4.

Bone Matrices

// 0x30/48 bytes long

These are the bone matrices, and as such, there is always one entry per bone. They define how each vertex is transformed from its original position in relation to the bone. They are 3x4 matrices.

Weight Check Data (DRW1)


This chunk helps the geometry determine if it is partially weighted or not. It consists of a header followed by the partial weight array and an index array.

Header

The header is made up of the magic, the chunk size, the number of boolean values in the boolean array, the offset to the partial weight array, and the offset to the index array.

// 0x14/20 bytes long
/*0x00*/ char[4] Magic_DRW1; // 'DRW1'
/*0x04*/ int ChunkSize;
/*0x08*/ short NumElements;
/*0x0A*/ short Padding1;
/*0x0C*/ int PartialWeightArrayOffset;
/*0x10*/ int IndexArrayOffset;

Partial Weight Array

This is an array of boolean values.

// 0x1/1 byte long
/*0x00*/ bool IsPartiallyWeighted;

If IsPartiallyWeighted is false, the element in the index array that shares its index with the index of this bool is the index of a bone, and the vertex that referenced this bool moves 1:1 with the bone.

If IsPartiallyWeighted is true, the element in the index array that shares its index with the index of this bool is the index of an EVP1 index array element.

Index Array

// 0x2/2 bytes long
/*0x00*/ short index;

For every bool in the weight bools section above that is true, there is an index to an EVP1 element. It appears that the IsPartiallyWeighted == true bools and indexes are stored twice. The reason for this is unknown.

Joint Data (JNT1)


JNT1Header

// 0x18/24 bytes long
/*0x00*/ char[4] Magic_JNT1; //'JNT1'
/*0x04*/ int ChunkSize; //Total bytes of chunk
/*0x08*/ short SectionCount; //How many joints there are
/*0x0A*/ short Padding;
/*0x0C*/ int JointEntryOffset; //Relative to JNT1Header start
/*0x10*/ int StringIdTable; //There are SectionCount ushorts at this point.
/*0x14*/ int StringTableOffset; //Names of Joints

The JointEntryOffset points to a SectionCount number of JointEntry structs. This is followed by an the UnknownOffset data which is always the numbers 0 to (count -1) (in that order). It may be a index-to-stringtable-index map, or it may be something similar to DZB's strange section.

JointEntry

// 0x40/64 bytes long
/*0x00*/ short Unknown1; //Always 0, 1 or 2
/*0x02*/ short Padding; //0x00FF in Mario, not always in Zelda
/*0x04*/ float ScaleX;
/*0x08*/ float ScaleY;
/*0x0C*/ float ScaleZ;
/*0x10*/ short RotationX; //-32768 = -180 deg, 32767 = 180 deg. float degrees = ((float)rotShort * 180.0f) / 32767.0f;
/*0x12*/ short RotationY;
/*0x14*/ short RotationZ;
/*0x16*/ short Padding;
/*0x18*/ float TranslationX;
/*0x1C*/ float TranslationY;
/*0x20*/ float TranslationZ;
/*0x24*/ float BoundingSphereRadius;
/*0x28*/ float BBoxMinX;
/*0x2C*/ float BBoxMinY;
/*0x30*/ float BBoxMinZ;
/*0x34*/ float BBoxMaxX;
/*0x38*/ float BBoxMaxY;
/*0x3C*/ float BBoxMaxZ;

StringIdTable

// 0x2/2 bytes long
/*0x00*/ short StringIndex; //Index into a string

This one is rather curious too. There's as many entries in the table as there are number of strings in the string table (which also matches the SectionCount of how many joints there are). They seem to increment from 0 to SectionCount-1 in order, without fail.

String Table Header

// 0x4/4 bytes long
/*0x00*/ short NumEntries; //Number of strings in table
/*0x02*/ short Padding;

Immediately following the StringTableHeader is NumEntries count of StringTableData's.

StringTableData

// 0x4/4 bytes long
/*0x00*/ short Hash // Hash of the string at StringOffset, see code below
/*0x02*/ short StringOffset //Offset from StringTableHeader to string.

Finally, following the StringTableData is NumEntries number of null-terminated strings.

String hash generation

Nintendo generates the hash referenced above in the following manner: First start with a hash value of zero, then multiply the original value by 3, and add the current character, increment into the string. Repeat until all of the characters in the string are consumed.

ushort calculateHash(string target)
{
	ushort hash = 0;
	foreach (char c in target)
	{
		hash *= 3;
		hash += (ushort)c;
	}

	return hash;
}

SHP1 Chunk


SHP1Header

// 0x2C/44 bytes long
/*0x00*/ int char[4] Magic_SHP1; //'SHP1'
/*0x04*/ int ChunkSize; //Total bytes of chunk
/*0x08*/ short SectionCount; //How many batches there are
/*0x0A*/ short Padding;
/*0x0C*/ int BatchDataOffset; //Offset to Batch's  (Relative to SHP1Header start)
/*0x10*/ int IndexTableOffset; //Offset into IndexTable... seen in other things here too.
/*0x14*/ int Unknown1;//Seems to usually be zero
/*0x18*/ int BatchAttribsOffset; //Offset to BatchAttribs
/*0x1C*/ int MatrixTableOffset; //Offset to MatrixTable
/*0x20*/ int PrimitiveDataOffset; //Offset to the actual primitive data
/*0x24*/ int MatrixDataOffset; //Offset to MatrixData
/*0x28*/ int PacketLocationOffset;

The SHP1 Chunk stores shapes in batches, and these batches store one or more packets. Each packet then stores a collection of primitives. Because primitives can have different attributes for rendering (ie: pos coordinate, normals, texcoords, etc.) they are broken into multiple batches by vertex attributes. Each batch can then have several "Packets" which are used for animation.

The MatrixTableOffset stores ToDo: SectionCount amount? an array of ushorts which map from the MatrixData indexes to the DRW1Data array indexes. If a batch contains multiple packets, the 2nd/3rd/etc. may contain 0xFFFF values which means that the corresponding index from the previous packet should be used.

Batch

// 0x28/40 bytes long
/*0x00*/ short Unknown; //Seems to always be 0x00FF
/*0x02*/ short PacketCount; //How many packets to this Batch
/*0x04*/ short AttribsOffset; //Relative to SHP1Header.BatchAttribsOffset
/*0x06*/ short MatrixDataOffset; //Index to 'PacketCount' consecutive indexes
/*0x08*/ short PacketDataOffset; //Index to first packet ('PacketCount' consecutive Indexes)
/*0x0A*/ short Padding;
/*0x0C*/ float BoundingSphereRadius;
/*0x10*/ float BBoxMinX;
/*0x14*/ float BBoxMinY;
/*0x18*/ float BBoxMinZ;
/*0x1C*/ float BBoxMaxX;
/*0x20*/ float BBoxMaxY;
/*0x24*/ float BBoxMaxZ;

There are SHP1Header.SectionCount Batch entries stored at SHP1Header.BatchDataOffset.

IndexTable

// 0x2/2 bytes long
/*0x0*/ short Index;

There are SHP1Header.SectionCount entries stored at SHPHeader.IndexTableOffset. They are a ushort long and count from 0 to SHP1Header.SectionCount-1.

BatchAttrib

// 0x8/8 bytes long
/*0x00*/ int AttribType; //See [ArrayTypes](https://github.com/LordNed/WindEditor/wiki/J3D-Documentation#vertexformat)
/*0x04*/ int DataType; //See [DataTypes](https://github.com/LordNed/WindEditor/wiki/J3D-Documentation#vertexformat)

PacketLocation

// 0x4/4 bytes long
/*0x00*/ int Size; //Size in bytes of the packet 
/*0x04*/ int Offset; //Offset relative to `SHP1Header.PacketDataOffset`

The Size refers to the total size of all Primitives in the packet + their data. Offset refers to the offset relative to SHP1Header.PacketDataOffset (ie: The start of the packet data). Packets seem to end with an empty Primitive + Primitive Data, or are padded to the nearest 32 byte alignment(?).

Primitive

// 0x3/3 bytes long
/*0x00*/ byte PrimitiveType //See below
/*0x01*/ short VertexCount //There are VertexCount amount of vertexes in this primitive.

Below are the possible values for PrimitiveType. In most cases, primitives are stored as TriangleStrips which eliminates the need for a traditional "Face" that specifies vertex indexes. On rare occasion the primitives are stored as trifans, but traditional triangles and quads have not been seen in BMD/BDL formats. (These primitive types come from ogc/gx.h so they're what the GX is capable of which doesn't necessarily imply usage.)

public enum PrimitiveTypes
{
    Points = 0xB8,
    Lines = 0xA8,
    LineStrip = 0xB0,
    Triangles = 0x90,
    TriangleStrip = 0x98,
    TriangleFan = 0xA0,
    Quads = 0x80,
}

MatrixData

// 0x8/8 bytes long
/*0x00*/ short Unknown
/*0x02*/ short Count //`Count` many consecutive indexes into MatrixTable
/*0x04*/ int Indexoffset //Relative to ??

MAT3 Chunk


Header

// 0x84/132 bytes long
/*0x00*/ string "MAT3";
/*0x04*/ int SectionSize;
/*0x08*/ short NumMaterials;
/*0x0A*/ short Padding1;
/*0x0C*/ int MaterialEntryDataOffset;
/*0x10*/ int MaterialRemapTableOffset;
/*0x14*/ int StringTableOffset;
/*0x18*/ int IndirectTexturingInfoOffset;
/*0x1C*/ int CullModeInfoOffset;
/*0x20*/ int MaterialColorsOffset;
/*0x24*/ int NumColorChannelsOffset;
/*0x28*/ int ColorChannelInfoOffset;
/*0x2C*/ int AmbientColorsOffset;
/*0x30*/ int LightInfoOffset;
/*0x34*/ int NumTexGensOffset;
/*0x38*/ int TexGenInfoOffset;
/*0x3C*/ int PostTexGenInfoOffset;
/*0x40*/ int TexMatrixInfoOffset;
/*0x44*/ int PostTexMatrixInfoOffset;
/*0x48*/ int TextureRemapTableOffset;
/*0x4C*/ int TevOrderInfoOffset;
/*0x50*/ int TevColorsOffset;
/*0x54*/ int TevKonstColorsOffset; 
/*0x58*/ int NumTevStagesOffset;
/*0x5C*/ int TevStageInfoOffset;
/*0x60*/ int TevSwapModeInfoOffset; 
/*0x64*/ int TevSwapModeTableInfoOffset;
/*0x68*/ int FogInfoOffset;
/*0x6C*/ int AlphaCompareInfoOffset;
/*0x70*/ int BlendModeInfoOffset;
/*0x74*/ int ZModeInfoOffset;
/*0x78*/ int ZCompareInfoOffset;
/*0x7C*/ int DitherInfoOffset;
/*0x80*/ int NBTScaleInfoOffset;

Material Entry

// 0x14C/332 bytes long
/*0x000*/ byte Flag; //Read by patched material, always 1?
/*0x001*/ byte CullModeIndex;
/*0x002*/ byte NumColorChannelControlsIndex;
/*0x003*/ byte NumTexGensIndex;
/*0x004*/ byte NumTevStagesIndex;
/*0x005*/ byte ZCompLocIndex;
/*0x006*/ byte ZModeIndex;
/*0x007*/ byte DitherIndex;
/*0x008*/ short[2] MaterialColorIndexes; 
/*0x00C*/ short[4] ColorChannelControlIndexes; 
/*0x014*/ short[2] AmbientColorIndexes;
/*0x018*/ short[8] LightColorIndexes;
/*0x028*/ short[8] TexGenInfoIndexes;
/*0x038*/ short[8] PostTexGenInfoIndexes;
/*0x048*/ short[10] TexMatrixIndexes;
/*0x05C*/ short[20] PostTexMatrixIndexes;
/*0x084*/ short[8] TextureIndexes;
/*0x094*/ short[4] TevKonstColorIndexes;
/*0x09C*/ byte[16] TevKonstColorSels;
/*0x0AC*/ byte[16] TevKonstAlphaSels;
/*0x0BC*/ short[16] TevOrderInfoIndexes;
/*0x0DC*/ short[4] TevColorIndexes;
/*0x0E4*/ short[16] TevStageInfoIndexes;
/*0x104*/ short[16] TevSwapModesInfoIndexes;
/*0x124*/ short[4] TevSwapModeTablesIndexes;
/*0x12C*/ short[12] UnknownIndexes;
/*0x144*/ short FogInfoIndex;
/*0x146*/ short AlphaCompareIndex;
/*0x148*/ short BlendModeIndex;
/*0x14A*/ short Unknown1;

MDL3 Chunk


MDL3 is only found in BDL models. It is the only major difference between BMD and BDL; if this chunk is removed, the header of the file is changed from J3D2BDL4 to J3D2BMD3, and the ChunkCount is changed from 9 to 8, a BDL file can function exactly like a BMD file.

MDL3 contains pre-compiled GX commands to speed up rendering time.

MDL3 Header

// 0x24/36 bytes long
/*0x00*/ string "MDL3";
/*0x04*/ int chunkSize;
/*0x08*/ short numEntries;
/*0x0A*/ short padding1;
/*0x0C*/ int packetOffset; //Packet Location Data
/*0x10*/ int subPacketOffset; //Sub-packet Location Offset
/*0x14*/ int matrixIndexOffset; //Matrix Index Offset
/*0x18*/ int unknown0Offset; //Unknown0 Offset
/*0x1C*/ int indicesOffset; 
/*0x20*/ int stringTableOffset; //See JNT1

Packet Location

// One set is 0x08/8 bytes long
/*0x00*/ int entryOffset;
/*0x04*/ int entrySize;

In the header of the entries subsection, there is an offset to an entry followed by its size for each entry. NOTE: Each offset starts from the beginning of the offset itself, NOT the start of the header.

Entries

Entries are made up of two components: BP commands and XF commands.

BP Commands
// 0x05/5 bytes long
/*0x00*/ byte 0x61; // From Dolphin: GX_LOAD_BP_REG
/*0x01*/ byte tevRegisterID;
/*0x02*/ byte value1;
/*0x03*/ byte value2;
/*0x04*/ byte value3;

Each command begins with the identifier 0x61. This is followed by the TEV register ID. The data stored in value1, value2, and value3 is put into the specified TEV register. Depending on the nature of the register, the values will cause different things to happen.

XF Commands
// Headers are 0x05/5 bytes long, data length is variable
/*0x00*/ byte 0x10; // From Dolphin: GX_LOAD_XF_REG
/*0x01*/ short dataLengthMultiplier - 1;
/*0x03*/ short xfRegisterID; //Not always valid?
/*0x05*/ byte[] data; //dataLength = 4 * dataLengthMultiplier

XF commands are denoted by a starting byte of 0x10. This is followed by multiplier for data size - 1, the XF register ID, and the data to store there. Multiplying 4 by dataLengthMultiplier will give the size of the actual data.

Sub-Packet Location

// 0x10/16 bytes long
/*0x00*/ short channelColorOffset;
/*0x02*/ short channelControlOffset;
/*0x04*/ short textureGenOffset;
/*0x06*/ short textureOffset;
/*0x08*/ short tevOffset;
/*0x0A*/ short pixelOffset;
/*0x0C*/ int padding1;

There is one of these for each entry. They appear to affect color and alpha, but how is not understood as of yet.

matrixIndexOffset

// 0x08/8 bytes long
/*0x00*/ float f1;
/*0x04*/ short unknwn1;
/*0x06*/ short unknwn2;

There is one of these for each entry. The first four bytes appear to be a float, but it is so small that it may not be. The shorts may also be other data types.

Unknown0

// 0x01/1 byte long
/*0x00*/ byte unknwn1;

There is a byte per entry here. The byte is typically 01 or 04.

Texture Data (TEX1)


The TEX1 chunk contains the textures used for materials. It is made up of a header followed by a specified number of BTI images.

Header

The header consists of the magic, the chunk size, the number of textures, the offset to the first image's header, and the offset to the string table.

// 0x14/20 bytes long
/*0x00*/ char[4] Magic_TEX1; //'TEX1'
/*0x04*/ int ChunkSize; //Total bytes of chunk
/*0x08*/ short TextureCount; //Number of textures
/*0x0A*/ short Padding; (Usually 0xFFFF)
/*0x0C*/ int TextureHeaderOffset; //TextureCount bti image headers are stored here. Relative to TEX1Header start.
/*0x10*/ int StringTableOffset; //Stores one filename for each texture. Relative to TEX1Header start.

Images

The images are stored in the BTI format, but with a slight quirk. All of the images' headers are stored one after another. After the last header, the image data that corresponds to each header flows uninterrupted to the end of the chunk.