-
Notifications
You must be signed in to change notification settings - Fork 113
max_geometry_formats
title: Max Geometry Formats description: published: true date: 2026-07-06T12:44:41.048Z tags: editor: markdown dateCreated: 2026-07-06T12:44:41.047Z
Scope: the custom payloads stored by the following scene entities inside a 3ds Max scene file: the Ryzom/NeL patch object (RPO) and Edit Patch modifier; Editable Poly object; Poly Select modifier; Edit Poly modifier; Editable Mesh object; Edit Normals modifier; shared paint-selection payloads; and eight material/texture types. The outer .max compound-file container, the scene reference graph, ParamBlock/ParamBlock2 encoding, and Mesh, PatchMesh, MNMesh, BitArray, Matrix3, Spline3D, named-set-list, and wide-string encodings are external formats referenced but not defined here.
All payloads live in a nestable tagged-chunk stream. Each chunk records a 16-bit ID and a byte length (framing is provided by the container; a reader can always determine a chunk's payload size and skip it). Chunks may nest. Chunk IDs are scoped to their owning object type and nesting level — identical numeric IDs recur with unrelated meanings; a reader must dispatch on (owner class, nesting context, ID), never on ID alone.
Two parser disciplines occur, and each stream below is labeled with one:
- ORDERED (peek-based): chunks must appear in exactly the documented order; each is individually optional, but an unexpected ID terminates the current phase. Extending such a stream is only safe by appending trailing chunks.
- DISPATCH (switch-loop): sibling chunks may appear in any order; unknown IDs are skipped silently. Safe to extend anywhere at that level.
All scalars are native little-endian x86/x64 Windows encodings, written raw with no alignment or endianness conversion:
| Type | Size | Notes |
|---|---|---|
int32 / BOOL / DWORD / uint32 / TimeValue
|
4 |
BOOL stores 0/1 |
uint16 |
2 | |
uint8 / bool8
|
1 | C++ bool
|
float32 |
4 | IEEE-754 |
Point3 |
12 | three consecutive float32 (x,y,z) |
| wide string | var | SDK wide-string chunk encoding (external) |
int and DWORD remain 4 bytes on 64-bit builds, so 32- and 64-bit writers agree. Some blocks are raw struct dumps and additionally depend on MSVC struct padding — flagged individually.
This is the format most relevant to exporting Ryzom Core assets.
Chunk 0x08FD (2301) "validity":
uint32 version ← format version of the following blob
<RPatchMesh blob> ← §A.2, raw sequential, inline
<PatchMesh> ← PatchMesh stream (external)
<Mesh> ← Mesh stream (external);
cached triangulation of the patch
Reader rule: if version is not a recognized value, skip the remainder of chunk 2301 (chunk framing bounds it) and treat the RPatchMesh state as defaults; the PatchMesh and Mesh streams still follow unconditionally.
Writers always emit the newest layout (v9). Readers must accept 1–9 and reject anything else.
v9 layout:
uint32 version
int32 patchCount
patchCount × PatchRecord:
int32 log2TilesU ← tile columns = 1 << log2TilesU
int32 log2TilesV ← tile rows = 1 << log2TilesV
int32 tileCount ← = (1<<log2TilesU) * (1<<log2TilesV)
tileCount × TileRecord:
uint16 tileNum
uint16 tileFlags
uint8 displaceNoise ← v9 only; see version table
3 × LayerRecord:
bool8 reserved ← always written 0; ignore on read
int32 tileId
int32 rotation
int32 colorCount ← = ((1<<log2TilesU)+1) * ((1<<log2TilesV)+1)
colorCount × uint32 vertexColor ← 0x00RRGGBB
4 × uint32 edgeFlags ← per patch edge (v7+; see table)
int32 vertexCount
vertexCount × BindRecord:
bool8 isBound
int32 bindType ← enum, see below
uint32 edgeIndex ← 0..3, edge of target patch
uint32 patchIndex ← target patch
uint32 before, before2 ← rebuildable cache indices
uint32 after, after2 ← "
uint32 tangent ← "
int32 bindType2 ← duplicate of bindType (written twice;
readers should read & discard)
uint32 primaryVertex ← primary vertex of the bind group
int32 tileTessLevel
bool8? modeTile ← width = declared member size; see A.3
bool8? keepMapping ← "
int32? transitionType ← WARNING: see A.4
int32? selLevel
Bind type enum: four values corresponding to bind fractions {0.25, 0.75, 0.50, single}; the enum ordering observed in the interpolation table is 25%, 75%, 50%, SINGLE = 0..3, but this must be confirmed against a sample file before relying on it. Width is that of a C enum (4 bytes).
Cache fields (before/after/tangent) are persisted but fully reconstructable from topology; an exporter may ignore them.
| Field group | v1–v2 | v3–v4 | v5–v6 | v7–v8 | v9 |
|---|---|---|---|---|---|
| Tile record | two int32 per tile, read and discarded; tiles empty |
uint16 num + 3 layers; flags forced 0; noise randomized on load |
+ uint16 flags
|
+ uint16 flags
|
+ uint8 noise
|
| Vertex colors | present, overwritten to white (≤v5) | same | v5 white / v6 kept | kept | kept |
| Edge flags | absent | absent | absent | present (4×uint32) |
present |
| Bind records | identical in all versions | ||||
| Trailer | tessLevel, modeTile, keepMapping | + transitionType (v4 only — see below) | ? | ? | + selLevel |
Loads of pre-v9 files are non-deterministic in the original tool (tile noise is randomized at load); an independent reader should just set noise = 0 or any fixed value.
The original reader consumes transitionType only when version == 4, while the writer emits it for every version. If the current write version is really 9, then in files as written the trailer bytes after keepMapping are: transitionType, selLevel — but a version-9-aware reader following the original logic would mis-assign them. Before finalizing your parser, hex-inspect a known v9 file: locate keepMapping and check whether one or two 4-byte fields precede end-of-chunk. Treat the trailer layout for v≥5 as empirically determined, and use the chunk length of the enclosing 0x08FD chunk to bound/validate the read.
| ID | Payload | Semantics |
|---|---|---|
| 0x1100 | empty | presence ⇒ named-selection sets need no fixup (newer files); absence ⇒ legacy fixup |
| 0x1001 | int32 |
subobject level (object/vertex/edge/patch/tile) |
| 0x1010 | BOOL |
display lattice |
| 0x1020 | BOOL |
display surface |
| 0x1040 |
int32 meshSteps; BOOL adaptive (always 0, reserved) |
viewport tessellation |
| 0x10E0 | int32 |
render steps; readers clamp to 0..100 else use 5 |
| 0x10F0 | BOOL |
show interior edges |
| 0x1090 / 0x10A0 / 0x10B0 | TessApprox (external SDK format) | view / production / displacement tessellation |
| 0x2000 | tileMode; tileLevel; keepMapping (member-sized raw fields) | Ryzom tiling globals |
| 0x2002 | includeMeshes | Ryzom |
| 0x2001 | transitionType | Ryzom |
| 0x10C0 |
BOOL viewTessNormals; BOOL prodTessNormals |
|
| 0x10D0 |
BOOL viewTessWeld; BOOL prodTessWeld |
|
| 0x1050 / 0x1060 / 0x1070 | container | named-set names for vertex / edge / patch level; contains repeated 0x1080 wide-string subchunks, one per set name |
| 0x1000 | int16 |
legacy read-only: old selection level; 1 → patch, 3 → vertex |
| 0x1030 | — | legacy read-only marker; obsolete-file indicator |
Note the 0x2000-range IDs here are unrelated to the 0x4000-range IDs in §B.2 despite identical meaning.
All wrapped in a single chunk 0x1000; its children (DISPATCH):
| ID | Payload |
|---|---|
| 0x1015 |
DWORD flags |
| 0x1020 |
int32 meshSteps; BOOL adaptive (0) |
| 0x1150 |
int32 renderSteps (clamped as above) |
| 0x1160 |
BOOL showInterior |
| 0x1070 / 0x1080 / 0x1090 | TessApprox view / prod / disp (external) |
| 0x1030 |
BOOL displaySurface; BOOL displayLattice |
| 0x1110 |
BOOL viewTessNormals; BOOL prodTessNormals |
| 0x1120 |
BOOL viewTessWeld; BOOL prodTessWeld |
| 0x4000 | tileMode; tileLevel; keepMapping |
| 0x4003 | includeMeshes |
| 0x4002 | transitionType |
| 0x1040 / 0x1050 / 0x1060 | named-set lists (vertex/edge/patch), external list format; only if non-empty |
| 0x1130 | vertex mapper — §B.3 |
| 0x1140 | PatchMesh (external) — the fully edited result patch |
| 0x4001 | RPatchMesh blob — the complete §A.2 format, nested here |
Legacy pre-R3 files instead contain an edit-history record list (per-operation records replayed at load). The record chunk IDs/payloads are not covered here; for asset extraction you can treat any file lacking 0x1140/0x4001 inside the local data as requiring the legacy path, which is out of scope. For exporting geometry, chunks 0x1140 + 0x4001 are the ones you want: they hold the final patch and its Ryzom tile/bind data.
One child chunk 0x1000:
int32 vertCount
vertCount × MapVertRecord ← raw struct dump
int32 vecCount
vecCount × MapVertRecord ← raw struct dump
MapVertRecord is, by usage: int32 currentIndex (−1 = unmapped), Point3 originalPos, Point3 delta, BOOL originalStored — nominally 32 bytes with no padding, but the on-disk size is compiler-defined and unversioned. Validate against chunk length: record size = (chunkLen − 8) / (vertCount + vecCount). This block only matters if you need to re-apply deltas against a changed base object; for straight export of the stored final patch it can be skipped.
A delta wrapper chunk 0x1000 contains three sibling chunks:
| ID | Payload |
|---|---|
| 0x1000 |
int32 n; n × Point3 — additive per-vertex position deltas |
| 0x1020 |
int32 n; n × int32 — per-vertex flag masks, applied by XOR (coplanar bit) |
| 0x1010 |
int32 n; n × Point3 — additive per-tangent-handle deltas |
Counts are independent and normally equal the patch's vertex/vertex/vector counts. This structure serves the legacy record system and is not needed for exporting current-format files.
Stream layout, exact order required:
[0x4039] paint-handler payload (§H.1) always present in current files
[0x403A] paint-host payload (§H.2) always present in current files
0–3 pairs, one per non-empty named-set level j ∈ {0=vertex,1=edge,2=face}:
[0x3003] int32 j
[0x3004] named-set list (external format) must immediately follow its 0x3003
[0x3002] int32 vertexControllerCount only if any animated vertices
[0x4038] DWORD subobjectLevel
<PolyObject base data> external; includes the MNMesh
Rules:
- Every chunk is optional (older files omit leading ones); order is mandatory. A 0x3003 not followed by 0x3004 ends named-set parsing.
- 0x3002 carries only a count; vertex controllers persist through the scene reference system, not this stream.
- Subobject level values: 0 object, 1 vertex, 2 edge, 3 border, 4 face, 5 element (inferred; the four base values are confirmed via the fallback mapping below, border/element by parallel with §D — verify against a sample).
- If 0x4038 is absent, derive level from the mesh's own stored level (object/vertex/edge/face; border and element are unrecoverable). If present, it is authoritative (border ⇒ mesh edge level, element ⇒ mesh face level).
- Current selection bit-arrays are not stored; rebuild them from per-component selection flags inside the mesh.
- The parameter block (soft selection, subdivision, cage colors, etc.) and point controllers are not in this stream.
Selection-level values (explicit): 0 object, 1 vertex, 2 edge, 3 border, 4 face, 5 element. Named-set levels: 0 vertex, 1 edge, 2 face; border shares edge sets, element shares face sets.
| ID | Payload |
|---|---|
| 0x0100 |
DWORD selectionLevel (0–5) |
| 0x0120 |
DWORD flags; bit 0x0001 = "show end result" toggle |
| 0x2805 | container: vertex-level set names |
| 0x2807 | container: edge-level set names |
| 0x2806 | container: face-level set names |
| 0x2848 | paint-handler payload (§H.1) |
⚠ Note the numeric inversion: face container (0x2806) < edge container (0x2807). Same inversion in §D.2.
Inside each name container (ORDERED pairing within a DISPATCH loop): repeated per set —
| ID | Payload |
|---|---|
| 0x2809 | wide string: set name; on read also assigns a provisional sequential ID (0,1,2,…) |
| 0x2810 |
DWORD set ID; overrides the provisional ID of the most recently read name |
A name chunk must precede its ID chunk; the ID chunk is per-set optional (legacy files keep sequential IDs).
| ID | Payload |
|---|---|
| 0x0200 | BitArray (external): vertex selection — always present |
| 0x0210 | BitArray: face selection — always present |
| 0x0220 | BitArray: edge selection — always present |
| 0x2845 | named-set list, vertex level (only if non-empty) |
| 0x2847 | named-set list, edge level (only if non-empty) |
| 0x2846 | named-set list, face level (only if non-empty) |
| 0x2849 | paint-host payload (§H.2) — always present |
Bit-array sizes are not stored against a mesh size; consumers resize to the incoming mesh preserving bits.
Name/ID split: set names live in the modifier stream, set bit-arrays live per node, joined by the DWORD ID — never by index or name. The two lists may drift (counts and order can differ); a reader must treat the union as truth with ID as the join key.
| ID | Payload |
|---|---|
| 0x0080 | modifier base data (external; includes the parameter block: animation, current-operation selector, slice plane, master point controller, per-vertex controllers) |
| 0x0100 | raw subobject level (same 6-level enum family as Part C) |
| 0x0110 | preserve-UVs per-map-channel bit set (external map-bit-array format) |
| 0x0120 | paint-handler state (external/§H family; format owned by the paint system) |
| 0x0138 |
int32 n — count of per-vertex point-controller range boundaries |
| 0x0130 | n × int32 — the boundaries (must follow 0x0138) |
| 0x0148 |
int32 n — count of node-name entries |
| 0x0140 | one wide string per chunk, repeated; associated to slots purely by encounter order after 0x0148 |
The last four are present only when animated vertex transforms exist.
| ID | Payload |
|---|---|
| 0x0200 / 0x0210 / 0x0220 | BitArray: vertex / edge / face selection |
| 0x0224 / 0x0228 | BitArray: vertex hide / face hide |
| 0x0240 | paint-host + named-set state (external paint format) |
| 0x0230 |
int32 operation ID — starts an operation-history triple
|
| 0x0234 | operation payload (§E.3); belongs to the immediately preceding 0x0230 |
| 0x0238 | operation local data (§E.4); optional; belongs to the same triple |
| 0x0260 |
int32 — ID of the operation currently being edited |
| 0x0268 | local data for that current operation |
Ordering rule: the triple 0x0230 → 0x0234 → [0x0238] must be adjacent and in that order, repeated once per committed operation in application order. 0x0260 must precede 0x0268. Unknown operation IDs are fatal to the original loader (no fallback); an independent exporter should record and skip unknown ops gracefully instead.
One wrapper chunk 0x0600 ("basics"), plus optional per-type sibling chunks. Inside 0x0600 (DISPATCH):
| ID | Payload |
|---|---|
| 0x0400 | BitArray — captured subobject selection |
| 0x0410 |
int32 paramID + raw parameter bytes; repeated once per parameter. Byte length is parameter-defined: 4 for int/float/world/angle, 12 for Point3, 64 for a raw 4×4×4-float matrix. |
| 0x0420 | Matrix3 (external) — transform (only if the op has one) |
| 0x0430 | Matrix3 — mod-context TM |
| 0x0440 | map-bit-array — preserve-map settings |
Unknown parameter IDs abort the load in the original, except five reserved IDs that are skipped. An independent reader should skip-with-warning using the chunk length.
Per-type sibling chunks next to 0x0600:
| Op type | ID | Payload |
|---|---|---|
| detach vertex / detach face | 0x0601 |
bool8 — detach-as-clone |
| remove edge | 0x1601 |
bool8 — ctrl-key (also remove verts) |
| extrude along spline | 0x0644 | Matrix3 — spline node TM at capture |
| extrude along spline | 0x0640 | Spline3D (external) — captured spline |
Extrude-along-spline also carries a world-to-object matrix as a parameter (0x0410 with 64-byte raw payload).
Each op type has its own child-chunk vocabulary. "count+array" = int32 n then n elements in the same chunk.
| Op | ID | Payload |
|---|---|---|
| create | 0x0600 | count+array of Point3 — new vertex positions |
| 0x0608 |
int32 — total new-face count |
|
| 0x0610 | repeated per face: int32 faceIndex, int32 degree, degree × int32 vertex indices |
|
| attach | 0x0E04 |
int32 n — number of attach slots |
| 0x0E08 |
int32 which — slot selector for the next mesh chunk |
|
| 0x0E00 | MNMesh (external) — attached mesh, into slot which
|
|
| 0x0E0C | n × int32 — per-attach material-ID modulus |
|
| cut | 0x0840 |
int32 n; n×int32 startLevel; n×int32 startIndex; n×Point3 start; n×Point3 end; n×Point3 normal |
| target weld (vert & edge) | 0x06C0 | count+array int32 — source indices |
| 0x06C4 | count+array int32 — target indices |
|
| create edge | 0x0640 | count+array int32 — flattened (v1,v2) pairs |
| edit triangulation | 0x0680 | count+array int32 — flattened diagonal pairs |
| turn edge | 0x06F0 | count+array int32 — diagonal indices |
| 0x06F2 | count+array int32 — parallel face indices |
|
| insert vertex (edge) | 0x1600 |
int32 n; n×int32 edge index; n×float32 parametric position |
| insert vertex (face) | 0x1608 |
int32 n — allocation count |
| 0x1610 | repeated: int32 which, int32 face, int32 numBary, numBary×float32
|
|
| hinge from edge | 0x32F0 |
int32 — hinge edge index |
| transform | 0x12C3 | int32 n |
| 0x12C4 | n × Point3 — per-vertex offsets (0x12C3 must precede) |
|
| 0x12C5 | wide string — node name |
ID collisions across ops (e.g. 0x0600, 0x0640) are safe because each payload is parsed only in its own op context.
Write order (each chunk optional on read; sequence fixed):
optional legacy block:
[0x3001] container — pre-R3 named sets:
[0x2845] vertex sets \ each contains repeated pairs:
[0x2846] face sets > [0x2849] BitArray (external)
[0x2847] edge sets / [0x2850] wide string name
(inner containers optional, any order; set/name pairing is by arrival order;
IDs are synthesized sequentially on migration: set i → ID i)
0–3 modern named-set pairs:
[0x3003] int32 levelIndex (0..2)
[0x3004] named-set list (external)
[0x3002] int32 vertexControllerCount
[0x4020] BOOL affectRegion
[0x4024] float32 falloff (written under a 4-byte int size; payload is float bits)
[0x4025] float32 pinch (−10..10)
[0x4026] float32 bubble (−10..10)
[0x402C] BOOL useEdgeDistance
[0x402D] int32 edgeIterations (1..999)
[0x4030] BOOL ignoreBackfacing (inverted sense vs. UI "affect backfacing")
[0x4034] DWORD objectFlags (bit meanings mostly external; one bit = show-end-result)
[0x4038] DWORD subobjectLevel (0 obj, 1 vert, 2 edge, 3 face, 4 polygon, 5 element)
[0x403B] int32 referenceLayoutVersion (always 1 in current files)
<TriObject base mesh data> external
Reader notes:
- A file contains either the legacy 0x3001 block or modern pairs, not both.
- If 0x4038 is absent, derive the level from the mesh's own level, mapping mesh-face-level to polygon (4), not face (3). If present, it is authoritative; face/polygon/element all map back to mesh face level.
- Reference layout: version 0 (chunk absent) means per-vertex controllers start at reference 0 and everything shifts up by one to make room for a master controller; version 1 means reference 0 is the master controller and vertices follow. Only relevant if you consume animated vertices via the reference system.
- Edge named-set bit-arrays are sized
numFaces × 3, indexedface*3 + side. Vertex sets sizenumVerts, face setsnumFaces. - The modern-pair loop tolerates a dangling 0x3003; the level index is not bounds-checked in the original (valid files use only 0–2).
Identity: Class ID (0x4AA52AE3, 0x35CA1CDE), object-space modifier. One reference (the parameter block; external encoding). Pblock parameters for completeness: select-by mode (int 0–3), ignore-backfacing (BOOL), show-handles (BOOL), display-length (float, default 10).
| ID | Payload |
|---|---|
| 0x0100 |
int32 — selection level (object / normal / vertex / edge / face; numeric values external, verify) |
| 0x0140 | container — named-set names; children (positional pairing): repeated { 0x0144 wide-string name → 0x0148 DWORD id }. Name must precede its ID; ID per-set optional (sequential provisional IDs otherwise). |
| ID | Payload |
|---|---|
| 0x0200 | named-set list (external) — per-set bit-arrays keyed by DWORD ID |
| 0x0240 | poly-mesh normal spec (external) — only for poly meshes |
| 0x0250 | tri-mesh normal spec (external) — only for tri meshes; mutually exclusive with 0x0240 |
| 0x0260 |
DWORD flags — only bit 0x08 ("tri-mesh mode") is durable; bits 0x01/0x02/0x04 are transient undo markers that may appear and should be ignored |
Cached meshes, validity intervals, and in-progress buffers are never persisted.
These payloads appear inside different container IDs per owner (Editable Poly: 0x4039/0x403A; Poly Select: 0x2848/0x2849) but have identical contents. Their internal chunk IDs are 0x0000 and 0x0001 (sequential-from-zero enums), safe only because of nesting.
| ID | Payload |
|---|---|
| 0x0000 |
BOOL — painted soft-selection active |
| 0x0001 |
BOOL — paint deformation active |
Both optional; default false.
| ID | Payload |
|---|---|
| 0x0000 |
float32[N] — soft-selection weights, one per vertex in vertex order; N = chunkLength / 4 (no explicit count) |
| 0x0001 |
Point3[M] — deformation offsets (object-space deltas); M = chunkLength / 12
|
Each written only if its count > 0. Weights nominally 0..1. Writers emit exactly the logical count, which may be smaller than a runtime buffer.
Universal rule: chunk 0x4000 in every material/texmap wraps the shared material-base payload (name, G-buffer channel, base flags — external format). All loaders here are DISPATCH; unknown IDs skip silently. Three payload kinds recur:
- data — carries bytes;
- presence-only — empty; existence is the boolean;
- marker — empty; selects a load path / migration.
Current files write only 0x4000; everything else lives in a ParamBlock2 (external). Legacy read-only chunks:
| ID | Kind | Meaning |
|---|---|---|
| 0x5002 | data int32
|
filter type |
| 0x5003 | data int32
|
alpha source; clamp to {0 file, 2 rgb, 3 none}, fallback 0. Note: the ParamBlock2 layer re-encodes these as 0/1/2. |
| 0x5004 | data wstring | legacy bitmap filename |
| 0x5010 | data | bitmap-info block (external); start/end frame forced to "undefined" after read |
| 0x5011 | data TimeValue
|
start time |
| 0x5012 | data float32
|
playback rate |
| 0x5013 | presence | alpha-as-mono = true |
| 0x5014 | data int32
|
end condition |
| 0x5016 | presence | alpha-as-RGB = true |
| 0x5021 | presence | very-old-file marker |
| 0x5030 | presence | premultiplied alpha = false |
| 0x5040 | presence | apply crop = true |
| 0x5050 | presence | place-image mode = true |
| 0x5060 | presence | jitter placement = true |
| 0x5001, 0x5022 | — | defined but never handled; dead |
Any 0x50xx chunk ⇒ legacy file ⇒ values migrate into the param block after load.
| ID | Kind | Payload |
|---|---|---|
| 0x4000 | data | material base |
| 0x5000 | data ULONG
|
flags |
| 0x5004 | data int32
|
shading model; clamp, fallback Phong |
| 0x5300 | data int32
|
version (current 9) |
| 0x5002 | data ULONG
|
per-map enable bitmask (bit i = map i on) |
| 0x5400 | presence | dim-reflect = true |
| 0x5100–0x510A | data float32
|
per-map amount (one chunk per map slot; written only when ≠ 1.0) |
Files with version < 9 require migration (map-amount blending, soften level) — for asset extraction, reading the values as-is is usually sufficient.
Save: 0x4000 base; 0x5300 int32 version (current 13); 0x5600 exposure-control payload (external). Load additionally accepts all §I.2 legacy chunks (0x5000/0x5004/0x5002/0x5400). Reserved IDs: 0x5500, 0x5510, 0x5511. Versions ≤ 10 stored parameters in an old flat block redistributed at load; 11–12 had a bump-scale fixup. Reference-slot order (for scene-graph remapping): old-pblock, texmaps, shader, shader-pblock, extended-pblock, sampling-pblock, maps-pblock, dynamics-pblock, sampler.
Texmap sub-container vocabulary (owned by the maps container, referenced by both variants): 0x5002 presence ⇒ obsolete file; 0x5003 data ULONG enable bitmask; 0x5100–0x510A per-map amount.
New namespace: 0x1000 = header (material base), 0x1001 = int32 fileVersion. Compatibility namespace: 0x4000 = old header, 0x0010 = int32 legacy sub-tex count. File versions: 0 = 2008-era, 1 = beta, 2 = 2009+. Collision hazard: new header 0x1000 equals the old map-offset base, and old header 0x4000 equals the universal base-header ID; disambiguation is stateful (first header chunk seen wins, and old/new headers are mutually exclusive). Old versions store maps/masks as direct references restructured into a param block on load; opacity was once integer, now float ×1.
0x4000 base; 0x1010 int32 version (current 2). Version 1 files carry one extra leading slot in each of three tab parameters (type/enable/amount) that must be dropped (delete index 0, shrinking from max-slots to max-slots−1). ID 0x1000 reserved (map-off range).
| ID | Kind | Payload |
|---|---|---|
| 0x4000 | data | base |
| 0x4005 | marker | "new sparse array" layout; implies 0x4003 semantics too |
| 0x4002 | data int32
|
sub-material count; sizes name/enable tables |
| 0x4001 | marker | obsolete layout; implies 0x4002 follows semantically (old writer fall-through) |
| 0x4010 | data | name table (external) |
| 0x4003 | marker | "ParamBlock2 present" |
| 0x1000–0x1FFF | presence range |
id − 0x1000 = sub-material index whose enable flag is FALSE |
Discriminators: no 0x4003 ⇒ oldest layout; no 0x4005 ⇒ old dense-array layout; both trigger table rebuild/reindex at load.
0x4000 base; 0x1010 marker "ParamBlock2 present". Legacy: 0x1000/0x1001 presence ⇒ facing/back material disabled; old files also rescale translucency ×100 into the param block.
0x4000 base. 0x5000 (presence: use-UVW) is written by old files but ignored on read in current code — state lives in the param block.
| ID | Distinct meanings |
|---|---|
| 0x1000 | multi map-off base / double-sided material-off base / composite-map new header / composite-material map-off / patch local-data wrapper / vertex-mapper data / point-delta table … |
| 0x1010 | double-sided marker / composite-material version |
| 0x4000 | universal material header and composite-map legacy header |
| 0x5000 | std-material flags / vertex-color use-UVW |
| 0x5002 | std-material map bitmask / texmap-container obsolete marker |