diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs index bbc274a5..c6e2d836 100644 --- a/Penumbra/Import/Models/ModelManager.cs +++ b/Penumbra/Import/Models/ModelManager.cs @@ -108,6 +108,40 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect return [GamePaths.Skeleton.Sklb.Path(info.GenderRace, EstManipulation.ToName(type), targetId)]; } + /// Try to resolve the absolute path to a .mtrl from the potentially-partial path provided by a model. + private string ResolveMtrlPath(string rawPath) + { + // TODO: this should probably be chosen in the export settings + var variantId = 1; + + // Get standardised paths + var absolutePath = rawPath.StartsWith('/') + ? LuminaMaterial.ResolveRelativeMaterialPath(rawPath, variantId) + : rawPath; + var relativePath = rawPath.StartsWith('/') + ? rawPath + : '/' + Path.GetFileName(rawPath); + + // TODO: this should be a recoverable warning + if (absolutePath == null) + throw new Exception("Failed to resolve material path."); + + var info = parser.GetFileInfo(absolutePath); + if (info.FileType is not FileType.Material) + throw new Exception($"Material path {rawPath} does not conform to material conventions."); + + var resolvedPath = info.ObjectType switch + { + ObjectType.Character => GamePaths.Character.Mtrl.Path( + info.GenderRace, info.BodySlot, info.PrimaryId, relativePath, out _, out _, info.Variant), + _ => absolutePath, + }; + + Penumbra.Log.Debug($"Resolved material {rawPath} to {resolvedPath}"); + + return resolvedPath; + } + private Task Enqueue(IAction action) { if (_disposed) @@ -188,18 +222,8 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect /// Read a .mtrl and hydrate its textures. private MaterialExporter.Material BuildMaterial(string relativePath, CancellationToken cancel) { - // TODO: this should probably be chosen in the export settings - var variantId = 1; - - var absolutePath = relativePath.StartsWith("/") - ? LuminaMaterial.ResolveRelativeMaterialPath(relativePath, variantId) - : relativePath; - - // TODO: this should be a recoverable warning - if (absolutePath == null) - throw new Exception("Failed to resolve material path."); - - var mtrl = new MtrlFile(read(absolutePath)); + var path = manager.ResolveMtrlPath(relativePath); + var mtrl = new MtrlFile(read(path)); return new MaterialExporter.Material { @@ -214,7 +238,20 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect /// Read a texture referenced by a .mtrl and convert it into an ImageSharp image. private Image ConvertImage(MtrlFile.Texture texture, CancellationToken cancel) { - using var textureData = new MemoryStream(read(texture.Path)); + // Work out the texture's path - the DX11 material flag controls a file name prefix. + var texturePath = texture.Path; + if (texture.DX11) + { + var lastSlashIndex = texturePath.LastIndexOf('/'); + var directory = lastSlashIndex == -1 ? texturePath : texturePath.Substring(0, lastSlashIndex); + var fileName = Path.GetFileName(texturePath); + if (!fileName.StartsWith("--")) + { + texturePath = $"{directory}/--{fileName}"; + } + } + + using var textureData = new MemoryStream(read(texturePath)); var image = TexFileParser.Parse(textureData); var pngImage = TextureManager.ConvertToPng(image, cancel).AsPng; if (pngImage == null)