Skip to content

Commit 15d272e

Browse files
authored
Merge pull request #76 from senko-forks/pr-atch
PMP stuff + avfx stuff + neck morph stuff
2 parents fcfc724 + 4f664ea commit 15d272e

File tree

8 files changed

+227
-10
lines changed

8 files changed

+227
-10
lines changed

xivModdingFramework/Models/DataContainers/LevelOfDetail.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public EMeshType GetMeshType(int offset)
9797

9898
/// <summary>
9999
/// Unknown Usage
100+
/// This appears to be multiple individual byte values, with the first 2 being related to neck morph data
100101
/// </summary>
101102
public int Unknown7 { get; set; }
102103

xivModdingFramework/Models/DataContainers/MdlModelData.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public class MdlModelData
172172
/// </summary>
173173
public byte BgCrestChangeMaterialIndex { get; set; }
174174

175-
public byte Unknown12 { get; set; }
175+
public byte NeckMorphTableSize { get; set; }
176176

177177
/// <summary>
178178
/// Unknown Usage
@@ -241,7 +241,7 @@ public static MdlModelData Read(BinaryReader br)
241241
BgChangeMaterialIndex = br.ReadByte(),
242242
BgCrestChangeMaterialIndex = br.ReadByte(),
243243

244-
Unknown12 = br.ReadByte(),
244+
NeckMorphTableSize = br.ReadByte(),
245245
BoneSetSize = br.ReadInt16(),
246246

247247
Unknown13 = br.ReadInt16(),
@@ -278,7 +278,7 @@ public void Write(BinaryWriter br)
278278
br.Write((byte)Flags3);
279279
br.Write(BgChangeMaterialIndex);
280280
br.Write(BgCrestChangeMaterialIndex);
281-
br.Write(Unknown12);
281+
br.Write(NeckMorphTableSize);
282282
br.Write(BoneSetSize);
283283
br.Write(Unknown13);
284284
br.Write(Unknown14);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// xivModdingFramework
2+
// Copyright © 2018 Rafael Gonzalez - All Rights Reserved
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
using SharpDX;
18+
using System.Collections.Generic;
19+
20+
namespace xivModdingFramework.Models.DataContainers
21+
{
22+
/// <summary>
23+
/// Class representing a neck morph table entry in a model, which modifies a vertex on the neck seam of a character's body when active.
24+
/// Typically 10 of these entries exist on a head mesh.
25+
/// It is unclear how each entry relates to individual vertex on the body mesh.
26+
/// </summary>
27+
public class NeckMorphEntry
28+
{
29+
/// <summary>
30+
/// Relative adjustment of the Position of the vertex.
31+
/// Its unclear how this is applied, but it seems affected by the referenced bones.
32+
/// </summary>
33+
public Vector3 PositionAdjust { get; set; }
34+
35+
/// <summary>
36+
/// This value is unclear but preserving it is important.
37+
/// If it is incorrect, the vertex adjustments disappear when viewed from certain camera angles.
38+
/// For all known working examples, it just has the value 0x00006699.
39+
/// </summary>
40+
public uint Unknown { get; set; }
41+
42+
/// <summary>
43+
/// Relative adjustment of the Normal of the vertex.
44+
/// Its unclear how this is applied.
45+
/// </summary>
46+
public Vector3 NormalAdjust { get; set; }
47+
48+
/// <summary>
49+
/// A list of bones, stored as indexes in to MdlPathData.BoneList.
50+
/// For all known working examples, the referenced bones are ["j_kubi", "j_sebo_c"].
51+
/// If the incorrect bones are referenced, the neck behavior becomes erratic.
52+
/// NOTE: In the model file these are actually stored as indexes in to BoneSet 0.
53+
/// </summary>
54+
public List<short> Bones { get; set; }
55+
}
56+
}

xivModdingFramework/Models/DataContainers/XivMdl.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,10 @@ public class XivMdl
141141
/// This happens when the sum of all LoD mesh counts is less than the model data mesh count
142142
/// </remarks>
143143
public List<MeshData> ExtraMeshData { get; set; }
144+
145+
/// <summary>
146+
/// This data is present on heads and seems to affect the shape of the neck on the body mesh
147+
/// </summary>
148+
public List<NeckMorphEntry> NeckMorphTable { get; set; }
144149
}
145150
}

