Skip to content

Commit 372f00e

Browse files
committed
- Addition of normal map inversion option for 3DS Max users.
- Fix crash on exporting/importing decals. - Character items now fully listed in the advanced/standard modpack creators. - Fix for repeat-error popup when viewing models with invalid materials.
1 parent 7a8cfc2 commit 372f00e

File tree

7 files changed

+206
-69
lines changed

7 files changed

+206
-69
lines changed

xivModdingFramework/Cache/XivDependencyGraph.cs

Lines changed: 139 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,7 +1646,7 @@ public static async Task<List<XivDependencyRoot>> GetDependencyRoots(string inte
16461646
/// <param name="primary"></param>
16471647
/// <param name="secondary"></param>
16481648
/// <returns></returns>
1649-
private static async Task<List<XivDependencyRootInfo>> TestAllRoots(Dictionary<string, XivDependencyRootInfo> combinedHashes, XivItemType primary, XivItemType secondary) {
1649+
private static async Task<List<XivDependencyRootInfo>> TestAllRoots(Dictionary<int, HashSet<int>> Hashes, XivItemType primary, XivItemType secondary) {
16501650

16511651

16521652
var result = new List<XivDependencyRootInfo>(3000);
@@ -1659,53 +1659,68 @@ await Task.Run(() => {
16591659
root.SecondaryType = (secondary == XivItemType.none ? null : (XivItemType?) secondary);
16601660
var eqp = new Eqp(XivCache.GameInfo.GameDirectory);
16611661
var races = (XivRace[])Enum.GetValues(typeof(XivRace));
1662+
var slots = XivItemTypes.GetAvailableSlots(primary);
1663+
if(secondary != XivItemType.none)
1664+
{
1665+
slots = XivItemTypes.GetAvailableSlots(secondary);
1666+
1667+
if(primary == XivItemType.human && secondary == XivItemType.body)
1668+
{
1669+
slots = XivItemTypes.GetAvailableSlots(XivItemType.equipment);
1670+
slots.Add("");
1671+
}
1672+
}
1673+
1674+
if(slots.Count == 0)
1675+
{
1676+
slots.Add("");
1677+
}
1678+
1679+
var usesImc = Imc.UsesImc(root);
16621680

16631681
for (int p = 0; p < 10000; p++)
16641682
{
16651683
root.PrimaryId = p;
16661684

16671685
if (secondary == XivItemType.none)
16681686
{
1687+
var folder = root.GetRootFolder();
1688+
folder = folder.Substring(0, folder.Length - 1);
16691689
if (primary == XivItemType.indoor || primary == XivItemType.outdoor)
16701690
{
1671-
// For furniture, they're valid as long as they have an SGD file we can find.
1672-
var folder = root.GetRootFolder() + "asset";
1673-
var file = root.GetSgdName();
1691+
// For furniture, they're valid as long as they have an assets folder we can find.
1692+
var assetFolder = folder + "/asset";
1693+
var folderHash = HashGenerator.GetHash(assetFolder);
16741694

1675-
var folderHash = HashGenerator.GetHash(folder);
1676-
var fileHash = HashGenerator.GetHash(file);
1677-
var key = fileHash.ToString() + folderHash.ToString();
1678-
if (combinedHashes.ContainsKey(key))
1695+
var sgdName = root.GetSgdName();
1696+
var sgdHash = HashGenerator.GetHash(sgdName);
1697+
1698+
if (Hashes.ContainsKey(folderHash) && Hashes[folderHash].Contains(sgdHash))
16791699
{
16801700
result.Add((XivDependencyRootInfo)root.Clone());
16811701
}
16821702
}
16831703
else
16841704
{
1685-
var folder = root.GetRootFolder() + "model";
1705+
// Test to see if the IMC file exists.
16861706
var folderHash = HashGenerator.GetHash(folder);
1687-
var slots = XivItemTypes.GetAvailableSlots(root.PrimaryType);
1707+
var imcName = root.GetBaseFileName(false) + ".imc";
1708+
var imcHash = HashGenerator.GetHash(imcName);
16881709

1689-
// For these, just let the EDP module verify if there are any races availble for the item?
1690-
foreach (var slot in slots)
1710+
if (Hashes.ContainsKey(folderHash) && Hashes[folderHash].Contains(imcHash))
16911711
{
1692-
root.Slot = slot;
1693-
1694-
// Check every possible race code, not just playables?
1695-
//for (int s = 0; s < 10000; s++)
1696-
foreach (var race in races)
1712+
foreach (var slot in slots)
16971713
{
1698-
//var modelName = root.GetRacialModelName(s);
1699-
var modelName = root.GetRacialModelName(race);
1700-
var fileHash = HashGenerator.GetHash(modelName);
1701-
var key = fileHash.ToString() + folderHash.ToString();
1702-
if (combinedHashes.ContainsKey(key))
1714+
var sl = slot == "" ? null : slot;
1715+
var nRoot = new XivDependencyRootInfo()
17031716
{
1704-
result.Add((XivDependencyRootInfo)root.Clone());
1705-
1706-
// We don't care how many models there are, just that there *are* any models.
1707-
break;
1708-
}
1717+
PrimaryId = root.PrimaryId,
1718+
PrimaryType = root.PrimaryType,
1719+
SecondaryId = root.SecondaryId,
1720+
SecondaryType = root.SecondaryType,
1721+
Slot = sl
1722+
};
1723+
result.Add(nRoot);
17091724
}
17101725
}
17111726
}
@@ -1715,36 +1730,105 @@ await Task.Run(() => {
17151730
for (int s = 0; s < 10000; s++)
17161731
{
17171732
root.SecondaryId = s;
1718-
var folder = root.GetRootFolder() + "model";
1719-
var folderHash = HashGenerator.GetHash(folder);
1720-
var slots = XivItemTypes.GetAvailableSlots((XivItemType)root.SecondaryType);
1733+
var folder = root.GetRootFolder();
1734+
folder = folder.Substring(0, folder.Length - 1);
17211735

1722-
if(primary == XivItemType.human && secondary == XivItemType.body)
1736+
// If their root folder exists (has an IMC entry in it) they're valid.
1737+
if (usesImc)
17231738
{
1724-
// Human body gets to be a special snowflake.
1725-
slots = XivItemTypes.GetAvailableSlots(XivItemType.equipment);
1726-
}
1739+
// Test to see if the IMC file exists.
1740+
var folderHash = HashGenerator.GetHash(folder);
1741+
var imcName = XivItemTypes.GetSystemPrefix((XivItemType)root.SecondaryType) + root.SecondaryId.ToString().PadLeft(4,'0') + ".imc";
1742+
var imcHash = HashGenerator.GetHash(imcName);
17271743

1728-
if (slots.Count == 0)
1729-
{
1730-
slots.Add("");
1731-
}
1732-
foreach (var slot in slots)
1733-
{
1734-
if (slot == "")
1735-
{
1736-
root.Slot = null;
1737-
}
1738-
else
1744+
if (Hashes.ContainsKey(folderHash) && Hashes[folderHash].Contains(imcHash))
17391745
{
1740-
root.Slot = slot;
1746+
foreach (var slot in slots)
1747+
{
1748+
var sl = slot == "" ? null : slot;
1749+
var nRoot = new XivDependencyRootInfo()
1750+
{
1751+
PrimaryId = root.PrimaryId,
1752+
PrimaryType = root.PrimaryType,
1753+
SecondaryId = root.SecondaryId,
1754+
SecondaryType = root.SecondaryType,
1755+
Slot = sl
1756+
};
1757+
result.Add(nRoot);
1758+
}
17411759
}
1742-
var modelName = root.GetSimpleModelName();
1743-
var fileHash = HashGenerator.GetHash(modelName);
1744-
var key = fileHash.ToString() + folderHash.ToString();
1745-
if (combinedHashes.ContainsKey(key))
1760+
}
1761+
else if(!usesImc)
1762+
{
1763+
1764+
var mfolder = folder + "/model";
1765+
var mfolderHash = HashGenerator.GetHash(mfolder);
1766+
var matFolder = folder + "/material";
1767+
var matFolderHash = HashGenerator.GetHash(matFolder);
1768+
var matFolder1 = folder + "/material/v0001";
1769+
var matFolder1Hash = HashGenerator.GetHash(matFolder1);
1770+
var texFolder = folder + "/texture";
1771+
var texFolderHash = HashGenerator.GetHash(texFolder);
1772+
1773+
// Things that don't use IMC files are basically only the human tree, which is a complete mess.
1774+
foreach (var slot in slots)
17461775
{
1747-
result.Add((XivDependencyRootInfo)root.Clone());
1776+
var sl = slot == "" ? null : slot;
1777+
var nRoot = new XivDependencyRootInfo()
1778+
{
1779+
PrimaryId = root.PrimaryId,
1780+
PrimaryType = root.PrimaryType,
1781+
SecondaryId = root.SecondaryId,
1782+
SecondaryType = root.SecondaryType,
1783+
Slot = sl
1784+
};
1785+
1786+
// If they have an MDL or MTRL we can resolve, they're valid.
1787+
1788+
var mdlFile = nRoot.GetBaseFileName(true) + ".mdl";
1789+
var mdlFileHash = HashGenerator.GetHash(mdlFile);
1790+
1791+
var mtrlFile = "mt_" + nRoot.GetBaseFileName(true) + "_a.mtrl";
1792+
var mtrlFileHash = HashGenerator.GetHash(mtrlFile);
1793+
1794+
var hasModel = Hashes.ContainsKey(mfolderHash) && Hashes[mfolderHash].Contains(mdlFileHash);
1795+
var hasMat = Hashes.ContainsKey(matFolderHash) && Hashes[matFolderHash].Contains(mtrlFileHash);
1796+
var hasMat1 = Hashes.ContainsKey(matFolder1Hash) && Hashes[matFolder1Hash].Contains(mtrlFileHash);
1797+
var hasTex = Hashes.ContainsKey(texFolderHash);
1798+
1799+
1800+
if (hasMat || hasMat1 || hasModel)
1801+
{
1802+
if (secondary == XivItemType.body)
1803+
{
1804+
var nRoot2 = new XivDependencyRootInfo()
1805+
{
1806+
PrimaryId = root.PrimaryId,
1807+
PrimaryType = root.PrimaryType,
1808+
SecondaryId = root.SecondaryId,
1809+
SecondaryType = root.SecondaryType,
1810+
Slot = null
1811+
};
1812+
result.Add(nRoot2);
1813+
}
1814+
else
1815+
{
1816+
foreach (var slot2 in slots)
1817+
{
1818+
var sl2 = slot2 == "" ? null : slot2;
1819+
var nRoot2 = new XivDependencyRootInfo()
1820+
{
1821+
PrimaryId = root.PrimaryId,
1822+
PrimaryType = root.PrimaryType,
1823+
SecondaryId = root.SecondaryId,
1824+
SecondaryType = root.SecondaryType,
1825+
Slot = sl2
1826+
};
1827+
result.Add(nRoot2);
1828+
}
1829+
}
1830+
break;
1831+
}
17481832
}
17491833
}
17501834
}
@@ -1771,21 +1855,10 @@ public static async Task CacheAllRealRoots()
17711855
{
17721856
ResetRootCache();
17731857
var index = new Index(XivCache.GameInfo.GameDirectory);
1774-
var mergedHashes = await index.GetFileDictionary(XivDataFile._04_Chara);
1775-
1776-
var mergedDict = new Dictionary<string, XivDependencyRootInfo>();
1777-
foreach (var kv in mergedHashes)
1778-
{
1779-
mergedDict.Add(kv.Key, new XivDependencyRootInfo());
1780-
}
17811858

1782-
var bgcHashes = await index.GetFileDictionary(XivDataFile._01_Bgcommon);
1859+
var hashes = await index.GetAllHashes(XivDataFile._04_Chara);
1860+
var bgcHashes = await index.GetAllHashes(XivDataFile._01_Bgcommon);
17831861

1784-
var bgcHashDict = new Dictionary<string, XivDependencyRootInfo>();
1785-
foreach (var kv in bgcHashes)
1786-
{
1787-
bgcHashDict.Add(kv.Key, new XivDependencyRootInfo());
1788-
}
17891862

17901863
var types = new Dictionary<XivItemType, List<XivItemType>>();
17911864
foreach (var type in DependencySupportedTypes)
@@ -1802,11 +1875,9 @@ public static async Task CacheAllRealRoots()
18021875
types[XivItemType.demihuman].Add(XivItemType.equipment);
18031876
types[XivItemType.equipment].Add(XivItemType.none);
18041877
types[XivItemType.accessory].Add(XivItemType.none);
1805-
types[XivItemType.indoor].Add(XivItemType.none);
18061878
types[XivItemType.outdoor].Add(XivItemType.none);
18071879

18081880

1809-
18101881
var tasks = new List<Task<List<XivDependencyRootInfo>>>();
18111882
foreach (var kv in types)
18121883
{
@@ -1815,11 +1886,11 @@ public static async Task CacheAllRealRoots()
18151886
{
18161887
if (primary == XivItemType.indoor || primary == XivItemType.outdoor)
18171888
{
1818-
tasks.Add(TestAllRoots(bgcHashDict, primary, secondary));
1889+
tasks.Add(TestAllRoots(bgcHashes, primary, secondary));
18191890
}
18201891
else
18211892
{
1822-
tasks.Add(TestAllRoots(mergedDict, primary, secondary));
1893+
tasks.Add(TestAllRoots(hashes, primary, secondary));
18231894
}
18241895
}
18251896
}

xivModdingFramework/Materials/FileTypes/Mtrl.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@ public async Task<XivMtrl> GetMtrlData(string mtrlFile, int materialSet = -1, in
276276

277277
mtrlOffset = await index.GetDataOffset(mtrlPath);
278278

279+
if(mtrlOffset == 0)
280+
{
281+
return null;
282+
}
283+
279284
var mtrlData = await GetMtrlData(mtrlOffset, mtrlPath, dxVersion);
280285

281286
return mtrlData;

xivModdingFramework/Models/DataContainers/TTModel.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,11 @@ public void SetFullModelDBMetaData(string filePath, string fullModelName)
15571557
/// <returns></returns>
15581558
public static TTModel FromRaw(XivMdl rawMdl, Action<bool, string> loggingFunction = null)
15591559
{
1560+
if(rawMdl == null)
1561+
{
1562+
return null;
1563+
}
1564+
15601565
if (loggingFunction == null)
15611566
{
15621567
loggingFunction = ModelModifiers.NoOp;

xivModdingFramework/Models/FileTypes/Mdl.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ public async Task<XivMdl> GetRawMdlData(string mdlPath, bool getOriginal = false
433433

434434
if (offset == 0)
435435
{
436-
throw new Exception($"Could not find offset for {mdlPath}");
436+
return null;
437437
}
438438

439439
var mdlData = await dat.GetType3Data(offset, _dataFile);

xivModdingFramework/Models/Helpers/ModelModifiers.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,7 @@ public static void CalculateTangents(TTModel model, Action<bool, string> logging
12871287
{
12881288
loggingFunction = NoOp;
12891289
}
1290+
if (model == null) return;
12901291

12911292
loggingFunction(false, "Calculating Tangents...");
12921293
var hasTangents = model.MeshGroups.Any(x => x.Parts.Any(x => x.Vertices.Any(x => x.Tangent != Vector3.Zero)));

xivModdingFramework/Models/ModelTextures/ModelTexture.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ public class CustomModelColors
5656
// Optional values.
5757
public Color? HairHighlightColor;
5858

59+
public bool InvertNormalGreen = false;
60+
5961

6062

6163
// One for each colorset row. Null for undyed.
@@ -75,6 +77,7 @@ public CustomModelColors()
7577
FurnitureColor = new Color(141, 60, 204, 255);
7678

7779
HairHighlightColor = new Color(77, 126, 240, 255);
80+
InvertNormalGreen = false;
7881

7982
DyeColors = new List<Color?>(16);
8083
for(int i = 0; i < 16; i++)
@@ -211,6 +214,11 @@ await Task.Run(() =>
211214
baseSpecularColor = new Color(specularPixels[i - 3], specularPixels[i - 2], specularPixels[i - 1], specularPixels[i]);
212215
}
213216

217+
if(colors != null && colors.InvertNormalGreen)
218+
{
219+
baseNormalColor[1] = (byte)(255 - baseNormalColor[1]);
220+
}
221+
214222
byte colorsetValue = baseNormalColor.A;
215223

216224
// Calculate real colors from the inputs and shader.

0 commit comments

Comments
 (0)