Wolfenstein Modloader Buy me a coffeeKaffee spendieren

Wiki / Latest research roundsNeueste Forschungsrunden

TNO Model Formats: Static Solved, Skinned Vertex/Index Block Now Decoded

Partly solvedTeilweise gelöst Original page in the GitHub wikiOriginalseite im GitHub-Wiki

In plain wordsIn einfachen Worten

This report covers the 3D model formats of Wolfenstein: The New Order. Static models were already fully decoded; the new result is that the vertex and triangle data of skinned (animated) meshes can now be read exactly. That works exactly on 35 of 150 random samples (23%), including character models with over 5,000 vertices. Everything comes from reading file bytes; nothing was seen rendering, and the skeleton and some header fields are still undecoded.

  • Static models and their textures are fully decoded and in use, error-free on two maps.
  • Skinned vertices are 32-byte big-endian records followed by a plain triangle index list.
  • The layout fits exactly on 35 of 150 random samples, up to 5,323 vertices / 8,645 triangles.
  • The old 'mesh count' field is wrong (reads 1 on all 755 samples); the real count's position is not yet found.
  • Skeleton: 211 joint and face-control names recovered, but the joint hierarchy is not decoded; estimate 2–3 days to a Studio reader.

Dieser Bericht behandelt die 3D-Modellformate von Wolfenstein: The New Order. Statische Modelle waren schon vollständig entschlüsselt; neu ist, dass sich Eckpunkte und Dreiecke von animierten Meshes jetzt exakt lesen lassen. Das passt bei 35 von 150 Zufallsproben (23 %) genau, auch bei Figurenmodellen mit über 5.000 Eckpunkten. Alles stammt aus dem Lesen der Dateibytes; nichts wurde tatsächlich angezeigt, und das Skelett sowie einige Kopffelder sind noch nicht entschlüsselt.

  • Statische Modelle und ihre Texturen sind vollständig entschlüsselt und im Einsatz, fehlerfrei auf zwei Karten.
  • Animierte Eckpunkte sind 32-Byte-Datensätze im Big-Endian-Format, gefolgt von einer einfachen Dreiecksliste.
  • Das Format passt exakt bei 35 von 150 Zufallsproben, bis zu 5.323 Eckpunkte / 8.645 Dreiecke.
  • Das alte Feld „Mesh-Anzahl“ ist falsch (zeigt bei allen 755 Proben 1); die Position der echten Anzahl ist noch nicht gefunden.
  • Skelett: 211 Gelenk- und Gesichtsnamen gefunden, die Gelenkhierarchie ist aber nicht entschlüsselt; geschätzt 2–3 Tage bis zum Studio-Leser.

Technical detailsTechnische Details

Der technische Teil ist auf Englisch – so, wie er im Wiki steht. Unterstrichene Begriffe haben eine Erklärung auf Deutsch.

Why the Studio asset browser shows no models for The New Order, and how far a reader can get on the skinned mesh format. Static TNO meshes and their material→texture resolution are fully decoded and already in production use, confirmed error-free on two independently checked maps. The Studio shows no skinned/animated models because that code path deliberately raises "unsupported" — a documented stub, not a bug. This pass's main result: the skinned vertex-and-index block, previously the admitted hard, unsolved core, is now decoded exactly and shown to generalize to roughly a quarter of a broad random sample, including multi-thousand-vertex character meshes. A prior "mesh count" header field is shown to be unreliable, and a first, partial step is made on the skeleton format (joint names only). Everything here is static analysis of archive bytes and executable string tables — TNO's code is encrypted and unreadable by any disassembler available to this project — so nothing was observed actually rendering.

Why the Studio shows nothing

The TNO model-loading function in the project's own codebase raises "unsupported" on purpose.

Static meshes — unchanged, fully solved

Static TNO meshes and material→texture resolution are fully decoded and wired end-to-end in production code, re-confirmed error-free on a second map this pass did not originally use.

Skinned vertex/index block (new this pass, the main result)

Format: a plain, big-endian, 32-byte interleaved vertex record —