xivModdingFramework/Models/FileTypes/Mdl.cs

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -894,7 +894,7 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
894894

895895
#endregion
896896

897-
#region Part Bone Sets & Padding
897+
#region Part Bone Sets
898898
// Bone index for Parts
899899
var partBoneSet = new BoneSet
900900
{
@@ -908,7 +908,43 @@ public static XivMdl GetXivMdl(byte[] mdlData, string mdlPath = "")
908908
}
909909

910910
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
911946

947+
#region Padding
912948
// Padding
913949
xivMdl.PaddingSize = br.ReadByte();
914950
xivMdl.PaddedBytes = br.ReadBytes(xivMdl.PaddingSize);
@@ -2987,8 +3023,10 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
29873023
basicModelBlock.Add(bgChangeIdx);
29883024
basicModelBlock.Add(crestChangeIdx);
29893025

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);
29923030

29933031
// We fix this pointer later after bone table is done.
29943032
var boneSetSizePointer = basicModelBlock.Count;
@@ -3537,6 +3575,64 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
35373575

35383576
#endregion
35393577

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+
35403636
// Padding
35413637
#region Padding Data Block
35423638

@@ -3692,7 +3788,7 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
36923788
// This is the offset to the beginning of the vertex data
36933789
var combinedDataBlockSize = _MdlHeaderSize + vertexInfoBlock.Count + pathInfoBlock.Count + basicModelBlock.Count + unknownDataBlock0.Length + (60 * ogMdl.LoDList.Count) + extraMeshesBlock.Count + meshDataBlock.Count +
36943790
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;
36963792

36973793
var lodDataBlock = new List<byte>();
36983794
List<int> indexStartInjectPointers = new List<int>();
@@ -3735,6 +3831,14 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
37353831
lodDataBlock.AddRange(BitConverter.GetBytes(ogMdl.LoDList[0].Unknown6));
37363832
lodDataBlock.AddRange(BitConverter.GetBytes(ogMdl.LoDList[0].Unknown7));
37373833

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+
37383842
// Vertex & Index Sizes
37393843
lodDataBlock.AddRange(BitConverter.GetBytes(vertexDataSize));
37403844
lodDataBlock.AddRange(BitConverter.GetBytes(indexDataSize));
@@ -3771,6 +3875,7 @@ public static byte[] MakeUncompressedMdlFile(TTModel ttModel, XivMdl ogMdl, Acti
37713875
modelDataBlock.AddRange(boneSetsBlock);
37723876
modelDataBlock.AddRange(FullShapeDataBlock);
37733877
modelDataBlock.AddRange(partBoneSetsBlock);
3878+
modelDataBlock.AddRange(neckMorphDataBlock);
37743879
modelDataBlock.AddRange(paddingDataBlock);
37753880
modelDataBlock.AddRange(boundingBoxDataBlock);
37763881
modelDataBlock.AddRange(boneBoundingBoxDataBlock);

xivModdingFramework/Mods/FileTypes/PmpManipulation.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ namespace xivModdingFramework.Mods.FileTypes
2525
[JsonSubtypes.KnownSubType(typeof(PMPEqdpManipulationWrapperJson), "Eqdp")]
2626
[JsonSubtypes.KnownSubType(typeof(PMPGmpManipulationWrapperJson), "Gmp")]
2727
[JsonSubtypes.KnownSubType(typeof(PMPRspManipulationWrapperJson), "Rsp")]
28+
[JsonSubtypes.KnownSubType(typeof(PMPAtchManipulationWrapperJson), "Atch")]
2829
[JsonSubtypes.KnownSubType(typeof(PMPGlobalEqpManipulationWrapperJson), "GlobalEqp")]
2930
public class PMPManipulationWrapperJson
3031
{
@@ -168,6 +169,23 @@ public override string GetNiceName()
168169
return base.GetNiceName() + " - " + Manipulation.SubRace + " " + Manipulation.Attribute;
169170
}
170171
}
172+
public class PMPAtchManipulationWrapperJson : PMPManipulationWrapperJson
173+
{
174+
[JsonProperty(Order = 2)]
175+
public PMPAtchManipulationJson Manipulation = new PMPAtchManipulationJson();
176+
public override object GetManipulation()
177+
{
178+
return Manipulation;
179+
}
180+
public override void SetManipulation(object o)
181+
{
182+
Manipulation = o as PMPAtchManipulationJson;
183+
}
184+
public override string GetNiceName()
185+
{
186+
return base.GetNiceName() + " - " + Manipulation.Race + " " + Manipulation.Gender + " " + Manipulation.Type + " [" + Manipulation.Index + "]";
187+
}
188+
}
171189
public class PMPGlobalEqpManipulationWrapperJson : PMPManipulationWrapperJson
172190
{
173191
[JsonProperty(Order = 2)]
@@ -793,6 +811,29 @@ public static List<PMPRspManipulationJson> FromRgspEntry(RacialGenderScalingPara
793811
}
794812

795813
}
814+
public class PMPAtchManipulationJson
815+
{
816+
public struct PMPAtchEntry
817+
{
818+
public string Bone;
819+
public float Scale;
820+
821+
public float OffsetX;
822+
public float OffsetY;
823+
public float OffsetZ;
824+
825+
public float RotationX;
826+
public float RotationY;
827+
public float RotationZ;
828+
}
829+
830+
public PMPAtchEntry Entry;
831+
public PMPGender Gender;
832+
public PMPModelRace Race;
833+
public string Type;
834+
public int Index;
835+
}
836+
796837
public class PMPGlobalEqpManipulationJson
797838
{
798839
public GlobalEqpType Type;

xivModdingFramework/Mods/WizardData.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ protected override bool CheckHasData()
8787
"Est",
8888
"Gmp",
8989
"Rsp",
90+
"Atch",
9091
"GlobalEqp"
9192
};
9293

