From aeb7bd5431d0e3822010305ebeeb87de5b52604b Mon Sep 17 00:00:00 2001 From: ackwell Date: Fri, 19 Apr 2024 00:34:08 +1000 Subject: [PATCH] Ensure materials contain at least one / --- .../ModEditWindow.Models.MdlTab.cs | 13 ++++- .../UI/AdvancedWindow/ModEditWindow.Models.cs | 54 +++++++++++++------ 2 files changed, 49 insertions(+), 18 deletions(-) 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)