diff --git a/Dalamud.Boot/logging.h b/Dalamud.Boot/logging.h index eff112a1b..1428ec7ff 100644 --- a/Dalamud.Boot/logging.h +++ b/Dalamud.Boot/logging.h @@ -111,9 +111,17 @@ namespace logging { * @param arg1 First format parameter. * @param args Second and further format parameters, if any. */ - template - void print(Level level, const char* fmt, Arg&& arg1, Args&&...args) { - print(level, std::vformat(fmt, std::make_format_args(to_format_arg(std::forward(arg1)), to_format_arg(std::forward(args))...))); + template + void print(Level level, const char* fmt, Arg&& arg1, Args&&... args) { + // make_format_args only accepts references now due to https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2905r2.html + // we can switch std::runtime_format in C++26 :) https://isocpp.org/files/papers/P2918R0.html + auto transformed_args = std::make_tuple(to_format_arg(arg1), to_format_arg(args)...); + auto format_args = std::apply( + [&](auto&... elems) { return std::make_format_args(elems...); }, + transformed_args + ); + + print(level, std::vformat(fmt, format_args)); } template void V(Args&&...args) { print(Level::Verbose, std::forward(args)...); } diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 2e9e9df48..67c220800 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -162,6 +162,12 @@ internal sealed class DalamudConfiguration : IInternalDisposableService [Obsolete("It happens that nobody touched this setting", true)] public float FontGammaLevel { get; set; } = 1.4f; + /// Gets or sets the opacity of the IME state indicator. + /// 0 will hide the state indicator. 1 will make the state indicator fully visible. Values outside the + /// range will be clamped to [0, 1]. + /// See to . + public float ImeStateIndicatorOpacity { get; set; } = 1f; + /// /// Gets or sets a value indicating whether or not plugin UI should be hidden. /// diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 364e8c90d..e8ec785b8 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -8,7 +8,7 @@ - 9.1.0.7 + 9.1.0.9 XIV Launcher addon framework $(DalamudVersion) $(DalamudVersion) diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs index a9b9ef5fa..8af02ba70 100644 --- a/Dalamud/Game/Addon/Events/AddonEventManager.cs +++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs @@ -221,7 +221,7 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo { this.plugin = plugin; - this.eventManagerService.AddPluginEventController(plugin.Manifest.WorkingPluginId); + this.eventManagerService.AddPluginEventController(plugin.EffectiveWorkingPluginId); } /// @@ -233,16 +233,16 @@ internal class AddonEventManagerPluginScoped : IInternalDisposableService, IAddo this.eventManagerService.ResetCursor(); } - this.eventManagerService.RemovePluginEventController(this.plugin.Manifest.WorkingPluginId); + this.eventManagerService.RemovePluginEventController(this.plugin.EffectiveWorkingPluginId); } /// public IAddonEventHandle? AddEvent(IntPtr atkUnitBase, IntPtr atkResNode, AddonEventType eventType, IAddonEventManager.AddonEventHandler eventHandler) - => this.eventManagerService.AddEvent(this.plugin.Manifest.WorkingPluginId, atkUnitBase, atkResNode, eventType, eventHandler); + => this.eventManagerService.AddEvent(this.plugin.EffectiveWorkingPluginId, atkUnitBase, atkResNode, eventType, eventHandler); /// public void RemoveEvent(IAddonEventHandle eventHandle) - => this.eventManagerService.RemoveEvent(this.plugin.Manifest.WorkingPluginId, eventHandle); + => this.eventManagerService.RemoveEvent(this.plugin.EffectiveWorkingPluginId, eventHandle); /// public void SetCursor(AddonCursorType cursor) diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs index 451d43065..5c87ad43b 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -15,11 +15,11 @@ public class AutoTranslatePayload : Payload, ITextProvider { private string text; - [JsonProperty] - private uint group; + [JsonProperty("group")] + public uint Group { get; private set; } - [JsonProperty] - private uint key; + [JsonProperty("key")] + public uint Key { get; private set; } /// /// Initializes a new instance of the class. @@ -34,8 +34,8 @@ public class AutoTranslatePayload : Payload, ITextProvider public AutoTranslatePayload(uint group, uint key) { // TODO: friendlier ctor? not sure how to handle that given how weird the tables are - this.group = group; - this.key = key; + this.Group = group; + this.Key = key; } /// @@ -69,20 +69,20 @@ public class AutoTranslatePayload : Payload, ITextProvider /// A string that represents the current object. public override string ToString() { - return $"{this.Type} - Group: {this.group}, Key: {this.key}, Text: {this.Text}"; + return $"{this.Type} - Group: {this.Group}, Key: {this.Key}, Text: {this.Text}"; } /// protected override byte[] EncodeImpl() { - var keyBytes = MakeInteger(this.key); + var keyBytes = MakeInteger(this.Key); var chunkLen = keyBytes.Length + 2; var bytes = new List() { START_BYTE, (byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen, - (byte)this.group, + (byte)this.Group, }; bytes.AddRange(keyBytes); bytes.Add(END_BYTE); @@ -95,9 +95,9 @@ public class AutoTranslatePayload : Payload, ITextProvider { // this seems to always be a bare byte, and not following normal integer encoding // the values in the table are all <70 so this is presumably ok - this.group = reader.ReadByte(); + this.Group = reader.ReadByte(); - this.key = GetInteger(reader); + this.Key = GetInteger(reader); } private string Resolve() @@ -112,13 +112,13 @@ public class AutoTranslatePayload : Payload, ITextProvider // try to get the row in the Completion table itself, because this is 'easiest' // The row may not exist at all (if the Key is for another table), or it could be the wrong row // (again, if it's meant for another table) - row = sheet.GetRow(this.key); + row = sheet.GetRow(this.Key); } catch { } // don't care, row will be null - if (row?.Group == this.group) + if (row?.Group == this.Group) { // if the row exists in this table and the group matches, this is actually the correct data value = row.Text; @@ -129,30 +129,30 @@ public class AutoTranslatePayload : Payload, ITextProvider { // we need to get the linked table and do the lookup there instead // in this case, there will only be one entry for this group id - row = sheet.First(r => r.Group == this.group); + row = sheet.First(r => r.Group == this.Group); // many of the names contain valid id ranges after the table name, but we don't need those var actualTableName = row.LookupTable.RawString.Split('[')[0]; var name = actualTableName switch { - "Action" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "ActionComboRoute" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "BuddyAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "ClassJob" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "Companion" => this.DataResolver.GetExcelSheet().GetRow(this.key).Singular, - "CraftAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "GeneralAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "GuardianDeity" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "MainCommand" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "Mount" => this.DataResolver.GetExcelSheet().GetRow(this.key).Singular, - "Pet" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "PetAction" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "PetMirage" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "PlaceName" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, - "Race" => this.DataResolver.GetExcelSheet().GetRow(this.key).Masculine, - "TextCommand" => this.DataResolver.GetExcelSheet().GetRow(this.key).Command, - "Tribe" => this.DataResolver.GetExcelSheet().GetRow(this.key).Masculine, - "Weather" => this.DataResolver.GetExcelSheet().GetRow(this.key).Name, + "Action" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "ActionComboRoute" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "BuddyAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "ClassJob" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "Companion" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Singular, + "CraftAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "GeneralAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "GuardianDeity" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "MainCommand" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "Mount" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Singular, + "Pet" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "PetAction" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "PetMirage" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "PlaceName" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, + "Race" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Masculine, + "TextCommand" => this.ResolveTextCommand(), + "Tribe" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Masculine, + "Weather" => this.DataResolver.GetExcelSheet().GetRow(this.Key).Name, _ => throw new Exception(actualTableName), }; @@ -160,10 +160,18 @@ public class AutoTranslatePayload : Payload, ITextProvider } catch (Exception e) { - Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this.Type} - Group: {this.group}, Key: {this.key}"); + Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this.Type} - Group: {this.Group}, Key: {this.Key}"); } } return value; } + + private Lumina.Text.SeString ResolveTextCommand() + { + // TextCommands prioritize the `Alias` field, if it not empty + // Example for this is /rangerpose2l which becomes /blackrangerposeb in chat + var result = this.DataResolver.GetExcelSheet().GetRow(this.Key); + return result.Alias.Payloads.Count > 0 ? result.Alias : result.Command; + } } diff --git a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs index 1138d4e07..eb096a771 100644 --- a/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs +++ b/Dalamud/Hooking/Internal/GameInteropProviderPluginScoped.cs @@ -47,7 +47,7 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IInternal } /// - public Hook HookFromFunctionPointerVariable(IntPtr address, T detour) where T : Delegate + public Hook HookFromFunctionPointerVariable(nint address, T detour) where T : Delegate { var hook = Hook.FromFunctionPointerVariable(address, detour); this.trackedHooks.Add(hook); @@ -71,13 +71,21 @@ internal class GameInteropProviderPluginScoped : IGameInteropProvider, IInternal } /// - public Hook HookFromAddress(IntPtr procAddress, T detour, IGameInteropProvider.HookBackend backend = IGameInteropProvider.HookBackend.Automatic) where T : Delegate + public Hook HookFromAddress(nint procAddress, T detour, IGameInteropProvider.HookBackend backend = IGameInteropProvider.HookBackend.Automatic) where T : Delegate { var hook = Hook.FromAddress(procAddress, detour, backend == IGameInteropProvider.HookBackend.MinHook); this.trackedHooks.Add(hook); return hook; } + /// + public Hook HookFromAddress(UIntPtr procAddress, T detour, IGameInteropProvider.HookBackend backend = IGameInteropProvider.HookBackend.Automatic) where T : Delegate + => this.HookFromAddress((nint)procAddress, detour, backend); + + /// + public unsafe Hook HookFromAddress(void* procAddress, T detour, IGameInteropProvider.HookBackend backend = IGameInteropProvider.HookBackend.Automatic) where T : Delegate + => this.HookFromAddress((nint)procAddress, detour, backend); + /// public Hook HookFromSignature(string signature, T detour, IGameInteropProvider.HookBackend backend = IGameInteropProvider.HookBackend.Automatic) where T : Delegate => this.HookFromAddress(this.scanner.ScanText(signature), detour, backend); diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs index 61ec24484..c061ec12d 100644 --- a/Dalamud/Interface/Internal/DalamudIme.cs +++ b/Dalamud/Interface/Internal/DalamudIme.cs @@ -11,6 +11,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Text.Unicode; +using Dalamud.Configuration.Internal; using Dalamud.Game.Text; using Dalamud.Hooking.WndProcHook; using Dalamud.Interface.Colors; @@ -74,6 +75,9 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService private static readonly delegate* unmanaged StbTextUndo; + [ServiceManager.ServiceDependency] + private readonly DalamudConfiguration dalamudConfiguration = Service.Get(); + [ServiceManager.ServiceDependency] private readonly WndProcHookManager wndProcHookManager = Service.Get(); @@ -774,30 +778,42 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService ImGui.GetStyle().WindowRounding); } + var stateOpacity = Math.Clamp(this.dalamudConfiguration.ImeStateIndicatorOpacity, 0, 1); + var stateBg = ImGui.GetColorU32( + new Vector4(1, 1, 1, MathF.Pow(stateOpacity, 2)) * *ImGui.GetStyleColorVec4(ImGuiCol.WindowBg)); + var stateFg = + ImGui.GetColorU32(new Vector4(1, 1, 1, stateOpacity) * *ImGui.GetStyleColorVec4(ImGuiCol.Text)); if (!expandUpward && drawIme) { - for (var dx = -2; dx <= 2; dx++) + if (stateBg >= 0x1000000) { - for (var dy = -2; dy <= 2; dy++) + for (var dx = -2; dx <= 2; dx++) { - if (dx != 0 || dy != 0) + for (var dy = -2; dy <= 2; dy++) { - imeIconFont.RenderChar( - drawList, - imeIconFont.FontSize, - cursor + new Vector2(dx, dy), - ImGui.GetColorU32(ImGuiCol.WindowBg), - ime.inputModeIcon); + if (dx != 0 || dy != 0) + { + imeIconFont.RenderChar( + drawList, + imeIconFont.FontSize, + cursor + new Vector2(dx, dy), + stateBg, + ime.inputModeIcon); + } } } } - imeIconFont.RenderChar( - drawList, - imeIconFont.FontSize, - cursor, - ImGui.GetColorU32(ImGuiCol.Text), - ime.inputModeIcon); + if (stateFg >= 0x1000000) + { + imeIconFont.RenderChar( + drawList, + imeIconFont.FontSize, + cursor, + stateFg, + ime.inputModeIcon); + } + cursor.Y += candTextSize.Y + spaceY; } @@ -851,28 +867,34 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService if (expandUpward && drawIme) { - for (var dx = -2; dx <= 2; dx++) + if (stateBg >= 0x1000000) { - for (var dy = -2; dy <= 2; dy++) + for (var dx = -2; dx <= 2; dx++) { - if (dx != 0 || dy != 0) + for (var dy = -2; dy <= 2; dy++) { - imeIconFont.RenderChar( - drawList, - imeIconFont.FontSize, - cursor + new Vector2(dx, dy), - ImGui.GetColorU32(ImGuiCol.WindowBg), - ime.inputModeIcon); + if (dx != 0 || dy != 0) + { + imeIconFont.RenderChar( + drawList, + imeIconFont.FontSize, + cursor + new Vector2(dx, dy), + ImGui.GetColorU32(ImGuiCol.WindowBg), + ime.inputModeIcon); + } } } } - imeIconFont.RenderChar( - drawList, - imeIconFont.FontSize, - cursor, - ImGui.GetColorU32(ImGuiCol.Text), - ime.inputModeIcon); + if (stateFg >= 0x1000000) + { + imeIconFont.RenderChar( + drawList, + imeIconFont.FontSize, + cursor, + ImGui.GetColorU32(ImGuiCol.Text), + ime.inputModeIcon); + } } return; diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index ac899a86c..e0579270c 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -2438,7 +2438,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (this.hasDevPlugins) { ImGuiHelpers.ScaledDummy(3); - ImGui.TextColored(ImGuiColors.DalamudGrey, $"WorkingPluginId: {manifest.WorkingPluginId}"); + ImGui.TextColored(ImGuiColors.DalamudGrey, $"WorkingPluginId: {plugin.EffectiveWorkingPluginId}"); ImGuiHelpers.ScaledDummy(3); } @@ -2621,10 +2621,10 @@ internal class PluginInstallerWindow : Window, IDisposable var applicableForProfiles = plugin.Manifest.SupportsProfiles /*&& !plugin.IsDev*/; var profilesThatWantThisPlugin = profileManager.Profiles - .Where(x => x.WantsPlugin(plugin.Manifest.WorkingPluginId) != null) + .Where(x => x.WantsPlugin(plugin.EffectiveWorkingPluginId) != null) .ToArray(); var isInSingleProfile = profilesThatWantThisPlugin.Length == 1; - var isDefaultPlugin = profileManager.IsInDefaultProfile(plugin.Manifest.WorkingPluginId); + var isDefaultPlugin = profileManager.IsInDefaultProfile(plugin.EffectiveWorkingPluginId); // Disable everything if the updater is running or another plugin is operating var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; @@ -2659,17 +2659,17 @@ internal class PluginInstallerWindow : Window, IDisposable foreach (var profile in profileManager.Profiles.Where(x => !x.IsDefaultProfile)) { - var inProfile = profile.WantsPlugin(plugin.Manifest.WorkingPluginId) != null; + var inProfile = profile.WantsPlugin(plugin.EffectiveWorkingPluginId) != null; if (ImGui.Checkbox($"###profilePick{profile.Guid}{plugin.Manifest.InternalName}", ref inProfile)) { if (inProfile) { - Task.Run(() => profile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, true)) + Task.Run(() => profile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, true)) .ContinueWith(this.DisplayErrorContinuation, Locs.Profiles_CouldNotAdd); } else { - Task.Run(() => profile.RemoveAsync(plugin.Manifest.WorkingPluginId)) + Task.Run(() => profile.RemoveAsync(plugin.EffectiveWorkingPluginId)) .ContinueWith(this.DisplayErrorContinuation, Locs.Profiles_CouldNotRemove); } } @@ -2689,11 +2689,11 @@ internal class PluginInstallerWindow : Window, IDisposable if (ImGuiComponents.IconButton(FontAwesomeIcon.Times)) { // TODO: Work this out - Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, plugin.IsLoaded, false)) + Task.Run(() => profileManager.DefaultProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, plugin.IsLoaded, false)) .GetAwaiter().GetResult(); foreach (var profile in profileManager.Profiles.Where(x => !x.IsDefaultProfile && x.Plugins.Any(y => y.InternalName == plugin.Manifest.InternalName))) { - Task.Run(() => profile.RemoveAsync(plugin.Manifest.WorkingPluginId, false)) + Task.Run(() => profile.RemoveAsync(plugin.EffectiveWorkingPluginId, false)) .GetAwaiter().GetResult(); } @@ -2718,7 +2718,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (ImGui.IsItemHovered()) ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); } - else if (this.enableDisableStatus == OperationStatus.InProgress && this.enableDisableWorkingPluginId == plugin.Manifest.WorkingPluginId) + else if (this.enableDisableStatus == OperationStatus.InProgress && this.enableDisableWorkingPluginId == plugin.EffectiveWorkingPluginId) { ImGuiComponents.DisabledToggleButton(toggleId, this.loadingIndicatorKind == LoadingIndicatorKind.EnablingSingle); } @@ -2743,9 +2743,9 @@ internal class PluginInstallerWindow : Window, IDisposable { // Reload the devPlugin manifest if it's a dev plugin // The plugin might rely on changed values in the manifest - if (plugin.IsDev) + if (plugin is LocalDevPlugin devPlugin) { - plugin.ReloadManifest(); + devPlugin.ReloadManifest(); } } catch (Exception ex) @@ -2761,13 +2761,13 @@ internal class PluginInstallerWindow : Window, IDisposable { this.enableDisableStatus = OperationStatus.InProgress; this.loadingIndicatorKind = LoadingIndicatorKind.DisablingSingle; - this.enableDisableWorkingPluginId = plugin.Manifest.WorkingPluginId; + this.enableDisableWorkingPluginId = plugin.EffectiveWorkingPluginId; Task.Run(async () => { await plugin.UnloadAsync(); await applicableProfile.AddOrUpdateAsync( - plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, false, false); + plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false); notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); }).ContinueWith(t => @@ -2782,9 +2782,9 @@ internal class PluginInstallerWindow : Window, IDisposable { this.enableDisableStatus = OperationStatus.InProgress; this.loadingIndicatorKind = LoadingIndicatorKind.EnablingSingle; - this.enableDisableWorkingPluginId = plugin.Manifest.WorkingPluginId; + this.enableDisableWorkingPluginId = plugin.EffectiveWorkingPluginId; - await applicableProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, true, false); + await applicableProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, true, false); await plugin.LoadAsync(PluginLoadReason.Installer); notifications.AddNotification(Locs.Notifications_PluginEnabled(plugin.Manifest.Name), Locs.Notifications_PluginEnabledTitle, NotificationType.Success); @@ -2805,7 +2805,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (shouldUpdate) { // We need to update the profile right here, because PM will not enable the plugin otherwise - await applicableProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, true, false); + await applicableProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, true, false); await this.UpdateSinglePlugin(availableUpdate); } else @@ -3076,7 +3076,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (localPlugin is LocalDevPlugin plugin) { var isInDefaultProfile = - Service.Get().IsInDefaultProfile(localPlugin.Manifest.WorkingPluginId); + Service.Get().IsInDefaultProfile(localPlugin.EffectiveWorkingPluginId); // https://colorswall.com/palette/2868/ var greenColor = new Vector4(0x5C, 0xB8, 0x5C, 0xFF) / 0xFF; @@ -3426,7 +3426,7 @@ internal class PluginInstallerWindow : Window, IDisposable this.pluginListAvailable.Sort((p1, p2) => p1.Name.CompareTo(p2.Name)); var profman = Service.Get(); - this.pluginListInstalled.Sort((p1, p2) => profman.IsInDefaultProfile(p1.Manifest.WorkingPluginId).CompareTo(profman.IsInDefaultProfile(p2.Manifest.WorkingPluginId))); + this.pluginListInstalled.Sort((p1, p2) => profman.IsInDefaultProfile(p1.EffectiveWorkingPluginId).CompareTo(profman.IsInDefaultProfile(p2.EffectiveWorkingPluginId))); break; default: throw new InvalidEnumArgumentException("Unknown plugin sort type."); diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs index 3198eae97..a5081bdb7 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/ProfileManagerWidget.cs @@ -324,7 +324,7 @@ internal class ProfileManagerWidget if (ImGui.Selectable($"{plugin.Manifest.Name}{(plugin is LocalDevPlugin ? "(dev plugin)" : string.Empty)}###selector{plugin.Manifest.InternalName}")) { - Task.Run(() => profile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, true, false)) + Task.Run(() => profile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, true, false)) .ContinueWith(this.installer.DisplayErrorContinuation, Locs.ErrorCouldNotChangeState); } } @@ -430,7 +430,7 @@ internal class ProfileManagerWidget foreach (var profileEntry in profile.Plugins.ToArray()) { didAny = true; - var pmPlugin = pm.InstalledPlugins.FirstOrDefault(x => x.Manifest.WorkingPluginId == profileEntry.WorkingPluginId); + var pmPlugin = pm.InstalledPlugins.FirstOrDefault(x => x.EffectiveWorkingPluginId == profileEntry.WorkingPluginId); var btnOffset = 2; if (pmPlugin != null) @@ -485,7 +485,7 @@ internal class ProfileManagerWidget FontAwesomeIcon.Check, "Yes, use this one")) { - profileEntry.WorkingPluginId = firstAvailableInstalled.Manifest.WorkingPluginId; + profileEntry.WorkingPluginId = firstAvailableInstalled.EffectiveWorkingPluginId; Task.Run(async () => { await profman.ApplyAllWantStatesAsync(); diff --git a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs index 2a8e6969a..fd4949533 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/SettingsWindow.cs @@ -152,13 +152,12 @@ internal class SettingsWindow : Window settingsTab.OnOpen(); } - if (ImGui.BeginChild($"###settings_scrolling_{settingsTab.Title}", new Vector2(-1, -1), false)) - { + using var tabChild = ImRaii.Child( + $"###settings_scrolling_{settingsTab.Title}", + new Vector2(-1, -1), + false); + if (tabChild) settingsTab.Draw(); - } - - ImGui.EndChild(); - ImGui.EndTabItem(); } else if (settingsTab.IsOpen) { @@ -208,33 +207,34 @@ internal class SettingsWindow : Window ImGui.SetCursorPos(windowSize - ImGuiHelpers.ScaledVector2(70)); - if (ImGui.BeginChild("###settingsFinishButton")) + using (var buttonChild = ImRaii.Child("###settingsFinishButton")) { - using var disabled = ImRaii.Disabled(this.tabs.Any(x => x.Entries.Any(y => !y.IsValid))); - - using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 100f)) + if (buttonChild) { - using var font = ImRaii.PushFont(InterfaceManager.IconFont); + using var disabled = ImRaii.Disabled(this.tabs.Any(x => x.Entries.Any(y => !y.IsValid))); - if (ImGui.Button(FontAwesomeIcon.Save.ToIconString(), new Vector2(40))) + using (ImRaii.PushStyle(ImGuiStyleVar.FrameRounding, 100f)) { - this.Save(); + using var font = ImRaii.PushFont(InterfaceManager.IconFont); - if (!ImGui.IsKeyDown(ImGuiKey.ModShift)) - this.IsOpen = false; + if (ImGui.Button(FontAwesomeIcon.Save.ToIconString(), new Vector2(40))) + { + this.Save(); + + if (!ImGui.IsKeyDown(ImGuiKey.ModShift)) + this.IsOpen = false; + } + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(!ImGui.IsKeyDown(ImGuiKey.ModShift) + ? Loc.Localize("DalamudSettingsSaveAndExit", "Save changes and close") + : Loc.Localize("DalamudSettingsSave", "Save changes")); } } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(!ImGui.IsKeyDown(ImGuiKey.ModShift) - ? Loc.Localize("DalamudSettingsSaveAndExit", "Save changes and close") - : Loc.Localize("DalamudSettingsSave", "Save changes")); - } } - ImGui.EndChild(); - ImGui.SetCursorPos(new Vector2(windowSize.X - 250, ImGui.GetTextLineHeightWithSpacing() + (ImGui.GetStyle().FramePadding.Y * 2))); ImGui.SetNextItemWidth(240); ImGui.InputTextWithHint("###searchInput", "Search for settings...", ref this.searchInput, 100); diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs index 8714fd666..d38f9219d 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabAbout.cs @@ -234,7 +234,9 @@ Contribute at: https://github.com/goatcorp/Dalamud { var windowSize = ImGui.GetWindowSize(); - ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar); + using var child = ImRaii.Child("scrolling", new Vector2(-1, -10 * ImGuiHelpers.GlobalScale), false, ImGuiWindowFlags.NoScrollbar); + if (!child) + return; if (this.resetNow) { @@ -295,8 +297,6 @@ Contribute at: https://github.com/goatcorp/Dalamud } } - ImGui.EndChild(); - base.Draw(); } diff --git a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs index 04a05bd76..a582761ba 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Tabs/SettingsTabLook.cs @@ -6,6 +6,7 @@ using System.Text; using CheapLoc; using Dalamud.Configuration.Internal; using Dalamud.Game; +using Dalamud.Game.Text; using Dalamud.Interface.Colors; using Dalamud.Interface.FontIdentifier; using Dalamud.Interface.GameFonts; @@ -136,6 +137,27 @@ public class SettingsTabLook : SettingsTab Loc.Localize("DalamudSettingReducedMotionHint", "This will suppress certain animations from Dalamud, such as the notification popup."), c => c.ReduceMotions ?? false, (v, c) => c.ReduceMotions = v), + + new SettingsEntry( + Loc.Localize("DalamudSettingImeStateIndicatorOpacity", "IME State Indicator Opacity (CJK only)"), + Loc.Localize("DalamudSettingImeStateIndicatorOpacityHint", "When any of CJK IMEs is in use, the state of IME will be shown with the opacity specified here."), + c => c.ImeStateIndicatorOpacity, + (v, c) => c.ImeStateIndicatorOpacity = v) + { + CustomDraw = static e => + { + ImGuiHelpers.SafeTextWrapped(e.Name!); + + var v = e.Value * 100f; + if (ImGui.SliderFloat($"###{e}", ref v, 0f, 100f, "%.1f%%")) + e.Value = v / 100f; + ImGui.SameLine(); + + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, v / 100); + ImGui.TextUnformatted("\uE020\uE021\uE022\uE023\uE024\uE025\uE026\uE027"); + ImGui.PopStyleVar(1); + }, + }, }; public override string Title => Loc.Localize("DalamudSettingsVisual", "Look & Feel"); diff --git a/Dalamud/Interface/Internal/Windows/Settings/Widgets/SettingsEntry{T}.cs b/Dalamud/Interface/Internal/Windows/Settings/Widgets/SettingsEntry{T}.cs index f2a8dc46f..2ac4187cf 100644 --- a/Dalamud/Interface/Internal/Windows/Settings/Widgets/SettingsEntry{T}.cs +++ b/Dalamud/Interface/Internal/Windows/Settings/Widgets/SettingsEntry{T}.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Numerics; using Dalamud.Configuration.Internal; @@ -50,10 +51,22 @@ internal sealed class SettingsEntry : SettingsEntry public delegate void SaveSettingDelegate(T? value, DalamudConfiguration config); - public T? Value => this.valueBacking == default ? default : (T)this.valueBacking; + public T? Value + { + get => this.valueBacking == default ? default : (T)this.valueBacking; + set + { + if (Equals(value, this.valueBacking)) + return; + this.valueBacking = value; + this.change?.Invoke(value); + } + } public string Description { get; } + public Action>? CustomDraw { get; init; } + public Func? CheckValidity { get; init; } public Func? CheckWarning { get; init; } @@ -68,7 +81,11 @@ internal sealed class SettingsEntry : SettingsEntry var type = typeof(T); - if (type == typeof(DirectoryInfo)) + if (this.CustomDraw is not null) + { + this.CustomDraw.Invoke(this); + } + else if (type == typeof(DirectoryInfo)) { ImGuiHelpers.SafeTextWrapped(this.Name); diff --git a/Dalamud/Interface/Utility/Raii/EndObjects.cs b/Dalamud/Interface/Utility/Raii/EndObjects.cs index 3377b51bc..401af5415 100644 --- a/Dalamud/Interface/Utility/Raii/EndObjects.cs +++ b/Dalamud/Interface/Utility/Raii/EndObjects.cs @@ -109,40 +109,31 @@ public static partial class ImRaii public static unsafe IEndObject TabItem(string label, ImGuiTabItemFlags flags) { + ArgumentNullException.ThrowIfNull(label); + // One-off for now, we should make this into a generic solution if we need it more often - const int ImGuiNET_Util_StackAllocationSizeLimit = 2048; + const int labelMaxAlloc = 2048; - byte* native_label; - int label_byteCount = 0; - if (label != null) + var labelByteCount = Encoding.UTF8.GetByteCount(label); + + if (labelByteCount > labelMaxAlloc) { - label_byteCount = Encoding.UTF8.GetByteCount(label); - - if (label_byteCount > ImGuiNET_Util_StackAllocationSizeLimit) - { - throw new ArgumentOutOfRangeException("label", "Label is too long. (Longer than 2048 bytes)"); - } - - byte* native_label_stackBytes = stackalloc byte[label_byteCount + 1]; - native_label = native_label_stackBytes; - - int native_label_offset; - fixed (char* utf16Ptr = label) - { - native_label_offset = Encoding.UTF8.GetBytes(utf16Ptr, label.Length, native_label, label_byteCount); - } - - native_label[native_label_offset] = 0; - } - else - { - native_label = null; + throw new ArgumentOutOfRangeException(nameof(label), $"Label is too long. (Longer than {labelMaxAlloc} bytes)"); } - byte* p_open = null; - byte ret = ImGuiNative.igBeginTabItem(native_label, p_open, flags); + var nativeLabelStackBytes = stackalloc byte[labelByteCount + 1]; - return new EndUnconditionally(ImGuiNative.igEndTabItem, ret != 0); + int nativeLabelOffset; + fixed (char* utf16Ptr = label) + { + nativeLabelOffset = Encoding.UTF8.GetBytes(utf16Ptr, label.Length, nativeLabelStackBytes, labelByteCount); + } + + nativeLabelStackBytes[nativeLabelOffset] = 0; + + var ret = ImGuiNative.igBeginTabItem(nativeLabelStackBytes, null, flags); + + return new EndConditionally(ImGuiNative.igEndTabItem, ret != 0); } public static IEndObject TabItem(string label, ref bool open) diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index 8f0da2871..3ddfa3101 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -398,7 +398,7 @@ public sealed class DalamudPluginInterface : IDisposable if (currentConfig == null) return; - this.configs.Save(currentConfig, this.plugin.InternalName, this.plugin.Manifest.WorkingPluginId); + this.configs.Save(currentConfig, this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId); } /// @@ -425,7 +425,7 @@ public sealed class DalamudPluginInterface : IDisposable } // this shouldn't be a thing, I think, but just in case - return this.configs.Load(this.plugin.InternalName, this.plugin.Manifest.WorkingPluginId); + return this.configs.Load(this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId); } /// diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index d64a3b9b0..7517ae413 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -1029,7 +1029,7 @@ internal class PluginManager : IInternalDisposableService { var plugin = metadata.InstalledPlugin; - var workingPluginId = metadata.InstalledPlugin.Manifest.WorkingPluginId; + var workingPluginId = metadata.InstalledPlugin.EffectiveWorkingPluginId; if (workingPluginId == Guid.Empty) throw new Exception("Existing plugin had no WorkingPluginId"); @@ -1331,16 +1331,16 @@ internal class PluginManager : IInternalDisposableService foreach (var installedPlugin in this.InstalledPlugins) { - if (installedPlugin.Manifest.WorkingPluginId == Guid.Empty) + if (installedPlugin.EffectiveWorkingPluginId == Guid.Empty) throw new Exception($"{(installedPlugin is LocalDevPlugin ? "DevPlugin" : "Plugin")} '{installedPlugin.Manifest.InternalName}' has an empty WorkingPluginId."); - if (seenIds.Contains(installedPlugin.Manifest.WorkingPluginId)) + if (seenIds.Contains(installedPlugin.EffectiveWorkingPluginId)) { throw new Exception( - $"{(installedPlugin is LocalDevPlugin ? "DevPlugin" : "Plugin")} '{installedPlugin.Manifest.InternalName}' has a duplicate WorkingPluginId '{installedPlugin.Manifest.WorkingPluginId}'"); + $"{(installedPlugin is LocalDevPlugin ? "DevPlugin" : "Plugin")} '{installedPlugin.Manifest.InternalName}' has a duplicate WorkingPluginId '{installedPlugin.EffectiveWorkingPluginId}'"); } - seenIds.Add(installedPlugin.Manifest.WorkingPluginId); + seenIds.Add(installedPlugin.EffectiveWorkingPluginId); } this.profileManager.ParanoiaValidateProfiles(); @@ -1388,7 +1388,7 @@ internal class PluginManager : IInternalDisposableService { // Only remove entries from the default profile that are NOT currently tied to an active LocalPlugin var guidsToRemove = this.profileManager.DefaultProfile.Plugins - .Where(x => this.InstalledPlugins.All(y => y.Manifest.WorkingPluginId != x.WorkingPluginId)) + .Where(x => this.InstalledPlugins.All(y => y.EffectiveWorkingPluginId != x.WorkingPluginId)) .Select(x => x.WorkingPluginId) .ToArray(); @@ -1560,9 +1560,9 @@ internal class PluginManager : IInternalDisposableService // This will also happen if you are installing a plugin with the installer, and that's intended! // It means that, if you have a profile which has unsatisfied plugins, installing a matching plugin will // enter it into the profiles it can match. - if (plugin.Manifest.WorkingPluginId == Guid.Empty) + if (plugin.EffectiveWorkingPluginId == Guid.Empty) throw new Exception("Plugin should have a WorkingPluginId at this point"); - this.profileManager.MigrateProfilesToGuidsForPlugin(plugin.Manifest.InternalName, plugin.Manifest.WorkingPluginId); + this.profileManager.MigrateProfilesToGuidsForPlugin(plugin.Manifest.InternalName, plugin.EffectiveWorkingPluginId); var wantedByAnyProfile = false; @@ -1573,7 +1573,7 @@ internal class PluginManager : IInternalDisposableService loadPlugin &= !isBoot; var wantsInDefaultProfile = - this.profileManager.DefaultProfile.WantsPlugin(plugin.Manifest.WorkingPluginId); + this.profileManager.DefaultProfile.WantsPlugin(plugin.EffectiveWorkingPluginId); if (wantsInDefaultProfile == null) { // We don't know about this plugin, so we don't want to do anything here. @@ -1582,7 +1582,7 @@ internal class PluginManager : IInternalDisposableService // Check if any profile wants this plugin. We need to do this here, since we want to allow loading a dev plugin if a non-default profile wants it active. // Note that this will not add the plugin to the default profile. That's done below in any other case. - wantedByAnyProfile = await this.profileManager.GetWantStateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, false, false); + wantedByAnyProfile = await this.profileManager.GetWantStateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false); // If it is wanted by any other profile, we do want to load it. if (wantedByAnyProfile) @@ -1592,28 +1592,28 @@ internal class PluginManager : IInternalDisposableService { // We didn't want this plugin, and StartOnBoot is on. That means we don't want it and it should stay off until manually enabled. Log.Verbose("DevPlugin {Name} disabled and StartOnBoot => disable", plugin.Manifest.InternalName); - await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, false, false); + await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false); loadPlugin = false; } else if (wantsInDefaultProfile == true && devPlugin.StartOnBoot) { // We wanted this plugin, and StartOnBoot is on. That means we actually do want it. Log.Verbose("DevPlugin {Name} enabled and StartOnBoot => enable", plugin.Manifest.InternalName); - await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, true, false); + await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, true, false); loadPlugin = !doNotLoad; } else if (wantsInDefaultProfile == true && !devPlugin.StartOnBoot) { // We wanted this plugin, but StartOnBoot is off. This means we don't want it anymore. Log.Verbose("DevPlugin {Name} enabled and !StartOnBoot => disable", plugin.Manifest.InternalName); - await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, false, false); + await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false); loadPlugin = false; } else if (wantsInDefaultProfile == false && !devPlugin.StartOnBoot) { // We didn't want this plugin, and StartOnBoot is off. We don't want it. Log.Verbose("DevPlugin {Name} disabled and !StartOnBoot => disable", plugin.Manifest.InternalName); - await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, false, false); + await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false); loadPlugin = false; } @@ -1626,7 +1626,7 @@ internal class PluginManager : IInternalDisposableService // Plugins that aren't in any profile will be added to the default profile with this call. // We are skipping a double-lookup for dev plugins that are wanted by non-default profiles, as noted above. - wantedByAnyProfile = wantedByAnyProfile || await this.profileManager.GetWantStateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, defaultState); + wantedByAnyProfile = wantedByAnyProfile || await this.profileManager.GetWantStateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, defaultState); Log.Information("{Name} defaultState: {State} wantedByAnyProfile: {WantedByAny} loadPlugin: {LoadPlugin}", plugin.Manifest.InternalName, defaultState, wantedByAnyProfile, loadPlugin); if (loadPlugin) diff --git a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs index d9c5ac787..e36e9908b 100644 --- a/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs +++ b/Dalamud/Plugin/Internal/Profiles/ProfileManager.cs @@ -183,8 +183,8 @@ internal class ProfileManager : IServiceType var installedPlugin = pm.InstalledPlugins.FirstOrDefault(x => x.Manifest.InternalName == plugin.InternalName); if (installedPlugin != null) { - Log.Information("Satisfying plugin {InternalName} for profile {Name} with {Guid}", plugin.InternalName, newModel.Name, installedPlugin.Manifest.WorkingPluginId); - plugin.WorkingPluginId = installedPlugin.Manifest.WorkingPluginId; + Log.Information("Satisfying plugin {InternalName} for profile {Name} with {Guid}", plugin.InternalName, newModel.Name, installedPlugin.EffectiveWorkingPluginId); + plugin.WorkingPluginId = installedPlugin.EffectiveWorkingPluginId; } else { @@ -237,7 +237,7 @@ internal class ProfileManager : IServiceType var pm = Service.Get(); foreach (var installedPlugin in pm.InstalledPlugins) { - var wantThis = wantActive.Any(x => x.WorkingPluginId == installedPlugin.Manifest.WorkingPluginId); + var wantThis = wantActive.Any(x => x.WorkingPluginId == installedPlugin.EffectiveWorkingPluginId); switch (wantThis) { case true when !installedPlugin.IsLoaded: diff --git a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs index 4b452322a..5a3552199 100644 --- a/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalDevPlugin.cs @@ -50,14 +50,6 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable Log.Verbose("{InternalName} was assigned new devPlugin GUID {Guid}", this.InternalName, this.devSettings.WorkingPluginId); configuration.QueueSave(); } - - // If the ID in the manifest is wrong, force the good one - if (this.DevImposedWorkingPluginId != this.manifest.WorkingPluginId) - { - Debug.Assert(this.DevImposedWorkingPluginId != Guid.Empty, "Empty guid for devPlugin"); - this.manifest.WorkingPluginId = this.DevImposedWorkingPluginId; - this.SaveManifest("dev imposed working plugin id"); - } if (this.AutomaticReload) { @@ -99,7 +91,10 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable /// Gets an ID uniquely identifying this specific instance of a devPlugin. /// public Guid DevImposedWorkingPluginId => this.devSettings.WorkingPluginId; - + + /// + public override Guid EffectiveWorkingPluginId => this.DevImposedWorkingPluginId; + /// /// Gets a list of validation problems that have been dismissed by the user. /// @@ -148,6 +143,23 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable } } + /// + /// Reload the manifest if it exists, to update possible changes. + /// + /// Thrown if the manifest could not be loaded. + public void ReloadManifest() + { + var manifestPath = LocalPluginManifest.GetManifestFile(this.DllFile); + if (manifestPath.Exists) + this.manifest = LocalPluginManifest.Load(manifestPath) ?? throw new Exception("Could not reload manifest."); + } + + /// + protected override void OnPreReload() + { + this.ReloadManifest(); + } + private void OnFileChanged(object sender, FileSystemEventArgs args) { var current = Interlocked.Increment(ref this.reloadCounter); diff --git a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs index a51e2c2ea..6c3ca8c0c 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPlugin.cs @@ -91,7 +91,7 @@ internal class LocalPlugin : IDisposable } // Create an installation instance ID for this plugin, if it doesn't have one yet - if (this.manifest.WorkingPluginId == Guid.Empty) + if (this.manifest.WorkingPluginId == Guid.Empty && !this.IsDev) { this.manifest.WorkingPluginId = Guid.NewGuid(); @@ -162,7 +162,7 @@ internal class LocalPlugin : IDisposable /// INCLUDES the default profile. /// public bool IsWantedByAnyProfile => - Service.Get().GetWantStateAsync(this.manifest.WorkingPluginId, this.Manifest.InternalName, false, false).GetAwaiter().GetResult(); + Service.Get().GetWantStateAsync(this.EffectiveWorkingPluginId, this.Manifest.InternalName, false, false).GetAwaiter().GetResult(); /// /// Gets a value indicating whether this plugin's API level is out of date. @@ -215,6 +215,11 @@ internal class LocalPlugin : IDisposable /// public Version EffectiveVersion => this.manifest.EffectiveVersion; + /// + /// Gets the effective working plugin ID for this plugin. + /// + public virtual Guid EffectiveWorkingPluginId => this.manifest.WorkingPluginId; + /// /// Gets the service scope for this plugin. /// @@ -271,11 +276,8 @@ internal class LocalPlugin : IDisposable await this.pluginLoadStateLock.WaitAsync(); try { - if (reloading && this.IsDev) - { - // Reload the manifest in-case there were changes here too. - this.ReloadManifest(); - } + if (reloading) + this.OnPreReload(); // If we reload a plugin we don't want to delete it. Makes sense, right? if (this.manifest.ScheduledForDeletion) @@ -578,24 +580,6 @@ internal class LocalPlugin : IDisposable this.SaveManifest("scheduling for deletion"); } - /// - /// Reload the manifest if it exists, preserve the internal Disabled state. - /// - public void ReloadManifest() - { - var manifestPath = LocalPluginManifest.GetManifestFile(this.DllFile); - if (manifestPath.Exists) - { - // Save some state that we do actually want to carry over - var guid = this.manifest.WorkingPluginId; - - this.manifest = LocalPluginManifest.Load(manifestPath) ?? throw new Exception("Could not reload manifest."); - this.manifest.WorkingPluginId = guid; - - this.SaveManifest("dev reload"); - } - } - /// /// Get the repository this plugin was installed from. /// @@ -620,6 +604,13 @@ internal class LocalPlugin : IDisposable /// /// Why it should be saved. protected void SaveManifest(string reason) => this.manifest.Save(this.manifestFile, reason); + + /// + /// Called before a plugin is reloaded. + /// + protected virtual void OnPreReload() + { + } private static void SetupLoaderConfig(LoaderConfig config) { diff --git a/Dalamud/Plugin/Services/IGameInteropProvider.cs b/Dalamud/Plugin/Services/IGameInteropProvider.cs index 217e08445..99e36c7ed 100644 --- a/Dalamud/Plugin/Services/IGameInteropProvider.cs +++ b/Dalamud/Plugin/Services/IGameInteropProvider.cs @@ -48,7 +48,7 @@ public interface IGameInteropProvider /// Callback function. Delegate must have a same original function prototype. /// The hook with the supplied parameters. /// Delegate of detour. - public Hook HookFromFunctionPointerVariable(IntPtr address, T detour) where T : Delegate; + public Hook HookFromFunctionPointerVariable(nint address, T detour) where T : Delegate; /// /// Creates a hook by rewriting import table address. @@ -85,7 +85,31 @@ public interface IGameInteropProvider /// Hooking library to use. /// The hook with the supplied parameters. /// Delegate of detour. - Hook HookFromAddress(IntPtr procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate; + Hook HookFromAddress(nint procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate; + + /// + /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. + /// The hook is not activated until Enable() method is called. + /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. + /// + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + /// Hooking library to use. + /// The hook with the supplied parameters. + /// Delegate of detour. + Hook HookFromAddress(nuint procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate; + + /// + /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. + /// The hook is not activated until Enable() method is called. + /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. + /// + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + /// Hooking library to use. + /// The hook with the supplied parameters. + /// Delegate of detour. + unsafe Hook HookFromAddress(void* procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate; /// /// Creates a hook from a signature into the Dalamud target module.