@@ -496,6 +497,12 @@ public async Task<ModOption> ToModOption()
496497
};
497498
mo.Mods.Add(path, mData);
498499
}
500+
501+
if (manips.OtherManipulations.Count > 0)
502+
{
503+
var manip0 = manips.OtherManipulations[0];
504+
throw new InvalidDataException("TTMP Does not support " + manip0.Type + " manipulations.");
505+
}
499506
}
500507

501508

@@ -546,6 +553,7 @@ public class WizardImcGroupData
546553
public ushort Variant;
547554
public XivImc BaseEntry = new XivImc();
548555
public bool AllVariants;
556+
public bool OnlyAttributes;
549557
}
550558

551559
/// <summary>
@@ -774,6 +782,7 @@ public static async Task<WizardGroupEntry> FromPMPGroup(PMPGroupJson pGroup, str
774782
Root = imcGroup.Identifier.GetRoot(),
775783
BaseEntry = imcGroup.DefaultEntry.ToXivImc(),
776784
AllVariants = imcGroup.AllVariants,
785+
OnlyAttributes = imcGroup.OnlyAttributes,
777786
};
778787
}
779788

@@ -889,6 +898,7 @@ public async Task<PMPGroupJson> ToPmpGroup(string tempFolder, Dictionary<string,
889898
imcG.Identifier = PmpIdentifierJson.FromRoot(ImcData.Root.Info, ImcData.Variant);
890899
imcG.DefaultEntry = PMPImcManipulationJson.PMPImcEntry.FromXivImc(ImcData.BaseEntry);
891900
imcG.AllVariants = ImcData.AllVariants;
901+
imcG.OnlyAttributes = ImcData.OnlyAttributes;
892902
}
893903
else
894904
{

xivModdingFramework/VFX/FileTypes/Avfx.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,9 @@ await Task.Run(() =>
5252
{
5353
data = br.ReadInt32();
5454
}
55-
catch (EndOfStreamException e)
55+
catch (EndOfStreamException)
5656
{
57-
throw new System.Exception("VFX textures were detected but no texture paths could be found.\n" +
58-
"VFX textures will not be accessible.");
57+
return;
5958
}
6059
}
6160

0 commit comments

Comments
 (0)