Skip to content

D3DMESH Version 1 (ERTM) format spec — Tales of Monkey Island / BttF / S&M S3 #9

@coccofresco

Description

@coccofresco

Summary

I've reverse-engineered the D3DMESH Version 1 (ERTM) format, which is currently listed as UNSUPPORTED in the version table of TelltaleGames_D3DMesh.ms. This covers:

  • Tales of Monkey Island (2009) — all 5 episodes
  • Back to the Future: The Game (2010-2011)
  • Sam & Max Season 3: The Devil's Playhouse (2010)
  • CSI: Deadly Intent / Fatal Conspiracy (2009-2010)
  • Poker Night at the Inventory (2010)
  • Wallace & Gromit Episode 4 (2009)

The format sits between V0.5 (Strong Bad/W&G Ep. 1-3, EarlyGameFix 4-9) and V2 (Jurassic Park). It uses the MTRE header with version byte 0x01.

What's documented

I've written a complete format specification covering:

  1. TriangleSet structure — CRC hash blocks, geometry fields (mStartIndex, mNumPrimitives, mMinVertIndex, mMaxVertIndex), bounding boxes, material block, multi-texture references
  2. Face index delta compression — the bitstream algorithm for T3IndexBuffer::Decompress, cross-validated PC↔iOS on multiple meshes
  3. Vertex buffer chain — 16-byte headers (count + stride + type + flags), 6 buffer types (positions, normals, weights, bone IDs, UVs, tangents), raw float32 on PC
  4. Multi-texture materials — first texture = diffuse, subsequent textures classified by naming convention (bmap_bump → normal, mapspec → specular, adv_meshes → lightmap)
  5. D3DTX textures — DXT1/DXT3/DXT5 + RGBA8 (for normal maps), mipmap count field, pixel data at end of file
  6. Triangle strip/fan extras — mTriStrips field, extra indices beyond nprim*3 encoded as strip within the same submesh

Key differences from V0.5

Feature V0.5 (Strong Bad/W&G) V1 (ToMI/BttF/S&M3)
Submesh name blocks String names or hashes by EarlyGameFix Always CRC64 hashes (12 bytes × 3)
Material texture slots 5-7 conditional slots Sequential name_len + name references
Vertex buffer headers Flag byte + count + length + type count(u32) + stride(u32) + type(u32) + flags(u32) = 16 bytes
Face data Raw face point indices Delta-compressed bitstream (always on PC)
Vertex compression CompressionCheck + optional None on PC (raw float32)

Verification

  • 358/358 meshes from Tales of Monkey Island EP1 parse and export correctly
  • 356/358 exact submesh count match with file header
  • 70/70 triangle match between PC compressed and iOS raw indices (fx_bubble)
  • Full vertex coverage (5639/5639 for sk20_guybrush, zero topological anomalies)

Resources

The full specification document (written in RTB script terminology), MaxScript pseudocode for the delta decompressor and bit reader, and a Python reference implementation are available here:

Format spec: CONTRIBUTION_RTB.md

Reference implementation (Python, pure stdlib):

  • export_mesh_glb.py — self-contained D3DMESH parser + glTF exporter with embedded textures
  • decode_d3dtx.py — DXT1/3/5 + RGBA8 texture decoder

Repository: https://github.com/coccofresco/telltale-explorer

Delta decompression pseudocode

The core algorithm that was missing — face index delta compression:

accumulator = ReadU16()  -- first index (seed)
bodySize = ReadU32()
body = ReadBytes(bodySize)

indices = [accumulator]
bitPos = 0

while bitPos + 11 <= bodySize * 8 AND #indices < FaceCount:
    deltaWidth = ReadBits(body, bitPos, 4);  bitPos += 4
    groupCount = ReadBits(body, bitPos, 7);  bitPos += 7
    if groupCount == 0: break

    for j = 1 to groupCount:
        sign = ReadBits(body, bitPos, 1);  bitPos += 1
        magnitude = ReadBits(body, bitPos, deltaWidth);  bitPos += deltaWidth
        delta = if sign then -magnitude else magnitude
        accumulator = (accumulator + delta) AND 0xFFFF
        append(indices, accumulator)

Bit reader is LSB-first within little-endian bytes.


Happy to help integrate this into the MaxScript if useful.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions