Mesh Data Section

This section holds position, normal, color, and uv mapping information for the vertices, as well as draw lists describing the triangles created with the vertices.

Header

Offset Type Description
0x0 Magic (0x00B749E0 for Sluggers)
0x4 uint32 User data length
0x8 uint32 User data pointer (relative to header)
0xc uint32 Number of meshes
0x10 uint32 Pointer to mesh header array (relative to header)

Mesh Header

Offset Type Description
0x0 uint32 Mesh data header pointer (relative to header)
0x4 char* Mesh name pointer (relative to header)

Mesh Data Header

Offset Type Description
0x0 uint32 Position data pointer (relative to mesh data header)
0x4 uint32 Color data pointer (relative to mesh data header)
0x8 uint32 UV mapping data pointer (relative to mesh data header)
0xc uint32 Normal data pointer (relative to mesh data header)
0x10 uint32 Draw list pointer (relative to mesh data header)
0x14 uint8 Number of texture channels
0x15 byte[3] Padding

Once you analyze the position, color, uv mapping, and normal data referenced by this header, you'll have an array for each of the four data types that the draw list will read from.

Position Data

Offset Type Description
0x0 uint32 Quantized raw position data ptr (relative to mesh data header)
0x4 uint16 Number of positions
0x6 uint8 Quantization
0x7 uint8 Component count (usually 3, 6 if the mesh is skinned and includes alternating position and normal data)

Color Data

Offset Type Description
0x0 uint32 Quantized raw color data ptr (relative to mesh data header)
0x4 uint16 Number of colors
0x6 uint8 Quantization
0x7 uint8 Component count

UV Map Data

Offset Type Description
0x0 uint32 Quantized raw uv (technically st) coord data ptr (relative to mesh data header)
0x4 uint16 Number of coords
0x6 uint8 Quantization
0x7 uint8 Component count
0x8 char* Palette name pointer (relative to mesh data header)
0xc uint32 Palette pointer (relative to mesh data header)

Normal Data

Offset Type Description
0x0 uint32 Quantized raw normal data ptr (relative to mesh data header)
0x4 uint16 Number of normals
0x6 uint8 Quantization
0x7 uint8 Component count
0x8 float Ambient percentage (?)

Draw List

Offset Type Description
0x0 uint32 Primitive data pointer (not used directly)
0x4 uint32 Entry array pointer (relative to mesh data header)
0x8 uint16 Number of entries in draw list
0xa byte[2] Padding

Once you've analyzed the other data sections, analyze the draw list entry by entry.

Draw List Entry

Offset Type Description
0x0 uint8 Entry type
0x1 byte[3] Padding
0x4 bitmap32 Setting
0x8 uint32 Primitive list pointer (relative to the mesh data header) (might be null)
0xc uint32 Primitive list size

Each time you analyze a draw list entry, do two things: update the draw state, and draw the triangles specified by the entry's primitive list if one exists.

Update the draw state

Each triangle drawn by the primitive list has three vertices, each with a combination of a position, a normal, up to two colors, and up to eight texture coordinates. The draw state specifies which of those components are in use and how data will be selected for each component.

How the draw state is changed depends on the entry type. The important entries I've run into are

Type 3

This type specifies which components are provided to the primitive list for each vertex and how the data is chosen for each. The psuedocode is

state.expected_components = []
components = ['position', 'normal', 'color0', 'color1', 'texture0', 'texture1', 'texture2', 'texture3', 'texture4', 'texture5', 'texture6', 'texture7']
// the first couple bytes have to do with something called the 'position matrix index', I just ignore them
setting = setting << 2
for component in components:
    // the two bits in the setting describing this component
    this_setting = setting & 0b11
    enabled = this_setting != 0
    // if this is set, the value is provided in the primitive list instead of an index into one of the data arrays in the mesh data
    // I haven't seen this used
    immediate = setting == 0b01
    indexed = !immediate
    index_size = 1 + (setting & 0b01)
    if enabled:
        expected_components.append({'name': component, 'immediate': immediate, 'index_size': index_size})
    setting = setting << 2

You may end up with a list like
[{'name': 'position', 'immediate': 0, 'index_size': 2},
{'name': 'normal', 'immediate': 0, 'index_size': 2},
{'name': 'color0', 'immediate': 0, 'index_size': 1},
{'name': 'texture0', 'immediate': 0, 'index_size': 2}
{'name': 'texture1', 'immediate': 0, 'index_size': 2}]

meaning that a primitive list at this point would expect 9 bytes per vertex: a two-byte index into the position list, a two-byte index into the normal list, a one-byte index into the vertex colors list, a two-byte index into the texture coordinates list to get a coordinate for texture layer 0, and another two-byte index to get a coordinate for texture layer 1, in that order.

Type 1

This type of entry is simpler, it just describes which texture entry from the texture section of the file is used by a layer. Pseudocode:

texture_index = setting & 0b0001111111111111
setting = setting >> 13
layer = setting & 0b111
setting = setting >> 3
wraps = setting & 0b1111
setting = setting >> 4
wrapt = setting & 0b1111
state.texture_layers[layer] = {'index': texture_index, 'wraps': wraps, 'wrapt': wrapt}

so a setting of 0b00000000 00000000 001 0000000000101 would set texture layer 1 to whatever the 6th texture in the texture section is. wraps and wrapt specify what happens when the s and t (x and y) coordinates extend past the edge of an image, but I just assume the default behavior of whatever modeling software I'm using which is usually fine.

Draw the primitives

A draw list entry won't always have a primitive list - if the primitive list pointer is 0 you skip this step. If there is a primitive list, then the structure is as follows:
Offset Type Description
0x0 uint8 Primitive Type (I believe 0x90 is triangles, 0x98 is triangle strips, 0x80 is quads)
0x1 uint16 Vertex count
0x3 binary Vertex data (follows the format set by entry type 3)

Once you've parsed that, you'll have a bunch of vertices, each looking something like (assuming immediate positioning isn't used)
{'position': positions[378],
'normal': normals[350],
'color0': colors[5],
'texture0': texture_coords[254],
'texture1': texture_coords[875]}

Now you draw triangular faces from those vertices. The code from my awful python parser looks like:

match primitive:
    case 0x90:
        # TRIANGLES
        faces += [vertexes[x:x+3] for x in range(0, len(vertexes), 3)]
    case 0x98:
        # TRIANGLESTRIP
        order = 1
        for i in range(0, len(vertexes) - 2, 1):
            if order:
                faces.append([vertexes[i], vertexes[i + 1], vertexes[i + 2]])
            else:
                faces.append([vertexes[i], vertexes[i + 2], vertexes[i + 1]])
            order = not order
    case 0x80:
        # QUADS
        for x in range(0, len(vertexes), 4):
            faces.append([vertexes[x], vertexes[x+1], vertexes[x+2]])
            faces.append([vertexes[x], vertexes[x+2], vertexes[x+3]])
    case _:
        print ("primitive type " + hex(primitive) + " not supported")
If you haven't parsed the entire primitive list by now, repeat the entire process. The list may draw a different type of primitive as well.