Skip to content

Liveries and vinyl designs

ForzaTechGameplay dataApplies toFH6

A livery is a tree of vinyl layers (a shape, a colour, a transform) grouped per region of the car body and composited into one 2,048 × 1,024 canvas that the paint shader samples. The saved designs the platform keeps for the player are plain, unencrypted containers. The layer fields (position, scale, rotation, colour, mask flag) and shape ids 101 (rectangle) and 102 (ellipse) with the scale divisors 127 and 63 are published by the ForzaDesigner6 research. Sourced · ForzaDesigner6 They match the file fields below; the file layout itself (container, grammar, tail, decal flag, shape-id rule) is not in either source. Observed

Every file of the shipped livery and of every plain design container of one player’s save (69 Livery_, 4 BaseLivery_, 7 LayerGroup_: 206,102 layers in all) parses to its last byte with the grammar below, and the tail’s per-section layer counts equal the parsed counts on all 73 liveries. Observed

media\Livery\Vinyls.zip 1,480 vinyl shapes <G>_<NN>.modelbin (2-D meshes), groups A B C D E F G H I K KK L LL M MM N NN O OO P PP Q QQ R RR S SS T TT U UU V VV WW XX
(no J, W, X); U and V hold 80 (U_01..80, V_01..40 + V_51..90), the others 40. K..XX are fonts (mesh names Text01 / Text001, NN = letter index: a = 1)
media\Livery\Decals.zip 270 decals: decalNNN.modelbin + Textures\decalNNN.swatchbin (BC7 sRGB)
media\Livery\DecalsHiRes.zip 188 higher-resolution decal textures
media\Livery\EntitlementLiveries.zip shipped designs: <name>\header, C_livery, bigThumb.webp (one: se_supra)
media\Cars\<CAR>.zip LiveryMasks\ Masks.xml + front/back/top/left/right/glass_*.swatchbin (2048×1024 BC4 UNORM)
media\ShadersDXIL.zip LiveryModel{.vshader,_OpaqueOnly,_NonOpaqueOnly,_OpaqueNonOpaque}.pshader, LiveryBlitMask,
LiveryBlitSectionToLivery, LiveryBlitGroupToFinal(_Node), ...
media\Rendering.zip Livery.xml method 22 (deflate + TFIT): the livery render settings, readable with the keys

The saved designs sit in the platform’s game-save folder as one directory per container, one file per blob (Saves and tunes): Livery_<carId>_<yyyymmddhhmmss> (C_livery, header, bigThumb.webp rendered by the game), BaseLivery_<carId>_m0_<time>, and LayerGroup_0000_<time> (C_group, header, thumb.webp, a saved vinyl group). SoulBoundLivery_* and User_* are TFIT-protected. Observed

C_livery and C_group are u32 compressedSize, u32 size, zlib stream (compressed size + 8 = file size; inflated length = size). Observed header is little-endian with UTF-16LE strings (u32 count plus code units): Observed

Livery headervariableFH6100% known
ProvenObservedSourcedInferredUnknownNot described
OffsetSizeTypeFieldConfidenceNotes
0x04u32 = 7versionObserved
0x4variablestr16titleObserved
nextvariablestr16descriptionObserved
next16SYSTEMTIMEtimeObservedall zero in the shipped design
next4u32kindObserved3 livery, 2 group
next8u64creator idObserved
nextvariablestr16creatorObserved
nextvariablebytesrestUnknownu32 1 and GUIDs
0x00 'vlrc' u32 2 (version) u32 u32 u32 carId (= the carbin car id) 6 bytes (byte 0x14 = 0..9, not decoded)
'yrvl' u32 0x13 19 bytes (contains the creator id; not decoded)
'gyvl' 11 root groups, one per Masks.xml region in this order:
Front Back Top Left Right Wing Glass_Front Glass_Back Glass_Top Glass_Left Glass_Right
tail 'yrvl' | 12 × u32 (layers in each of the 11 sections, then 0)
'yrvl' | u8 0 | u8 2 | u32 count | u32 0 | count × paint-slot record (27 B)
u32 m | m × u64 slot hash | 'yrvl' | u32

A group is recursive: f32 x, f32 y, f32 scale, f32 rotation (deg) | u8 flags | [f32 scaleY if flags & 0x10, else scaleY = scale] | u16 n | u32 k = ceil(n/8) | k bytes (bit i, LSB first, set = child i is a group) | [u8 extra (0 or 1, not decoded) if flags & 0x20], then n children in draw order (group or layer). Flags seen: 00, 20, 30, 60, 70; 0x40 has no payload and is set on groups that hold mask-flag layers (not all of them). Observed

Livery layer32 bytesFH6100% known
ProvenObservedSourcedInferredUnknownNot described
OffsetSizeTypeFieldConfidenceNotes
0x01u8 = 2tagObserved
0x12u16shapeObservedbit 15 clear = vinyl shape id; set = decal id (bits 0 to 14)
0x34f32rotationObserveddegrees
0x78f32[2]position x, yObserved
0xF8f32[2]scale x, yObserved
0x174f32skewObserved
0x1B4u8[4]colourObservedB, G, R, A as stored; alpha 0 to 255
0x1F1u8maskObserved0 or 1
Paint-slot record27 bytesFH6100% known
ProvenObservedSourcedInferredUnknownNot described
OffsetSizeTypeFieldConfidenceNotes
0x08u64slot hashObservedFNV-1a-64 of the carbin material-slot name; 0xF7DBE8A7C839A675 = Body
0x81u8 = 2twoObserved
0x91u8has colourObserved
0xA4u8[4]colourObservedB, G, R, A
0xE5u8[5]zeroObserved
0x134i32paint typeObserved3 or −1; the index → file table is not readable
0x174i32flagObserved0 or 1

