@@ -348,7 +348,6 @@ public static async Task<XivMdl> GetXivMdl(string mdlPath, bool getOriginal = fa
348
348
349
349
public static XivMdl GetXivMdl ( byte [ ] mdlData , string mdlPath = "" )
350
350
{
351
-
352
351
var xivMdl = new XivMdl { MdlPath = mdlPath } ;
353
352
int totalNonNullMaterials = 0 ;
354
353
var getShapeData = true ;
@@ -894,7 +893,7 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
894
893
895
894
#endregion
896
895
897
- #region Part Bone Sets & Padding
896
+ #region Part Bone Sets
898
897
// Bone index for Parts
899
898
var partBoneSet = new BoneSet
900
899
{
@@ -908,7 +907,43 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
908
907
}
909
908
910
909
xivMdl . PartBoneSets = partBoneSet ;
910
+ #endregion
911
911
912
+ #region Neck Morph Data
913
+ // Neck morph data (appears on face models new in Patch 7.1)
914
+ xivMdl . NeckMorphTable = new List < NeckMorphEntry > ( ) ;
915
+ for ( var i = 0 ; i < xivMdl . ModelData . NeckMorphTableSize ; ++ i )
916
+ {
917
+ var neckMorphDataEntry = new NeckMorphEntry
918
+ {
919
+ PositionAdjust = new Vector3 ( br . ReadSingle ( ) , br . ReadSingle ( ) , br . ReadSingle ( ) ) ,
920
+ Unknown = br . ReadUInt32 ( ) ,
921
+ NormalAdjust = new Vector3 ( br . ReadSingle ( ) , br . ReadSingle ( ) , br . ReadSingle ( ) ) ,
922
+ Bones = new List < short > ( )
923
+ } ;
924
+ byte [ ] neckBoneTable = br . ReadBytes ( 4 ) ;
925
+ // Weird code alert:
926
+ // - Most vanilla heads legitimately have a zero value in the second slot of the table.
927
+ // - Female Hrothgar heads legitimately have a zero value in the first slot of the table.
928
+ // - Values in the third and fourth slot seem to be unused, and also seem to use 0 as a padding or null value.
929
+ // - However, at least one vanilla model seems to have a non-zero value in the third slot of the table.
930
+ // Therefore, only in the third and fourth slot is a zero value treated as an early list terminator.
931
+ // This means the table should always contain at least two bones.
932
+ for ( int j = 0 ; j < neckBoneTable . Length ; ++ j )
933
+ {
934
+ int boneset0_index = neckBoneTable [ j ] ;
935
+ if ( j >= 2 && boneset0_index == 0 ) break ; // Treat this case as an early list terminator.
936
+ // Resolve the bone to an index in the bone path table here, to make the in-memory representation a little more normal
937
+ if ( xivMdl . MeshBoneSets . Count > 0 && xivMdl . MeshBoneSets [ 0 ] . BoneIndices . Count ( ) > boneset0_index )
938
+ {
939
+ neckMorphDataEntry . Bones . Add ( xivMdl . MeshBoneSets [ 0 ] . BoneIndices [ boneset0_index ] ) ;
940
+ }
941
+ }
942
+ xivMdl . NeckMorphTable . Add ( neckMorphDataEntry ) ;
943
+ }
944
+ #endregion
945
+
946
+ #region Padding
912
947
// Padding
913
948
xivMdl . PaddingSize = br . ReadByte ( ) ;
914
949
xivMdl . PaddedBytes = br . ReadBytes ( xivMdl . PaddingSize ) ;
@@ -970,7 +1005,8 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
970
1005
}
971
1006
else {
972
1007
if ( xivMdl . LoDList [ 0 ] . VertexDataOffset < br . BaseStream . Position
973
- || ( xivMdl . LoDList [ 0 ] . VertexDataOffset % 8 != br . BaseStream . Position % 8 ) )
1008
+ || ( xivMdl . LoDList [ 0 ] . VertexDataOffset % 8 != br . BaseStream . Position % 8 )
1009
+ && xivMdl . LoDList [ 1 ] . VertexDataSize == 0 )
974
1010
{
975
1011
976
1012
var delta = ( int ) ( xivMdl . LoDList [ 0 ] . VertexDataOffset - br . BaseStream . Position ) ;
@@ -982,7 +1018,7 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
982
1018
}
983
1019
984
1020
985
- var lodNum = 0 ;
1021
+ var lodNum = 0 ;
986
1022
var totalMeshNum = 0 ;
987
1023
foreach ( var lod in xivMdl . LoDList )
988
1024
{
@@ -994,16 +1030,12 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
994
1030
{
995
1031
throw new Exception ( "Failed to parse some meshes in previous LoD level." ) ;
996
1032
}
997
-
998
- // Seek to the start of the LoD.
999
- br . BaseStream . Seek ( lod . VertexDataOffset , SeekOrigin . Begin ) ;
1000
- var LoDStart = br . BaseStream . Position ;
1001
1033
1002
1034
var mIdx = 0 ;
1003
1035
1004
1036
foreach ( var meshData in meshDataList )
1005
1037
{
1006
- MdlVertexReader . ReadVertexData ( br , meshData , lod . VertexDataOffset , lod . IndexDataOffset ) ;
1038
+ MdlVertexReader . ReadVertexData ( mdlData , meshData , lod . VertexDataOffset , lod . IndexDataOffset ) ;
1007
1039
1008
1040
mIdx ++ ;
1009
1041
totalMeshNum ++ ;
@@ -2986,8 +3018,10 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
2986
3018
basicModelBlock . Add ( bgChangeIdx ) ;
2987
3019
basicModelBlock . Add ( crestChangeIdx ) ;
2988
3020
2989
- // More Unknowns
2990
- basicModelBlock . Add ( ogModelData . Unknown12 ) ;
3021
+ // Using neck morph data from original modal
3022
+ // The field currently named LevelOfDetail.Unknown7 also contains this number, and also gets copied from the original model
3023
+ var neckMorphTableSizePointer = basicModelBlock . Count ; // we want to reset this to 0 later if the neck data cannot be preserved
3024
+ basicModelBlock . Add ( ogModelData . NeckMorphTableSize ) ;
2991
3025
2992
3026
// We fix this pointer later after bone table is done.
2993
3027
var boneSetSizePointer = basicModelBlock . Count ;
@@ -3536,6 +3570,64 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
3536
3570
3537
3571
#endregion
3538
3572
3573
+ // Neck Morph Data
3574
+ #region Neck Morph Data
3575
+ var neckMorphDataBlock = new List < byte > ( ) ;
3576
+ // Preserve the original model's neck morph data if present -- but update the bone references inside of it
3577
+ // Bone references are made via BoneSet[0]
3578
+ for ( int i = 0 ; i < ogMdl . NeckMorphTable . Count ; ++ i )
3579
+ {
3580
+ // Extract the original data (except the bone list)
3581
+ var positionAdjust = ogMdl . NeckMorphTable [ i ] . PositionAdjust ;
3582
+ var unknown = ogMdl . NeckMorphTable [ i ] . Unknown ;
3583
+ var normalAdjust = ogMdl . NeckMorphTable [ i ] . NormalAdjust ;
3584
+ var bones = new List < byte > ( ) ;
3585
+
3586
+ // Look up the originally referenced bone by name, and map it to the same bone in the imported model
3587
+ for ( int j = 0 ; j < ogMdl . NeckMorphTable [ i ] . Bones . Count ; ++ j )
3588
+ {
3589
+ string ogBoneName = ogMdl . PathData . BoneList [ ogMdl . NeckMorphTable [ i ] . Bones [ j ] ] ;
3590
+ int boneset0Index = - 1 ;
3591
+
3592
+ if ( ttModel . MeshGroups . Count > 0 )
3593
+ {
3594
+ boneset0Index = ttModel . MeshGroups [ 0 ] . Bones . FindIndex ( x => x == ogBoneName ) ;
3595
+ }
3596
+
3597
+ // If a bone can't be located in the new model, just discard all of the neck morph data
3598
+ if ( boneset0Index == - 1 || boneset0Index > 255 )
3599
+ {
3600
+ loggingFunction ( true , "Could not match bones in neck morph data, so the data was discarded!" ) ;
3601
+ // Reset the table size to 0 in the model header and drop the data
3602
+ // (Two bytes are also cleared later on when writing the LODs)
3603
+ basicModelBlock [ neckMorphTableSizePointer ] = 0 ;
3604
+ neckMorphDataBlock = new List < byte > ( ) ;
3605
+ break ;
3606
+ }
3607
+
3608
+ bones . Add ( ( byte ) boneset0Index ) ;
3609
+ }
3610
+
3611
+ // Serialize
3612
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( positionAdjust . X ) ) ;
3613
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( positionAdjust . Y ) ) ;
3614
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( positionAdjust . Z ) ) ;
3615
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( unknown ) ) ;
3616
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( normalAdjust . X ) ) ;
3617
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( normalAdjust . Y ) ) ;
3618
+ neckMorphDataBlock . AddRange ( BitConverter . GetBytes ( normalAdjust . Z ) ) ;
3619
+
3620
+ // Bone list is always 4 bytes -- pad with zeroes
3621
+ for ( int j = 0 ; j < 4 ; ++ j )
3622
+ {
3623
+ if ( j < bones . Count )
3624
+ neckMorphDataBlock . Add ( bones [ j ] ) ;
3625
+ else
3626
+ neckMorphDataBlock . Add ( 0 ) ;
3627
+ }
3628
+ }
3629
+ #endregion
3630
+
3539
3631
// Padding
3540
3632
#region Padding Data Block
3541
3633
@@ -3691,7 +3783,7 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
3691
3783
// This is the offset to the beginning of the vertex data
3692
3784
var combinedDataBlockSize = _MdlHeaderSize + vertexInfoBlock . Count + pathInfoBlock . Count + basicModelBlock . Count + unknownDataBlock0 . Length + ( 60 * ogMdl . LoDList . Count ) + extraMeshesBlock . Count + meshDataBlock . Count +
3693
3785
attributePathDataBlock . Count + ( unknownDataBlock1 ? . Length ?? 0 ) + meshPartDataBlock . Count + unknownDataBlock2 . Length + matPathOffsetDataBlock . Count + bonePathOffsetDataBlock . Count +
3694
- boneSetsBlock . Count + FullShapeDataBlock . Count + partBoneSetsBlock . Count + paddingDataBlock . Count + boundingBoxDataBlock . Count + boneBoundingBoxDataBlock . Count ;
3786
+ boneSetsBlock . Count + FullShapeDataBlock . Count + partBoneSetsBlock . Count + neckMorphDataBlock . Count + paddingDataBlock . Count + boundingBoxDataBlock . Count + boneBoundingBoxDataBlock . Count ;
3695
3787
3696
3788
var lodDataBlock = new List < byte > ( ) ;
3697
3789
List < int > indexStartInjectPointers = new List < int > ( ) ;
@@ -3734,6 +3826,14 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
3734
3826
lodDataBlock . AddRange ( BitConverter . GetBytes ( ogMdl . LoDList [ 0 ] . Unknown6 ) ) ;
3735
3827
lodDataBlock . AddRange ( BitConverter . GetBytes ( ogMdl . LoDList [ 0 ] . Unknown7 ) ) ;
3736
3828
3829
+ // If the neck morph data was discarded -- clear the morph counts here too
3830
+ // (The first 2 bytes of what is currently named "Unknown7" seem to refer to the size of this table)
3831
+ if ( ogMdl . NeckMorphTable . Count > 0 && neckMorphDataBlock . Count == 0 )
3832
+ {
3833
+ lodDataBlock [ lodDataBlock . Count - 4 ] = 0 ;
3834
+ lodDataBlock [ lodDataBlock . Count - 3 ] = 0 ;
3835
+ }
3836
+
3737
3837
// Vertex & Index Sizes
3738
3838
lodDataBlock . AddRange ( BitConverter . GetBytes ( vertexDataSize ) ) ;
3739
3839
lodDataBlock . AddRange ( BitConverter . GetBytes ( indexDataSize ) ) ;
@@ -3770,6 +3870,7 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
3770
3870
modelDataBlock . AddRange ( boneSetsBlock ) ;
3771
3871
modelDataBlock . AddRange ( FullShapeDataBlock ) ;
3772
3872
modelDataBlock . AddRange ( partBoneSetsBlock ) ;
3873
+ modelDataBlock . AddRange ( neckMorphDataBlock ) ;
3773
3874
modelDataBlock . AddRange ( paddingDataBlock ) ;
3774
3875
modelDataBlock . AddRange ( boundingBoxDataBlock ) ;
3775
3876
modelDataBlock . AddRange ( boneBoundingBoxDataBlock ) ;
0 commit comments