diff --git a/Penumbra/Import/Models/Import/ModelImporter.cs b/Penumbra/Import/Models/Import/ModelImporter.cs index d02d143c..0d8c029d 100644 --- a/Penumbra/Import/Models/Import/ModelImporter.cs +++ b/Penumbra/Import/Models/Import/ModelImporter.cs @@ -4,7 +4,7 @@ using SharpGLTF.Schema2; namespace Penumbra.Import.Models.Import; -public partial class ModelImporter(ModelRoot _model) +public partial class ModelImporter(ModelRoot model) { public static MdlFile Import(ModelRoot model) { @@ -105,7 +105,7 @@ public partial class ModelImporter(ModelRoot _model) /// Returns an iterator over sorted, grouped mesh nodes. private IEnumerable> GroupedMeshNodes() - => _model.LogicalNodes + => model.LogicalNodes .Where(node => node.Mesh != null) .Select(node => { @@ -184,6 +184,14 @@ public partial class ModelImporter(ModelRoot _model) _shapeValues.AddRange(meshShapeKey.ShapeValues); } + + // The number of shape values in a model is bounded by the count + // value, which is stored as a u16. + // While technically there are similar bounds on other shape struct + // arrays, values is practically guaranteed to be the highest of the + // group, so a failure on any of them will be a failure on it. + if (_shapeValues.Count > ushort.MaxValue) + throw new Exception($"Importing this file would require more than the maximum of {ushort.MaxValue} shape values.\nTry removing or applying shape keys that do not need to be changed at runtime in-game."); } private ushort GetMaterialIndex(string materialName) diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs index bae9569f..f099a0e0 100644 --- a/Penumbra/Import/Models/ModelManager.cs +++ b/Penumbra/Import/Models/ModelManager.cs @@ -37,7 +37,12 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect public Task ImportGltf(string inputPath) { var action = new ImportGltfAction(inputPath); - return Enqueue(action).ContinueWith(_ => action.Out); + return Enqueue(action).ContinueWith(task => + { + if (task.IsFaulted && task.Exception != null) + throw task.Exception; + return action.Out; + }); } /// Try to find the .sklb paths for a .mdl file. /// .mdl file to look up the skeletons for. diff --git a/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs index 81f6d552..2f717491 100644 --- a/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs +++ b/Penumbra/Interop/Hooks/Meta/ChangeCustomize.cs @@ -1,5 +1,6 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; -using OtterGui.Services; +using OtterGui.Services; +using Penumbra.Collections; using Penumbra.GameData; using Penumbra.GameData.Structs; using Penumbra.Interop.PathResolving; @@ -29,6 +30,7 @@ public sealed unsafe class ChangeCustomize : FastHook using var decal2 = _metaState.ResolveDecal(_metaState.CustomizeChangeCollection, false); var ret = Task.Result.Original.Invoke(human, data, skipEquipment); Penumbra.Log.Excessive($"[Change Customize] Invoked on {(nint)human:X} with {(nint)data:X}, {skipEquipment} -> {ret}."); + _metaState.CustomizeChangeCollection = ResolveData.Invalid; return ret; } } diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs index bd599133..d38d8d92 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs @@ -20,9 +20,9 @@ public partial class ModEditWindow public List? GamePaths { get; private set; } public int GamePathIndex; - private bool _dirty; - public bool PendingIo { get; private set; } - public string? IoException { get; private set; } + private bool _dirty; + public bool PendingIo { get; private set; } + public List IoExceptions { get; private set; } = []; public MdlTab(ModEditWindow edit, byte[] bytes, string path) { @@ -87,7 +87,7 @@ public partial class ModEditWindow task.ContinueWith(t => { - IoException = t.Exception?.ToString(); + RecordIoExceptions(t.Exception); GamePaths = t.Result; PendingIo = false; }); @@ -123,7 +123,7 @@ public partial class ModEditWindow } catch (Exception exception) { - IoException = exception.ToString(); + RecordIoExceptions(exception); return; } @@ -131,7 +131,7 @@ public partial class ModEditWindow _edit._models.ExportToGltf(Mdl, skeletons, outputPath) .ContinueWith(task => { - IoException = task.Exception?.ToString(); + RecordIoExceptions(task.Exception); PendingIo = false; }); } @@ -144,7 +144,7 @@ public partial class ModEditWindow _edit._models.ImportGltf(inputPath) .ContinueWith(task => { - IoException = task.Exception?.ToString(); + RecordIoExceptions(task.Exception); if (task is { IsCompletedSuccessfully: true, Result: not null }) FinalizeImport(task.Result); PendingIo = false; @@ -177,6 +177,15 @@ public partial class ModEditWindow } } + private void RecordIoExceptions(Exception? exception) + { + IoExceptions = exception switch { + null => [], + AggregateException ae => ae.Flatten().InnerExceptions.ToList(), + Exception other => [other], + }; + } + /// Read a .sklb from the active collection or game. /// Game path to the .sklb to load. private SklbFile ReadSklb(string sklbPath) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs index 9a69a4e8..fbdfcc74 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.cs @@ -7,6 +7,7 @@ using Penumbra.GameData; using Penumbra.GameData.Files; using Penumbra.Import.Models; using Penumbra.String.Classes; +using Penumbra.UI.Classes; namespace Penumbra.UI.AdvancedWindow; @@ -61,8 +62,7 @@ public partial class ModEditWindow ImGui.SameLine(); DrawExport(tab, childSize, disabled); - if (tab.IoException != null) - ImGuiUtil.TextWrapped(tab.IoException); + DrawIoExceptions(tab); } private void DrawImport(MdlTab tab, Vector2 size, bool _1) @@ -100,10 +100,10 @@ public partial class ModEditWindow if (tab.GamePaths == null) { - if (tab.IoException == null) + if (tab.IoExceptions.Count == 0) ImGui.TextUnformatted("Resolving model game paths."); else - ImGuiUtil.TextWrapped(tab.IoException); + ImGui.TextUnformatted("Failed to resolve model game paths."); return; } @@ -127,6 +127,30 @@ public partial class ModEditWindow false ); } + + private void DrawIoExceptions(MdlTab tab) + { + if (tab.IoExceptions.Count == 0) + return; + + var size = new Vector2(ImGui.GetContentRegionAvail().X, 0); + using var frame = ImRaii.FramedGroup("Exceptions", size, headerPreIcon: FontAwesomeIcon.TimesCircle, borderColor: Colors.RegexWarningBorder); + + var spaceAvail = ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X - 100; + foreach (var exception in tab.IoExceptions) + { + var message = $"{exception.GetType().Name}: {exception.Message}"; + var textSize = ImGui.CalcTextSize(message).X; + if (textSize > spaceAvail) + message = message.Substring(0, (int)Math.Floor(message.Length * (spaceAvail / textSize))) + "..."; + + using (var exceptionNode = ImRaii.TreeNode(message)) + { + if (exceptionNode) + ImGuiUtil.TextWrapped(exception.ToString()); + } + } + } private void DrawGamePathCombo(MdlTab tab) { diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index e9facdf4..34d0800c 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -108,11 +108,14 @@ public partial class ModEditWindow MipMapInput(); var canSaveInPlace = Path.IsPathRooted(_left.Path) && _left.Type is TextureType.Tex or TextureType.Dds or TextureType.Png; + var isActive = _config.DeleteModModifier.IsActive(); + var tt = isActive + ? "This saves the texture in place. This is not revertible." + : $"This saves the texture in place. This is not revertible. Hold {_config.DeleteModModifier} to save."; var buttonSize2 = new Vector2((ImGui.GetContentRegionAvail().X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); if (ImGuiUtil.DrawDisabledButton("Save in place", buttonSize2, - "This saves the texture in place. This is not revertible.", - !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) + tt, !isActive || !canSaveInPlace || _center.IsLeftCopy && _currentSaveAs == (int)CombinedTexture.TextureSaveType.AsIs)) { _center.SaveAs(_left.Type, _textures, _left.Path, (CombinedTexture.TextureSaveType)_currentSaveAs, _addMipMaps); AddReloadTask(_left.Path, false); diff --git a/Penumbra/UI/Classes/Colors.cs b/Penumbra/UI/Classes/Colors.cs index 0e3b9377..93d7e091 100644 --- a/Penumbra/UI/Classes/Colors.cs +++ b/Penumbra/UI/Classes/Colors.cs @@ -33,7 +33,6 @@ public enum ColorId public static class Colors { // These are written as 0xAABBGGRR. - public const uint PressEnterWarningBg = 0xFF202080; public const uint RegexWarningBorder = 0xFF0000B0; public const uint MetaInfoText = 0xAAFFFFFF; diff --git a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs index d63e42ef..195c07d6 100644 --- a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs @@ -209,7 +209,7 @@ public class ModPanelSettingsTab : ITab { using var id = ImRaii.PushId(groupIdx); var selectedOption = _empty ? (int)group.DefaultSettings : (int)_settings.Settings[groupIdx]; - var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); + var minWidth = Widget.BeginFramedGroup(group.Name, description:group.Description); void DrawOptions() { @@ -288,7 +288,7 @@ public class ModPanelSettingsTab : ITab { using var id = ImRaii.PushId(groupIdx); var flags = _empty ? group.DefaultSettings : _settings.Settings[groupIdx]; - var minWidth = Widget.BeginFramedGroup(group.Name, group.Description); + var minWidth = Widget.BeginFramedGroup(group.Name, description: group.Description); void DrawOptions() {