16. Clothing DLCs (add-on, freemode)
Derived 2026-08-04/05 from in-game-running community add-on packs (mpClothes-style) cross-checked
against vanilla mpbeach/mpbusiness2, with byte-level round-trips through CodeWalker.Core.
✅ tier: the structure below matches packs verified running in-game on this machine.
Pack shape
dlc.rpf (OPEN encryption - no keys involved)
├─ content.xml SHOP_PED_APPAREL_META_FILE + RPF_FILE entries,
│ both enabled by ONE <NAME>_GEN changeset
├─ setup2.xml EXTRACONTENT_COMPAT_PACK, GROUP_STARTUP, order 999,
│ isLevelPack=false, deviceName dlc_<name>
├─ common/data/mp_m_freemode_01_<name>.meta ShopPedApparel
└─ x64/models/cdimages/<name>_male.rpf (OPEN child rpf)
├─ mp_m_freemode_01_<name>.ymt CPedVariationInfo
└─ mp_m_freemode_01_<name>/ the drawables + textures
Female is symmetric: mp_f_freemode_01, <name>_female.rpf,
eCharacter SCR_CHAR_MULTIPLAYER_F (male: SCR_CHAR_MULTIPLAYER).
An empty <creatureMetaData /> is valid: vanilla mpbusiness2 ships one;
community packs often carry a dangling reference, which the game tolerates. Empty
pedOutfits/pedComponents/pedProps lists are valid too.
File naming grammar
| Kind | Pattern | Example |
|---|---|---|
| Component drawable | <comp>_<nnn>_<flag>.ydd | jbib_000_u.ydd |
| Component texture | <comp>_diff_<nnn>_<letter>_<race>.ytd | jbib_diff_000_a_uni.ytd |
| Prop drawable | p_<anchor>_<nnn>.ydd | p_head_000.ydd |
| Prop texture | p_<anchor>_diff_<nnn>_<letter>.ytd (no race suffix) | p_head_diff_000_a.ytd |
The 12 components, in PV slot order: head berd hair uppr lowr hand feet teef accs task decl jbib.
Prop anchors, in enum order (= anchorId): head eyes ears mouth lhand rhand lwrist rwrist hip lfoot rfoot.
Numbering must be dense from 000 per component / per anchor.
Naming law: in 400 vanilla clothing ydds + 400 ytds sampled, the internal dictionary / texture names matched the filename in 100% of files. The game keys clothing by filename; renaming files without re-keying internals is universal community practice and what running packs do.
CPedVariationInfo (.ymt)
Lives inside the cdimages rpf, next to the ped folder. Buildable from XML via
XmlMeta.GetData(doc, MetaFormat.RSC, ""), which round-trips XML-exact against real packs, and
the output carries the RSC7 header RpfFile.CreateFile needs to classify it as a resource.
availComp: 12 slots in the component order above; value = dense index intoaComponentData3for comps present,255otherwise.- Per drawable (
CPVDrawblData):propMask 1,numAlternatives 0, oneaTexDataitem per texture (texId 0,distribution 255),clothData.ownsCloth= whether a.yldships. compInfos: oneCComponentInfoper drawable (pedXml_compIdx= slot,pedXml_drawblIdx= number, audio idsnone, zeros elsewhere).dlcName= JOAAT of the pack name.
propInfo (props in the same ymt)
- Per prop (
CPedPropMetaData):texDataitems with incrementingtexId0,1,2… (unlike components),anchorId= anchor enum index,propId= per-anchor ordinal. propFlags 65569for head props (hat flags, plusexpressionMods "-0.5 0 0 0 0"for the hair squash);65536otherwise;renderFlags PRF_ALPHAon eyes (glasses).aAnchors: per anchor with props,<props>= space-separated per-prop texture counts,<anchor>= enum name (ANCHOR_HEAD, …).
Textures & UVs
- Clothing diffuse
.ytd: one DXT5 texture, full mip chain to 1×1 (≥9 levels → sources ≥256, power-of-two, ≤4096). - Clothing UVs are commonly negative (V in [-1,0]) and rely on GPU wrap addressing. D3D and CodeWalker wrap by default. A renderer that clamps (three.js default) smears the edge texels across the mesh; set repeat wrapping.
- DXT encoder gotcha: naive 565 truncation green-shifts dark greys ((4,4,4) → (0,4,0)). Round-to-nearest with a neutrality bias on near-achromatic colors fixes it.
Limits
The engine loads a fixed number of variation .ymt files per gender: about 80
before b2612 (mpg9ec), ~100 after. The ceiling matters less than the slots Rockstar has
left, which shrinks with every Online DLC:
| Build | DLC | Free ymt slots (per gender) |
|---|---|---|
| b2612 | mpg9ec | ~20 |
| b3095 | mp2023_02 | ~10 |
| b3407 | mp2024_02 | ~6 |
| b3570 | mp2025_01 | ~5 |
| b3717 | mp2025_02 | ~4 |
| b3889 | mp2026_01 | ~3 |
"Three free slots" means three for male and three for female. The budget is per gender,
not shared. A pack with male and female clothing is two ymts; add high heels or head-prop hair
modifiers and it also emits a shared mp_creaturemetadata_<name>.ymt, making three.
If a pack uses cut-hairs or remove-hairs head props and no creaturemetadata ymt was
built, the hair modifier silently does nothing.
- 128 drawables per component per gender is a legacy synchronization boundary, not a file-format cap. FiveM builds carrying the component-sync patch accept component IDs 0-255 (client and server both need it); props keep the 128 advisory because that patch doesn't cover prop sync. Past 255 is the real collection boundary.
- 255 global prop index: older runtimes hold part of the global prop index in 8 bits, shared with base-game and DLC props. Past it, addon props turn invisible rather than crash. Patched on FiveM; singleplayer needs the pedprop limit-adjuster ASI. It is not a 255-item cap inside one collection.
- High heels above global component index 255 hit the same truncation class in the component-expression lookup, so heel height silently stops applying. Patched on FiveM.
TxdStore Pool Full, Size == 95500duringINIT_SESSION for CExtraContentWrappermeans too many.ytdfiles are loading across all mods, not just clothing. Tattoo packs weigh heavily since they're ytd-based.
Limit figures in this subsection come from the Durty Cloth Tool documentation (read 2026-08-07), not from our own testing, unlike the pack structure above, which is byte-verified here. Rockstar changes these every update; check the source for current builds.
The DLC Builder's Variant Splitter and Add-on Pack tools implement all of the above client-side.
17. Merging DLC packs
Derived 2026-08-07 from two in-the-wild community packs (a helmet prop pack and a tattoo pack) merged and byte-verified through CodeWalker.Core. ✅ tier for structure; the in-game load test of a merged pack is still open.
Why merge at all
The usual reason isn't saving dlcpacks slots. It's removing conflicts. Add-on packs
patch vanilla archives by name. If one pack overlays mpstunt for clothes and another
overlays it for tattoos, installing both means only one loads; the other silently doesn't appear.
Combining them into a single pack lets both sets of files coexist and load together.
Community build tools also tend to emit the same device name and changeset name for every
pack they produce (dlc_pck / pck_AUTOGEN is common), so two such packs collide
on identity as well as content.
What a merge has to do
- Copy file payloads verbatim. Only
content.xmlandsetup2.xmlare regenerated. - Rewrite the device prefix. Every
dlc_<old>:/path in dataFiles and changesets becomesdlc_<merged>:/. - Keep one changeset per source pack, renamed so two
pck_AUTOGENs don't collide. A duplicate changeset name means one pack's enable list is shadowed and its content never loads. - Union colliding nested rpfs. If both packs ship
mpstunt_ped_mp_overlay_txds.rpf, rebuild it holding both packs' entries. Picking a winner would reproduce the very conflict the merge is meant to fix. - Union the metadata that declares them. Unioning texture archives isn't enough
if the file listing the entries gets overwritten. A pack overriding
common/data/effects/peds/multiplayer_overlays.xmlships the entire vanilla<presets>list plus its own additions, so two tattoo packs reconcile by unioning those items keyed on<nameHash>: shared vanilla entries dedupe, each pack's custom tattoos survive. Only a genuine same-nameHashclash needs a winner. - Re-add the RSC7 header when copying resources. Extracting an
RpfResourceFileEntryyields headerless pages; writing those back produces a binary entry, and the game dies withERR_SYS_INVALIDRESOURCE_5at stream time even though the payload bytes are identical. - Leave nested rpfs uncompressed. The game mounts them by seeking to their offset, so a deflated child archive won't mount.
Things that look wrong but aren't
- The output is much smaller than the inputs. RPF doesn't reclaim space when entries are replaced or deleted. Freed blocks stay allocated until the archive is defragmented, so packs edited in OpenIV carry dead space. One real 391 MB tattoo pack held ~200 KB of live content; 547 MB of input merged down to 91 MB. Writing a fresh entry table reclaims the slack.
- A
dataFilesentry pointing at a file the pack doesn't ship. Real packs do this and leave itdisabled; it's inert. Only an enabled missing file is a problem.
What merging does not fix
Merging clothing packs saves a dlcpacks slot, not a ymt slot: each source pack's
.ymt is carried across as-is, so two merged clothing packs still consume two ymt slots per
gender. Given ~3 free slots on b3889, that ceiling is usually the real constraint. Getting under it
needs true CPedVariationInfo merging with drawable renumbering, which is a different job.
List-shaped metadata is only unioned for formats a merger actually understands. Ours currently
knows PedDecorationCollection (tattoos/decorations) and nothing else. Anything
unrecognised has to fall back to last-writer-wins and say so, rather than guess at a structure it
can't validate and silently corrupt it.
The DLC Builder's DLC Merger implements the rules above client-side.
27. CodeWalker .ydd.xml (the clothing import path)
To get a new skinned clothing mesh into the game, emit CodeWalker's XML form of a DrawableDictionary and import it (CodeWalker or Sollumz → .ydd). The schema below imports cleanly and was reverse-engineered from a real CodeWalker export of a freemode jacket (jbib_000_u). The XML has no published spec, so the only true validation is a CodeWalker import.
Top-level structure
<?xml version="1.0" encoding="UTF-8"?>
<DrawableDictionary>
<Item> <!-- one per drawable; clothing = 1 -->
<Name>jbib_000_u</Name>
<BoundingSphereCenter x=".." y=".." z=".." />
<BoundingSphereRadius value=".." />
<BoundingBoxMin x=".." y=".." z=".." />
<BoundingBoxMax x=".." y=".." z=".." />
<LodDistHigh value="9998" /> <LodDistMed value="9998" />
<LodDistLow value="9998" /> <LodDistVlow value="9998" />
<FlagsHigh value="1" /> <FlagsMed value="0" /> <FlagsLow value="0" /> <FlagsVlow value="0" />
<ShaderGroup> ... </ShaderGroup>
<DrawableModelsHigh> ... </DrawableModelsHigh> <!-- High alone imports and previews -->
<Lights />
</Item>
</DrawableDictionary>
Bounds are computed from all vertex positions. No <Skeleton> block for the common clothing case: the garment binds to the ped's external skeleton (from mp_*_freemode_01.yft), and the rig lives entirely in per-geometry <BoneIDs> plus per-vertex BlendIndices. A dictionary drawable can embed its own skeleton (some ped components do), but a writer targeting freemode clothing omits it. RAGE caps skin influences at 4 per vertex.
ShaderGroup
A minimal ped shader imports: <Name>ped</Name> <FileName>ped.sps</FileName> <RenderBucket value="0" />, texture parameters DiffuseSampler / BumpSampler / SpecSampler (plus VolumeSampler), and the usual vector parameters (bumpiness, specularIntensityMult, specularFalloffMult, specularFresnel, umGlobalParams, envEffFatThickness, …). The embedded <TextureDictionary> can carry normal/spec entries, or be an empty <TextureDictionary /> for a first pass with the diffuse shipped in a separate .ytd.
Model and geometry
<DrawableModelsHigh>
<Item>
<RenderMask value="255" /> <Flags value="1" />
<HasSkin value="1" /> <!-- REQUIRED for clothing -->
<BoneIndex value="0" /> <Unknown1 value="128" />
<Geometries>
<Item>
<ShaderIndex value="0" />
<BoundingBoxMin x=".." y=".." z=".." w="0" />
<BoundingBoxMax x=".." y=".." z=".." w="0" />
<BoneIDs>0, 1, 2, ... 127</BoneIDs> <!-- remap table; BlendIndices index INTO it -->
<VertexBuffer>
<Flags value="0" />
<Layout type="GTAV1">
<Position /> <BlendWeights /> <BlendIndices /> <Normal />
<Colour0 /> <Colour1 /> <TexCoord0 /> <TexCoord1 /> <Tangent />
</Layout>
<Data2> ...one row per vertex, columns in Layout order... </Data2>
</VertexBuffer>
<IndexBuffer><Data> ...triangle indices, whitespace-separated... </Data></IndexBuffer>
</Item>
</Geometries>
</Item>
</DrawableModelsHigh>
Two details are easy to miss. Skinned meshes put vertex rows in <Data2>, not <Data>, and <BoneIDs> is a remap table: BlendIndices values index into that list, not into the skeleton directly (an identity list 0..N works).
GTAV1 vertex row
Px Py Pz W0 W1 W2 W3 I0 I1 I2 I3 Nx Ny Nz C0r C0g C0b C0a C1r C1g C1b C1a U0 V0 U1 V1 Tx Ty Tz Tw
| Column group | Type | Notes |
|---|---|---|
| Position | float3 | model space |
| BlendWeights | 4 × byte | must sum to 255 |
| BlendIndices | 4 × byte | index into <BoneIDs> |
| Normal | float3 | unit length |
| Colour0 / Colour1 | 4 × byte each | observed defaults on a real jacket: 255 255 255 255 and 255 255 255 0; these feed lighting/wind, replicate observed values unless you know better |
| TexCoord0 | float2 | V stored negated (e.g. -0.7578), the same convention as the binary (§18) |
| TexCoord1 | float2 | often a copy of TexCoord0, or zeros |
| Tangent | float4 | xyz + handedness w (usually 1); compute from positions + UVs + normals if the source mesh has none |
A converter starting from a bare mesh (positions/UVs/indices/weights) must synthesize the rest: normals (from the source or per-face-averaged), tangents (per-triangle accumulation over UVs, normalized, w = handedness), Colour0/1 defaults as above, TexCoord1 = TexCoord0, BoneIDs = identity. High-LOD-only imports and previews fine; in-game use wants Med/Low too.
Provenance: schema read from a real CodeWalker export of a running freemode jacket and cross-checked against Sollumz's format handling; import-validated through the CodeWalker GUI, which remains the only authoritative acceptance test.