From f6cf266e99ae7958c122e8418fd9fe78d7fcb764 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Sat, 6 Sep 2025 23:51:46 -0700 Subject: [PATCH 1/6] fix: Schedule removal of CS types from API - Remove AtkValueSpan from addon args - Don't expose SmnGauge enum - Replace HelpMarker vector4 --- .../Lifecycle/AddonArgTypes/AddonRefreshArgs.cs | 4 ++++ .../Lifecycle/AddonArgTypes/AddonSetupArgs.cs | 4 ++++ Dalamud/Game/Addon/Lifecycle/AddonEvent.cs | 16 ++++++++-------- .../Game/ClientState/JobGauge/Types/SMNGauge.cs | 3 +++ Dalamud/Game/Gui/Dtr/DtrBarEntry.cs | 2 +- Dalamud/Interface/Animation/Easing.cs | 2 +- .../Components/ImGuiComponents.HelpMarker.cs | 3 +++ ...i13ToDoAttribute.cs => Api14ToDoAttribute.cs} | 11 ++++++++--- Dalamud/Utility/Util.cs | 2 +- 9 files changed, 33 insertions(+), 14 deletions(-) rename Dalamud/Utility/{Api13ToDoAttribute.cs => Api14ToDoAttribute.cs} (64%) diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs index d28631c3c..83a0ba3e3 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonRefreshArgs.cs @@ -1,3 +1,5 @@ +using Dalamud.Utility; + using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; @@ -31,6 +33,8 @@ public class AddonRefreshArgs : AddonArgs, ICloneable /// /// Gets the AtkValues in the form of a span. /// + [Api14ToDo(Api14ToDoAttribute.Remove)] + [Obsolete($"Exposed ClientStructs type; use {nameof(AtkValues)}/{nameof(AtkValueCount)} instead.", false)] public unsafe Span AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount); /// diff --git a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs index 0dd9ecee2..b5f50b551 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonArgTypes/AddonSetupArgs.cs @@ -1,3 +1,5 @@ +using Dalamud.Utility; + using FFXIVClientStructs.FFXIV.Component.GUI; namespace Dalamud.Game.Addon.Lifecycle.AddonArgTypes; @@ -31,6 +33,8 @@ public class AddonSetupArgs : AddonArgs, ICloneable /// /// Gets the AtkValues in the form of a span. /// + [Api14ToDo(Api14ToDoAttribute.Remove)] + [Obsolete($"Exposed ClientStructs type; use {nameof(AtkValues)}/{nameof(AtkValueCount)} instead.", false)] public unsafe Span AtkValueSpan => new(this.AtkValues.ToPointer(), (int)this.AtkValueCount); /// diff --git a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs index 5fd0ac964..b5a7e4919 100644 --- a/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs +++ b/Dalamud/Game/Addon/Lifecycle/AddonEvent.cs @@ -12,11 +12,11 @@ public enum AddonEvent /// /// An event that is fired prior to an addon being setup with its implementation of /// . This event is useful for modifying the initial data contained within - /// prior to the addon being created. + /// prior to the addon being created. /// /// PreSetup, - + /// /// An event that is fired after an addon has finished its initial setup. This event is particularly useful for /// developers seeking to add custom elements to now-initialized and populated node lists, as well as reading data @@ -64,7 +64,7 @@ public enum AddonEvent /// /// PreFinalize, - + /// /// An event that is fired before a call to is made in response to a /// change in the subscribed or @@ -81,13 +81,13 @@ public enum AddonEvent /// to the Free Company's overview. /// PreRequestedUpdate, - + /// /// An event that is fired after an addon has finished processing an ArrayData update. /// See for more information. /// PostRequestedUpdate, - + /// /// An event that is fired before an addon calls its method. Refreshes are /// generally triggered in response to certain user interactions such as changing tabs, and are primarily used to @@ -96,13 +96,13 @@ public enum AddonEvent /// /// PreRefresh, - + /// /// An event that is fired after an addon has finished its refresh. /// See for more information. /// PostRefresh, - + /// /// An event that is fired before an addon begins processing a user-driven event via /// , such as mousing over an element or clicking a button. This event @@ -112,7 +112,7 @@ public enum AddonEvent /// /// PreReceiveEvent, - + /// /// An event that is fired after an addon finishes calling its method. /// See for more information. diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs index 899ea78eb..0bb5d2463 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs @@ -1,4 +1,6 @@ using Dalamud.Game.ClientState.JobGauge.Enums; +using Dalamud.Utility; + using FFXIVClientStructs.FFXIV.Client.Game.Gauge; namespace Dalamud.Game.ClientState.JobGauge.Types; @@ -69,6 +71,7 @@ public unsafe class SMNGauge : JobGaugeBase /// Gets the current aether flags. /// Use the summon accessors instead. /// + [Api14ToDo("Declare our own enum for this to avoid CS type.")] public AetherFlags AetherFlags => this.Struct->AetherFlags; /// diff --git a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs index 6fdc504ca..42268a5ec 100644 --- a/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs +++ b/Dalamud/Game/Gui/Dtr/DtrBarEntry.cs @@ -143,7 +143,7 @@ internal sealed unsafe class DtrBarEntry : IDisposable, IDtrBarEntry } /// - [Api13ToDo("Maybe make this config scoped to internal name?")] + [Api14ToDo("Maybe make this config scoped to internal name?")] public bool UserHidden => this.configuration.DtrIgnore?.Contains(this.Title) ?? false; /// diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index 0d2057b3b..cc1f48ce7 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -48,7 +48,7 @@ public abstract class Easing /// Gets the current value of the animation, following unclamped logic. /// [Obsolete($"This field has been deprecated. Use either {nameof(ValueClamped)} or {nameof(ValueUnclamped)} instead.", true)] - [Api13ToDo("Map this field to ValueClamped, probably.")] + [Api14ToDo("Map this field to ValueClamped, probably.")] public double Value => this.ValueUnclamped; /// diff --git a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs index 57a4bd150..71edfc759 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs @@ -1,5 +1,7 @@ using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility.Raii; +using Dalamud.Utility; + using FFXIVClientStructs.FFXIV.Common.Math; namespace Dalamud.Interface.Components; @@ -21,6 +23,7 @@ public static partial class ImGuiComponents /// The text to display on hover. /// The icon to use. /// The color of the icon. + [Api14ToDo("Replace CS Vector4 with System.Numerics.Vector4")] public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null) { using var col = new ImRaii.Color(); diff --git a/Dalamud/Utility/Api13ToDoAttribute.cs b/Dalamud/Utility/Api14ToDoAttribute.cs similarity index 64% rename from Dalamud/Utility/Api13ToDoAttribute.cs rename to Dalamud/Utility/Api14ToDoAttribute.cs index 576401cda..f24fcb539 100644 --- a/Dalamud/Utility/Api13ToDoAttribute.cs +++ b/Dalamud/Utility/Api14ToDoAttribute.cs @@ -4,7 +4,7 @@ namespace Dalamud.Utility; /// Utility class for marking something to be changed for API 13, for ease of lookup. /// [AttributeUsage(AttributeTargets.All, Inherited = false)] -internal sealed class Api13ToDoAttribute : Attribute +internal sealed class Api14ToDoAttribute : Attribute { /// /// Marks that this should be made internal. @@ -12,11 +12,16 @@ internal sealed class Api13ToDoAttribute : Attribute public const string MakeInternal = "Make internal."; /// - /// Initializes a new instance of the class. + /// Marks that this should be removed entirely. + /// + public const string Remove = "Remove."; + + /// + /// Initializes a new instance of the class. /// /// The explanation. /// The explanation 2. - public Api13ToDoAttribute(string what, string what2 = "") + public Api14ToDoAttribute(string what, string what2 = "") { _ = what; _ = what2; diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index a1c2eb6b2..cb13c463e 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -75,7 +75,7 @@ public static partial class Util /// /// Gets the Dalamud version. /// - [Api13ToDo("Remove. Make both versions here internal. Add an API somewhere.")] + [Api14ToDo("Remove. Make both versions here internal. Add an API somewhere.")] public static string AssemblyVersion { get; } = Assembly.GetAssembly(typeof(ChatHandlers))!.GetName().Version!.ToString(); From 80ec740b5814b4ff3318942d327a4dd0106035f3 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 11 Sep 2025 00:39:23 -0700 Subject: [PATCH 2/6] test: Add tests to see what happens --- Dalamud.Test/Compliance/PublicApiTests.cs | 76 ++++++++++++++++++++ Dalamud.Test/Dalamud.Test.csproj | 14 ++-- Dalamud/Memory/MemoryHelper.cs | 4 ++ Dalamud/Utility/Numerics/VectorExtensions.cs | 2 + 4 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 Dalamud.Test/Compliance/PublicApiTests.cs diff --git a/Dalamud.Test/Compliance/PublicApiTests.cs b/Dalamud.Test/Compliance/PublicApiTests.cs new file mode 100644 index 000000000..4e51bb932 --- /dev/null +++ b/Dalamud.Test/Compliance/PublicApiTests.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Reflection; + +using Dalamud.Utility; + +using Xunit; +using Xunit.Abstractions; + + +namespace Dalamud.Test.Compliance; + +public class PublicApiTests +{ + private readonly ITestOutputHelper testOutputHelper; + public PublicApiTests(ITestOutputHelper testOutputHelper) + { + this.testOutputHelper = testOutputHelper; + } + + [Fact] + public void NoClientStructsTypes() + { + var clientStructsAssembly = typeof(FFXIVClientStructs.ThisAssembly).Assembly; + + var publicTypes = typeof(Dalamud).Assembly.GetTypes().Where(t => t.IsPublic); + + foreach (var t in publicTypes) + { + if (t.GetCustomAttribute() != null) continue; + + var methods = t.GetMethods().Where(m => m.IsPublic && !m.IsSpecialName); + + foreach (var m in methods) + { + if (m.GetCustomAttribute() != null || m.GetCustomAttribute() != null) continue; + if (m.IsPrivate) continue; + + if (m.ReturnType.Assembly == clientStructsAssembly) + { + Assert.Fail($"Method {t.FullName}.{m.Name} returns a type from FFXIVClientStructs: {m.ReturnType.FullName}"); + } + + foreach (var param in m.GetParameters()) + { + if (param.ParameterType.Assembly == clientStructsAssembly) + { + Assert.Fail($"Method {t.FullName}.{m.Name} has a parameter from FFXIVClientStructs: {param.ParameterType.FullName}"); + } + } + } + + foreach (var p in t.GetProperties()) + { + if (p.GetCustomAttribute() != null || p.GetCustomAttribute() != null) continue; + if (p.GetMethod?.IsPrivate == true && p.SetMethod?.IsPrivate == true) continue; + + if (p.PropertyType.Assembly == clientStructsAssembly) + { + Assert.Fail($"Property {t.FullName}.{p.Name} is a type from FFXIVClientStructs: {p.PropertyType.FullName}"); + } + } + + foreach (var field in t.GetFields()) + { + if (field.GetCustomAttribute() != null || field.GetCustomAttribute() != null) continue; + if (field.IsPrivate) continue; + + if (field.FieldType.Assembly == clientStructsAssembly) + { + Assert.Fail($"Field {t.FullName}.{field.Name} is of a type from FFXIVClientStructs: {field.FieldType.FullName}"); + } + } + } + } +} diff --git a/Dalamud.Test/Dalamud.Test.csproj b/Dalamud.Test/Dalamud.Test.csproj index c6c8f8e8a..0839a39d3 100644 --- a/Dalamud.Test/Dalamud.Test.csproj +++ b/Dalamud.Test/Dalamud.Test.csproj @@ -43,14 +43,14 @@ - + - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Dalamud/Memory/MemoryHelper.cs b/Dalamud/Memory/MemoryHelper.cs index 2eae1be6d..acdc37a34 100644 --- a/Dalamud/Memory/MemoryHelper.cs +++ b/Dalamud/Memory/MemoryHelper.cs @@ -382,6 +382,8 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The read in string. [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete("CS types in Dalamud are deprecated.")] + [Api14ToDo(Api14ToDoAttribute.Remove)] public static SeString ReadSeString(Utf8String* utf8String) => utf8String == null ? string.Empty : SeString.Parse(utf8String->AsSpan()); @@ -613,6 +615,8 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The read in string. [MethodImpl(MethodImplOptions.AggressiveInlining)] + [Obsolete("CS types in Dalamud are deprecated.")] + [Api14ToDo(Api14ToDoAttribute.Remove)] public static unsafe void ReadSeString(Utf8String* utf8String, out SeString value) => value = ReadSeString(utf8String); diff --git a/Dalamud/Utility/Numerics/VectorExtensions.cs b/Dalamud/Utility/Numerics/VectorExtensions.cs index 910dbdd00..ffb32b266 100644 --- a/Dalamud/Utility/Numerics/VectorExtensions.cs +++ b/Dalamud/Utility/Numerics/VectorExtensions.cs @@ -56,6 +56,8 @@ public static class VectorExtensions return new Vector2(v.X, y); } + [Obsolete("CS type is deprecated. Use ByteColor instead.")] + [Api14ToDo(Api14ToDoAttribute.Remove)] public static ByteColor ToByteColor(this Vector4 v) { return new ByteColor { A = (byte)(v.W * 255), R = (byte)(v.X * 255), G = (byte)(v.Y * 255), B = (byte)(v.Z * 255) }; From 8cd84de9d3f243e9e9361e8187d87a723f33915e Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 11 Sep 2025 00:40:24 -0700 Subject: [PATCH 3/6] bweh --- Dalamud.Test/Compliance/PublicApiTests.cs | 38 ++++++++++------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/Dalamud.Test/Compliance/PublicApiTests.cs b/Dalamud.Test/Compliance/PublicApiTests.cs index 4e51bb932..2edf44be2 100644 --- a/Dalamud.Test/Compliance/PublicApiTests.cs +++ b/Dalamud.Test/Compliance/PublicApiTests.cs @@ -5,19 +5,12 @@ using System.Reflection; using Dalamud.Utility; using Xunit; -using Xunit.Abstractions; namespace Dalamud.Test.Compliance; public class PublicApiTests { - private readonly ITestOutputHelper testOutputHelper; - public PublicApiTests(ITestOutputHelper testOutputHelper) - { - this.testOutputHelper = testOutputHelper; - } - [Fact] public void NoClientStructsTypes() { @@ -29,46 +22,49 @@ public class PublicApiTests { if (t.GetCustomAttribute() != null) continue; - var methods = t.GetMethods().Where(m => m.IsPublic && !m.IsSpecialName); - - foreach (var m in methods) + foreach (var m in t.GetMethods().Where(m => m.IsPublic && !m.IsSpecialName)) { - if (m.GetCustomAttribute() != null || m.GetCustomAttribute() != null) continue; - if (m.IsPrivate) continue; + if (m.GetCustomAttribute() != null || + m.GetCustomAttribute() != null) continue; if (m.ReturnType.Assembly == clientStructsAssembly) { - Assert.Fail($"Method {t.FullName}.{m.Name} returns a type from FFXIVClientStructs: {m.ReturnType.FullName}"); + Assert.Fail( + $"Method {t.FullName}.{m.Name} returns a type from FFXIVClientStructs: {m.ReturnType.FullName}"); } foreach (var param in m.GetParameters()) { if (param.ParameterType.Assembly == clientStructsAssembly) { - Assert.Fail($"Method {t.FullName}.{m.Name} has a parameter from FFXIVClientStructs: {param.ParameterType.FullName}"); + Assert.Fail( + $"Method {t.FullName}.{m.Name} has a parameter from FFXIVClientStructs: {param.ParameterType.FullName}"); } } } foreach (var p in t.GetProperties()) { - if (p.GetCustomAttribute() != null || p.GetCustomAttribute() != null) continue; + if (p.GetCustomAttribute() != null || + p.GetCustomAttribute() != null) continue; if (p.GetMethod?.IsPrivate == true && p.SetMethod?.IsPrivate == true) continue; if (p.PropertyType.Assembly == clientStructsAssembly) { - Assert.Fail($"Property {t.FullName}.{p.Name} is a type from FFXIVClientStructs: {p.PropertyType.FullName}"); + Assert.Fail( + $"Property {t.FullName}.{p.Name} is a type from FFXIVClientStructs: {p.PropertyType.FullName}"); } } - foreach (var field in t.GetFields()) + foreach (var f in t.GetFields().Where(f => f.IsPublic && !f.IsSpecialName)) { - if (field.GetCustomAttribute() != null || field.GetCustomAttribute() != null) continue; - if (field.IsPrivate) continue; + if (f.GetCustomAttribute() != null || + f.GetCustomAttribute() != null) continue; - if (field.FieldType.Assembly == clientStructsAssembly) + if (f.FieldType.Assembly == clientStructsAssembly) { - Assert.Fail($"Field {t.FullName}.{field.Name} is of a type from FFXIVClientStructs: {field.FieldType.FullName}"); + Assert.Fail( + $"Field {t.FullName}.{f.Name} is of a type from FFXIVClientStructs: {f.FieldType.FullName}"); } } } From 77a15a533d7e0a3d57df74fa730becec14030d5d Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 11 Sep 2025 09:56:25 -0700 Subject: [PATCH 4/6] test: Swap test strategy to allow-listing --- Dalamud.Test/Compliance/PublicApiTests.cs | 81 +++++++++++++++-------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/Dalamud.Test/Compliance/PublicApiTests.cs b/Dalamud.Test/Compliance/PublicApiTests.cs index 2edf44be2..05a05fe3f 100644 --- a/Dalamud.Test/Compliance/PublicApiTests.cs +++ b/Dalamud.Test/Compliance/PublicApiTests.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; -using Dalamud.Utility; +using Dalamud.Bindings.ImGui; +using Dalamud.Bindings.ImGuizmo; +using Dalamud.Bindings.ImPlot; using Xunit; @@ -11,62 +14,86 @@ namespace Dalamud.Test.Compliance; public class PublicApiTests { + private static List PermittedAssemblies { get; } = + [ + typeof(object).Assembly, + typeof(Dalamud).Assembly, + + typeof(SharpDX.Color).Assembly, + typeof(ImGui).Assembly, + typeof(ImGuizmo).Assembly, + typeof(ImPlot).Assembly, + + // exposed to plugins via API + typeof(Lumina.GameData).Assembly, + typeof(Lumina.Excel.Sheets.Action).Assembly, + ]; + + private static List PermittedTypes { get; } = [ + typeof(Serilog.ILogger), + typeof(Serilog.Core.LoggingLevelSwitch), + typeof(Serilog.Events.LogEventLevel), + ]; + [Fact] - public void NoClientStructsTypes() + public void NoRestrictedTypes() { - var clientStructsAssembly = typeof(FFXIVClientStructs.ThisAssembly).Assembly; - - var publicTypes = typeof(Dalamud).Assembly.GetTypes().Where(t => t.IsPublic); - - foreach (var t in publicTypes) + foreach (var type in typeof(Dalamud).Assembly.GetTypes().Where(t => t.IsPublic)) { - if (t.GetCustomAttribute() != null) continue; + if (type.GetCustomAttribute() != null) continue; - foreach (var m in t.GetMethods().Where(m => m.IsPublic && !m.IsSpecialName)) + foreach (var m in type.GetMethods().Where(m => m.IsPublic && !m.IsSpecialName)) { - if (m.GetCustomAttribute() != null || - m.GetCustomAttribute() != null) continue; + if (m.GetCustomAttribute() != null) continue; - if (m.ReturnType.Assembly == clientStructsAssembly) + if (!this.IsPermittedType(m.ReturnType)) { - Assert.Fail( - $"Method {t.FullName}.{m.Name} returns a type from FFXIVClientStructs: {m.ReturnType.FullName}"); + Assert.Fail($"Method {type.FullName}.{m.Name} returns invalid type: {m.ReturnType.FullName}"); } foreach (var param in m.GetParameters()) { - if (param.ParameterType.Assembly == clientStructsAssembly) + if (!this.IsPermittedType(param.ParameterType)) { - Assert.Fail( - $"Method {t.FullName}.{m.Name} has a parameter from FFXIVClientStructs: {param.ParameterType.FullName}"); + Assert.Fail($"Method {type.FullName}.{m.Name} uses invalid type: {param.ParameterType.FullName}"); } } } - foreach (var p in t.GetProperties()) + foreach (var p in type.GetProperties()) { - if (p.GetCustomAttribute() != null || - p.GetCustomAttribute() != null) continue; + if (p.GetCustomAttribute() != null) continue; if (p.GetMethod?.IsPrivate == true && p.SetMethod?.IsPrivate == true) continue; - if (p.PropertyType.Assembly == clientStructsAssembly) + if (!this.IsPermittedType(p.PropertyType)) { Assert.Fail( - $"Property {t.FullName}.{p.Name} is a type from FFXIVClientStructs: {p.PropertyType.FullName}"); + $"Property {type.FullName}.{p.Name} is invalid type: {p.PropertyType.FullName}"); } } - foreach (var f in t.GetFields().Where(f => f.IsPublic && !f.IsSpecialName)) + foreach (var f in type.GetFields().Where(f => f.IsPublic && !f.IsSpecialName)) { - if (f.GetCustomAttribute() != null || - f.GetCustomAttribute() != null) continue; + if (f.GetCustomAttribute() != null) continue; - if (f.FieldType.Assembly == clientStructsAssembly) + if (!this.IsPermittedType(f.FieldType)) { Assert.Fail( - $"Field {t.FullName}.{f.Name} is of a type from FFXIVClientStructs: {f.FieldType.FullName}"); + $"Field {type.FullName}.{f.Name} is invalid type: {f.FieldType.FullName}"); } } } } + + private bool IsPermittedType(Type subject) + { + if (subject.IsGenericType && !subject.GetGenericArguments().All(this.IsPermittedType)) + { + return false; + } + + return subject.Namespace?.StartsWith("System") == true || + PermittedTypes.Any(t => t == subject) || + PermittedAssemblies.Any(a => a == subject.Assembly); + } } From a1cc4fa91b91452c00c1746dc41016a3c42a91f0 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 11 Sep 2025 10:20:29 -0700 Subject: [PATCH 5/6] fix: Mark remaining as obsolete --- Dalamud.Test/Compliance/PublicApiTests.cs | 15 ++++-- .../ClientState/JobGauge/Types/SMNGauge.cs | 13 ++--- .../Components/ImGuiComponents.HelpMarker.cs | 47 ++++++++++++++++--- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/Dalamud.Test/Compliance/PublicApiTests.cs b/Dalamud.Test/Compliance/PublicApiTests.cs index 05a05fe3f..93a3da514 100644 --- a/Dalamud.Test/Compliance/PublicApiTests.cs +++ b/Dalamud.Test/Compliance/PublicApiTests.cs @@ -14,6 +14,11 @@ namespace Dalamud.Test.Compliance; public class PublicApiTests { + private static List IgnoredTypes { get; } = + [ + typeof(Utility.CStringExtensions), + ]; + private static List PermittedAssemblies { get; } = [ typeof(object).Assembly, @@ -38,7 +43,7 @@ public class PublicApiTests [Fact] public void NoRestrictedTypes() { - foreach (var type in typeof(Dalamud).Assembly.GetTypes().Where(t => t.IsPublic)) + foreach (var type in typeof(Dalamud).Assembly.GetTypes().Where(t => t.IsPublic).Except(IgnoredTypes)) { if (type.GetCustomAttribute() != null) continue; @@ -48,14 +53,14 @@ public class PublicApiTests if (!this.IsPermittedType(m.ReturnType)) { - Assert.Fail($"Method {type.FullName}.{m.Name} returns invalid type: {m.ReturnType.FullName}"); + Assert.Fail($"Method {type.FullName}.{m.Name} returns unapproved type: {m.ReturnType.FullName}"); } foreach (var param in m.GetParameters()) { if (!this.IsPermittedType(param.ParameterType)) { - Assert.Fail($"Method {type.FullName}.{m.Name} uses invalid type: {param.ParameterType.FullName}"); + Assert.Fail($"Method {type.FullName}.{m.Name} uses unapproved type: {param.ParameterType.FullName}"); } } } @@ -68,7 +73,7 @@ public class PublicApiTests if (!this.IsPermittedType(p.PropertyType)) { Assert.Fail( - $"Property {type.FullName}.{p.Name} is invalid type: {p.PropertyType.FullName}"); + $"Property {type.FullName}.{p.Name} is unapproved type: {p.PropertyType.FullName}"); } } @@ -79,7 +84,7 @@ public class PublicApiTests if (!this.IsPermittedType(f.FieldType)) { Assert.Fail( - $"Field {type.FullName}.{f.Name} is invalid type: {f.FieldType.FullName}"); + $"Field {type.FullName}.{f.Name} is unapproved type: {f.FieldType.FullName}"); } } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs index 0bb5d2463..55b08cf2e 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs @@ -72,37 +72,38 @@ public unsafe class SMNGauge : JobGaugeBase /// Use the summon accessors instead. /// [Api14ToDo("Declare our own enum for this to avoid CS type.")] + [Obsolete("Use specific accessors instead until API14.")] public AetherFlags AetherFlags => this.Struct->AetherFlags; /// /// Gets a value indicating whether Bahamut is ready to be summoned. /// /// true or false. - public bool IsBahamutReady => !this.AetherFlags.HasFlag(AetherFlags.PhoenixReady); + public bool IsBahamutReady => !this.Struct->AetherFlags.HasFlag(AetherFlags.PhoenixReady); /// /// Gets a value indicating whether if Phoenix is ready to be summoned. /// /// true or false. - public bool IsPhoenixReady => this.AetherFlags.HasFlag(AetherFlags.PhoenixReady); + public bool IsPhoenixReady => this.Struct->AetherFlags.HasFlag(AetherFlags.PhoenixReady); /// /// Gets a value indicating whether if Ifrit is ready to be summoned. /// /// true or false. - public bool IsIfritReady => this.AetherFlags.HasFlag(AetherFlags.IfritReady); + public bool IsIfritReady => this.Struct->AetherFlags.HasFlag(AetherFlags.IfritReady); /// /// Gets a value indicating whether if Titan is ready to be summoned. /// /// true or false. - public bool IsTitanReady => this.AetherFlags.HasFlag(AetherFlags.TitanReady); + public bool IsTitanReady => this.Struct->AetherFlags.HasFlag(AetherFlags.TitanReady); /// /// Gets a value indicating whether if Garuda is ready to be summoned. /// /// true or false. - public bool IsGarudaReady => this.AetherFlags.HasFlag(AetherFlags.GarudaReady); + public bool IsGarudaReady => this.Struct->AetherFlags.HasFlag(AetherFlags.GarudaReady); /// /// Gets a value indicating whether if Ifrit is currently attuned. @@ -131,5 +132,5 @@ public unsafe class SMNGauge : JobGaugeBase /// /// Gets the amount of Aetherflow available. /// - public byte AetherflowStacks => (byte)(this.AetherFlags & AetherFlags.Aetherflow); + public byte AetherflowStacks => (byte)(this.Struct->AetherFlags & AetherFlags.Aetherflow); } diff --git a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs index 71edfc759..19e6eced8 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs @@ -4,6 +4,8 @@ using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Common.Math; +#pragma warning disable CS0618 // Type or member is obsolete. To be fixed with API14. + namespace Dalamud.Interface.Components; /// @@ -23,15 +25,10 @@ public static partial class ImGuiComponents /// The text to display on hover. /// The icon to use. /// The color of the icon. - [Api14ToDo("Replace CS Vector4 with System.Numerics.Vector4")] - public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null) + public static void HelpMarker(string helpText, FontAwesomeIcon icon, System.Numerics.Vector4 color) { using var col = new ImRaii.Color(); - - if (color.HasValue) - { - col.Push(ImGuiCol.TextDisabled, color.Value); - } + col.Push(ImGuiCol.TextDisabled, color); ImGui.SameLine(); @@ -51,4 +48,40 @@ public static partial class ImGuiComponents } } } + + /// + /// HelpMarker component to add a custom icon with text on hover. + /// + /// The text to display on hover. + /// The icon to use. + /// The color of the icon. + [Api14ToDo(Api14ToDoAttribute.Remove)] + [Obsolete("CS type is deprecated. Use System.Numerics.Vector4 instead.")] + public static void HelpMarker(string helpText, FontAwesomeIcon icon, Vector4? color = null) + { + if (color.HasValue) + { + HelpMarker(helpText, icon, color.Value); + return; + } + + // FIXME: Code duplication is easier than splitting up the Nullable in a way that doesn't break the API. + ImGui.SameLine(); + + using (ImRaii.PushFont(UiBuilder.IconFont)) + { + ImGui.TextDisabled(icon.ToIconString()); + } + + if (ImGui.IsItemHovered()) + { + using (ImRaii.Tooltip()) + { + using (ImRaii.TextWrapPos(ImGui.GetFontSize() * 35.0f)) + { + ImGui.Text(helpText); + } + } + } + } } From 95e7299017333ea9e0a736ff6110fa73bb809b32 Mon Sep 17 00:00:00 2001 From: Kaz Wolfe Date: Thu, 11 Sep 2025 10:22:49 -0700 Subject: [PATCH 6/6] test: add docs --- Dalamud.Test/Compliance/PublicApiTests.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Dalamud.Test/Compliance/PublicApiTests.cs b/Dalamud.Test/Compliance/PublicApiTests.cs index 93a3da514..1ce8b19fa 100644 --- a/Dalamud.Test/Compliance/PublicApiTests.cs +++ b/Dalamud.Test/Compliance/PublicApiTests.cs @@ -3,10 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using Dalamud.Bindings.ImGui; -using Dalamud.Bindings.ImGuizmo; -using Dalamud.Bindings.ImPlot; - using Xunit; @@ -24,10 +20,11 @@ public class PublicApiTests typeof(object).Assembly, typeof(Dalamud).Assembly, + // Imgui and friends typeof(SharpDX.Color).Assembly, - typeof(ImGui).Assembly, - typeof(ImGuizmo).Assembly, - typeof(ImPlot).Assembly, + typeof(Bindings.ImGui.ImGui).Assembly, + typeof(Bindings.ImGuizmo.ImGuizmo).Assembly, + typeof(Bindings.ImPlot.ImPlot).Assembly, // exposed to plugins via API typeof(Lumina.GameData).Assembly, @@ -35,6 +32,7 @@ public class PublicApiTests ]; private static List PermittedTypes { get; } = [ + // Used for IPluginLog, limited serilog exposure is OK. typeof(Serilog.ILogger), typeof(Serilog.Core.LoggingLevelSwitch), typeof(Serilog.Events.LogEventLevel),