# AlternativeFuta: DirectSkin custom hair fix A patch for **RunRudolf.AlternativeFuta v14** (`AlternativeFuta.cs`, "Version 2026-09-15") that lets custom hair using the **DirectSkin** scalp load on AlternativeFuta characters. ## What's in this archive | File | Purpose | |---|---| | `AlternativeFuta-DirectSkinHair.patch` | Unified diff against v14. It only adds code: 381 lines, none removed. | | `Custom/Scripts/RunRudolf/AlternativeFuta/AlternativeFuta.cs` | The same file with the patch already applied, for dropping in or diffing. | | `README.md` | This file. | ## The problem Hair saved from VaM's Hair Creator with the `DirectSkinFemale` or `DirectSkinMale` scalp stores one strand slot per base vertex of the skin it was made on: | Skin | Base vertices | |---|---| | Stock female | 23,008 | | AlternativeFuta (female plus male graft) | 24,333 | On an AlternativeFuta character: 1. **Hair made on the futa skin** (for example testicle or pubic hair painted onto the AltFuta anatomy) saves correctly but fails to load: ``` Exception while loading Custom/Hair/.../X.vab System.Exception: Binary hair data not compatible with chosen scalp mesh ``` Every later load of that item then fails with: ``` Exception while loading Custom/Hair/.../X.vab System.IndexOutOfRangeException: Array index is out of range. at GPUTools.Hair.Scripts.Geometry.Create.RuntimeHairGeometryCreator.LoadFromBinaryReader (System.IO.BinaryReader binReader) at MeshVR.DAZDynamic.Load (Boolean createWithExclude) ``` 2. **Hair made on the stock female skin**, including published DirectSkin body fur, tails and similar, always fails with `Binary hair data not compatible with chosen scalp mesh`. ## Cause - **VaM only resizes the strand table in one place.** `RuntimeHairGeometryCreator` sizes the table when its `ScalpProvider` object is assigned. - **The DirectSkin providers never change object.** They are `PreCalcMeshProviderHolder`s wrapped around the atom's *current* skin. `DAZHairGroup.Connect()` repoints the holder at a new skin but never resizes the table. - **AltFuta connects hair too early.** `AddMaleGenitalia()` selects the new character before the male graft is merged; `WaitForLoad` finishes the merge later. Hair that connects in that window, or that a scene or preset restores before AltFuta is ready, sizes its table for 23,008 vertices. - **The overrun.** Once the skin reaches 24,333 vertices, the vertex-count check in `LoadFromBinaryReader` passes and `strands[j]` runs past the end of the 23,008-entry table. Stock hair can still be made valid on the futa skin. The futa skin's base vertices are the stock female vertices, in the same order, followed by the 1,325 base vertices of `male_gen_dazmesh`, because `DAZMergedMesh` places graft 2 at `startGraft2VertIndex`, after the target mesh and graft 1. Stock-skin hair therefore only needs its table padded with 1,325 empty slots for the graft. ## The fix When `WaitForLoad` sets `status` to `"ready"`, a new coroutine, `WatchDirectSkinHair()`, starts. As long as that futa skin is up, it checks the atom's active, saved DirectSkin hair once a second, starting after any scene load finishes. Because it keeps checking, hair added after AltFuta is ready is covered too. A hair item needs attention when its loaded mask and strand table differ in size (`LoadFromBinaryReader` reads the mask before it fails), or when its table fits neither the futa nor the stock vertex count. The fix then depends on which skin the `.vab` was made for: - **Made for the futa skin.** The creator is pointed at another DirectSkin holder, then `DAZDynamic.Load()` is called. Load assigns the `.vab`'s own provider back, and that switch rebuilds the table from the finished skin. - **Made for the stock skin.** The fix writes a copy of the item's `.vam` and `.vaj`, plus a `.vab` whose mask and strand table are padded with 1,325 empty, masked slots, to `Custom/PluginData/RunRudolf/AlternativeFuta/DirectSkinHair/`. It points the item's `DAZDynamic` at that folder for one native `Load()`, then restores the item's original names, so scenes and presets keep referring to the original hair item. - Plugins can't use `System.IO`, so the data can't be passed to `LoadFromBinaryReader` directly. The copy is written with `FileManagerSecure` to a plugin folder that needs no user confirmation, and VaM's own loader reads it. - **Made for any other skin.** Left alone, as is the Hair Creator item. Other behavior: - `Load()` re-applies the item's own `.vaj` values. Non-default values of the item's storables (sim settings, colors, scalp materials) are captured before `Load()` and restored afterwards, so per-scene hair settings survive. - An item isn't retried until VaM loads it again, so hair that genuinely can't load doesn't produce a stream of errors. - The code sticks to lists and arrays walked with index loops. VaM's runtime compiler fails with `CS584` on some nested generic enumerators, such as `foreach` over `Dictionary.Keys`. VaM's own first `not compatible` error still appears once, before the fix steps in about a second later. ## Applying the patch From the VaM root: ``` git apply AlternativeFuta-DirectSkinHair.patch ``` or: ``` patch -p1 --binary < AlternativeFuta-DirectSkinHair.patch ``` `AlternativeFuta.cs` uses CRLF line endings, and the patch keeps them. Pass `--binary` to GNU `patch` on Windows. Opening and re-saving the patch in an editor, or pasting it into a web form, can strip the carriage returns and stop it applying. Alternatively, copy the included `AlternativeFuta.cs` over the original. The patch is against **RunRudolf.AlternativeFuta v14**. ## Verification done - **Tested in VaM:** the patched plugin loads and works as described above. - The v14 source used as the base is byte-identical to the copy inside `RunRudolf.AlternativeFuta.14.var`. - The original and patched files both compile against VaM's own `Assembly-CSharp.dll` and Unity assemblies (C# 6, .NET 3.5 profile). The patch adds no new warnings. - The patch applies to a pristine v14 with `git apply` (with `core.autocrlf` both `true` and `false`) and with `patch -p1 --binary`. Each result is byte-identical to the included `AlternativeFuta.cs`. - The padding step was ported to Python line for line and run on a real 23,008-vertex DirectSkin `.vab`. The output parses cleanly at 24,333 vertices, every existing strand is byte-identical, and all data after the strand table is unchanged. ## How to reproduce and test 1. Load AlternativeFuta from the patched `AlternativeFuta.cs` on a female Person. 2. **Futa-skin hair:** with AltFuta active, create hair in Hair Creator with the `DirectSkinMale` or `DirectSkinFemale` scalp on the male anatomy, and save it. Remove it, then add the saved item. It should appear about a second after VaM's single `not compatible` error. With the patch, no `IndexOutOfRangeException` should follow. 3. **Stock-skin hair:** add any DirectSkin hair made on a stock female, such as published body fur or a tail. It should appear the same way, and a padded copy should appear under `Custom/PluginData/RunRudolf/AlternativeFuta/DirectSkinHair/`. 4. Change a hair setting such as a color, save the scene, and reload it. Both items should come back with the changed setting. 5. Trigger **Reload Skin** in AltFuta. The hair should survive the skin rebuild. ## Known limitations - **Turning AltFuta off.** The atom goes back to the stock skin, and padded hair keeps its 24,333-entry table until VaM reloads the item. Plugins get no convenient hook at that point, so this case isn't handled. - **Leftover copies.** Padded copies stay in `Custom/PluginData/RunRudolf/AlternativeFuta/DirectSkinHair/` and are rewritten whenever they're needed. - **Hair made on other skins.** Hair made for any other skin, such as the stock male skin, isn't converted.