@@ -894,7 +894,7 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
894
894
895
895
#endregion
896
896
897
- #region Part Bone Sets & Padding
897
+ #region Part Bone Sets
898
898
// Bone index for Parts
899
899
var partBoneSet = new BoneSet
900
900
{
@@ -908,7 +908,43 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
908
908
}
909
909
910
910
xivMdl . PartBoneSets = partBoneSet ;
911
+ #endregion
912
+
913
+ #region Neck Morph Data
914
+ // Neck morph data (appears on face models new in Patch 7.1)
915
+ xivMdl . NeckMorphTable = new List < NeckMorphEntry > ( ) ;
916
+ for ( var i = 0 ; i < xivMdl . ModelData . NeckMorphTableSize ; ++ i )
917
+ {
918
+ var neckMorphDataEntry = new NeckMorphEntry
919
+ {
920
+ PositionAdjust = new Vector3 ( br . ReadSingle ( ) , br . ReadSingle ( ) , br . ReadSingle ( ) ) ,
921
+ Unknown = br . ReadUInt32 ( ) ,
922
+ NormalAdjust = new Vector3 ( br . ReadSingle ( ) , br . ReadSingle ( ) , br . ReadSingle ( ) ) ,
923
+ Bones = new List < short > ( )
924
+ } ;
925
+ byte [ ] neckBoneTable = br . ReadBytes ( 4 ) ;
926
+ // Weird code alert:
927
+ // - Most vanilla heads legitimately have a zero value in the second slot of the table.
928
+ // - Female Hrothgar heads legitimately have a zero value in the first slot of the table.
929
+ // - Values in the third and fourth slot seem to be unused, and also seem to use 0 as a padding or null value.
930
+ // - However, at least one vanilla model seems to have a non-zero value in the third slot of the table.
931
+ // Therefore, only in the third and fourth slot is a zero value treated as an early list terminator.
932
+ // This means the table should always contain at least two bones.
933
+ for ( int j = 0 ; j < neckBoneTable . Length ; ++ j )
934
+ {
935
+ int boneset0_index = neckBoneTable [ j ] ;
936
+ if ( j >= 2 && boneset0_index == 0 ) break ; // Treat this case as an early list terminator.
937
+ // Resolve the bone to an index in the bone path table here, to make the in-memory representation a little more normal
938
+ if ( xivMdl . MeshBoneSets . Count > 0 && xivMdl . MeshBoneSets [ 0 ] . BoneIndices . Count ( ) > boneset0_index )
939
+ {
940
+ neckMorphDataEntry . Bones . Add ( xivMdl . MeshBoneSets [ 0 ] . BoneIndices [ boneset0_index ] ) ;
941
+ }
942
+ }
943
+ xivMdl . NeckMorphTable . Add ( neckMorphDataEntry ) ;
944
+ }
945
+ #endregion
911
946
947
+ #region Padding
912
948
// Padding
913
949
xivMdl . PaddingSize = br . ReadByte ( ) ;
914
950
xivMdl . PaddedBytes = br . ReadBytes ( xivMdl . PaddingSize ) ;
@@ -2987,8 +3023,10 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
2987
3023
basicModelBlock . Add ( bgChangeIdx ) ;
2988
3024
basicModelBlock . Add ( crestChangeIdx ) ;
2989
3025
2990
- // More Unknowns
2991
- basicModelBlock . Add ( ogModelData . Unknown12 ) ;
3026
+ // Using neck morph data from original modal
3027
+ // The field currently named LevelOfDetail.Unknown7 also contains this number, and also gets copied from the original model
3028
+ var neckMorphTableSizePointer = basicModelBlock . Count ; // we want to reset this to 0 later if the neck data cannot be preserved
3029
+ basicModelBlock . Add ( ogModelData . NeckMorphTableSize ) ;
2992
3030
2993
3031
// We fix this pointer later after bone table is done.
2994
3032
var boneSetSizePointer = basicModelBlock . Count ;
@@ -3537,6 +3575,64 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
3537
3575
3538
3576
#endregion
3539
3577
3578
+ // Neck Morph Data
3579
+ #region Neck Morph Data
3580
+ var neckMorphDataBlock = new List < byte > ( ) ;
3581
+ // Preserve the original model's neck morph data if present -- but update the bone references inside of it
3582
+ // Bone references are made via BoneSet[0]
3583
+ for ( int i = 0 ; i < ogMdl . NeckMorphTable . Count ; ++ i )
3584
+ {
3585
+ // Extract the original data (except the bone list)
3586
+ var positionAdjust = ogMdl . NeckMorphTable [ i ] . PositionAdjust ;
3587
+ var unknown = ogMdl . NeckMorphTable [ i ] . Unknown ;
3588
+ var normalAdjust = ogMdl . NeckMorphTable [ i ] . NormalAdjust ;
3589
+ var bones = new List < byte > ( ) ;
3590
+
3591
+ // Look up the originally referenced bone by name, and map it to the same bone in the imported model
3592
+ for ( int j = 0 ; j < ogMdl . NeckMorphTable [ i ] . Bones . Count ; ++ j )
3593
+ {
3594
+ string ogBoneName = ogMdl . PathData . BoneList [ ogMdl . NeckMorphTable [ i ] . Bones [ j ] ] ;
3595
+ int boneset0Index = - 1 ;
3596
+
3597
+ if ( ttModel . MeshGroups . Count > 0 )
3598
+ {
3599
+ boneset0Index = ttModel . MeshGroups [ 0 ] . Bones . FindIndex ( x => x == ogBoneName ) ;
3600
+ }
3601
+
3602
+ // If a bone can't be located in the new model, just discard all of the neck morph data
3603
+ if ( boneset0Index == - 1 || boneset0Index > 255 )
3604
+ {
3605
+ loggingFunction ( true , "Could not match bones in neck morph data, so the data was discarded!" ) ;
3606
+ // Reset the table size to 0 in the model header and drop the data
3607
+ // (Two bytes are also cleared later on when writing the LODs)
3608
+ basicModelBlock [ neckMorphTableSizePointer ] = 0 ;
3609
+ neckMorphDataBlock = new List < byte > ( ) ;
3610
+ break ;
3611
+ }
3612
+
3613
+ bones . Add ( ( byte ) boneset0Index ) ;
3614
+ }
3615
+
3616
+ // Serialize
3617
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( positionAdjust . X ) ) ;
3618
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( positionAdjust . Y ) ) ;
3619
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( positionAdjust . Z ) ) ;
3620
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( unknown ) ) ;
3621
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( normalAdjust . X ) ) ;
3622
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( normalAdjust . Y ) ) ;
3623
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( normalAdjust . Z ) ) ;
3624
+
3625
+ // Bone list is always 4 bytes -- pad with zeroes
3626
+ for ( int j = 0 ; j < 4 ; ++ j )
3627
+ {
3628
+ if ( j < bones . Count )
3629
+ neckMorphDataBlock . Add ( bones [ j ] ) ;
3630
+ else
3631
+ neckMorphDataBlock . Add ( 0 ) ;
3632
+ }
3633
+ }
3634
+ #endregion
3635
+
3540
3636
// Padding
3541
3637
#region Padding Data Block
3542
3638
@@ -3692,7 +3788,7 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
3692
3788
// This is the offset to the beginning of the vertex data
3693
3789
var combinedDataBlockSize = _MdlHeaderSize + vertexInfoBlock . Count + pathInfoBlock . Count + basicModelBlock . Count + unknownDataBlock0 . Length + ( 60 * ogMdl . LoDList . Count ) + extraMeshesBlock . Count + meshDataBlock . Count +
3694
3790
attributePathDataBlock . Count + ( unknownDataBlock1 ? . Length ?? 0 ) + meshPartDataBlock . Count + unknownDataBlock2 . Length + matPathOffsetDataBlock . Count + bonePathOffsetDataBlock . Count +
3695
- boneSetsBlock . Count + FullShapeDataBlock . Count + partBoneSetsBlock . Count + paddingDataBlock . Count + boundingBoxDataBlock . Count + boneBoundingBoxDataBlock . Count ;
3791
+ boneSetsBlock . Count + FullShapeDataBlock . Count + partBoneSetsBlock . Count + neckMorphDataBlock . Count + paddingDataBlock . Count + boundingBoxDataBlock . Count + boneBoundingBoxDataBlock . Count ;
3696
3792
3697
3793
var lodDataBlock = new List < byte > ( ) ;
3698
3794
List < int > indexStartInjectPointers = new List < int > ( ) ;
@@ -3735,6 +3831,14 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
3735
3831
lodDataBlock . AddRange ( BitConverter . GetBytes ( ogMdl . LoDList [ 0 ] . Unknown6 ) ) ;
3736
3832
lodDataBlock . AddRange ( BitConverter . GetBytes ( ogMdl . LoDList [ 0 ] . Unknown7 ) ) ;
3737
3833
3834
+ // If the neck morph data was discarded -- clear the morph counts here too
3835
+ // (The first 2 bytes of what is currently named "Unknown7" seem to refer to the size of this table)
3836
+ if ( ogMdl . NeckMorphTable . Count > 0 && neckMorphDataBlock . Count == 0 )
3837
+ {
3838
+ lodDataBlock [ lodDataBlock . Count - 4 ] = 0 ;
3839
+ lodDataBlock [ lodDataBlock . Count - 3 ] = 0 ;
3840
+ }
3841
+
3738
3842
// Vertex & Index Sizes
3739
3843
lodDataBlock . AddRange ( BitConverter . GetBytes ( vertexDataSize ) ) ;
3740
3844
lodDataBlock . AddRange ( BitConverter . GetBytes ( indexDataSize ) ) ;
@@ -3771,6 +3875,7 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
3771
3875
modelDataBlock . AddRange ( boneSetsBlock ) ;
3772
3876
modelDataBlock . AddRange ( FullShapeDataBlock ) ;
3773
3877
modelDataBlock . AddRange ( partBoneSetsBlock ) ;
3878
+ modelDataBlock . AddRange ( neckMorphDataBlock ) ;
3774
3879
modelDataBlock . AddRange ( paddingDataBlock ) ;
3775
3880
modelDataBlock . AddRange ( boundingBoxDataBlock ) ;
3776
3881
modelDataBlock . AddRange ( boneBoundingBoxDataBlock ) ;
0 commit comments