A design’s colour for a slot is e.g. “959 Gulf” Body = #BCE3EB, “Baby blue” #0085FF; designs with no colour record keep the car’s own paint (the garage car’s colour is not in the design). Observed

'gyvl' | 9 bytes (u32 1, u32 0, u8 3 on all 7) | one group without its 0x20 byte | u8 (that byte, stored last). The children follow the group grammar. Observed

  • Vinyl id = 100 × G + n → Vinyls.zip entry <group>_<nn>.modelbin, n = the file number. The id → group table is in no readable file. Confirmed by comparison with the game’s own thumbnails: G 1 to 9 are A to I (101 = A_01 square, 102 = A_02 circle, matching the published 101 and 102), and G 21 = U (“Super Wheelspin” draws its sunburst rays and the black plates behind its text with 2110 = U_10, a parallelogram; “Top Replica!” its Subaru stars with four 2104 = U_04). Observed
  • G 19 and G 35 are fonts with n = letter (a = 1): 19 spells “rallyemontecarlo”, 35 “R BURNS” and “R REID”. Which of the 23 font groups each id names is not confirmed. Observed
  • Over all 206,102 layers, G 10, 11, 12, 25, 32 and 33 never occur and ids reach G = 38 (36 to 38: 622 layers), so the table has at least 38 entries for 35 groups. The “G-th name of the ordinal-sorted list” rule is disproven for 21 (it gives a font) and unconfirmed for the other groups. Ids with G = 36, 37, 38 and ids 2000 and 3000 (n = 0) have no file under that rule. Observed
  • A decal id d → Decals.zip decal<d, 3 digits>.modelbin + Textures/decal<d>.swatchbin (se_supra uses decal364). Observed
  • Mesh: positions after the mesh’s position scale and offset, x and y used (z = 0), no axis change; COLOR0.a is the per-vertex alpha (anti-aliased edges); decal uv = TEXCOORD0 after the mesh uv transform; A_01 spans ±0.25. Observed

Read from the livery shaders. LiveryModel.vshader: p = (POSITION · positionScale + positionOffset) × element 3×4 matrix × 4×4 view-projection; uv = TEXCOORD0 · scale + offset; COLOR0.a passes through. LiveryModel_*.pshader: colour = constant buffer rgb, or the decal texel when the decal switch is set (the layer rgb is then unused); α = cb.a · COLOR0.a (· texel α); output (colour·α, α), premultiplied. _OpaqueOnly discards α < 1, _NonOpaqueOnly discards α = 0 and α = 1 (two passes, equivalent to drawing in order when the opaque pass writes depth by draw order). LiveryBlitMask writes 1 where mask.r ≥ 0.001 (the region clip). LiveryBlitGroupToFinal_Node: the group target is a Texture2DMS; the resolve averages all samples. LiveryBlitGroupToFinal and LiveryBlitSectionToLivery: result = lerp(dst, cb1.rgb, t.a · cb1.w) when cb1.w > 0, else a copy. Observed

The node and region transforms, confirmed by comparing rendered canvases with the game’s own thumbnails of “7 Eleven X Forza” (se_supra), “959 Gulf”, BRAT “199 Ghymkhana”, and the Impreza “Top Replica!” and “Super Wheelspin”: Observed

node(x, y, sx, sy, rot, skew) = translate(x, y) · rotate(rot, CCW, y up) · shear(x += skew·y) · scale(sx, sy)
layer to region: root group · … · group · layer, shape vertex × 254 (the 127 / 63 divisors: A_01 full width = 127 at scale 1)
region to canvas: X = xorigin + x·xScale, Y = yorigin + y·yScale; pixel = (1024 + X, 512 − Y) (Masks.xml)
draw: layers in file order, pixel-centre coverage, premultiplied "over"; pixels with mask.r < 0.001 skipped
mask-flag layer: erases its group's earlier layers (dst × (1 − α)); the group is composited "over" its parent colour
colour bytes are sRGB-decoded to linear (decals are BC7 sRGB)

The mask-layer erase rule (CPU blend state) is checked on thumbnails only. Observed

In car_automotive_paint (Car materials): with UserLiverySwitch (cb0[27].x) and cb11[0].x ≤ −1, L = sample(canvas, (TEXCOORD3.u × 0.5, TEXCOORD3.v)) (× 0.5 when cb4[29].y ≠ 0, the car paint case; established by overlaying UV3 on the masks), L.rgb /= L.a, paint = lerp(paint, L, L.a) after the colour flop, FlakeCoverage' = FlakeCoverage · (1 − L.a) and FlakeRoughness' = lerp(FlakeRoughness, 0.4, L.a); the coat roughness is lerped by L.a towards a term with the run-time cb4[62].y. So the livery replaces the flakes under it as well as the paint colour; the se_supra and 959 renders go from washed-out to matching once it is applied. Observed UniqueLiverySwitch (cb0[26].w) lerps the paint to the material’s BaseColorAlpha (a per-car livery texture) by its alpha mask, not a design. UserColorOverride (cb0[26].x) makes PaintColor and FlakeColor both UserColor0 (cb0[2]). The glass families sample only the canvas alpha; the glass livery colour path was not traced. Observed

What do the unconfirmed livery fields mean?

The vlrc +0x14 byte, the 19-byte yrvl block, the 0x20 extra byte of a group, the group-to-shape table for vinyl groups other than A to I and U, and the paint type values 3 and −1 are read but not interpreted.

Sources

  • ForzaDesigner6 — MIT. Published in-memory layer semantics (position, scale, rotation, colour, mask flag; shape 101 rectangle, 102 ellipse; scale divisors 127 and 63)
  • forza-painter-fh6 — MIT. The research ForzaDesigner6 credits