.bmd file format ================ Version 1.0 TODO: overview on stringtables, are used in more than one section Note: ogc/gx.h contains tons of useful information Overview -------- The .bmd file format is used by supermario sunshine to store its model data. I heard it's used by some other games as well (windwaker, double dash, ...). The .bmd format consists of a "generic" file header (used by other supermario sunshine formats as well) and eight subsections. Each subsection starts with a 4 char tag and an u32, the size in bytes of this subsection. The subsections have the following names and purposes (they are always included in this order, no subsection is ever missing): INF1 - joint hierarchy VTX1 - Stores vertex arrays for positions, normals, colors, tex coords etc. Also stores in which data format these arrays are stored (fixed point, float, ...) EVP1 - vertex weights for skinning DRW1 - Stores which matrices are weighted and which are used directly (for sceletal animation) JNT1 - Stores the model's joints SHP1 - Stores shape information (tristrips most of the time). There may be different tristrip kinds (some with positions and normals, some with positions, normals and colors, ...), the tristrip types are stored as well. MAT3 - Stores materials (= render settings) TEX1 - Stores texture images. File header --------------- struct FileHeader { char tag[4]; //'JEFF', 'J3D1', 'J3D2' (more?) char type[4]; //'bmd3' for models (.bmd), 'jpa1' for particles (.jpa), 'bck1' for animations (.bck), ... u32 size; //size of file (but not for all files?) u32 sectionCount; //number of sections in file (?) u32 padding[4]; }; INF1 section ---------------- struct Inf1Header { char tag[4]; //'INF1' u32 sizeOfSection; u16 unknown1; u16 pad; //0xffff u32 unknown2; u32 vertexCount; //number of coords in VTX1 section u32 offsetToEntries; //offset relative to Inf1Header start }; //This stores the scene graph of the file struct Inf1Entry { //0x10: Joint //0x11: Material //0x12: Shape (ie Batch) //0x01: Hierarchy down (insert node), new child //0x02: Hierarchy up, close child //0x00: Terminator u16 type; //Index into Joint, Material or Shape table //always zero for types 0, 1 and 2 u16 index; }; VTX1 section ---------------- // VTX1 ///////////////////////////////////////////////////////////// struct Vtx1Header { char tag[4]; //'VTX1' u32 sizeOfSection; u32 arrayFormatOffset; //for each offsets[i] != 0, an ArrayFormat //is stored for that offset, describing //the format of the data at offsets[i]. //offset relative to Vtx1Header start u32 offsets[13]; //offsets relative to Vtx1Header start }; struct ArrayFormat { //see ogc/gx.h for a more complete list of these values: u32 arrayType; //9: coords, a: normal, b: color, d: tex0 (gx.h: "Attribute") u32 componentCount; //meaning depends on dataType (gx.h: "CompCount") u32 dataType; //3: s16, 4: float, 5: rgba8 (gx.h: "CompType") //values i've seem for this: 7, e, 8, b, 0 //-> number of mantissa bits for fixed point numbers! //(position of decimal point) u8 decimalPoint; u8 unknown3; //seems to be always 0xff u16 unknown4; //seems to be always 0xffff }; ogc/gx.h "CompType": 0: U8 1: S8 2: U16 3: S16 4: F32 0: RGB565 1: RGB8 2: RGBX8 3: RGBA4 4: RGBA6 5: RGBA8 ogc/gx.h "Attribute" (only some of these are used, the other values are used in SHP section): I think 9-20 and 25 can be used in the VTX1 section 0: POSITION_MATRIX_INDEX 1: TEX0_MATRIX_INDEX 2: TEX1_MATRIX_INDEX 3: TEX2_MATRIX_INDEX 4: TEX3_MATRIX_INDEX 5: TEX4_MATRIX_INDEX 6: TEX5_MATRIX_INDEX 7: TEX6_MATRIX_INDEX 8: TEX7_MATRIX_INDEX 9: POSITION 10: NORMAL 11: COLOR0 12: COLOR1 13: TEX0 14: TEX1 15: TEX2 16: TEX3 17: TEX4 18: TEX5 19: TEX6 20: TEX7 21: POSITION_MATRIX_ARRAY 22: NORMAL_MATRIX_ARRAY 23: TEXTURE_MATRIX_ARRAY 24: LIT_MATRIX_ARRAY 25: NORMAL_BINORMAL_TANGENT 26: MAX_ATTR 0xff: NULL_ATTR For each ArrayFormat, data is stored at corresponding Vtx1Header.offsets. You don't need to know how many coords/normals/colors/texCoords are stored, because these arrays are indexed by the tristrips (number of coords is stored in INF1 section, though). The tristrips are stored in the SHP1 section. EVP1 section ---------------- // EVP1 ///////////////////////////////////////////////////////////// struct Evp1Header { char tag[4] 'EVP1'; u32 sizeOfSection; u16 count; u16 pad; //0 - count many bytes (counts for each bone?) //1 - sum over all bytes in 0 many shorts (index into some joint stuff? into matrix table?) //2 - bone weights table (as many floats as shorts in 1) //3 - matrix table (matrix is 3x4 float array) u32 offsets[4]; }; DRW1 section ---------------- Now, the way animation matrices are handled is a littlebit convoluted: If a packet uses more than one Matrix, each Vertex stores a position matrix index. You have to divide this index by 3 (no, I don't know why :-P) and use it as an index into the packets matrix table. This gives you an index into the Draw section matrix table which stores if this matrix is weighted or not and, if it is _not_ weighted, the index into the global matrix table (which has to be created as specified in the scene graph). If it _is_ weighted, things are more complicated and you need the EVP1 section as well (to be written...). The Draw section stores // DRW1 ///////////////////////////////////////////////////////////// struct Drw1Header { char tag[4]; u32 sizeOfSection; u16 count; u16 pad; //stores for each matrix if it's weighted (normal (0)/skinned (1) matrix types) u32 offsetToIsWeighted; //for normal (0) matrices, this is an index into the global matrix //table (which stores a matrix for every joint). for skinned //matrices (1), I'm not yet totally sure how this works (but it's //probably an offset into the Evp1-array) u32 offsetToData; }; JNT1 section ---------------- The Joint section stores the model's joints struct Jnt1Header { char tag[4]; //'JNT1' u32 sizeOfSection; u16 count; //number of joints u16 pad; //padding u16 (?) u32 jntEntryOffset; //joints are stored at this place //offset relative to Jnt1Header start u32 unknownOffset; //there are count u16's stored at this point, //always the numbers 0 to count - 1 (in that order). //perhaps an index-to-stringtable-index map? //offset relative to Jnt1Header start u32 stringTableOffset; //names of joints }; struct JntEntry { u16 unknown; //no idea how this works...always 0, 1 or 2 u16 pad; //always 0x00ff in mario, but not in zelda float sx, sy, sz; //scale s16 rx, ry, rz; //-32768 = -180 deg, 32767 = 180 deg u16 pad2; //always 0xffff float tx, ty, tz; //translation float unknown2; float bbMin[3]; //bounding box (?) float bbMax[3]; //bounding box (?) }; SHP1 section ---------------- Stores face information (tristrips etc.) primitive types: from ogc/gx.h: 0xb8: POINTS 0xa8: LINES 0xb0: LINESTRIP 0x80: TRIANGLES 0x98: TRIANGLESTRIP 0xa0: TRIANGLEFAN 0x80: QUADS Most of the time only tristrips are used, seldom trifans. I haven't seen the other primitives in a file until now. Overview: Primitives have different attributes: Some have only position coordinates, some have normals, some texcoords etc.. Because of this, primitives are put into different "Batches", each Batch has a fixed set of vertex attributes. Each Batch can have several "Packets" - they are used for animation, which is not (yet?) covered in this file. A Packet is simply a collection of primitives. struct Shp1Header { char tag[4]; u32 sizeOfSection; u16 batchCount; //number of batches u16 pad; //?? u32 offsetToBatches; //should be 0x2c (batch info starts here) u32 offsetUnknown; //?? u32 zero; //?? u32 offsetToBatchAttribs; //batch vertex attrib start //The matrixTable is an array of u16, which maps from the matrix data indices //to Drw1Data arrays indices. If a batch contains multiple packets, for the //2nd, 3rd, ... packet this array may contain 0xffff values, which means that //the corresponding index from the previous packet should be used. u32 offsetToMatrixTable; u32 offsetData; //start of the actual primitive data u32 offsetToMatrixData; u32 offsetToPacketLocations; //offset to packet start/length info //(all offsets relative to Shp1Header start) }; //Shp1Header.batchCount many of these structs are //stored at Shp1Header.offsetToBatches struct Batch { u16 unknown; //seems to be always 0x00ff ("matrix type, unk") u16 packetCount; //number of packets belonging to this batch //attribs used for the strips in this batch. relative to //Shp1Header.offsetToBatchAttribs //Read StripTypes until you encounter an 0x000000ff/0x00000000, //for all these types indices are included. If, for example, //a Batch has types (9, 3), (a, 3), (0xff, 0), then for this batch two shorts (= 3) //are stored per vertex: position index and normal index u16 offsetToAttribs; u16 firstMatrixData; //index to first matrix data (packetCount consecutive indices) u16 firstPacketLocation; //index to first packet location (packetCount consecutive indices) u16 unknown3; //0xffff float unknown4[7]; //great... (seems to match the last 7 floats of joint info sometimes) //(one unknown float, 6 floats bounding box?) }; struct BatchAttrib { u32 attrib; //cf. ArrayFormat.arrayType u32 dataType; //cf. ArrayFormat.dataType (always bytes or shorts...) }; struct PacketLocation { u32 size; //size in bytes of packet u32 offset; //relative to Shp1Header.offsetData }; struct Primitive { u8 primitiveType; //see above u16 numVertices; //that many vertices included in this primitive - for //each vertex indices are stored according to batch type }; struct MatrixData //from yaz0r's source (animation stuff) { u16 unknown1; u16 count; //count many consecutive indices into matrixTable u32 firstIndex; //first index into matrix table }; TODO: insert information MAT3 section ---------------- Stores materials stuff (texture blending, ...) struct Mat3Header { char tag[4]; //'MAT3' u32 sizeOfSection; u16 count; u16 pad; /* 0 - MatInit array 1 - array of shorts with 0, 1, 2, ..., count (nearly...sometimes small deviations) -> index into offset[0] 2 - string table 3 - divisible by Mat3Header.count - so the Mat3Entries are stored here 15 - index to texture table? */ u32 offsets[30]; //lots... }; struct MatInit { u8 unknown1[132]; u16 texStages[8]; u8 unknown2[332 - 132 - 8*2]; }; struct MatEntry { //size = 312 = 0x138 u8 data[312]; }; /* //from yaz0r: struct J3DMaterialBlock { unsigned long int header; //0 unsigned long int size; //4 unsigned short int numMaterial; //8 unsigned short int unkA; //A unsigned long int offsetToMaterialInitData; //C unsigned long int offsetToMaterialData1; //10 unsigned long int offsetToNameTable; //14 unsigned long int var18; //18 unsigned long int offsetToCullMode; // 1C unsigned long int offsetToColor; // 20 unsigned long int offsetToMaterialData2; // 24 unsigned long int offsetToColorChanInfo; // 28 unsigned long int offsetToColor2; // 2C unsigned long int offsetToLightInfo; // 30 unsigned long int offsetToMaterialData3; // 34 unsigned long int offsetToTexCoordInfo; // 38 unsigned long int offsetToTexCoord2Info; // 3C unsigned long int offsetToTexMtxInfo; // 40 unsigned long int offsetToTexMtxInfo2; // 44 unsigned long int offsetToMaterialData4; // 48 unsigned long int offsetToTevOrderInfo; // 4C unsigned long int offsetToColorS10; // 50 unsigned long int offsetToColor3; // 54 unsigned long int offsetToMaterialData5; // 58 unsigned long int offsetToTevStageInfo; // 5C unsigned long int offsetToTevSwapModeInfo; // 60 unsigned long int offsetToTevSwapModeTableInfo; // 64 unsigned long int offsetToFogInfo; // 68 unsigned long int offsetToAlphaCompInfo; // 6C unsigned long int offsetToBlendInfo; // 70 unsigned long int offsetToZModeInfo; // 74 unsigned long int offsetToMaterialData6; // 78 unsigned long int offsetToMaterialData7; // 7C unsigned long int offsetToNBTScaleInfo; // 80 }; */ TEX1 section ---------------- //header format for 'bmd3' files, seems to be slightly different for 'jpa1' struct Tex1Header { char tag[4]; //'TEX1' u32 sizeOfSection; u16 numImages; u16 unknown; //padding, usually 0xffff u32 textureHeaderOffset; //numImages bti image headers are stored here (see bti spec) //note: several image headers may point to same image data //offset relative to Tex1Header start u32 stringTableOffset; //stores one filename for each image (TODO: details on stringtables) //offset relative to Tex1Header start }; Thanks to yaz0r for lots of help and information :-) thakis www.amnoid.de/gc/