f32 pos[3]   -- position
f32 uv[2]    -- texture coordinate
12 bytes     -- not yet decoded (very likely normal/tangent/colour, by exact
                analogy with the already-solved STATIC mesh vertex, which
                uses the identical stride and leading layout)

— followed directly by a bare list of big-endian u16 triangle indices, with no length prefix of its own.

Proven exactly (zero free parameters) on three structurally different single-mesh samples — a 1,001-vertex weapon, a 38-vertex static prop, a 14-vertex non-rendering collision volume — by decoding vertices until one fails a sanity check (finite, non-subnormal, inside the mesh's own bounding box) to get a vertex count, then independently finding the exact byte offset where the mesh's own material name string recurs later in the file (a plain substring search, nothing to do with vertex/index counting) to get the index block's exact byte length with no further free parameter. Little-endian and the other game's own 48-byte vertex stride both fail immediately as negative controls.

A broad, unselective random sample of 150 further entries, decoded the same zero-free-parameter way, fits exactly on 35 (23%) — spanning weapons, vehicle parts, world props, and character meshes up to 5,323 vertices / 8,645 triangles.

Each single-mesh asset's trailing structure: a whole-file (not per-mesh) materials list — one byte count, then that many length-prefixed names — followed by a constant 20-byte tail.

Whole-model header: mesh-count field corrected

The field previously read as "mesh count" reads exactly 1 on every one of 755 sampled model entries and is most likely a different, real field by its RTTI name ("number of meshes before splitting by material"), not the true count. A real counter-example: one model reads 1 there but has 7 real sub-meshes, found by an unbiased full-file string scan. A second field further in the header is the true mesh count and is correct on every sample reached — but the exact byte-width rule that locates that second field corpus-wide is still unsolved (a simple fixed-6-byte-gap guess only works on 270 of 755 entries; two further concrete guesses were tried and explicitly rejected).

Multi-mesh packing (assets with more than one sub-mesh): the per-mesh materials footer found above does not repeat between meshes — consecutive meshes sit back-to-back with a short gap whose exact width is not yet reconciled (one data point requires an odd-byte gap, another fits a clean 4 bytes).

Skeleton — first, partial step

The same brute-force string-scanning technique used for mesh names/materials, applied to a character skeleton, recovers its full joint and facial-control name list for free: 211 plausible names on one sample — a complete, sane body-joint hierarchy down to individual finger phalanges, plus roughly 140 further facial/blend-shape control names. The binary header itself — true joint count, parent-index hierarchy, bind-pose transforms, and which of the 211 names are real skinning joints versus animation-only controls — is not decoded.

Other findings

A field that sits next to the model types in the type table and looked like it might be more mesh-format work turns out to be plain JSON text, not a binary container. Several other asset types (glass/foliage/particle models, animation, joint-conversion data) each open with a short text-like tag, not decoded further. Megatexture-bound models are not a separate format at all: identical geometry/UV/material decode to any other static mesh — only the pixel data behind the material is unavailable, a separate, pre-existing limitation.

Effort estimate for a Studio reader

  • Static models: half a day to a day — almost entirely integration, since the parser, material resolver, and validation discipline already exist and are proven on two maps.
  • Skinned models: materially de-risked this pass but not finished. Roughly one more focused session for the mesh-count/gap bookkeeping, a further short session for the skeleton header (joint names are already free), plus half a day to wire into the Studio — call it 2–3 days total, down from a prior estimate of about a work-week, since the single biggest unknown (the vertex/index layout) is now resolved.

Open questions

  • The exact corpus-wide rule that locates the true mesh-count field (two concrete guesses tried and rejected).
  • The exact multi-mesh inter-mesh gap width (4 bytes fits one sample, an odd count is structurally required for another).
  • Why roughly a third of the random sample does not fit the simple vertex/index template exactly — plausibly genuine per-vertex skin-weight data for true multi-joint meshes, but not confirmed.
  • The 12 unknown vertex bytes' and the per-mesh preamble's field-by-field meaning.
  • The skeleton's binary joint hierarchy (parent indices, bind-pose transforms).

No in-game test applies yet — the milestone that would need one (an animated model rendering plausibly in the Studio or map viewer) depends on finishing the mesh-count/gap bookkeeping above first.