diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
index cca8fe10..b8c0176a 100644
--- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
+++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs
@@ -49,7 +49,7 @@ public partial class ModEditWindow
///
public bool Valid
- => Mdl.Valid;
+ => Mdl.Valid && Mdl.Materials.All(ValidateMaterial);
///
public byte[] Write()
@@ -285,6 +285,17 @@ public partial class ModEditWindow
: _edit._gameData.GetFile(resolvedPath.InternalName.ToString())?.Data;
}
+ /// Validate the specified material.
+ ///
+ /// While materials can be relative (`/mt_...`) or absolute (`bg/...`),
+ /// they invariably must contain at least one directory seperator.
+ /// Missing this can lead to a crash.
+ ///
+ public bool ValidateMaterial(string material)
+ {
+ return material.Contains('/');
+ }
+
/// Remove the material given by the index.
/// Meshes using the removed material are redirected to material 0, and those after the index are corrected.
public void RemoveMaterial(int materialIndex)
diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs
index 6cd9b912..1cfa7585 100644
--- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs
+++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs
@@ -1,4 +1,5 @@
using Dalamud.Interface;
+using Dalamud.Interface.Components;
using ImGuiNET;
using OtterGui;
using OtterGui.Custom;
@@ -295,7 +296,7 @@ public partial class ModEditWindow
if (!ImGui.CollapsingHeader("Materials"))
return false;
- using var table = ImRaii.Table(string.Empty, disabled ? 2 : 3, ImGuiTableFlags.SizingFixedFit);
+ using var table = ImRaii.Table(string.Empty, disabled ? 2 : 4, ImGuiTableFlags.SizingFixedFit);
if (!table)
return false;
@@ -305,7 +306,10 @@ public partial class ModEditWindow
ImGui.TableSetupColumn("index", ImGuiTableColumnFlags.WidthFixed, 80 * UiHelpers.Scale);
ImGui.TableSetupColumn("path", ImGuiTableColumnFlags.WidthStretch, 1);
if (!disabled)
+ {
ImGui.TableSetupColumn("actions", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X);
+ ImGui.TableSetupColumn("help", ImGuiTableColumnFlags.WidthFixed, UiHelpers.IconButtonSize.X);
+ }
var inputFlags = disabled ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None;
for (var materialIndex = 0; materialIndex < materials.Length; materialIndex++)
@@ -321,12 +325,15 @@ public partial class ModEditWindow
ImGui.InputTextWithHint("##newMaterial", "Add new material...", ref _modelNewMaterial, Utf8GamePath.MaxGamePathLength, inputFlags);
var validName = _modelNewMaterial.Length > 0 && _modelNewMaterial[0] == '/';
ImGui.TableNextColumn();
- if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize, string.Empty, !validName, true))
- return ret;
+ if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), UiHelpers.IconButtonSize, string.Empty, !validName, true))
+ {
+ ret |= true;
+ tab.Mdl.Materials = materials.AddItem(_modelNewMaterial);
+ _modelNewMaterial = string.Empty;
+ }
+ ImGui.TableNextColumn();
- tab.Mdl.Materials = materials.AddItem(_modelNewMaterial);
- _modelNewMaterial = string.Empty;
- return true;
+ return ret;
}
private bool DrawMaterialRow(MdlTab tab, bool disabled, string[] materials, int materialIndex, ImGuiInputTextFlags inputFlags)
@@ -353,20 +360,33 @@ public partial class ModEditWindow
return ret;
ImGui.TableNextColumn();
-
// Need to have at least one material.
- if (materials.Length <= 1)
- return ret;
+ if (materials.Length > 1)
+ {
+ var tt = "Delete this material.\nAny meshes targeting this material will be updated to use material #1.";
+ var modifierActive = _config.DeleteModModifier.IsActive();
+ if (!modifierActive)
+ tt += $"\nHold {_config.DeleteModModifier} to delete.";
- var tt = "Delete this material.\nAny meshes targeting this material will be updated to use material #1.";
- var modifierActive = _config.DeleteModModifier.IsActive();
- if (!modifierActive)
- tt += $"\nHold {_config.DeleteModModifier} to delete.";
- if (!ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize, tt, !modifierActive, true))
- return ret;
+ if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), UiHelpers.IconButtonSize, tt, !modifierActive, true))
+ {
+ tab.RemoveMaterial(materialIndex);
+ ret |= true;
+ }
+ }
- tab.RemoveMaterial(materialIndex);
- return true;
+ ImGui.TableNextColumn();
+ // Add markers to invalid materials.
+ if (!tab.ValidateMaterial(temp))
+ using (var colorHandle = ImRaii.PushColor(ImGuiCol.TextDisabled, 0xFF0000FF, true))
+ {
+ ImGuiComponents.HelpMarker(
+ "Materials must be either relative (e.g. \"/filename.mtrl\")\n"
+ + "or absolute (e.g. \"chara/full/path/to/filename.mtrl\").",
+ FontAwesomeIcon.TimesCircle);
+ }
+
+ return ret;
}
private bool DrawModelLodDetails(MdlTab tab, int lodIndex, bool disabled)