This file format needs more research. Researches on headers / bodies structures are partially achieved. |
Gotcha Force Models
Gotcha Force models work as those from the game Super Smash Bros Melee (SSBM) investigated by its community.
A model is a file format that organizes objects and resources serialized to be used by the Hal SysDolphin (HSD) library. The HSD library is used in several Capcom games. These are graphic materials structured in a hierarchical way. Some pictures present in the models seem to indicate the use of matcap technic. This library uses Dolphin SDK functions like GX / MTX and so on.
Affected files
All files in the AFS having _mdl in their names are models described in this page. We notice also .arz which are compressed models. collision.arc and tdc00.arc to tdc09.arc are also models.
Model files are also found in the borgs' pzz (plxxxx.pzz) at positions 004 to 009.
HSDRaw
HSDRaw, a dotnet 3D editor for this format, is unstable on my Windows (algoflash). However, it has been proven that editing the _mdl starting at offset 0x100 - after the preamble - allows visualizing the file on HSDRaw and obtaining the 3D rendering of the Borg (click on the first JOBJ folder - then the JOBJ inside and double-click on the violet node). This should also allow editing certain properties?
General Structure
The model format is divided into several blocks. The preamble is 0x100 bytes at the very beginning of the file. It is often absent. All values are in unsigned big endian, and the pad is in Null bytes (\x00). Offsets found after the beginning of the Data Block (DB) are relative to the beginning of the DB. The relocation table is an array of 4-byte offsets (relative to the DB) used to translate the offsets of structures into absolute addressing during memory loading. The relocation table, for example, helps delimit the structures because it is unlikely that an offset points in the middle of a structure.
All files following this format have a single root_node of type SObj (Scene data).
Preamble: (0x100 bytes / 0 bytes)
Position | Size | Description | Observations |
---|---|---|---|
0x0 | 4 bytes | HSD Header offset | |
0x4 | 4 bytes | Joints relation table offset | Offset of the relationship joints |
0x8 | 4 bytes | Joints array position | Position of the joint in the array |
0xC | 20 bytes | Pad | |
0x20 | 4 bytes [32] | Joints relation table | |
0x70 | 32 bytes | Pad | |
0xC0 | 32 bytes | Joints array position | |
0xE0 | 32 bytes | Pad |
HSD Header - (0x20 bytes)
- 4 bytes - Total file size
- 4 bytes - Data block size
- 4 bytes - Number of entries in the Relocation Table
- 4 bytes - root0_count
- 4 bytes - root1_count
- 12 bytes - Pad - (always)
Data block:
- JOBJ, etc. -> These structures are detailed below.
Relocation Table:
- Array of 4-byte offsets - Offsets of structures in the DB.
Root Nodes (2):
- Array of size 8 * root0_count
- 4 bytes - root_offset - relative to DB
- 4 bytes - string_table_offset - relative to StringTable
- Array of size 8 * root1_count
- 4 bytes - root_offset - relative to DB
- 4 bytes - string_table_offset - relative to StringTable
String Table:
- Series of strings terminated by Null, the number of root0_count + root1_count
Structures encountered in the DB
Note: The hierarchy is cumulative on transformations for parent->child relationships
Root Nodes with the name "scene_data" point to an SObj:
SObj: (0x10 bytes)
- 4 bytes - JObjDescs_offsets_list_offset
- 4 bytes - Cameras_list_offset
- 4 bytes - Lights_list_offset
- 4 bytes - Fog
"scene_data" == name of the root node JObjDescs_offsets_list_offset, cameras... point to a list of offsets terminated by 4 bytes at 0x00. lights_list_offset points to pairs (4 bytes - LObj_offset; 4 bytes - light_anim_pointer_offset)
JObjDesc: (0x10 bytes)
- 4 bytes - root_joint_offset - JObj
- 4 bytes - joint_animations_offset - ?
- 4 bytes - material_animations_offset - ?
- 4 bytes - shape_animations_offset - ?
CObj: (0x38 bytes)
- 4 bytes - flags
- 4 bytes - projection_type - ProjectionTypeEnum
- 2 bytes - viewport_left
- 2 bytes - viewport_right
- 2 bytes - viewport_top
- 2 bytes - viewport_bottom
- 4 bytes - proj_width
- 4 bytes - proj_height
- 4 bytes - eye_offset - WObj
- 4 bytes - target_offset - WObj
- 4 bytes - roll
- 4 bytes - up - Vec3
- 4 bytes - float - near_clip
- 4 bytes - float - far_clip
- 4 bytes - float - field_of_view
- 4 bytes - float - aspect
Camera object. Note: for frustrum or ortho, the structure is different.
- ProjectionTypeEnum:
- PERSPECTIVE = 1
- FRUSTRUM = 2
- ORTHO = 3
WObj: (0x14 bytes)
- 4 bytes - Unknown
- 4 bytes - float - v1
- 4 bytes - float - v2
- 4 bytes - float - v3
- 4 bytes - Unknown
eye / target
LObj: (0x1c bytes)
- 4 bytes - class_name
- 4 bytes - next_offset
- 2 bytes - flags - LObjFlagsEnum
- 2 bytes - attenuation_flags - LObjAttenuationFlagsEnum
- 1 byte - color_r
- 1 byte - color_g
- 1 byte - color_b
- 1 byte - color_alpha
- 4 bytes - lobj_point_offset - position
- 4 bytes - infinite_data
- 4 bytes - point_spot_data
light
LObjFlagsEnum :
- LOBJ_AMBIANT = 00 00
- LOBJ_INFINITE = 00 01
- LOBJ_POINT = 00 02
- LOBJ_SPOT = 00 03
- LOBJ_DIFFUSE = 00 04
- LOBJ_SPECULAR = 00 08
- LOBJ_ALPHA = 00 10
- LOBJ_HIDDEN = 00 20
- LOBJ_RAW_PARAM = 00 40
- LOBJ_DIFF_DIRTY = 00 80
- LOBJ_SPEC_DIRTY = 01 00
LObjAttenuationFlagsEnum :
- LOBJ_LIGHT_ATTN_NONE = 00 00
- LOBJ_LIGHT_ATTN = 00 01
LObjPoint : (0x14 bytes)
4 bytes - class_name 4 bytes - float - x 4 bytes - float - y 4 bytes - float - z 4 bytes - Unknown
LightAnimPointer : (0x10 bytes)
- 4 bytes - next_offset - (LightAnimPointer)
- 4 bytes - light_anim - ?
- 4 bytes - position_anim - ?
- 4 bytes - interst_anim - ?
Bugs in HSDRaw - to be tested
JObj : (0x40 bytes)
4 bytes - Unknown 4 bytes - flags 4 bytes - child_offset (JObj) 4 bytes - next_offset (JObj) 4 bytes - dobj_offset (DObj) 4 bytes [3] - float3 - rotation_xyz 4 bytes [3] - float3 - scale_xyz 4 bytes [3] - float3 - translation_xyz 4 bytes - inverse_world_transform_offset (inverse world transform = 4x3 matrix) 4 bytes - robj_offset Pointed by the lists in Root Node scene_data.
JObjFlagsEnum : (can be cumulative)
- NULL = 00 00 00 00
- SKELETON = 00 00 00 01
- SKELETON_ROOT = 00 00 00 02
- ENVELOPE_MODEL = 00 00 00 04
- CLASSICAL_SCALING = 00 00 00 08
- HIDDEN = 00 00 00 10
- PTCL = 00 00 00 20
- MTX_DIRTY = 00 00 00 40
- LIGHTING = 00 00 00 80
- TEXGEN = 00 00 01 00
- BILLBOARD = 00 00 02 00
- VBILLBOARD = 00 00 04 00
- HBILLBOARD = 00 00 06 00
- RBILLBOARD = 00 00 08 00
- INSTANCE = 00 00 10 00
- PBILLBOARD = 00 00 20 00
- SPLINE = 00 00 40 00
- FLIP_IK = 00 00 80 00
- SPECULAR = 00 01 00 00
- USE_QUATERNION = 00 02 00 00
- OPA = 00 04 00 00
- XLU = 00 08 00 00
- TEXEDGE = 00 10 00 00
- JOINT1 = 00 20 00 00
- JOINT2 = 00 40 00 00
- EFFECTOR = 00 60 00 00
- USER_DEFINED_MTX = 00 80 00 00
- MTX_INDEPEND_PARENT = 01 00 00 00
- MTX_INDEPEND_SRT = 02 00 00 00
- MTX_SCALE_COMPENSATE = 04 00 00 00
- ROOT_OPA = 10 00 00 00
- ROOT_XLU = 20 00 00 00
- ROOT_TEXEDGE = 40 00 00 00
InverseWorldTransform : (0x30 bytes)
4 bytes - float - M11 4 bytes - float - M12 4 bytes - float - M13 4 bytes - float - M14 4 bytes - float - M21 4 bytes - float - M22 4 bytes - float - M23 4 bytes - float - M24 4 bytes - float - M31 4 bytes - float - M32 4 bytes - float - M33 4 bytes - float - M34
DObj : (0x10 bytes)
4 bytes - Unknown 4 bytes - next_offset - next DOBJ 4 bytes - mobj_offset - material offset 4 bytes - pobj_offset - mesh offset Linked list that allows retrieving all materials & meshes for the associated JObj.
MObj : (0x18 bytes)
4 bytes - Unknown 4 bytes - render_flags - RenderFlagsEnum 4 bytes - tobj_offset - texture offset - may be invalid if there is no texture for the material 4 bytes - material_offset - material colors? 8 bytes - PEDesc? Contains textures & information about material colors.
RenderFlagsEnum : (cumulative)
- USER = 80 00 00 00
- CONSTANT = 00 00 00 01
- VERTEX = 00 00 00 02
- BOTH = 00 00 00 03
- DIFFUSE = 00 00 00 04
- SPECULAR = 00 00 00 08
- TEX0 = 00 00 00 10
- TEX1 = 00 00 00 20
- TEX2 = 00 00 00 40
- TEX3 = 00 00 00 80
- TEX4 = 00 00 01 00
- TEX5 = 00 00 02 00
- TEX6 = 00 00 04 00
- TEX7 = 00 00 08 00
- TOON = 00 00 10 00
- ALPHA_MAT = 00 00 20 00
- ALPHA_VTX = 00 00 40 00
- ALPHA_BOTH = 00 00 60 00
- ZOFST = 01 00 00 00
- EFFECT = 02 00 00 00
- SHADOW = 04 00 00 00
- ZMODE_ALWAYS = 08 00 00 00
- DF_ALL = 10 00 00 00
- NO_ZUPDATE = 20 00 00 00
- XLU = 40 00 00 00
TObj : (0x5c octets)
- 4 bytes - ?
- 4 bytes - ?
- 4 bytes - tex_map_id - TexMapIdEnum
- 4 bytes - gx_tex_gen_src - GXTexGenSrcEnum
- 4 bytes - float - rx
- 4 bytes - float - ry
- 4 bytes - float - rz
- 4 bytes - float - sx
- 4 bytes - float - sy
- 4 bytes - float - sz
- 4 bytes - float - tx
- 4 bytes - float - ty
- 4 bytes - float - tz
- 4 bytes - wrap_s - WrapEnum
- 4 bytes - wrap_t - WrapEnum
- 1 byte- w_scale
- 1 byte- h_scale
- 2 bytes - ?
1 byte - bump_map - (False = 00 ; True = 01) 4 bits - alpha_operation - AlphaOperationEnum 4 bits - color_operation - ColorOperationEnum 1 byte - shadow_lightmap - (False = 00 ; True = 01) (2 bytes with a lightmap limit Enum) 1 bit - ext_lightmap - (False = 0 ; True = 1) 1 bit - ambient_lightmap - (False = 0 ; True = 1) 1 bit - specular_lightmap - (False = 0 ; True = 1) 1 bit - diffuse_lightmap - (False = 0 ; True = 1) 4 bits - coord_type - CoordTypeEnum 4 bytes - float - blending - used when color_operation or alpha_operation == BLEND 4 bytes - mag_filter - MagFilterEnum
- 4 bytes - ?
- 4 bytes - ?
- 4 bytes - ?
- 4 bytes - ?
Texture Information: The TObj contains information about texture environment parameters used for graphic rendering, as well as the offsets of the image and palette data used for this texture. The most important aspects here are the image offset and the palette/TLUT (Texture Look-Up Table) - if the image is not indexed (RGBA, CMPR, etc.), then the palette/TLUT description structure is not used.
- TexMapIdEnum :
- GX_TEXMAP0 = 00 00 00 00
- GX_TEXMAP1 = 00 00 00 01
- GX_TEXMAP2 = 00 00 00 02
- GX_TEXMAP3 = 00 00 00 03
- GX_TEXMAP4 = 00 00 00 04
- GX_TEXMAP5 = 00 00 00 05
- GX_TEXMAP6 = 00 00 00 06
- GX_TEXMAP7 = 00 00 00 07
- GX_MAX_TEXMAP = 00 00 00 08
- GX_TEXMAP_NULL = 00 00 00 09
- GX_TEXMAP_DISABLE = 00 00 00 0A
- GXTexGenSrcEnum :
- GX_TG_POS = 00 00 00 00
- GX_TG_NRM = 00 00 00 01
- GX_TG_BINRM = 00 00 00 02
- GX_TG_TANGENT = 00 00 00 03
- GX_TG_TEX0 = 00 00 00 04
- GX_TG_TEX1 = 00 00 00 05
- GX_TG_TEX2 = 00 00 00 06
- GX_TG_TEX3 = 00 00 00 07
- GX_TG_TEX4 = 00 00 00 08
- GX_TG_TEX5 = 00 00 00 09
- GX_TG_TEX6 = 00 00 00 0A
- GX_TG_TEX7 = 00 00 00 0B
- GX_TG_TEXCOORD0 = 00 00 00 0C
- GX_TG_TEXCOORD1 = 00 00 00 0D
- GX_TG_TEXCOORD2 = 00 00 00 0E
- GX_TG_TEXCOORD3 = 00 00 00 0F
- GX_TG_TEXCOORD4 = 00 00 00 10
- GX_TG_TEXCOORD5 = 00 00 00 11
- GX_TG_TEXCOORD6 = 00 00 00 12
- GX_TG_COLOR0 = 00 00 00 13
- GX_TG_COLOR1 = 00 00 00 14
- AlphaOperationEnum:
- NONE = 0X
- ALPHAMASK = 1X
- BLEND = 2X
- MODULATE = 3X
- REPLACE = 4X
- PASS = 5X
- ADD = 6X
- SUB = 7X
- ColorOperationEnum:
- NONE = X0
- ALPHA_MASK = X1
- RGB_MASK = X2
- BLEND = X3
- MODULATE = X4
- REPLACE = X5
- PASS = X6
- ADD = X7
- SUB = X8
- CoordTypeEnum:
- UV = X0
- REFLECTION = X1
- HILIGHT = X2
- SHADOW = X3
- TOON = X4
- GRADATION = X5
- MagFilterEnum:
- GX_NEAR = 00 00 00 00
- GX_LINEAR = 00 00 00 01
- GX_NEAR_MIP_NEAR = 00 00 00 02
- GX_LIN_MIP_NEAR = 00 00 00 03
- GX_NEAR_MIP_LIN = 00 00 00 04
- GX_LIN_MIP_LIN = 00 00 00 05
- WrapEnum:
- CLAMP = 00 00 00 00
- REPEAT = 00 00 00 01
- MIRROR = 00 00 00 02
Material : (0x14 octets)
- 1 byte - AMB_R - (ambient)
- 1 byte - AMB_G
- 1 byte - AMB_B
- 1 byte - AMB_A
- 1 byte - DIF_R - (diffuse)
- 1 byte - DIF_G
- 1 byte - DIF_B
- 1 byte - DIF_A
- 1 byte - SPC_R - (specular)
- 1 byte - SPC_G
- 1 byte - SPC_B
- 1 byte - SPC_A
- 4 bytes - float - alpha
- 4 bytes - float - shininess
PObj : (0x18 octets)
- 4 bytes - Inconnu
- 4 bytes - next_offset - pobj
- 4 bytes - vertex_attr_list_offset
- 2 bytes - Flags - PObjFlagsEnum
- 2 bytes - display_list_size - number of 0x20 (32) byte blocks occupied by display list data
- 4 bytes - display_list_offset
- 4 bytes - weight_list_offset - ? Verify that the envelope_weights array is indeed found at this offset.
As indicated by next_offset, it is a list of meshes to be displayed for a given material referenced by a dobj. PObj contains the offsets of vertex attributes / display list / joint weight list, which is sufficient and allows interpreting and processing vertex, normal, and texture coordinates.
Vertex attributes are the most important - the specified parameters control everything: from the format to the size of the data for each vertex, normal, and texture coordinates, and how these values are dimensioned (scaled) based on the presence and size of each index value appearing in the display list information.
- PObjFlagsEnum :
Unknown0 = 00 01 Unknown1 = 00 02
- ANIM = 00 08
- SHAPE_ANIM = 10 00
- ENVELOPE = 20 00
- CULLBACK = 40 00
- CULLFRONT = 80 00
EnvelopeWeights : (0x? octets)
- 4 bytes - envelope_count
- [envelope_count]:
- 4 bytes - float - weight
- 4 bytes - pad ?
- 4 bytes - jobj_list_offset
not verified yet
- IMAGE_HEADER :
- 4 bytes - image_offset - image data
- 2 bytes - width
- 2 bytes - height
- 4 bytes - image_format
PALETTE_HEADER : (0x10 bytes)
- 4 bytes - palette_offset - palette data
- 4 bytes - palette_format
- 4 bytes - Unknown
- 2 bytes - color_count
- 2 bytes - Unknown
The image_offset and palette_offset are often shared by several textures. Knowing this allows you to find all the information about the textures' image data (width, height, format), as well as the format and the number of colors in the palette/TLUT.
The image format does not determine the total number of colors in the palette it uses. For example, an 8-bit indexed image would have a maximum of 256 colors, but only 136 or 221 colors may be currently used. In this way, the palette will not use the total size it should use.
Images follow the structure generally used by the GameCube:
IMAGE FORMATS :
- case 0: //i4
- case 1: //i8
- case 2: //i4a4
- case 3: //i8a8
- case 4: //r5g6b5
- case 5: //rgb5a3
- case 6: //r8g8b8a8
- case 8: //index4
- case 9: //index8
- case 0xa: //index14x2
- case 0xe: //s3tc1
The indexed formats also use palette/TLUT information, and the data can appear in various formats:
PALETTE FORMATS :
- case 0: //ia8
- case 1: //r5g6b5
- case 2: //rgb5a3
---> "Joint Data - Accessing Geometry, Mesh, and Vertex Attributes" https://smashboards.com/threads/melee-dat-format.292603/
// Declaration of a vertex and attribute information // attr, type, cnt, data_type, flags?, file_offset
- ATTR_DATA
- 4 bytes - GXAttr vtx_attr_offset - attr
- 4 bytes - GXAttrType vtx_attr_type - index_type
- 4 bytes - GXCompCnt comp_cnt - cnt
- 4 bytes - GXCompType comp_type - data_type
- 1 bytes - scale
- 1 bytes - unknown
- 2 bytes - vtx_stride
- 2 bytes - data_offset
vertex_attr_offset points to a list of vertex attributes. The number of these attributes is not provided. The data in this structure follows the information and types designed by the GameCube/WII architecture and its API. The list is terminated by a specific value of vtx_attr signaling the end of this list. With access to the SDK documentation, it is strongly recommended to read the section about "vertex attributes and other specifications."
The value of vtx_attr determines the type of data that this attribute represents and can indicate a number of native types used for passing data between software and hardware interfaces.
The possible values are as follows: enum GXAttr (4 bytes)
- GX_VA_PNMTXIDX = 0, // position/normal matrix index
- GX_VA_TEX0MTXIDX, // texture 0 matrix index
- GX_VA_TEX1MTXIDX, // texture 1 matrix index
- GX_VA_TEX2MTXIDX, // texture 2 matrix index
- GX_VA_TEX3MTXIDX, // texture 3 matrix index
- GX_VA_TEX4MTXIDX, // texture 4 matrix index
- GX_VA_TEX5MTXIDX, // texture 5 matrix index
- GX_VA_TEX6MTXIDX, // texture 6 matrix index
- GX_VA_TEX7MTXIDX, // texture 7 matrix index
- GX_VA_POS = 9, // position
- GX_VA_NRM, // normal
- GX_VA_CLR0, // color 0
- GX_VA_CLR1, // color 1
- GX_VA_TEX0, // input texture coordinate 0
- GX_VA_TEX1, // input texture coordinate 1
- GX_VA_TEX2, // input texture coordinate 2
- GX_VA_TEX3, // input texture coordinate 3
- GX_VA_TEX4, // input texture coordinate 4
- GX_VA_TEX5, // input texture coordinate 5
- GX_VA_TEX6, // input texture coordinate 6
- GX_VA_TEX7, // input texture coordinate 7
- GX_POS_MTX_ARRAY, // position matrix array pointer
- GX_NRM_MTX_ARRAY, // normal matrix array pointer
- GX_TEX_MTX_ARRAY, // texture matrix array pointer
- GX_LIGHT_ARRAY, // light parameter array pointer
- GX_VA_NBT, // normal, bi-normal, tangent
- GX_VA_MAX_ATTR, // maximum number of vertex attributes
- GX_VA_NULL = 0xff // NULL attribute (to mark the end of lists)
All the necessary data to represent a mesh can be specified and indexed in one way or another. Similarly, GX_VA_TEX0MTXIDX, GX_VA_CLR0, and others are also possible - the most frequent values in the SSBM data are: GX_VA_PNMTXIDX, GX_VA_POS, GX_VA_NRM, and GX_VA_TEX0. This is to be expected as joint matrices, vertex positions/normals, and texture coordinates respectively are probably the most basic elements needed to display a textured mesh, whether static or animated.
When the value of vtx_attr == GX_VA_NULL (0xFF), the end of the vertex attribute array has been reached.
The vtx_attr_type values is actually associated with how the value is indexed, and can thus also determine the size of the index value within the display list data.
- 4 bytes - enum GXAttrType
- GX_NONE = 0,
- GX_DIRECT,
- GX_INDEX8,
- GX_INDEX16
- Structures COLL_DATA : (collision data)
- 4 bytes - vertex_offset (2 floats per entry corresponding to probably 2D data)
- 4 bytes - vertex_count
- 4 bytes - index_offset
- 4 bytes - index_count
- 4 bytes [5] - Unknown
- 2 bytes - index_start
- 2 bytes - index_count
- 4 bytes - Unknown
- 4 bytes - Unknown