From 514b0e7f30f4a792726a1bca9c811bdf0a373ee2 Mon Sep 17 00:00:00 2001 From: Exter-N Date: Thu, 27 Feb 2025 00:10:24 +0100 Subject: [PATCH 1/5] Add file types to Resource Tree and require Ctrl+Shift for some quick imports --- Penumbra.GameData | 2 +- .../ResolveContext.PathResolution.cs | 84 ++++++++++++----- .../Interop/ResourceTree/ResolveContext.cs | 93 ++++++++++++++++++- Penumbra/Interop/ResourceTree/ResourceNode.cs | 11 ++- Penumbra/Interop/ResourceTree/ResourceTree.cs | 50 ++++++++-- Penumbra/Interop/Structs/StructExtensions.cs | 12 +++ .../ModEditWindow.QuickImport.cs | 5 +- 7 files changed, 224 insertions(+), 33 deletions(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index b4a0806e..c59b1da6 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit b4a0806e00be4ce8cf3103fd526e4a412b4770b7 +Subproject commit c59b1da61610e656b3e89f9c33113d08f97ae6c7 diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs index bdf66a16..cd6b8568 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs @@ -22,6 +22,13 @@ internal partial record ResolveContext private static bool IsEquipmentSlot(uint slotIndex) => slotIndex is < 5 or 16 or 17; + private unsafe Variant Variant + => ModelType switch + { + ModelType.Monster => (byte)((Monster*)CharacterBase)->Variant, + _ => Equipment.Variant, + }; + private Utf8GamePath ResolveModelPath() { // Correctness: @@ -92,7 +99,7 @@ internal partial record ResolveContext => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName), ModelType.DemiHuman => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName), ModelType.Weapon => ResolveWeaponMaterialPath(modelPath, imc, mtrlFileName), - ModelType.Monster => ResolveMonsterMaterialPath(modelPath, imc, mtrlFileName), + ModelType.Monster => ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName), _ => ResolveMaterialPathNative(mtrlFileName), }; } @@ -100,7 +107,7 @@ internal partial record ResolveContext [SkipLocalsInit] private unsafe Utf8GamePath ResolveEquipmentMaterialPath(Utf8GamePath modelPath, ResourceHandle* imc, byte* mtrlFileName) { - var variant = ResolveMaterialVariant(imc, Equipment.Variant); + var variant = ResolveImcData(imc).MaterialId; var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName); Span pathBuffer = stackalloc byte[CharaBase.PathBufferSize]; @@ -118,9 +125,9 @@ internal partial record ResolveContext return Utf8GamePath.FromString(GamePaths.Weapon.Mtrl.Path(2001, 1, 1, "c"), out var path) ? path : Utf8GamePath.Empty; // Some offhands share materials with the corresponding mainhand - if (ItemData.AdaptOffhandImc(Equipment.Set.Id, out var mirroredSetId)) + if (ItemData.AdaptOffhandImc(Equipment.Set, out var mirroredSetId)) { - var variant = ResolveMaterialVariant(imc, Equipment.Variant); + var variant = ResolveImcData(imc).MaterialId; var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName); Span mirroredFileName = stackalloc byte[32]; @@ -141,31 +148,16 @@ internal partial record ResolveContext return ResolveEquipmentMaterialPath(modelPath, imc, mtrlFileName); } - private unsafe Utf8GamePath ResolveMonsterMaterialPath(Utf8GamePath modelPath, ResourceHandle* imc, byte* mtrlFileName) - { - var variant = ResolveMaterialVariant(imc, (byte)((Monster*)CharacterBase)->Variant); - var fileName = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(mtrlFileName); - - Span pathBuffer = stackalloc byte[CharaBase.PathBufferSize]; - pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName); - - return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty; - } - - private unsafe byte ResolveMaterialVariant(ResourceHandle* imc, Variant variant) + private unsafe ImcEntry ResolveImcData(ResourceHandle* imc) { var imcFileData = imc->GetDataSpan(); if (imcFileData.IsEmpty) { Penumbra.Log.Warning($"IMC resource handle with path {imc->FileName.AsByteString()} doesn't have a valid data span"); - return variant.Id; + return default; } - var entry = ImcFile.GetEntry(imcFileData, SlotIndex.ToEquipSlot(), variant, out var exists); - if (!exists) - return variant.Id; - - return entry.MaterialId; + return ImcFile.GetEntry(imcFileData, SlotIndex.ToEquipSlot(), Variant, out _); } private static Span AssembleMaterialPath(Span materialPathBuffer, ReadOnlySpan modelPath, byte variant, @@ -317,4 +309,52 @@ internal partial record ResolveContext var path = CharacterBase->ResolveSkpPathAsByteString(partialSkeletonIndex); return Utf8GamePath.FromByteString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; } + + private Utf8GamePath ResolvePhysicsModulePath(uint partialSkeletonIndex) + { + // Correctness and Safety: + // Resolving a physics module path through the game's code can use EST metadata for human skeletons. + // Additionally, it can dereference null pointers for human equipment skeletons. + return ModelType switch + { + ModelType.Human => ResolveHumanPhysicsModulePath(partialSkeletonIndex), + _ => ResolvePhysicsModulePathNative(partialSkeletonIndex), + }; + } + + private Utf8GamePath ResolveHumanPhysicsModulePath(uint partialSkeletonIndex) + { + var (raceCode, slot, set) = ResolveHumanSkeletonData(partialSkeletonIndex); + if (set == 0) + return Utf8GamePath.Empty; + + var path = GamePaths.Skeleton.Phyb.Path(raceCode, slot, set); + return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; + } + + private unsafe Utf8GamePath ResolvePhysicsModulePathNative(uint partialSkeletonIndex) + { + var path = CharacterBase->ResolvePhybPathAsByteString(partialSkeletonIndex); + return Utf8GamePath.FromByteString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; + } + + private unsafe Utf8GamePath ResolveMaterialAnimationPath(ResourceHandle* imc) + { + var animation = ResolveImcData(imc).MaterialAnimationId; + if (animation == 0) + return Utf8GamePath.Empty; + + var path = CharacterBase->ResolveMaterialPapPathAsByteString(SlotIndex, animation); + return Utf8GamePath.FromByteString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; + } + + private unsafe Utf8GamePath ResolveDecalPath(ResourceHandle* imc) + { + var decal = ResolveImcData(imc).DecalId; + if (decal == 0) + return Utf8GamePath.Empty; + + var path = GamePaths.Equipment.Decal.Path(decal); + return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty; + } } diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.cs b/Penumbra/Interop/ResourceTree/ResolveContext.cs index 54612070..f33bf041 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.cs @@ -180,7 +180,15 @@ internal unsafe partial record ResolveContext( return GetOrCreateNode(ResourceType.Tex, (nint)tex->Texture, &tex->ResourceHandle, path); } - public ResourceNode? CreateNodeFromModel(Model* mdl, ResourceHandle* imc) + public ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex, Utf8GamePath gamePath) + { + if (tex == null) + return null; + + return GetOrCreateNode(ResourceType.Tex, (nint)tex->Texture, &tex->ResourceHandle, gamePath); + } + + public ResourceNode? CreateNodeFromModel(Model* mdl, ResourceHandle* imc, TextureResourceHandle* decalHandle, ResourceHandle* mpapHandle) { if (mdl == null || mdl->ModelResourceHandle == null) return null; @@ -210,6 +218,14 @@ internal unsafe partial record ResolveContext( } } + var decalNode = CreateNodeFromDecal(decalHandle, imc); + if (null != decalNode) + node.Children.Add(decalNode); + + var mpapNode = CreateNodeFromMaterialPap(mpapHandle, imc); + if (null != mpapNode) + node.Children.Add(mpapNode); + Global.Nodes.Add((path, (nint)mdl->ModelResourceHandle), node); return node; @@ -301,7 +317,59 @@ internal unsafe partial record ResolveContext( } } - public ResourceNode? CreateNodeFromPartialSkeleton(PartialSkeleton* sklb, uint partialSkeletonIndex) + public ResourceNode? CreateNodeFromDecal(TextureResourceHandle* decalHandle, ResourceHandle* imc) + { + if (decalHandle == null) + return null; + + var path = ResolveDecalPath(imc); + if (path.IsEmpty) + return null; + + var node = CreateNodeFromTex(decalHandle, path)!; + if (Global.WithUiData) + node.FallbackName = "Decal"; + + return node; + } + + public ResourceNode? CreateNodeFromMaterialPap(ResourceHandle* mpapHandle, ResourceHandle* imc) + { + if (mpapHandle == null) + return null; + + var path = ResolveMaterialAnimationPath(imc); + if (path.IsEmpty) + return null; + + if (Global.Nodes.TryGetValue((path, (nint)mpapHandle), out var cached)) + return cached; + + var node = CreateNode(ResourceType.Pap, 0, mpapHandle, path); + if (Global.WithUiData) + node.FallbackName = "Material Animation"; + + return node; + } + + public ResourceNode? CreateNodeFromMaterialSklb(SkeletonResourceHandle* sklbHandle) + { + if (sklbHandle == null) + return null; + + if (!Utf8GamePath.FromString(GamePaths.Skeleton.Sklb.MaterialAnimationSkeletonPath, out var path)) + return null; + + if (Global.Nodes.TryGetValue((path, (nint)sklbHandle), out var cached)) + return cached; + + var node = CreateNode(ResourceType.Sklb, 0, (ResourceHandle*)sklbHandle, path); + node.ForceInternal = true; + + return node; + } + + public ResourceNode? CreateNodeFromPartialSkeleton(PartialSkeleton* sklb, ResourceHandle* phybHandle, uint partialSkeletonIndex) { if (sklb == null || sklb->SkeletonResourceHandle == null) return null; @@ -315,6 +383,9 @@ internal unsafe partial record ResolveContext( var skpNode = CreateParameterNodeFromPartialSkeleton(sklb, partialSkeletonIndex); if (skpNode != null) node.Children.Add(skpNode); + var phybNode = CreateNodeFromPhyb(phybHandle, partialSkeletonIndex); + if (phybNode != null) + node.Children.Add(phybNode); Global.Nodes.Add((path, (nint)sklb->SkeletonResourceHandle), node); return node; @@ -338,6 +409,24 @@ internal unsafe partial record ResolveContext( return node; } + private ResourceNode? CreateNodeFromPhyb(ResourceHandle* phybHandle, uint partialSkeletonIndex) + { + if (phybHandle == null) + return null; + + var path = ResolvePhysicsModulePath(partialSkeletonIndex); + + if (Global.Nodes.TryGetValue((path, (nint)phybHandle), out var cached)) + return cached; + + var node = CreateNode(ResourceType.Phyb, 0, phybHandle, path, false); + if (Global.WithUiData) + node.FallbackName = "Physics Module"; + Global.Nodes.Add((path, (nint)phybHandle), node); + + return node; + } + internal ResourceNode.UiData GuessModelUiData(Utf8GamePath gamePath) { var path = gamePath.Path.Split((byte)'/'); diff --git a/Penumbra/Interop/ResourceTree/ResourceNode.cs b/Penumbra/Interop/ResourceTree/ResourceNode.cs index 60cc48de..24cb8f02 100644 --- a/Penumbra/Interop/ResourceTree/ResourceNode.cs +++ b/Penumbra/Interop/ResourceTree/ResourceNode.cs @@ -21,6 +21,8 @@ public class ResourceNode : ICloneable public readonly WeakReference Mod = new(null!); public string? ModRelativePath; public CiByteString AdditionalData; + public bool ForceInternal; + public bool ForceProtected; public readonly ulong Length; public readonly List Children; internal ResolveContext? ResolveContext; @@ -37,8 +39,13 @@ public class ResourceNode : ICloneable } } + /// Whether to treat the file as internal (hide from user unless debug mode is on). public bool Internal - => Type is ResourceType.Eid or ResourceType.Imc; + => ForceInternal || Type is ResourceType.Eid or ResourceType.Imc; + + /// Whether to treat the file as protected (require holding the Mod Deletion Modifier to make a quick import). + public bool Protected + => ForceProtected || Internal || Type is ResourceType.Shpk or ResourceType.Sklb or ResourceType.Pbd; internal ResourceNode(ResourceType type, nint objectAddress, nint resourceHandle, ulong length, ResolveContext? resolveContext) { @@ -67,6 +74,8 @@ public class ResourceNode : ICloneable Mod = other.Mod; ModRelativePath = other.ModRelativePath; AdditionalData = other.AdditionalData; + ForceInternal = other.ForceInternal; + ForceProtected = other.ForceProtected; Length = other.Length; Children = other.Children; ResolveContext = other.ResolveContext; diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index b50fc695..ac1f889c 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -1,7 +1,9 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character; +using FFXIVClientStructs.FFXIV.Client.Graphics.Physics; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using FFXIVClientStructs.Interop; using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; @@ -10,6 +12,7 @@ using Penumbra.UI; using CustomizeData = FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData; using CustomizeIndex = Dalamud.Game.ClientState.Objects.Enums.CustomizeIndex; using ModelType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase.ModelType; +using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle; namespace Penumbra.Interop.ResourceTree; @@ -74,6 +77,18 @@ public class ResourceTree var genericContext = globalContext.CreateContext(model); + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) + var mpapArrayPtr = *(ResourceHandle***)((nint)model + 0x948); + var mpapArray = null != mpapArrayPtr ? new ReadOnlySpan>(mpapArrayPtr, model->SlotCount) : []; + var decalArray = modelType switch + { + ModelType.Human => human->SlotDecalsSpan, + ModelType.DemiHuman => ((Demihuman*)model)->SlotDecals, + ModelType.Weapon => [((Weapon*)model)->Decal], + ModelType.Monster => [((Monster*)model)->Decal], + _ => [], + }; + for (var i = 0u; i < model->SlotCount; ++i) { var slotContext = modelType switch @@ -100,7 +115,8 @@ public class ResourceTree } var mdl = model->Models[i]; - var mdlNode = slotContext.CreateNodeFromModel(mdl, imc); + var mdlNode = slotContext.CreateNodeFromModel(mdl, imc, i < decalArray.Length ? decalArray[(int)i].Value : null, + i < mpapArray.Length ? mpapArray[(int)i].Value : null); if (mdlNode != null) { if (globalContext.WithUiData) @@ -109,7 +125,9 @@ public class ResourceTree } } - AddSkeleton(Nodes, genericContext, model->EID, model->Skeleton); + AddSkeleton(Nodes, genericContext, model->EID, model->Skeleton, model->BonePhysicsModule); + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) + AddMaterialAnimationSkeleton(Nodes, genericContext, *(SkeletonResourceHandle**)((nint)model + 0x940)); AddWeapons(globalContext, model); @@ -140,6 +158,10 @@ public class ResourceTree var genericContext = globalContext.CreateContext(subObject, 0xFFFFFFFFu, slot, equipment, weaponType); + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) + var mpapArrayPtr = *(ResourceHandle***)((nint)subObject + 0x948); + var mpapArray = null != mpapArrayPtr ? new ReadOnlySpan>(mpapArrayPtr, subObject->SlotCount) : []; + for (var i = 0; i < subObject->SlotCount; ++i) { var slotContext = globalContext.CreateContext(subObject, (uint)i, slot, equipment, weaponType); @@ -154,7 +176,7 @@ public class ResourceTree } var mdl = subObject->Models[i]; - var mdlNode = slotContext.CreateNodeFromModel(mdl, imc); + var mdlNode = slotContext.CreateNodeFromModel(mdl, imc, weapon->Decal, i < mpapArray.Length ? mpapArray[i].Value : null); if (mdlNode != null) { if (globalContext.WithUiData) @@ -163,7 +185,9 @@ public class ResourceTree } } - AddSkeleton(weaponNodes, genericContext, subObject->EID, subObject->Skeleton, $"Weapon #{weaponIndex}, "); + AddSkeleton(weaponNodes, genericContext, subObject->EID, subObject->Skeleton, subObject->BonePhysicsModule, $"Weapon #{weaponIndex}, "); + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) + AddMaterialAnimationSkeleton(weaponNodes, genericContext, *(SkeletonResourceHandle**)((nint)subObject + 0x940), $"Weapon #{weaponIndex}, "); ++weaponIndex; } @@ -216,6 +240,7 @@ public class ResourceTree var legacyDecalNode = genericContext.CreateNodeFromTex(human->LegacyBodyDecal, legacyDecalPath); if (legacyDecalNode != null) { + legacyDecalNode.ForceProtected = !hasLegacyDecal; if (globalContext.WithUiData) { legacyDecalNode = legacyDecalNode.Clone(); @@ -227,7 +252,7 @@ public class ResourceTree } } - private unsafe void AddSkeleton(List nodes, ResolveContext context, void* eid, Skeleton* skeleton, string prefix = "") + private unsafe void AddSkeleton(List nodes, ResolveContext context, void* eid, Skeleton* skeleton, BonePhysicsModule* physics, string prefix = "") { var eidNode = context.CreateNodeFromEid((ResourceHandle*)eid); if (eidNode != null) @@ -242,7 +267,9 @@ public class ResourceTree for (var i = 0; i < skeleton->PartialSkeletonCount; ++i) { - var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i], (uint)i); + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) + var phybHandle = physics != null ? ((ResourceHandle**)((nint)physics + 0x190))[i] : null; + var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i], phybHandle, (uint)i); if (sklbNode != null) { if (context.Global.WithUiData) @@ -251,4 +278,15 @@ public class ResourceTree } } } + + private unsafe void AddMaterialAnimationSkeleton(List nodes, ResolveContext context, SkeletonResourceHandle* sklbHandle, string prefix = "") + { + var sklbNode = context.CreateNodeFromMaterialSklb(sklbHandle); + if (sklbNode == null) + return; + + if (context.Global.WithUiData) + sklbNode.FallbackName = $"{prefix}Material Animation Skeleton"; + nodes.Add(sklbNode); + } } diff --git a/Penumbra/Interop/Structs/StructExtensions.cs b/Penumbra/Interop/Structs/StructExtensions.cs index 9dd9a96d..8b5974f0 100644 --- a/Penumbra/Interop/Structs/StructExtensions.cs +++ b/Penumbra/Interop/Structs/StructExtensions.cs @@ -33,6 +33,12 @@ internal static class StructExtensions return ToOwnedByteString(character.ResolveMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex, mtrlFileName)); } + public static CiByteString ResolveMaterialPapPathAsByteString(ref this CharacterBase character, uint slotIndex, uint unkSId) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveMaterialPapPath(pathBuffer, slotIndex, unkSId)); + } + public static CiByteString ResolveSklbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) { Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; @@ -45,6 +51,12 @@ internal static class StructExtensions return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex)); } + public static CiByteString ResolvePhybPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolvePhybPath(pathBuffer, partialSkeletonIndex)); + } + private static unsafe CiByteString ToOwnedByteString(byte* str) => str == null ? CiByteString.Empty : new CiByteString(str).Clone(); diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs index 6fb223df..a49d2933 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs @@ -110,8 +110,11 @@ public partial class ModEditWindow _quickImportActions.Add((resourceNode.GamePath, writable), quickImport); } + var canQuickImport = quickImport.CanExecute; + var quickImportEnabled = canQuickImport && (!resourceNode.Protected || _config.DeleteModModifier.IsActive()); if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), buttonSize, - $"Add a copy of this file to {quickImport.OptionName}.", !quickImport.CanExecute, true)) + $"Add a copy of this file to {quickImport.OptionName}.{(canQuickImport && !quickImportEnabled ? $"\nHold {_config.DeleteModModifier} while clicking to add." : string.Empty)}", + !quickImportEnabled, true)) { quickImport.Execute(); _quickImportActions.Remove((resourceNode.GamePath, writable)); From 776a93dc73b03f87dbcea4165f4ab571f4f21b0c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 27 Feb 2025 05:38:42 +0100 Subject: [PATCH 2/5] Some null-check cleanup. --- .../ResolveContext.PathResolution.cs | 10 +-- .../Interop/ResourceTree/ResolveContext.cs | 81 ++++++++----------- 2 files changed, 39 insertions(+), 52 deletions(-) diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs index cd6b8568..b1ca24b0 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.PathResolution.cs @@ -248,7 +248,7 @@ internal partial record ResolveContext if (faceId < 201) faceId -= tribe switch { - 0xB when modelType == 4 => 100, + 0xB when modelType is 4 => 100, 0xE | 0xF => 100, _ => 0, }; @@ -297,7 +297,7 @@ internal partial record ResolveContext private Utf8GamePath ResolveHumanSkeletonParameterPath(uint partialSkeletonIndex) { var (raceCode, slot, set) = ResolveHumanSkeletonData(partialSkeletonIndex); - if (set == 0) + if (set.Id is 0) return Utf8GamePath.Empty; var path = GamePaths.Skeleton.Skp.Path(raceCode, slot, set); @@ -325,7 +325,7 @@ internal partial record ResolveContext private Utf8GamePath ResolveHumanPhysicsModulePath(uint partialSkeletonIndex) { var (raceCode, slot, set) = ResolveHumanSkeletonData(partialSkeletonIndex); - if (set == 0) + if (set.Id is 0) return Utf8GamePath.Empty; var path = GamePaths.Skeleton.Phyb.Path(raceCode, slot, set); @@ -341,7 +341,7 @@ internal partial record ResolveContext private unsafe Utf8GamePath ResolveMaterialAnimationPath(ResourceHandle* imc) { var animation = ResolveImcData(imc).MaterialAnimationId; - if (animation == 0) + if (animation is 0) return Utf8GamePath.Empty; var path = CharacterBase->ResolveMaterialPapPathAsByteString(SlotIndex, animation); @@ -351,7 +351,7 @@ internal partial record ResolveContext private unsafe Utf8GamePath ResolveDecalPath(ResourceHandle* imc) { var decal = ResolveImcData(imc).DecalId; - if (decal == 0) + if (decal is 0) return Utf8GamePath.Empty; var path = GamePaths.Equipment.Decal.Path(decal); diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.cs b/Penumbra/Interop/ResourceTree/ResolveContext.cs index f33bf041..81904819 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.cs @@ -52,7 +52,7 @@ internal unsafe partial record ResolveContext( private ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, CiByteString gamePath) { - if (resourceHandle == null) + if (resourceHandle is null) return null; if (gamePath.IsEmpty) return null; @@ -65,7 +65,7 @@ internal unsafe partial record ResolveContext( [SkipLocalsInit] private ResourceNode? CreateNodeFromTex(TextureResourceHandle* resourceHandle, CiByteString gamePath, bool dx11) { - if (resourceHandle == null) + if (resourceHandle is null) return null; Utf8GamePath path; @@ -105,7 +105,7 @@ internal unsafe partial record ResolveContext( private ResourceNode GetOrCreateNode(ResourceType type, nint objectAddress, ResourceHandle* resourceHandle, Utf8GamePath gamePath) { - if (resourceHandle == null) + if (resourceHandle is null) throw new ArgumentNullException(nameof(resourceHandle)); if (Global.Nodes.TryGetValue((gamePath, (nint)resourceHandle), out var cached)) @@ -117,7 +117,7 @@ internal unsafe partial record ResolveContext( private ResourceNode CreateNode(ResourceType type, nint objectAddress, ResourceHandle* resourceHandle, Utf8GamePath gamePath, bool autoAdd = true) { - if (resourceHandle == null) + if (resourceHandle is null) throw new ArgumentNullException(nameof(resourceHandle)); var fileName = (ReadOnlySpan)resourceHandle->FileName.AsSpan(); @@ -141,7 +141,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromEid(ResourceHandle* eid) { - if (eid == null) + if (eid is null) return null; if (!Utf8GamePath.FromByteString(CharacterBase->ResolveEidPathAsByteString(), out var path)) @@ -152,7 +152,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromImc(ResourceHandle* imc) { - if (imc == null) + if (imc is null) return null; if (!Utf8GamePath.FromByteString(CharacterBase->ResolveImcPathAsByteString(SlotIndex), out var path)) @@ -163,7 +163,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromPbd(ResourceHandle* pbd) { - if (pbd == null) + if (pbd is null) return null; return GetOrCreateNode(ResourceType.Pbd, 0, pbd, PreBoneDeformerReplacer.PreBoneDeformerPath); @@ -171,7 +171,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex, string gamePath) { - if (tex == null) + if (tex is null) return null; if (!Utf8GamePath.FromString(gamePath, out var path)) @@ -182,7 +182,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex, Utf8GamePath gamePath) { - if (tex == null) + if (tex is null) return null; return GetOrCreateNode(ResourceType.Tex, (nint)tex->Texture, &tex->ResourceHandle, gamePath); @@ -190,7 +190,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromModel(Model* mdl, ResourceHandle* imc, TextureResourceHandle* decalHandle, ResourceHandle* mpapHandle) { - if (mdl == null || mdl->ModelResourceHandle == null) + if (mdl is null || mdl->ModelResourceHandle is null) return null; var mdlResource = mdl->ModelResourceHandle; @@ -205,12 +205,12 @@ internal unsafe partial record ResolveContext( for (var i = 0; i < mdl->MaterialCount; i++) { var mtrl = mdl->Materials[i]; - if (mtrl == null) + if (mtrl is null) continue; var mtrlFileName = mdlResource->GetMaterialFileNameBySlot((uint)i); var mtrlNode = CreateNodeFromMaterial(mtrl, ResolveMaterialPath(path, imc, mtrlFileName)); - if (mtrlNode != null) + if (mtrlNode is not null) { if (Global.WithUiData) mtrlNode.FallbackName = $"Material #{i}"; @@ -218,12 +218,10 @@ internal unsafe partial record ResolveContext( } } - var decalNode = CreateNodeFromDecal(decalHandle, imc); - if (null != decalNode) + if (CreateNodeFromDecal(decalHandle, imc) is { } decalNode) node.Children.Add(decalNode); - var mpapNode = CreateNodeFromMaterialPap(mpapHandle, imc); - if (null != mpapNode) + if (CreateNodeFromMaterialPap(mpapHandle, imc) is { } mpapNode) node.Children.Add(mpapNode); Global.Nodes.Add((path, (nint)mdl->ModelResourceHandle), node); @@ -233,7 +231,7 @@ internal unsafe partial record ResolveContext( private ResourceNode? CreateNodeFromMaterial(Material* mtrl, Utf8GamePath path) { - if (mtrl == null || mtrl->MaterialResourceHandle == null) + if (mtrl is null || mtrl->MaterialResourceHandle is null) return null; var resource = mtrl->MaterialResourceHandle; @@ -242,15 +240,15 @@ internal unsafe partial record ResolveContext( var node = CreateNode(ResourceType.Mtrl, (nint)mtrl, &resource->ResourceHandle, path, false); var shpkNode = CreateNodeFromShpk(resource->ShaderPackageResourceHandle, new CiByteString(resource->ShpkName)); - if (shpkNode != null) + if (shpkNode is not null) { if (Global.WithUiData) shpkNode.Name = "Shader Package"; node.Children.Add(shpkNode); } - var shpkNames = Global.WithUiData && shpkNode != null ? Global.TreeBuildCache.ReadShaderPackageNames(shpkNode.FullPath) : null; - var shpk = Global.WithUiData && shpkNode != null ? (ShaderPackage*)shpkNode.ObjectAddress : null; + var shpkNames = Global.WithUiData && shpkNode is not null ? Global.TreeBuildCache.ReadShaderPackageNames(shpkNode.FullPath) : null; + var shpk = Global.WithUiData && shpkNode is not null ? (ShaderPackage*)shpkNode.ObjectAddress : null; var alreadyProcessedSamplerIds = new HashSet(); for (var i = 0; i < resource->TextureCount; i++) @@ -263,7 +261,7 @@ internal unsafe partial record ResolveContext( if (Global.WithUiData) { string? name = null; - if (shpk != null) + if (shpk is not null) { var index = GetTextureIndex(mtrl, resource->Textures[i].Flags, alreadyProcessedSamplerIds); var samplerId = index != 0x001F @@ -319,7 +317,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromDecal(TextureResourceHandle* decalHandle, ResourceHandle* imc) { - if (decalHandle == null) + if (decalHandle is null) return null; var path = ResolveDecalPath(imc); @@ -335,7 +333,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromMaterialPap(ResourceHandle* mpapHandle, ResourceHandle* imc) { - if (mpapHandle == null) + if (mpapHandle is null) return null; var path = ResolveMaterialAnimationPath(imc); @@ -354,7 +352,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromMaterialSklb(SkeletonResourceHandle* sklbHandle) { - if (sklbHandle == null) + if (sklbHandle is null) return null; if (!Utf8GamePath.FromString(GamePaths.Skeleton.Sklb.MaterialAnimationSkeletonPath, out var path)) @@ -371,7 +369,7 @@ internal unsafe partial record ResolveContext( public ResourceNode? CreateNodeFromPartialSkeleton(PartialSkeleton* sklb, ResourceHandle* phybHandle, uint partialSkeletonIndex) { - if (sklb == null || sklb->SkeletonResourceHandle == null) + if (sklb is null || sklb->SkeletonResourceHandle is null) return null; var path = ResolveSkeletonPath(partialSkeletonIndex); @@ -379,12 +377,10 @@ internal unsafe partial record ResolveContext( if (Global.Nodes.TryGetValue((path, (nint)sklb->SkeletonResourceHandle), out var cached)) return cached; - var node = CreateNode(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, path, false); - var skpNode = CreateParameterNodeFromPartialSkeleton(sklb, partialSkeletonIndex); - if (skpNode != null) + var node = CreateNode(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, path, false); + if (CreateParameterNodeFromPartialSkeleton(sklb, partialSkeletonIndex) is { } skpNode) node.Children.Add(skpNode); - var phybNode = CreateNodeFromPhyb(phybHandle, partialSkeletonIndex); - if (phybNode != null) + if (CreateNodeFromPhyb(phybHandle, partialSkeletonIndex) is { } phybNode) node.Children.Add(phybNode); Global.Nodes.Add((path, (nint)sklb->SkeletonResourceHandle), node); @@ -393,7 +389,7 @@ internal unsafe partial record ResolveContext( private ResourceNode? CreateParameterNodeFromPartialSkeleton(PartialSkeleton* sklb, uint partialSkeletonIndex) { - if (sklb == null || sklb->SkeletonParameterResourceHandle == null) + if (sklb is null || sklb->SkeletonParameterResourceHandle is null) return null; var path = ResolveSkeletonParameterPath(partialSkeletonIndex); @@ -411,7 +407,7 @@ internal unsafe partial record ResolveContext( private ResourceNode? CreateNodeFromPhyb(ResourceHandle* phybHandle, uint partialSkeletonIndex) { - if (phybHandle == null) + if (phybHandle is null) return null; var path = ResolvePhysicsModulePath(partialSkeletonIndex); @@ -431,7 +427,9 @@ internal unsafe partial record ResolveContext( { var path = gamePath.Path.Split((byte)'/'); // Weapons intentionally left out. - var isEquipment = path.Count >= 2 && path[0].Span.SequenceEqual("chara"u8) && (path[1].Span.SequenceEqual("accessory"u8) || path[1].Span.SequenceEqual("equipment"u8)); + var isEquipment = path.Count >= 2 + && path[0].Span.SequenceEqual("chara"u8) + && (path[1].Span.SequenceEqual("accessory"u8) || path[1].Span.SequenceEqual("equipment"u8)); if (isEquipment) foreach (var item in Global.Identifier.Identify(Equipment.Set, 0, Equipment.Variant, Slot.ToSlot())) { @@ -447,7 +445,7 @@ internal unsafe partial record ResolveContext( } var dataFromPath = GuessUiDataFromPath(gamePath); - if (dataFromPath.Name != null) + if (dataFromPath.Name is not null) return dataFromPath; return isEquipment @@ -462,24 +460,13 @@ internal unsafe partial record ResolveContext( var name = obj.Key; if (obj.Value is IdentifiedCustomization) name = name[14..].Trim(); - if (name != "Unknown") + if (name is not "Unknown") return new ResourceNode.UiData(name, obj.Value.GetIcon().ToFlag()); } return new ResourceNode.UiData(null, ChangedItemIconFlag.Unknown); } - private static string? SafeGet(ReadOnlySpan array, Index index) - { - var i = index.GetOffset(array.Length); - return i >= 0 && i < array.Length ? array[i] : null; - } - private static ulong GetResourceHandleLength(ResourceHandle* handle) - { - if (handle == null) - return 0; - - return handle->GetLength(); - } + => handle is null ? 0ul : handle->GetLength(); } From e4cfd674ee1443f876574fa5f8e351413d739d7d Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 27 Feb 2025 05:39:19 +0100 Subject: [PATCH 3/5] Probably unnecessary size optimization. --- Penumbra/Interop/ResourceTree/ResourceNode.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Penumbra/Interop/ResourceTree/ResourceNode.cs b/Penumbra/Interop/ResourceTree/ResourceNode.cs index 24cb8f02..3699ae0b 100644 --- a/Penumbra/Interop/ResourceTree/ResourceNode.cs +++ b/Penumbra/Interop/ResourceTree/ResourceNode.cs @@ -17,12 +17,12 @@ public class ResourceNode : ICloneable public Utf8GamePath[] PossibleGamePaths; public FullPath FullPath; public PathStatus FullPathStatus; + public bool ForceInternal; + public bool ForceProtected; public string? ModName; public readonly WeakReference Mod = new(null!); public string? ModRelativePath; public CiByteString AdditionalData; - public bool ForceInternal; - public bool ForceProtected; public readonly ulong Length; public readonly List Children; internal ResolveContext? ResolveContext; From 70844610d8a913ffd915d0348882cb14bd50a89f Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 27 Feb 2025 05:45:06 +0100 Subject: [PATCH 4/5] Primary constructor and some null-check cleanup. --- Penumbra/Interop/ResourceTree/ResourceTree.cs | 132 ++++++++---------- 1 file changed, 60 insertions(+), 72 deletions(-) diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index ac1f889c..5e3f52d4 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -16,42 +16,35 @@ using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.Re namespace Penumbra.Interop.ResourceTree; -public class ResourceTree +public class ResourceTree( + string name, + string anonymizedName, + int gameObjectIndex, + nint gameObjectAddress, + nint drawObjectAddress, + bool localPlayerRelated, + bool playerRelated, + bool networked, + string collectionName, + string anonymizedCollectionName) { - public readonly string Name; - public readonly string AnonymizedName; - public readonly int GameObjectIndex; - public readonly nint GameObjectAddress; - public readonly nint DrawObjectAddress; - public readonly bool LocalPlayerRelated; - public readonly bool PlayerRelated; - public readonly bool Networked; - public readonly string CollectionName; - public readonly string AnonymizedCollectionName; - public readonly List Nodes; - public readonly HashSet FlatNodes; + public readonly string Name = name; + public readonly string AnonymizedName = anonymizedName; + public readonly int GameObjectIndex = gameObjectIndex; + public readonly nint GameObjectAddress = gameObjectAddress; + public readonly nint DrawObjectAddress = drawObjectAddress; + public readonly bool LocalPlayerRelated = localPlayerRelated; + public readonly bool PlayerRelated = playerRelated; + public readonly bool Networked = networked; + public readonly string CollectionName = collectionName; + public readonly string AnonymizedCollectionName = anonymizedCollectionName; + public readonly List Nodes = []; + public readonly HashSet FlatNodes = []; public int ModelId; public CustomizeData CustomizeData; public GenderRace RaceCode; - public ResourceTree(string name, string anonymizedName, int gameObjectIndex, nint gameObjectAddress, nint drawObjectAddress, - bool localPlayerRelated, bool playerRelated, bool networked, string collectionName, string anonymizedCollectionName) - { - Name = name; - AnonymizedName = anonymizedName; - GameObjectIndex = gameObjectIndex; - GameObjectAddress = gameObjectAddress; - DrawObjectAddress = drawObjectAddress; - LocalPlayerRelated = localPlayerRelated; - Networked = networked; - PlayerRelated = playerRelated; - CollectionName = collectionName; - AnonymizedCollectionName = anonymizedCollectionName; - Nodes = []; - FlatNodes = []; - } - public void ProcessPostfix(Action action) { foreach (var node in Nodes) @@ -73,13 +66,13 @@ public class ResourceTree }; ModelId = character->ModelContainer.ModelCharaId; CustomizeData = character->DrawData.CustomizeData; - RaceCode = human != null ? (GenderRace)human->RaceSexId : GenderRace.Unknown; + RaceCode = human is not null ? (GenderRace)human->RaceSexId : GenderRace.Unknown; var genericContext = globalContext.CreateContext(model); // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) var mpapArrayPtr = *(ResourceHandle***)((nint)model + 0x948); - var mpapArray = null != mpapArrayPtr ? new ReadOnlySpan>(mpapArrayPtr, model->SlotCount) : []; + var mpapArray = mpapArrayPtr is not null ? new ReadOnlySpan>(mpapArrayPtr, model->SlotCount) : []; var decalArray = modelType switch { ModelType.Human => human->SlotDecalsSpan, @@ -105,19 +98,17 @@ public class ResourceTree : globalContext.CreateContext(model, i), }; - var imc = (ResourceHandle*)model->IMCArray[i]; - var imcNode = slotContext.CreateNodeFromImc(imc); - if (imcNode != null) + var imc = (ResourceHandle*)model->IMCArray[i]; + if (slotContext.CreateNodeFromImc(imc) is { } imcNode) { if (globalContext.WithUiData) imcNode.FallbackName = $"IMC #{i}"; Nodes.Add(imcNode); } - var mdl = model->Models[i]; - var mdlNode = slotContext.CreateNodeFromModel(mdl, imc, i < decalArray.Length ? decalArray[(int)i].Value : null, - i < mpapArray.Length ? mpapArray[(int)i].Value : null); - if (mdlNode != null) + var mdl = model->Models[i]; + if (slotContext.CreateNodeFromModel(mdl, imc, i < decalArray.Length ? decalArray[(int)i].Value : null, + i < mpapArray.Length ? mpapArray[(int)i].Value : null) is { } mdlNode) { if (globalContext.WithUiData) mdlNode.FallbackName = $"Model #{i}"; @@ -131,7 +122,7 @@ public class ResourceTree AddWeapons(globalContext, model); - if (human != null) + if (human is not null) AddHumanResources(globalContext, human); } @@ -141,12 +132,12 @@ public class ResourceTree var weaponNodes = new List(); foreach (var baseSubObject in model->DrawObject.Object.ChildObjects) { - if (baseSubObject->GetObjectType() != FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType.CharacterBase) + if (baseSubObject->GetObjectType() is not FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType.CharacterBase) continue; var subObject = (CharacterBase*)baseSubObject; - if (subObject->GetModelType() != ModelType.Weapon) + if (subObject->GetModelType() is not ModelType.Weapon) continue; var weapon = (Weapon*)subObject; @@ -160,24 +151,22 @@ public class ResourceTree // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) var mpapArrayPtr = *(ResourceHandle***)((nint)subObject + 0x948); - var mpapArray = null != mpapArrayPtr ? new ReadOnlySpan>(mpapArrayPtr, subObject->SlotCount) : []; + var mpapArray = mpapArrayPtr is not null ? new ReadOnlySpan>(mpapArrayPtr, subObject->SlotCount) : []; for (var i = 0; i < subObject->SlotCount; ++i) { var slotContext = globalContext.CreateContext(subObject, (uint)i, slot, equipment, weaponType); - var imc = (ResourceHandle*)subObject->IMCArray[i]; - var imcNode = slotContext.CreateNodeFromImc(imc); - if (imcNode != null) + var imc = (ResourceHandle*)subObject->IMCArray[i]; + if (slotContext.CreateNodeFromImc(imc) is { } imcNode) { if (globalContext.WithUiData) imcNode.FallbackName = $"Weapon #{weaponIndex}, IMC #{i}"; weaponNodes.Add(imcNode); } - var mdl = subObject->Models[i]; - var mdlNode = slotContext.CreateNodeFromModel(mdl, imc, weapon->Decal, i < mpapArray.Length ? mpapArray[i].Value : null); - if (mdlNode != null) + var mdl = subObject->Models[i]; + if (slotContext.CreateNodeFromModel(mdl, imc, weapon->Decal, i < mpapArray.Length ? mpapArray[i].Value : null) is { } mdlNode) { if (globalContext.WithUiData) mdlNode.FallbackName = $"Weapon #{weaponIndex}, Model #{i}"; @@ -185,9 +174,11 @@ public class ResourceTree } } - AddSkeleton(weaponNodes, genericContext, subObject->EID, subObject->Skeleton, subObject->BonePhysicsModule, $"Weapon #{weaponIndex}, "); + AddSkeleton(weaponNodes, genericContext, subObject->EID, subObject->Skeleton, subObject->BonePhysicsModule, + $"Weapon #{weaponIndex}, "); // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) - AddMaterialAnimationSkeleton(weaponNodes, genericContext, *(SkeletonResourceHandle**)((nint)subObject + 0x940), $"Weapon #{weaponIndex}, "); + AddMaterialAnimationSkeleton(weaponNodes, genericContext, *(SkeletonResourceHandle**)((nint)subObject + 0x940), + $"Weapon #{weaponIndex}, "); ++weaponIndex; } @@ -200,28 +191,25 @@ public class ResourceTree var genericContext = globalContext.CreateContext(&human->CharacterBase); var cache = globalContext.Collection._cache; - if (cache != null && cache.CustomResources.TryGetValue(PreBoneDeformerReplacer.PreBoneDeformerPath, out var pbdHandle)) + if (cache is not null + && cache.CustomResources.TryGetValue(PreBoneDeformerReplacer.PreBoneDeformerPath, out var pbdHandle) + && genericContext.CreateNodeFromPbd(pbdHandle.ResourceHandle) is { } pbdNode) { - var pbdNode = genericContext.CreateNodeFromPbd(pbdHandle.ResourceHandle); - if (pbdNode != null) + if (globalContext.WithUiData) { - if (globalContext.WithUiData) - { - pbdNode = pbdNode.Clone(); - pbdNode.FallbackName = "Racial Deformer"; - pbdNode.IconFlag = ChangedItemIconFlag.Customization; - } - - Nodes.Add(pbdNode); + pbdNode = pbdNode.Clone(); + pbdNode.FallbackName = "Racial Deformer"; + pbdNode.IconFlag = ChangedItemIconFlag.Customization; } + + Nodes.Add(pbdNode); } var decalId = (byte)(human->Customize[(int)CustomizeIndex.Facepaint] & 0x7F); - var decalPath = decalId != 0 + var decalPath = decalId is not 0 ? GamePaths.Human.Decal.FaceDecalPath(decalId) : GamePaths.Tex.TransparentPath; - var decalNode = genericContext.CreateNodeFromTex(human->Decal, decalPath); - if (decalNode != null) + if (genericContext.CreateNodeFromTex(human->Decal, decalPath) is { } decalNode) { if (globalContext.WithUiData) { @@ -237,8 +225,7 @@ public class ResourceTree var legacyDecalPath = hasLegacyDecal ? GamePaths.Human.Decal.LegacyDecalPath : GamePaths.Tex.TransparentPath; - var legacyDecalNode = genericContext.CreateNodeFromTex(human->LegacyBodyDecal, legacyDecalPath); - if (legacyDecalNode != null) + if (genericContext.CreateNodeFromTex(human->LegacyBodyDecal, legacyDecalPath) is { } legacyDecalNode) { legacyDecalNode.ForceProtected = !hasLegacyDecal; if (globalContext.WithUiData) @@ -252,7 +239,8 @@ public class ResourceTree } } - private unsafe void AddSkeleton(List nodes, ResolveContext context, void* eid, Skeleton* skeleton, BonePhysicsModule* physics, string prefix = "") + private unsafe void AddSkeleton(List nodes, ResolveContext context, void* eid, Skeleton* skeleton, BonePhysicsModule* physics, + string prefix = "") { var eidNode = context.CreateNodeFromEid((ResourceHandle*)eid); if (eidNode != null) @@ -269,8 +257,7 @@ public class ResourceTree { // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) var phybHandle = physics != null ? ((ResourceHandle**)((nint)physics + 0x190))[i] : null; - var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i], phybHandle, (uint)i); - if (sklbNode != null) + if (context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i], phybHandle, (uint)i) is { } sklbNode) { if (context.Global.WithUiData) sklbNode.FallbackName = $"{prefix}Skeleton #{i}"; @@ -279,10 +266,11 @@ public class ResourceTree } } - private unsafe void AddMaterialAnimationSkeleton(List nodes, ResolveContext context, SkeletonResourceHandle* sklbHandle, string prefix = "") + private unsafe void AddMaterialAnimationSkeleton(List nodes, ResolveContext context, SkeletonResourceHandle* sklbHandle, + string prefix = "") { var sklbNode = context.CreateNodeFromMaterialSklb(sklbHandle); - if (sklbNode == null) + if (sklbNode is null) return; if (context.Global.WithUiData) From 9b25193d4e6feb39136b69a522b84b395b35fce2 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 27 Feb 2025 05:51:25 +0100 Subject: [PATCH 5/5] ImUtf8 and null-check cleanup. --- .../ModEditWindow.QuickImport.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs index a49d2933..00caaabc 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs @@ -1,8 +1,7 @@ using Dalamud.Interface; using ImGuiNET; using Lumina.Data; -using OtterGui; -using OtterGui.Raii; +using OtterGui.Text; using Penumbra.Api.Enums; using Penumbra.GameData.Files; using Penumbra.Interop.ResourceTree; @@ -43,7 +42,7 @@ public partial class ModEditWindow private void DrawQuickImportTab() { - using var tab = ImRaii.TabItem("Import from Screen"); + using var tab = ImUtf8.TabItem("Import from Screen"u8); if (!tab) { _quickImportActions.Clear(); @@ -73,14 +72,14 @@ public partial class ModEditWindow else { var file = _gameData.GetFile(path); - writable = file == null ? null : new RawGameFileWritable(file); + writable = file is null ? null : new RawGameFileWritable(file); } _quickImportWritables.Add(resourceNode.FullPath, writable); } - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Save.ToIconString(), buttonSize, "Export this file.", - resourceNode.FullPath.FullName.Length == 0 || writable == null, true)) + if (ImUtf8.IconButton(FontAwesomeIcon.Save, "Export this file."u8, buttonSize, + resourceNode.FullPath.FullName.Length is 0 || writable is null)) { var fullPathStr = resourceNode.FullPath.FullName; var ext = resourceNode.PossibleGamePaths.Length == 1 @@ -112,16 +111,17 @@ public partial class ModEditWindow var canQuickImport = quickImport.CanExecute; var quickImportEnabled = canQuickImport && (!resourceNode.Protected || _config.DeleteModModifier.IsActive()); - if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.FileImport.ToIconString(), buttonSize, + if (ImUtf8.IconButton(FontAwesomeIcon.FileImport, $"Add a copy of this file to {quickImport.OptionName}.{(canQuickImport && !quickImportEnabled ? $"\nHold {_config.DeleteModModifier} while clicking to add." : string.Empty)}", - !quickImportEnabled, true)) + buttonSize, + !quickImportEnabled)) { quickImport.Execute(); _quickImportActions.Remove((resourceNode.GamePath, writable)); } } - private record class RawFileWritable(string Path) : IWritable + private record RawFileWritable(string Path) : IWritable { public bool Valid => true; @@ -130,7 +130,7 @@ public partial class ModEditWindow => File.ReadAllBytes(Path); } - private record class RawGameFileWritable(FileResource FileResource) : IWritable + private record RawGameFileWritable(FileResource FileResource) : IWritable { public bool Valid => true; @@ -188,19 +188,19 @@ public partial class ModEditWindow public static QuickImportAction Prepare(ModEditWindow owner, Utf8GamePath gamePath, IWritable? file) { var editor = owner._editor; - if (editor == null) + if (editor is null) return new QuickImportAction(owner._editor, FallbackOptionName, gamePath); var subMod = editor.Option!; var optionName = subMod is IModOption o ? o.FullName : FallbackOptionName; - if (gamePath.IsEmpty || file == null || editor.FileEditor.Changes) + if (gamePath.IsEmpty || file is null || editor.FileEditor.Changes) return new QuickImportAction(editor, optionName, gamePath); if (subMod.Files.ContainsKey(gamePath) || subMod.FileSwaps.ContainsKey(gamePath)) return new QuickImportAction(editor, optionName, gamePath); var mod = owner.Mod; - if (mod == null) + if (mod is null) return new QuickImportAction(editor, optionName, gamePath); var (preferredPath, subDirs) = GetPreferredPath(mod, subMod as IModOption, owner._config.ReplaceNonAsciiOnImport); @@ -235,7 +235,7 @@ public partial class ModEditWindow { var path = mod.ModPath; var subDirs = 0; - if (subMod == null) + if (subMod is null) return (path, subDirs); var name = subMod.Name;