From 1561fbac006d8afb4536b49dd89fb47abdb4366b Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Thu, 18 Nov 2021 15:23:40 +0100 Subject: [PATCH] Revert "refactor(Dalamud): switch to file-scoped namespaces" This reverts commit b5f34c3199943b42a9fc31a35a9ff8b2a09c8591. --- Dalamud/ClientLanguage.cs | 41 +- Dalamud/ClientLanguageExtensions.cs | 35 +- Dalamud/Configuration/IPluginConfiguration.cs | 17 +- .../Internal/DalamudConfiguration.cs | 513 +- .../Internal/DevPluginLocationSettings.cs | 35 +- .../Internal/DevPluginSettings.cs | 25 +- .../Internal/EnvironmentConfiguration.cs | 53 +- .../Internal/ThirdPartyRepoSettings.cs | 43 +- Dalamud/Configuration/PluginConfigurations.cs | 213 +- Dalamud/Dalamud.cs | 647 +- Dalamud/DalamudStartInfo.cs | 85 +- Dalamud/Data/DataManager.cs | 587 +- Dalamud/EntryPoint.cs | 411 +- Dalamud/Game/BaseAddressResolver.cs | 179 +- Dalamud/Game/ChatHandlers.cs | 489 +- Dalamud/Game/ClientState/Buddy/BuddyList.cs | 287 +- Dalamud/Game/ClientState/Buddy/BuddyMember.cs | 121 +- Dalamud/Game/ClientState/ClientState.cs | 297 +- .../ClientState/ClientStateAddressResolver.cs | 191 +- .../Game/ClientState/Conditions/Condition.cs | 243 +- .../ClientState/Conditions/ConditionFlag.cs | 899 +- Dalamud/Game/ClientState/Fates/Fate.cs | 231 +- Dalamud/Game/ClientState/Fates/FateState.cs | 49 +- Dalamud/Game/ClientState/Fates/FateTable.cs | 229 +- .../ClientState/GamePad/GamepadButtons.cs | 147 +- .../Game/ClientState/GamePad/GamepadInput.cs | 125 +- .../Game/ClientState/GamePad/GamepadState.cs | 477 +- .../ClientState/JobGauge/Enums/BOTDState.cs | 33 +- .../ClientState/JobGauge/Enums/CardType.cs | 81 +- .../JobGauge/Enums/DismissedFairy.cs | 25 +- .../Game/ClientState/JobGauge/Enums/Mudras.cs | 33 +- .../ClientState/JobGauge/Enums/PetGlam.cs | 41 +- .../ClientState/JobGauge/Enums/SealType.cs | 41 +- .../Game/ClientState/JobGauge/Enums/Sen.cs | 43 +- .../Game/ClientState/JobGauge/Enums/Song.cs | 41 +- .../ClientState/JobGauge/Enums/SummonPet.cs | 41 +- .../Game/ClientState/JobGauge/JobGauges.cs | 69 +- .../ClientState/JobGauge/Types/ASTGauge.cs | 55 +- .../ClientState/JobGauge/Types/BLMGauge.cs | 117 +- .../ClientState/JobGauge/Types/BRDGauge.cs | 61 +- .../ClientState/JobGauge/Types/DNCGauge.cs | 99 +- .../ClientState/JobGauge/Types/DRGGauge.cs | 51 +- .../ClientState/JobGauge/Types/DRKGauge.cs | 63 +- .../ClientState/JobGauge/Types/GNBGauge.cs | 51 +- .../JobGauge/Types/JobGaugeBase.cs | 31 +- .../JobGauge/Types/JobGaugeBase{T}.cs | 33 +- .../ClientState/JobGauge/Types/MCHGauge.cs | 95 +- .../ClientState/JobGauge/Types/MNKGauge.cs | 31 +- .../ClientState/JobGauge/Types/NINGauge.cs | 51 +- .../ClientState/JobGauge/Types/PLDGauge.cs | 31 +- .../ClientState/JobGauge/Types/RDMGauge.cs | 41 +- .../ClientState/JobGauge/Types/SAMGauge.cs | 87 +- .../ClientState/JobGauge/Types/SCHGauge.cs | 61 +- .../ClientState/JobGauge/Types/SMNGauge.cs | 99 +- .../ClientState/JobGauge/Types/WARGauge.cs | 31 +- .../ClientState/JobGauge/Types/WHMGauge.cs | 51 +- Dalamud/Game/ClientState/Keys/KeyState.cs | 285 +- Dalamud/Game/ClientState/Keys/VirtualKey.cs | 2075 +-- .../Objects/Enums/BattleNpcSubKind.cs | 41 +- .../Objects/Enums/CustomizeIndex.cs | 219 +- .../ClientState/Objects/Enums/ObjectKind.cs | 129 +- .../ClientState/Objects/Enums/StatusFlags.cs | 83 +- .../Game/ClientState/Objects/ObjectTable.cs | 233 +- .../ClientState/Objects/SubKinds/BattleNpc.cs | 39 +- .../ClientState/Objects/SubKinds/EventObj.cs | 23 +- .../Game/ClientState/Objects/SubKinds/Npc.cs | 23 +- .../Objects/SubKinds/PlayerCharacter.cs | 53 +- .../Game/ClientState/Objects/TargetManager.cs | 303 +- .../ClientState/Objects/Types/BattleChara.cs | 113 +- .../ClientState/Objects/Types/Character.cs | 185 +- .../ClientState/Objects/Types/GameObject.cs | 311 +- Dalamud/Game/ClientState/Party/PartyList.cs | 313 +- Dalamud/Game/ClientState/Party/PartyMember.cs | 191 +- .../ClientState/Resolvers/ExcelResolver{T}.cs | 43 +- Dalamud/Game/ClientState/Statuses/Status.cs | 111 +- .../Game/ClientState/Statuses/StatusList.cs | 255 +- .../Game/ClientState/Structs/StatusEffect.cs | 51 +- Dalamud/Game/Command/CommandInfo.cs | 79 +- Dalamud/Game/Command/CommandManager.cs | 269 +- Dalamud/Game/Framework.cs | 511 +- Dalamud/Game/FrameworkAddressResolver.cs | 83 +- Dalamud/Game/GameVersion.cs | 737 +- Dalamud/Game/GameVersionConverter.cs | 123 +- Dalamud/Game/Gui/ChatGui.cs | 877 +- Dalamud/Game/Gui/ChatGuiAddressResolver.cs | 199 +- Dalamud/Game/Gui/FlyText/FlyTextGui.cs | 547 +- .../Gui/FlyText/FlyTextGuiAddressResolver.cs | 47 +- Dalamud/Game/Gui/FlyText/FlyTextKind.cs | 455 +- Dalamud/Game/Gui/GameGui.cs | 1037 +- Dalamud/Game/Gui/GameGuiAddressResolver.cs | 159 +- Dalamud/Game/Gui/HoverActionKind.cs | 75 +- Dalamud/Game/Gui/HoveredAction.cs | 33 +- Dalamud/Game/Gui/Internal/DalamudIME.cs | 387 +- .../PartyFinder/Internal/PartyFinderPacket.cs | 35 +- .../Internal/PartyFinderPacketListing.cs | 181 +- .../PartyFinder/PartyFinderAddressResolver.cs | 25 +- .../Game/Gui/PartyFinder/PartyFinderGui.cs | 207 +- .../Gui/PartyFinder/Types/ConditionFlags.cs | 35 +- .../Gui/PartyFinder/Types/DutyCategory.cs | 73 +- .../Types/DutyFinderSettingsFlags.cs | 43 +- .../Game/Gui/PartyFinder/Types/DutyType.cs | 33 +- .../Game/Gui/PartyFinder/Types/JobFlags.cs | 227 +- .../PartyFinder/Types/JobFlagsExtensions.cs | 89 +- .../Gui/PartyFinder/Types/LootRuleFlags.cs | 35 +- .../Gui/PartyFinder/Types/ObjectiveFlags.cs | 43 +- .../PartyFinder/Types/PartyFinderListing.cs | 433 +- .../Types/PartyFinderListingEventArgs.cs | 41 +- .../Gui/PartyFinder/Types/PartyFinderSlot.cs | 69 +- .../Gui/PartyFinder/Types/SearchAreaFlags.cs | 51 +- Dalamud/Game/Gui/Toast/QuestToastOptions.cs | 49 +- Dalamud/Game/Gui/Toast/QuestToastPosition.cs | 33 +- Dalamud/Game/Gui/Toast/ToastGui.cs | 757 +- .../Game/Gui/Toast/ToastGuiAddressResolver.cs | 49 +- Dalamud/Game/Gui/Toast/ToastOptions.cs | 25 +- Dalamud/Game/Gui/Toast/ToastPosition.cs | 25 +- Dalamud/Game/Gui/Toast/ToastSpeed.cs | 25 +- Dalamud/Game/Internal/AntiDebug.cs | 191 +- .../DXGI/Definitions/ID3D11DeviceVtbl.cs | 359 +- .../DXGI/Definitions/IDXGISwapChainVtbl.cs | 165 +- .../DXGI/ISwapChainAddressResolver.cs | 25 +- .../Internal/DXGI/SwapChainSigResolver.cs | 43 +- .../Internal/DXGI/SwapChainVtableResolver.cs | 111 +- Dalamud/Game/Internal/DalamudAtkTweaks.cs | 389 +- Dalamud/Game/Libc/LibcFunction.cs | 111 +- .../Game/Libc/LibcFunctionAddressResolver.cs | 41 +- Dalamud/Game/Libc/OwnedStdString.cs | 165 +- Dalamud/Game/Libc/StdString.cs | 109 +- Dalamud/Game/Network/GameNetwork.cs | 285 +- .../Network/GameNetworkAddressResolver.cs | 41 +- .../IMarketBoardUploader.cs | 45 +- .../MarketBoardItemRequest.cs | 93 +- .../Types/UniversalisHistoryEntry.cs | 89 +- .../Types/UniversalisHistoryUploadRequest.cs | 49 +- .../UniversalisItemListingDeleteRequest.cs | 59 +- .../Types/UniversalisItemListingsEntry.cs | 149 +- .../UniversalisItemListingsUploadRequest.cs | 49 +- .../Types/UniversalisItemMateria.cs | 29 +- .../Universalis/Types/UniversalisTaxData.cs | 69 +- .../Types/UniversalisTaxUploadRequest.cs | 39 +- .../UniversalisMarketBoardUploader.cs | 291 +- .../Game/Network/Internal/NetworkHandlers.cs | 469 +- .../Game/Network/Internal/WinSockHandlers.cs | 89 +- .../Game/Network/NetworkMessageDirection.cs | 25 +- .../Structures/MarketBoardCurrentOfferings.cs | 383 +- .../Network/Structures/MarketBoardHistory.cs | 199 +- .../Network/Structures/MarketBoardPurchase.cs | 63 +- .../Structures/MarketBoardPurchaseHandler.cs | 91 +- .../Game/Network/Structures/MarketTaxRates.cs | 101 +- Dalamud/Game/SigScanner.cs | 871 +- Dalamud/Game/Text/Sanitizer/ISanitizer.cs | 61 +- Dalamud/Game/Text/Sanitizer/Sanitizer.cs | 187 +- Dalamud/Game/Text/SeIconChar.cs | 1479 +- .../Text/SeStringHandling/BitmapFontIcon.cs | 869 +- .../Text/SeStringHandling/ITextProvider.cs | 17 +- Dalamud/Game/Text/SeStringHandling/Payload.cs | 739 +- .../Game/Text/SeStringHandling/PayloadType.cs | 129 +- .../Payloads/AutoTranslatePayload.cs | 263 +- .../Payloads/DalamudLinkPayload.cs | 83 +- .../Payloads/EmphasisItalicPayload.cs | 117 +- .../SeStringHandling/Payloads/IconPayload.cs | 103 +- .../SeStringHandling/Payloads/ItemPayload.cs | 293 +- .../Payloads/MapLinkPayload.cs | 401 +- .../Payloads/NewLinePayload.cs | 49 +- .../Payloads/PlayerPayload.cs | 179 +- .../SeStringHandling/Payloads/QuestPayload.cs | 109 +- .../SeStringHandling/Payloads/RawPayload.cs | 179 +- .../Payloads/SeHyphenPayload.cs | 49 +- .../Payloads/StatusPayload.cs | 109 +- .../SeStringHandling/Payloads/TextPayload.cs | 145 +- .../Payloads/UIForegroundPayload.cs | 171 +- .../Payloads/UIGlowPayload.cs | 173 +- .../Game/Text/SeStringHandling/SeString.cs | 613 +- .../Text/SeStringHandling/SeStringManager.cs | 199 +- Dalamud/Game/Text/XivChatEntry.cs | 49 +- Dalamud/Game/Text/XivChatType.cs | 389 +- Dalamud/Game/Text/XivChatTypeExtensions.cs | 23 +- Dalamud/Game/Text/XivChatTypeInfoAttribute.cs | 61 +- Dalamud/Hooking/AsmHook.cs | 295 +- Dalamud/Hooking/AsmHookBehaviour.cs | 35 +- Dalamud/Hooking/Hook.cs | 427 +- Dalamud/Hooking/IDalamudHook.cs | 41 +- Dalamud/Hooking/Internal/HookInfo.cs | 115 +- Dalamud/Hooking/Internal/HookManager.cs | 231 +- Dalamud/Interface/Animation/AnimUtil.cs | 55 +- Dalamud/Interface/Animation/Easing.cs | 199 +- .../Animation/EasingFunctions/InCirc.cs | 37 +- .../Animation/EasingFunctions/InCubic.cs | 37 +- .../Animation/EasingFunctions/InElastic.cs | 49 +- .../Animation/EasingFunctions/InOutCirc.cs | 41 +- .../Animation/EasingFunctions/InOutCubic.cs | 37 +- .../Animation/EasingFunctions/InOutElastic.cs | 53 +- .../Animation/EasingFunctions/InOutQuint.cs | 37 +- .../Animation/EasingFunctions/InOutSine.cs | 37 +- .../Animation/EasingFunctions/InQuint.cs | 37 +- .../Animation/EasingFunctions/InSine.cs | 37 +- .../Animation/EasingFunctions/OutCirc.cs | 37 +- .../Animation/EasingFunctions/OutCubic.cs | 37 +- .../Animation/EasingFunctions/OutElastic.cs | 49 +- .../Animation/EasingFunctions/OutQuint.cs | 37 +- .../Animation/EasingFunctions/OutSine.cs | 37 +- Dalamud/Interface/Colors/ImGuiColors.cs | 161 +- .../ImGuiComponents.ColorPickerWithPalette.cs | 93 +- .../ImGuiComponents.DisabledButton.cs | 103 +- .../Components/ImGuiComponents.HelpMarker.cs | 39 +- .../Components/ImGuiComponents.IconButton.cs | 177 +- .../Components/ImGuiComponents.Test.cs | 19 +- .../ImGuiComponents.TextWithLabel.cs | 41 +- .../Interface/Components/ImGuiComponents.cs | 13 +- Dalamud/Interface/FontAwesomeExtensions.cs | 43 +- Dalamud/Interface/FontAwesomeIcon.cs | 14089 ++++++++-------- Dalamud/Interface/GlyphRangesJapanese.cs | 19 +- .../ImGuiFileDialog/FileDialog.Files.cs | 695 +- .../ImGuiFileDialog/FileDialog.Filters.cs | 177 +- .../ImGuiFileDialog/FileDialog.Helpers.cs | 37 +- .../ImGuiFileDialog/FileDialog.Structs.cs | 83 +- .../ImGuiFileDialog/FileDialog.UI.cs | 1569 +- .../Interface/ImGuiFileDialog/FileDialog.cs | 379 +- .../ImGuiFileDialog/FileDialogManager.cs | 177 +- .../ImGuiFileDialog/ImGuiFileDialogFlags.cs | 83 +- Dalamud/Interface/ImGuiHelpers.cs | 215 +- Dalamud/Interface/Internal/DalamudCommands.cs | 625 +- .../Interface/Internal/DalamudInterface.cs | 1233 +- .../Interface/Internal/InterfaceManager.cs | 1167 +- .../ManagedAsserts/ImGuiContextOffsets.cs | 31 +- .../ManagedAsserts/ImGuiManagedAsserts.cs | 213 +- .../Notifications/NotificationManager.cs | 529 +- .../Notifications/NotificationType.cs | 49 +- .../Internal/PluginCategoryManager.cs | 669 +- Dalamud/Interface/Internal/UiDebug.cs | 1201 +- .../Internal/Windows/ChangelogWindow.cs | 175 +- .../Internal/Windows/ColorDemoWindow.cs | 51 +- .../Internal/Windows/ComponentDemoWindow.cs | 253 +- .../Internal/Windows/ConsoleWindow.cs | 819 +- .../Internal/Windows/CreditsWindow.cs | 223 +- .../Interface/Internal/Windows/DataWindow.cs | 2967 ++-- .../Windows/GamepadModeNotifierWindow.cs | 69 +- .../Interface/Internal/Windows/IMEWindow.cs | 93 +- .../Internal/Windows/PluginImageCache.cs | 765 +- .../Internal/Windows/PluginInstallerWindow.cs | 3961 ++--- .../Internal/Windows/PluginStatWindow.cs | 423 +- .../AgingSteps/ActorTableAgingStep.cs | 63 +- .../SelfTest/AgingSteps/ChatAgingStep.cs | 105 +- .../SelfTest/AgingSteps/ConditionAgingStep.cs | 49 +- .../AgingSteps/EnterTerritoryAgingStep.cs | 99 +- .../SelfTest/AgingSteps/FateTableAgingStep.cs | 63 +- .../AgingSteps/GamepadStateAgingStep.cs | 51 +- .../AgingSteps/HandledExceptionAgingStep.cs | 49 +- .../SelfTest/AgingSteps/HoverAgingStep.cs | 77 +- .../Windows/SelfTest/AgingSteps/IAgingStep.cs | 35 +- .../SelfTest/AgingSteps/KeyStateAgingStep.cs | 55 +- .../AgingSteps/LoginEventAgingStep.cs | 79 +- .../AgingSteps/LogoutEventAgingStep.cs | 79 +- .../SelfTest/AgingSteps/LuminaAgingStep.cs | 53 +- .../AgingSteps/PartyFinderAgingStep.cs | 81 +- .../SelfTest/AgingSteps/TargetAgingStep.cs | 105 +- .../SelfTest/AgingSteps/ToastAgingStep.cs | 41 +- .../AgingSteps/WaitFramesAgingStep.cs | 55 +- .../Windows/SelfTest/SelfTestStepResult.cs | 41 +- .../Windows/SelfTest/SelfTestWindow.cs | 393 +- .../Internal/Windows/SettingsWindow.cs | 1256 +- .../Windows/StyleEditor/StyleEditorWindow.cs | 693 +- Dalamud/Interface/Style/DalamudColors.cs | 170 +- Dalamud/Interface/Style/StyleModel.cs | 211 +- Dalamud/Interface/Style/StyleModelV1.cs | 561 +- Dalamud/Interface/UiBuilder.cs | 539 +- Dalamud/Interface/Windowing/Window.cs | 535 +- Dalamud/Interface/Windowing/WindowSystem.cs | 223 +- .../IoC/Internal/InterfaceVersionAttribute.cs | 33 +- Dalamud/IoC/Internal/ObjectInstance.cs | 43 +- Dalamud/IoC/Internal/ServiceContainer.cs | 361 +- Dalamud/IoC/PluginInterfaceAttribute.cs | 15 +- Dalamud/IoC/PluginServiceAttribute.cs | 17 +- Dalamud/IoC/RequiredVersionAttribute.cs | 33 +- Dalamud/Localization.cs | 231 +- Dalamud/Logging/Internal/ModuleLog.cs | 231 +- Dalamud/Logging/Internal/SerilogEventSink.cs | 71 +- Dalamud/Logging/Internal/TaskTracker.cs | 355 +- Dalamud/Logging/PluginLog.cs | 409 +- .../Exceptions/MemoryAllocationException.cs | 69 +- Dalamud/Memory/Exceptions/MemoryException.cs | 69 +- .../Exceptions/MemoryPermissionException.cs | 69 +- .../Memory/Exceptions/MemoryReadException.cs | 69 +- .../Memory/Exceptions/MemoryWriteException.cs | 69 +- Dalamud/Memory/MemoryHelper.cs | 1241 +- Dalamud/Memory/MemoryProtection.cs | 193 +- Dalamud/NativeFunctions.cs | 3543 ++-- Dalamud/Plugin/DalamudPluginInterface.cs | 615 +- Dalamud/Plugin/IDalamudPlugin.cs | 17 +- .../Exceptions/BannedPluginException.cs | 31 +- .../Exceptions/DuplicatePluginException.cs | 39 +- .../Exceptions/InvalidPluginException.cs | 31 +- .../InvalidPluginOperationException.cs | 31 +- .../Internal/Exceptions/PluginException.cs | 13 +- Dalamud/Plugin/Internal/LocalDevPlugin.cs | 259 +- Dalamud/Plugin/Internal/LocalPlugin.cs | 801 +- Dalamud/Plugin/Internal/PluginManager.cs | 2061 +-- Dalamud/Plugin/Internal/PluginRepository.cs | 175 +- .../Internal/Types/AvailablePluginUpdate.cs | 59 +- .../Internal/Types/LocalPluginManifest.cs | 125 +- .../Plugin/Internal/Types/PluginManifest.cs | 261 +- .../Internal/Types/PluginRepositoryState.cs | 41 +- Dalamud/Plugin/Internal/Types/PluginState.cs | 49 +- .../Internal/Types/PluginUpdateStatus.cs | 41 +- .../Internal/Types/RemotePluginManifest.cs | 23 +- Dalamud/Plugin/Ipc/Exceptions/IpcError.cs | 51 +- .../Ipc/Exceptions/IpcLengthMismatchError.cs | 25 +- .../Plugin/Ipc/Exceptions/IpcNotReadyError.cs | 21 +- .../Ipc/Exceptions/IpcTypeMismatchError.cs | 27 +- Dalamud/Plugin/Ipc/ICallGateProvider.cs | 253 +- Dalamud/Plugin/Ipc/ICallGateSubscriber.cs | 217 +- Dalamud/Plugin/Ipc/Internal/CallGate.cs | 43 +- .../Plugin/Ipc/Internal/CallGateChannel.cs | 301 +- Dalamud/Plugin/Ipc/Internal/CallGatePubSub.cs | 631 +- .../Plugin/Ipc/Internal/CallGatePubSubBase.cs | 161 +- Dalamud/Plugin/PluginLoadReason.cs | 49 +- Dalamud/SafeMemory.cs | 475 +- Dalamud/Service{T}.cs | 199 +- Dalamud/Support/BugBait.cs | 95 +- Dalamud/Support/Troubleshooting.cs | 160 +- Dalamud/Utility/EnumExtensions.cs | 37 +- Dalamud/Utility/SeStringExtensions.cs | 25 +- Dalamud/Utility/StringExtensions.cs | 47 +- Dalamud/Utility/TexFileExtensions.cs | 41 +- Dalamud/Utility/Util.cs | 479 +- Dalamud/Utility/VectorExtensions.cs | 81 +- 325 files changed, 45549 insertions(+), 45209 deletions(-) diff --git a/Dalamud/ClientLanguage.cs b/Dalamud/ClientLanguage.cs index 4e04d4a54..ddd69576d 100644 --- a/Dalamud/ClientLanguage.cs +++ b/Dalamud/ClientLanguage.cs @@ -1,27 +1,28 @@ -namespace Dalamud; - -/// -/// Enum describing the language the game loads in. -/// -public enum ClientLanguage +namespace Dalamud { /// - /// Indicating a Japanese game client. + /// Enum describing the language the game loads in. /// - Japanese, + public enum ClientLanguage + { + /// + /// Indicating a Japanese game client. + /// + Japanese, - /// - /// Indicating an English game client. - /// - English, + /// + /// Indicating an English game client. + /// + English, - /// - /// Indicating a German game client. - /// - German, + /// + /// Indicating a German game client. + /// + German, - /// - /// Indicating a French game client. - /// - French, + /// + /// Indicating a French game client. + /// + French, + } } diff --git a/Dalamud/ClientLanguageExtensions.cs b/Dalamud/ClientLanguageExtensions.cs index b7e021f61..dccefb93f 100644 --- a/Dalamud/ClientLanguageExtensions.cs +++ b/Dalamud/ClientLanguageExtensions.cs @@ -1,26 +1,27 @@ using System; -namespace Dalamud; - -/// -/// Extension methods for the class. -/// -public static class ClientLanguageExtensions +namespace Dalamud { /// - /// Converts a Dalamud ClientLanguage to the corresponding Lumina variant. + /// Extension methods for the class. /// - /// Langauge to convert. - /// Converted langauge. - public static Lumina.Data.Language ToLumina(this ClientLanguage language) + public static class ClientLanguageExtensions { - return language switch + /// + /// Converts a Dalamud ClientLanguage to the corresponding Lumina variant. + /// + /// Langauge to convert. + /// Converted langauge. + public static Lumina.Data.Language ToLumina(this ClientLanguage language) { - ClientLanguage.Japanese => Lumina.Data.Language.Japanese, - ClientLanguage.English => Lumina.Data.Language.English, - ClientLanguage.German => Lumina.Data.Language.German, - ClientLanguage.French => Lumina.Data.Language.French, - _ => throw new ArgumentOutOfRangeException(nameof(language)), - }; + return language switch + { + ClientLanguage.Japanese => Lumina.Data.Language.Japanese, + ClientLanguage.English => Lumina.Data.Language.English, + ClientLanguage.German => Lumina.Data.Language.German, + ClientLanguage.French => Lumina.Data.Language.French, + _ => throw new ArgumentOutOfRangeException(nameof(language)), + }; + } } } diff --git a/Dalamud/Configuration/IPluginConfiguration.cs b/Dalamud/Configuration/IPluginConfiguration.cs index dcc93d8d7..884e38871 100644 --- a/Dalamud/Configuration/IPluginConfiguration.cs +++ b/Dalamud/Configuration/IPluginConfiguration.cs @@ -1,12 +1,13 @@ -namespace Dalamud.Configuration; - -/// -/// Configuration to store settings for a dalamud plugin. -/// -public interface IPluginConfiguration +namespace Dalamud.Configuration { /// - /// Gets or sets configuration version. + /// Configuration to store settings for a dalamud plugin. /// - int Version { get; set; } + public interface IPluginConfiguration + { + /// + /// Gets or sets configuration version. + /// + int Version { get; set; } + } } diff --git a/Dalamud/Configuration/Internal/DalamudConfiguration.cs b/Dalamud/Configuration/Internal/DalamudConfiguration.cs index 7e9718f2d..b4263679e 100644 --- a/Dalamud/Configuration/Internal/DalamudConfiguration.cs +++ b/Dalamud/Configuration/Internal/DalamudConfiguration.cs @@ -8,267 +8,268 @@ using Newtonsoft.Json; using Serilog; using Serilog.Events; -namespace Dalamud.Configuration.Internal; - -/// -/// Class containing Dalamud settings. -/// -[Serializable] -internal sealed class DalamudConfiguration +namespace Dalamud.Configuration.Internal { - private static readonly JsonSerializerSettings SerializerSettings = new() + /// + /// Class containing Dalamud settings. + /// + [Serializable] + internal sealed class DalamudConfiguration { - TypeNameHandling = TypeNameHandling.All, - TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, - Formatting = Formatting.Indented, - }; - - [JsonIgnore] - private string configPath; - - /// - /// Delegate for the event that occurs when the dalamud configuration is saved. - /// - /// The current dalamud configuration. - public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration); - - /// - /// Event that occurs when dalamud configuration is saved. - /// - public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved; - - /// - /// Gets or sets a list of muted works. - /// - public List BadWords { get; set; } - - /// - /// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found. - /// - public bool DutyFinderTaskbarFlash { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found. - /// - public bool DutyFinderChatMessage { get; set; } = true; - - /// - /// Gets or sets the language code to load Dalamud localization with. - /// - public string LanguageOverride { get; set; } = null; - - /// - /// Gets or sets the last loaded Dalamud version. - /// - public string LastVersion { get; set; } = null; - - /// - /// Gets or sets the last loaded Dalamud version. - /// - public string LastChangelogMajorMinor { get; set; } = null; - - /// - /// Gets or sets the chat type used by default for plugin messages. - /// - public XivChatType GeneralChatType { get; set; } = XivChatType.Debug; - - /// - /// Gets or sets a value indicating whether or not plugin testing builds should be shown. - /// - public bool DoPluginTest { get; set; } = false; - - /// - /// Gets or sets a value indicating whether or not Dalamud testing builds should be used. - /// - public bool DoDalamudTest { get; set; } = false; - - /// - /// Gets or sets a value indicating whether or not XL should download the Dalamud .NET runtime. - /// - public bool DoDalamudRuntime { get; set; } = false; - - /// - /// Gets or sets a list of custom repos. - /// - public List ThirdRepoList { get; set; } = new(); - - /// - /// Gets or sets a list of hidden plugins. - /// - public List HiddenPluginInternalName { get; set; } = new(); - - /// - /// Gets or sets a list of seen plugins. - /// - public List SeenPluginInternalName { get; set; } = new(); - - /// - /// Gets or sets a list of additional settings for devPlugins. The key is the absolute path - /// to the plugin DLL. This is automatically generated for any plugins in the devPlugins folder. - /// However by specifiying this value manually, you can add arbitrary files outside the normal - /// file paths. - /// - public Dictionary DevPluginSettings { get; set; } = new(); - - /// - /// Gets or sets a list of additional locations that dev plugins should be loaded from. This can - /// be either a DLL or folder, but should be the absolute path, or a path relative to the currently - /// injected Dalamud instance. - /// - public List DevPluginLoadLocations { get; set; } = new(); - - /// - /// Gets or sets the global UI scale. - /// - public float GlobalUiScale { get; set; } = 1.0f; - - /// - /// Gets or sets a value indicating whether or not plugin UI should be hidden. - /// - public bool ToggleUiHide { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes. - /// - public bool ToggleUiHideDuringCutscenes { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose. - /// - public bool ToggleUiHideDuringGpose { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login. - /// - public bool PrintPluginsWelcomeMsg { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not plugins should be auto-updated. - /// - public bool AutoUpdatePlugins { get; set; } - - /// - /// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu. - /// - public bool DoButtonsSystemMenu { get; set; } = true; - - /// - /// Gets or sets the default Dalamud debug log level on startup. - /// - public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information; - - /// - /// Gets or sets a value indicating whether or not the debug log should scroll automatically. - /// - public bool LogAutoScroll { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not the debug log should open at startup. - /// - public bool LogOpenAtStartup { get; set; } - - /// - /// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup. - /// - public bool AssertsEnabledAtStartup { get; set; } - - /// - /// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui. - /// - public bool IsDocking { get; set; } - - /// - /// Gets or sets a value indicating whether viewports should always be disabled. - /// - public bool IsDisableViewport { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui. - /// - public bool IsGamepadNavigationEnabled { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not focus management is enabled. - /// - public bool IsFocusManagementEnabled { get; set; } = true; - - /// - /// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup. - /// - public bool IsAntiAntiDebugEnabled { get; set; } = false; - - /// - /// Gets or sets the kind of beta to download when is set to true. - /// - public string DalamudBetaKind { get; set; } - - /// - /// Gets or sets a value indicating whether or not all plugins, regardless of API level, should be loaded. - /// - public bool LoadAllApiLevels { get; set; } - - /// - /// Gets or sets a value indicating whether or not banned plugins should be loaded. - /// - public bool LoadBannedPlugins { get; set; } - - /// - /// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started. - /// It is reset immediately when read. - /// - public bool PluginSafeMode { get; set; } - - /// - /// Gets or sets a list of saved styles. - /// - [JsonProperty("SavedStyles")] - public List? SavedStylesOld { get; set; } - - /// - /// Gets or sets a list of saved styles. - /// - [JsonProperty("SavedStylesVersioned")] - public List? SavedStyles { get; set; } - - /// - /// Gets or sets the name of the currently chosen style. - /// - public string ChosenStyle { get; set; } = "Dalamud Standard"; - - /// - /// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled. - /// - public bool DisableRmtFiltering { get; set; } - - /// - /// Load a configuration from the provided path. - /// - /// The path to load the configuration file from. - /// The deserialized configuration file. - public static DalamudConfiguration Load(string path) - { - DalamudConfiguration deserialized; - try + private static readonly JsonSerializerSettings SerializerSettings = new() { - deserialized = JsonConvert.DeserializeObject(File.ReadAllText(path), SerializerSettings); - } - catch (Exception ex) + TypeNameHandling = TypeNameHandling.All, + TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, + Formatting = Formatting.Indented, + }; + + [JsonIgnore] + private string configPath; + + /// + /// Delegate for the event that occurs when the dalamud configuration is saved. + /// + /// The current dalamud configuration. + public delegate void DalamudConfigurationSavedDelegate(DalamudConfiguration dalamudConfiguration); + + /// + /// Event that occurs when dalamud configuration is saved. + /// + public event DalamudConfigurationSavedDelegate DalamudConfigurationSaved; + + /// + /// Gets or sets a list of muted works. + /// + public List BadWords { get; set; } + + /// + /// Gets or sets a value indicating whether or not the taskbar should flash once a duty is found. + /// + public bool DutyFinderTaskbarFlash { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not a message should be sent in chat once a duty is found. + /// + public bool DutyFinderChatMessage { get; set; } = true; + + /// + /// Gets or sets the language code to load Dalamud localization with. + /// + public string LanguageOverride { get; set; } = null; + + /// + /// Gets or sets the last loaded Dalamud version. + /// + public string LastVersion { get; set; } = null; + + /// + /// Gets or sets the last loaded Dalamud version. + /// + public string LastChangelogMajorMinor { get; set; } = null; + + /// + /// Gets or sets the chat type used by default for plugin messages. + /// + public XivChatType GeneralChatType { get; set; } = XivChatType.Debug; + + /// + /// Gets or sets a value indicating whether or not plugin testing builds should be shown. + /// + public bool DoPluginTest { get; set; } = false; + + /// + /// Gets or sets a value indicating whether or not Dalamud testing builds should be used. + /// + public bool DoDalamudTest { get; set; } = false; + + /// + /// Gets or sets a value indicating whether or not XL should download the Dalamud .NET runtime. + /// + public bool DoDalamudRuntime { get; set; } = false; + + /// + /// Gets or sets a list of custom repos. + /// + public List ThirdRepoList { get; set; } = new(); + + /// + /// Gets or sets a list of hidden plugins. + /// + public List HiddenPluginInternalName { get; set; } = new(); + + /// + /// Gets or sets a list of seen plugins. + /// + public List SeenPluginInternalName { get; set; } = new(); + + /// + /// Gets or sets a list of additional settings for devPlugins. The key is the absolute path + /// to the plugin DLL. This is automatically generated for any plugins in the devPlugins folder. + /// However by specifiying this value manually, you can add arbitrary files outside the normal + /// file paths. + /// + public Dictionary DevPluginSettings { get; set; } = new(); + + /// + /// Gets or sets a list of additional locations that dev plugins should be loaded from. This can + /// be either a DLL or folder, but should be the absolute path, or a path relative to the currently + /// injected Dalamud instance. + /// + public List DevPluginLoadLocations { get; set; } = new(); + + /// + /// Gets or sets the global UI scale. + /// + public float GlobalUiScale { get; set; } = 1.0f; + + /// + /// Gets or sets a value indicating whether or not plugin UI should be hidden. + /// + public bool ToggleUiHide { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not plugin UI should be hidden during cutscenes. + /// + public bool ToggleUiHideDuringCutscenes { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not plugin UI should be hidden during GPose. + /// + public bool ToggleUiHideDuringGpose { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not a message containing detailed plugin information should be sent at login. + /// + public bool PrintPluginsWelcomeMsg { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not plugins should be auto-updated. + /// + public bool AutoUpdatePlugins { get; set; } + + /// + /// Gets or sets a value indicating whether or not Dalamud should add buttons to the system menu. + /// + public bool DoButtonsSystemMenu { get; set; } = true; + + /// + /// Gets or sets the default Dalamud debug log level on startup. + /// + public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information; + + /// + /// Gets or sets a value indicating whether or not the debug log should scroll automatically. + /// + public bool LogAutoScroll { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not the debug log should open at startup. + /// + public bool LogOpenAtStartup { get; set; } + + /// + /// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup. + /// + public bool AssertsEnabledAtStartup { get; set; } + + /// + /// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui. + /// + public bool IsDocking { get; set; } + + /// + /// Gets or sets a value indicating whether viewports should always be disabled. + /// + public bool IsDisableViewport { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not navigation via a gamepad should be globally enabled in ImGui. + /// + public bool IsGamepadNavigationEnabled { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not focus management is enabled. + /// + public bool IsFocusManagementEnabled { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not the anti-anti-debug check is enabled on startup. + /// + public bool IsAntiAntiDebugEnabled { get; set; } = false; + + /// + /// Gets or sets the kind of beta to download when is set to true. + /// + public string DalamudBetaKind { get; set; } + + /// + /// Gets or sets a value indicating whether or not all plugins, regardless of API level, should be loaded. + /// + public bool LoadAllApiLevels { get; set; } + + /// + /// Gets or sets a value indicating whether or not banned plugins should be loaded. + /// + public bool LoadBannedPlugins { get; set; } + + /// + /// Gets or sets a value indicating whether or not any plugin should be loaded when the game is started. + /// It is reset immediately when read. + /// + public bool PluginSafeMode { get; set; } + + /// + /// Gets or sets a list of saved styles. + /// + [JsonProperty("SavedStyles")] + public List? SavedStylesOld { get; set; } + + /// + /// Gets or sets a list of saved styles. + /// + [JsonProperty("SavedStylesVersioned")] + public List? SavedStyles { get; set; } + + /// + /// Gets or sets the name of the currently chosen style. + /// + public string ChosenStyle { get; set; } = "Dalamud Standard"; + + /// + /// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled. + /// + public bool DisableRmtFiltering { get; set; } + + /// + /// Load a configuration from the provided path. + /// + /// The path to load the configuration file from. + /// The deserialized configuration file. + public static DalamudConfiguration Load(string path) { - Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path); - deserialized = new DalamudConfiguration(); + DalamudConfiguration deserialized; + try + { + deserialized = JsonConvert.DeserializeObject(File.ReadAllText(path), SerializerSettings); + } + catch (Exception ex) + { + Log.Warning(ex, "Failed to load DalamudConfiguration at {0}", path); + deserialized = new DalamudConfiguration(); + } + + deserialized.configPath = path; + + return deserialized; } - deserialized.configPath = path; - - return deserialized; - } - - /// - /// Save the configuration at the path it was loaded from. - /// - public void Save() - { - File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings)); - this.DalamudConfigurationSaved?.Invoke(this); + /// + /// Save the configuration at the path it was loaded from. + /// + public void Save() + { + File.WriteAllText(this.configPath, JsonConvert.SerializeObject(this, SerializerSettings)); + this.DalamudConfigurationSaved?.Invoke(this); + } } } diff --git a/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs b/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs index 2bf34c4fa..995fb1a23 100644 --- a/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs +++ b/Dalamud/Configuration/Internal/DevPluginLocationSettings.cs @@ -1,23 +1,24 @@ -namespace Dalamud.Configuration.Internal; - -/// -/// Additional locations to load dev plugins from. -/// -internal sealed class DevPluginLocationSettings +namespace Dalamud.Configuration { /// - /// Gets or sets the dev pluign path. + /// Additional locations to load dev plugins from. /// - public string Path { get; set; } + internal sealed class DevPluginLocationSettings + { + /// + /// Gets or sets the dev pluign path. + /// + public string Path { get; set; } - /// - /// Gets or sets a value indicating whether the third party repo is enabled. - /// - public bool IsEnabled { get; set; } + /// + /// Gets or sets a value indicating whether the third party repo is enabled. + /// + public bool IsEnabled { get; set; } - /// - /// Clone this object. - /// - /// A shallow copy of this object. - public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings; + /// + /// Clone this object. + /// + /// A shallow copy of this object. + public DevPluginLocationSettings Clone() => this.MemberwiseClone() as DevPluginLocationSettings; + } } diff --git a/Dalamud/Configuration/Internal/DevPluginSettings.cs b/Dalamud/Configuration/Internal/DevPluginSettings.cs index 939b03eca..17350cba0 100644 --- a/Dalamud/Configuration/Internal/DevPluginSettings.cs +++ b/Dalamud/Configuration/Internal/DevPluginSettings.cs @@ -1,17 +1,18 @@ -namespace Dalamud.Configuration.Internal; - -/// -/// Settings for DevPlugins. -/// -internal sealed class DevPluginSettings +namespace Dalamud.Configuration.Internal { /// - /// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up. + /// Settings for DevPlugins. /// - public bool StartOnBoot { get; set; } = true; + internal sealed class DevPluginSettings + { + /// + /// Gets or sets a value indicating whether this plugin should automatically start when Dalamud boots up. + /// + public bool StartOnBoot { get; set; } = true; - /// - /// Gets or sets a value indicating whether this plugin should automatically reload on file change. - /// - public bool AutomaticReloading { get; set; } = false; + /// + /// Gets or sets a value indicating whether this plugin should automatically reload on file change. + /// + public bool AutomaticReloading { get; set; } = false; + } } diff --git a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs index 42885e8e0..2cb89915c 100644 --- a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs +++ b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs @@ -1,37 +1,38 @@ using System; -namespace Dalamud.Configuration.Internal; - -/// -/// Environmental configuration settings. -/// -internal class EnvironmentConfiguration +namespace Dalamud.Configuration.Internal { /// - /// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled. + /// Environmental configuration settings. /// - public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX"); + internal class EnvironmentConfiguration + { + /// + /// Gets a value indicating whether the XL_WINEONLINUX setting has been enabled. + /// + public static bool XlWineOnLinux { get; } = GetEnvironmentVariable("XL_WINEONLINUX"); - /// - /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled. - /// - public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS"); + /// + /// Gets a value indicating whether the DALAMUD_NOT_HAVE_PLUGINS setting has been enabled. + /// + public static bool DalamudNoPlugins { get; } = GetEnvironmentVariable("DALAMUD_NOT_HAVE_PLUGINS"); - /// - /// Gets a value indicating whether the DalamudForceReloaded setting has been enabled. - /// - public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED"); + /// + /// Gets a value indicating whether the DalamudForceReloaded setting has been enabled. + /// + public static bool DalamudForceReloaded { get; } = GetEnvironmentVariable("DALAMUD_FORCE_RELOADED"); - /// - /// Gets a value indicating whether the DalamudForceMinHook setting has been enabled. - /// - public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK"); + /// + /// Gets a value indicating whether the DalamudForceMinHook setting has been enabled. + /// + public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_MINHOOK"); - /// - /// Gets a value indicating whether or not Dalamud should wait for a debugger to be attached when initializing. - /// - public static bool DalamudWaitForDebugger { get; } = GetEnvironmentVariable("DALAMUD_WAIT_DEBUGGER"); + /// + /// Gets a value indicating whether or not Dalamud should wait for a debugger to be attached when initializing. + /// + public static bool DalamudWaitForDebugger { get; } = GetEnvironmentVariable("DALAMUD_WAIT_DEBUGGER"); - private static bool GetEnvironmentVariable(string name) - => bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false"); + private static bool GetEnvironmentVariable(string name) + => bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false"); + } } diff --git a/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs b/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs index 94bfc96e7..cafb96a47 100644 --- a/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs +++ b/Dalamud/Configuration/Internal/ThirdPartyRepoSettings.cs @@ -1,28 +1,29 @@ -namespace Dalamud.Configuration.Internal; - -/// -/// Third party repository for dalamud plugins. -/// -internal sealed class ThirdPartyRepoSettings +namespace Dalamud.Configuration { /// - /// Gets or sets the third party repo url. + /// Third party repository for dalamud plugins. /// - public string Url { get; set; } + internal sealed class ThirdPartyRepoSettings + { + /// + /// Gets or sets the third party repo url. + /// + public string Url { get; set; } - /// - /// Gets or sets a value indicating whether the third party repo is enabled. - /// - public bool IsEnabled { get; set; } + /// + /// Gets or sets a value indicating whether the third party repo is enabled. + /// + public bool IsEnabled { get; set; } - /// - /// Gets or sets a short name for the repo url. - /// - public string Name { get; set; } + /// + /// Gets or sets a short name for the repo url. + /// + public string Name { get; set; } - /// - /// Clone this object. - /// - /// A shallow copy of this object. - public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings; + /// + /// Clone this object. + /// + /// A shallow copy of this object. + public ThirdPartyRepoSettings Clone() => this.MemberwiseClone() as ThirdPartyRepoSettings; + } } diff --git a/Dalamud/Configuration/PluginConfigurations.cs b/Dalamud/Configuration/PluginConfigurations.cs index 349d51e57..faa01af99 100644 --- a/Dalamud/Configuration/PluginConfigurations.cs +++ b/Dalamud/Configuration/PluginConfigurations.cs @@ -2,128 +2,129 @@ using System.IO; using Newtonsoft.Json; -namespace Dalamud.Configuration; - -/// -/// Configuration to store settings for a dalamud plugin. -/// -public sealed class PluginConfigurations +namespace Dalamud.Configuration { - private readonly DirectoryInfo configDirectory; - /// - /// Initializes a new instance of the class. + /// Configuration to store settings for a dalamud plugin. /// - /// Directory for storage of plugin configuration files. - public PluginConfigurations(string storageFolder) + public sealed class PluginConfigurations { - this.configDirectory = new DirectoryInfo(storageFolder); - this.configDirectory.Create(); - } + private readonly DirectoryInfo configDirectory; - /// - /// Save/Load plugin configuration. - /// NOTE: Save/Load are still using Type information for now, - /// despite LoadForType superseding Load and not requiring or using it. - /// It might be worth removing the Type info from Save, to strip it from all future saved configs, - /// and then Load() can probably be removed entirely. - /// - /// Plugin configuration. - /// Plugin name. - public void Save(IPluginConfiguration config, string pluginName) - { - File.WriteAllText(this.GetConfigFile(pluginName).FullName, JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings + /// + /// Initializes a new instance of the class. + /// + /// Directory for storage of plugin configuration files. + public PluginConfigurations(string storageFolder) { - TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, - TypeNameHandling = TypeNameHandling.Objects, - })); - } + this.configDirectory = new DirectoryInfo(storageFolder); + this.configDirectory.Create(); + } - /// - /// Load plugin configuration. - /// - /// Plugin name. - /// Plugin configuration. - public IPluginConfiguration? Load(string pluginName) - { - var path = this.GetConfigFile(pluginName); - - if (!path.Exists) - return null; - - return JsonConvert.DeserializeObject( - File.ReadAllText(path.FullName), - new JsonSerializerSettings + /// + /// Save/Load plugin configuration. + /// NOTE: Save/Load are still using Type information for now, + /// despite LoadForType superseding Load and not requiring or using it. + /// It might be worth removing the Type info from Save, to strip it from all future saved configs, + /// and then Load() can probably be removed entirely. + /// + /// Plugin configuration. + /// Plugin name. + public void Save(IPluginConfiguration config, string pluginName) + { + File.WriteAllText(this.GetConfigFile(pluginName).FullName, JsonConvert.SerializeObject(config, Formatting.Indented, new JsonSerializerSettings { TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, TypeNameHandling = TypeNameHandling.Objects, - }); - } + })); + } - /// - /// Delete the configuration file and folder for the specified plugin. - /// This will throw an if the plugin did not correctly close its handles. - /// - /// The name of the plugin. - public void Delete(string pluginName) - { - var directory = this.GetDirectoryPath(pluginName); - if (directory.Exists) - directory.Delete(true); - - var file = this.GetConfigFile(pluginName); - if (file.Exists) - file.Delete(); - } - - /// - /// Get plugin directory. - /// - /// Plugin name. - /// Plugin directory path. - public string GetDirectory(string pluginName) - { - try + /// + /// Load plugin configuration. + /// + /// Plugin name. + /// Plugin configuration. + public IPluginConfiguration? Load(string pluginName) { - var path = this.GetDirectoryPath(pluginName); + var path = this.GetConfigFile(pluginName); + if (!path.Exists) - { - path.Create(); - } + return null; - return path.FullName; + return JsonConvert.DeserializeObject( + File.ReadAllText(path.FullName), + new JsonSerializerSettings + { + TypeNameAssemblyFormatHandling = TypeNameAssemblyFormatHandling.Simple, + TypeNameHandling = TypeNameHandling.Objects, + }); } - catch + + /// + /// Delete the configuration file and folder for the specified plugin. + /// This will throw an if the plugin did not correctly close its handles. + /// + /// The name of the plugin. + public void Delete(string pluginName) { - return string.Empty; + var directory = this.GetDirectoryPath(pluginName); + if (directory.Exists) + directory.Delete(true); + + var file = this.GetConfigFile(pluginName); + if (file.Exists) + file.Delete(); } + + /// + /// Get plugin directory. + /// + /// Plugin name. + /// Plugin directory path. + public string GetDirectory(string pluginName) + { + try + { + var path = this.GetDirectoryPath(pluginName); + if (!path.Exists) + { + path.Create(); + } + + return path.FullName; + } + catch + { + return string.Empty; + } + } + + /// + /// Load Plugin configuration. Parameterized deserialization. + /// Currently this is called via reflection from DalamudPluginInterface.GetPluginConfig(). + /// Eventually there may be an additional pluginInterface method that can call this directly + /// without reflection - for now this is in support of the existing plugin api. + /// + /// Plugin Name. + /// Configuration Type. + /// Plugin Configuration. + public T LoadForType(string pluginName) where T : IPluginConfiguration + { + var path = this.GetConfigFile(pluginName); + + return !path.Exists ? default : JsonConvert.DeserializeObject(File.ReadAllText(path.FullName)); + + // intentionally no type handling - it will break when updating a plugin at runtime + // and turns out to be unnecessary when we fully qualify the object type + } + + /// + /// Get FileInfo to plugin config file. + /// + /// InternalName of the plugin. + /// FileInfo of the config file. + public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json")); + + private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName)); } - - /// - /// Load Plugin configuration. Parameterized deserialization. - /// Currently this is called via reflection from DalamudPluginInterface.GetPluginConfig(). - /// Eventually there may be an additional pluginInterface method that can call this directly - /// without reflection - for now this is in support of the existing plugin api. - /// - /// Plugin Name. - /// Configuration Type. - /// Plugin Configuration. - public T LoadForType(string pluginName) where T : IPluginConfiguration - { - var path = this.GetConfigFile(pluginName); - - return !path.Exists ? default : JsonConvert.DeserializeObject(File.ReadAllText(path.FullName)); - - // intentionally no type handling - it will break when updating a plugin at runtime - // and turns out to be unnecessary when we fully qualify the object type - } - - /// - /// Get FileInfo to plugin config file. - /// - /// InternalName of the plugin. - /// FileInfo of the config file. - public FileInfo GetConfigFile(string pluginName) => new(Path.Combine(this.configDirectory.FullName, $"{pluginName}.json")); - - private DirectoryInfo GetDirectoryPath(string pluginName) => new(Path.Combine(this.configDirectory.FullName, pluginName)); } diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index eaf325ebf..f5460af2f 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -33,378 +33,379 @@ using Serilog.Events; [assembly: InternalsVisibleTo("Dalamud.Test")] -namespace Dalamud; - -/// -/// The main Dalamud class containing all subsystems. -/// -internal sealed class Dalamud : IDisposable +namespace Dalamud { - #region Internals - - private readonly ManualResetEvent unloadSignal; - private readonly ManualResetEvent finishUnloadSignal; - private MonoMod.RuntimeDetour.Hook processMonoHook; - private bool hasDisposedPlugins = false; - - #endregion - /// - /// Initializes a new instance of the class. + /// The main Dalamud class containing all subsystems. /// - /// DalamudStartInfo instance. - /// LoggingLevelSwitch to control Serilog level. - /// Signal signalling shutdown. - /// The Dalamud configuration. - public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration) + internal sealed class Dalamud : IDisposable { - this.ApplyProcessPatch(); + #region Internals - Service.Set(this); - Service.Set(info); - Service.Set(configuration); + private readonly ManualResetEvent unloadSignal; + private readonly ManualResetEvent finishUnloadSignal; + private MonoMod.RuntimeDetour.Hook processMonoHook; + private bool hasDisposedPlugins = false; - this.LogLevelSwitch = loggingLevelSwitch; + #endregion - this.unloadSignal = new ManualResetEvent(false); - this.unloadSignal.Reset(); - - this.finishUnloadSignal = finishSignal; - this.finishUnloadSignal.Reset(); - } - - /// - /// Gets LoggingLevelSwitch for Dalamud and Plugin logs. - /// - internal LoggingLevelSwitch LogLevelSwitch { get; private set; } - - /// - /// Gets location of stored assets. - /// - internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory); - - /// - /// Runs tier 1 of the Dalamud initialization process. - /// - public void LoadTier1() - { - try + /// + /// Initializes a new instance of the class. + /// + /// DalamudStartInfo instance. + /// LoggingLevelSwitch to control Serilog level. + /// Signal signalling shutdown. + /// The Dalamud configuration. + public Dalamud(DalamudStartInfo info, LoggingLevelSwitch loggingLevelSwitch, ManualResetEvent finishSignal, DalamudConfiguration configuration) { - SerilogEventSink.Instance.LogLine += SerilogOnLogLine; + this.ApplyProcessPatch(); - Service.Set(); + Service.Set(this); + Service.Set(info); + Service.Set(configuration); - // Initialize the process information. - Service.Set(new SigScanner(true)); - Service.Set(); + this.LogLevelSwitch = loggingLevelSwitch; - // Initialize FFXIVClientStructs function resolver - FFXIVClientStructs.Resolver.Initialize(); - Log.Information("[T1] FFXIVClientStructs initialized!"); + this.unloadSignal = new ManualResetEvent(false); + this.unloadSignal.Reset(); - // Initialize game subsystem - var framework = Service.Set(); - Log.Information("[T1] Framework OK!"); - -#if DEBUG - Service.Set(); - Log.Information("[T1] TaskTracker OK!"); -#endif - Service.Set(); - Service.Set(); - - framework.Enable(); - - Log.Information("[T1] Load complete!"); + this.finishUnloadSignal = finishSignal; + this.finishUnloadSignal.Reset(); } - catch (Exception ex) - { - Log.Error(ex, "Tier 1 load failed."); - this.Unload(); - } - } - /// - /// Runs tier 2 of the Dalamud initialization process. - /// - /// Whether or not the load succeeded. - public bool LoadTier2() - { - try - { - var configuration = Service.Get(); + /// + /// Gets LoggingLevelSwitch for Dalamud and Plugin logs. + /// + internal LoggingLevelSwitch LogLevelSwitch { get; private set; } - var antiDebug = Service.Set(); - if (!antiDebug.IsEnabled) + /// + /// Gets location of stored assets. + /// + internal DirectoryInfo AssetDirectory => new(Service.Get().AssetDirectory); + + /// + /// Runs tier 1 of the Dalamud initialization process. + /// + public void LoadTier1() + { + try { + SerilogEventSink.Instance.LogLine += SerilogOnLogLine; + + Service.Set(); + + // Initialize the process information. + Service.Set(new SigScanner(true)); + Service.Set(); + + // Initialize FFXIVClientStructs function resolver + FFXIVClientStructs.Resolver.Initialize(); + Log.Information("[T1] FFXIVClientStructs initialized!"); + + // Initialize game subsystem + var framework = Service.Set(); + Log.Information("[T1] Framework OK!"); + #if DEBUG - antiDebug.Enable(); + Service.Set(); + Log.Information("[T1] TaskTracker OK!"); +#endif + Service.Set(); + Service.Set(); + + framework.Enable(); + + Log.Information("[T1] Load complete!"); + } + catch (Exception ex) + { + Log.Error(ex, "Tier 1 load failed."); + this.Unload(); + } + } + + /// + /// Runs tier 2 of the Dalamud initialization process. + /// + /// Whether or not the load succeeded. + public bool LoadTier2() + { + try + { + var configuration = Service.Get(); + + var antiDebug = Service.Set(); + if (!antiDebug.IsEnabled) + { +#if DEBUG + antiDebug.Enable(); #else if (configuration.IsAntiAntiDebugEnabled) antiDebug.Enable(); #endif + } + + Log.Information("[T2] AntiDebug OK!"); + + Service.Set(); + Log.Information("[T2] WinSock OK!"); + + Service.Set(); + Log.Information("[T2] NH OK!"); + + try + { + Service.Set().Initialize(this.AssetDirectory.FullName); + } + catch (Exception e) + { + Log.Error(e, "Could not initialize DataManager."); + this.Unload(); + return false; + } + + Log.Information("[T2] Data OK!"); + + var clientState = Service.Set(); + Log.Information("[T2] CS OK!"); + + var localization = Service.Set(new Localization(Path.Combine(this.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_")); + if (!string.IsNullOrEmpty(configuration.LanguageOverride)) + { + localization.SetupWithLangCode(configuration.LanguageOverride); + } + else + { + localization.SetupWithUiCulture(); + } + + Log.Information("[T2] LOC OK!"); + + // This is enabled in ImGuiScene setup + Service.Set(); + Log.Information("[T2] IME OK!"); + + Service.Set().Enable(); + Log.Information("[T2] IM OK!"); + +#pragma warning disable CS0618 // Type or member is obsolete + Service.Set(); +#pragma warning restore CS0618 // Type or member is obsolete + + Log.Information("[T2] SeString OK!"); + + // Initialize managers. Basically handlers for the logic + Service.Set(); + + Service.Set().SetupCommands(); + + Log.Information("[T2] CM OK!"); + + Service.Set(); + + Log.Information("[T2] CH OK!"); + + clientState.Enable(); + Log.Information("[T2] CS ENABLE!"); + + Service.Set().Enable(); + + Log.Information("[T2] Load complete!"); } - - Log.Information("[T2] AntiDebug OK!"); - - Service.Set(); - Log.Information("[T2] WinSock OK!"); - - Service.Set(); - Log.Information("[T2] NH OK!"); - - try + catch (Exception ex) { - Service.Set().Initialize(this.AssetDirectory.FullName); - } - catch (Exception e) - { - Log.Error(e, "Could not initialize DataManager."); + Log.Error(ex, "Tier 2 load failed."); this.Unload(); return false; } - Log.Information("[T2] Data OK!"); - - var clientState = Service.Set(); - Log.Information("[T2] CS OK!"); - - var localization = Service.Set(new Localization(Path.Combine(this.AssetDirectory.FullName, "UIRes", "loc", "dalamud"), "dalamud_")); - if (!string.IsNullOrEmpty(configuration.LanguageOverride)) - { - localization.SetupWithLangCode(configuration.LanguageOverride); - } - else - { - localization.SetupWithUiCulture(); - } - - Log.Information("[T2] LOC OK!"); - - // This is enabled in ImGuiScene setup - Service.Set(); - Log.Information("[T2] IME OK!"); - - Service.Set().Enable(); - Log.Information("[T2] IM OK!"); - -#pragma warning disable CS0618 // Type or member is obsolete - Service.Set(); -#pragma warning restore CS0618 // Type or member is obsolete - - Log.Information("[T2] SeString OK!"); - - // Initialize managers. Basically handlers for the logic - Service.Set(); - - Service.Set().SetupCommands(); - - Log.Information("[T2] CM OK!"); - - Service.Set(); - - Log.Information("[T2] CH OK!"); - - clientState.Enable(); - Log.Information("[T2] CS ENABLE!"); - - Service.Set().Enable(); - - Log.Information("[T2] Load complete!"); - } - catch (Exception ex) - { - Log.Error(ex, "Tier 2 load failed."); - this.Unload(); - return false; + return true; } - return true; - } - - /// - /// Runs tier 3 of the Dalamud initialization process. - /// - /// Whether or not the load succeeded. - public bool LoadTier3() - { - try + /// + /// Runs tier 3 of the Dalamud initialization process. + /// + /// Whether or not the load succeeded. + public bool LoadTier3() { - Log.Information("[T3] START!"); - - var pluginManager = Service.Set(); - Service.Set(); - try { - _ = pluginManager.SetPluginReposFromConfigAsync(false); + Log.Information("[T3] START!"); - pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting; + var pluginManager = Service.Set(); + Service.Set(); - Log.Information("[T3] PM OK!"); + try + { + _ = pluginManager.SetPluginReposFromConfigAsync(false); - pluginManager.CleanupPlugins(); - Log.Information("[T3] PMC OK!"); + pluginManager.OnInstalledPluginsChanged += Troubleshooting.LogTroubleshooting; - pluginManager.LoadAllPlugins(); - Log.Information("[T3] PML OK!"); + Log.Information("[T3] PM OK!"); + + pluginManager.CleanupPlugins(); + Log.Information("[T3] PMC OK!"); + + pluginManager.LoadAllPlugins(); + Log.Information("[T3] PML OK!"); + } + catch (Exception ex) + { + Log.Error(ex, "Plugin load failed."); + } + + Service.Set(); + Log.Information("[T3] DUI OK!"); + + Troubleshooting.LogTroubleshooting(); + + Log.Information("Dalamud is ready."); } catch (Exception ex) { - Log.Error(ex, "Plugin load failed."); + Log.Error(ex, "Tier 3 load failed."); + this.Unload(); + + return false; } - Service.Set(); - Log.Information("[T3] DUI OK!"); - - Troubleshooting.LogTroubleshooting(); - - Log.Information("Dalamud is ready."); - } - catch (Exception ex) - { - Log.Error(ex, "Tier 3 load failed."); - this.Unload(); - - return false; + return true; } - return true; - } - - /// - /// Queue an unload of Dalamud when it gets the chance. - /// - public void Unload() - { - Log.Information("Trigger unload"); - this.unloadSignal.Set(); - } - - /// - /// Wait for an unload request to start. - /// - public void WaitForUnload() - { - this.unloadSignal.WaitOne(); - } - - /// - /// Wait for a queued unload to be finalized. - /// - public void WaitForUnloadFinish() - { - this.finishUnloadSignal?.WaitOne(); - } - - /// - /// Dispose subsystems related to plugin handling. - /// - public void DisposePlugins() - { - this.hasDisposedPlugins = true; - - // this must be done before unloading interface manager, in order to do rebuild - // the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game - // will not receive any windows messages - Service.GetNullable()?.Dispose(); - - // this must be done before unloading plugins, or it can cause a race condition - // due to rendering happening on another thread, where a plugin might receive - // a render call after it has been disposed, which can crash if it attempts to - // use any resources that it freed in its own Dispose method - Service.GetNullable()?.Dispose(); - - Service.GetNullable()?.Dispose(); - - Service.GetNullable()?.Dispose(); - } - - /// - /// Dispose Dalamud subsystems. - /// - public void Dispose() - { - try + /// + /// Queue an unload of Dalamud when it gets the chance. + /// + public void Unload() { - if (!this.hasDisposedPlugins) + Log.Information("Trigger unload"); + this.unloadSignal.Set(); + } + + /// + /// Wait for an unload request to start. + /// + public void WaitForUnload() + { + this.unloadSignal.WaitOne(); + } + + /// + /// Wait for a queued unload to be finalized. + /// + public void WaitForUnloadFinish() + { + this.finishUnloadSignal?.WaitOne(); + } + + /// + /// Dispose subsystems related to plugin handling. + /// + public void DisposePlugins() + { + this.hasDisposedPlugins = true; + + // this must be done before unloading interface manager, in order to do rebuild + // the correct cascaded WndProc (IME -> RawDX11Scene -> Game). Otherwise the game + // will not receive any windows messages + Service.GetNullable()?.Dispose(); + + // this must be done before unloading plugins, or it can cause a race condition + // due to rendering happening on another thread, where a plugin might receive + // a render call after it has been disposed, which can crash if it attempts to + // use any resources that it freed in its own Dispose method + Service.GetNullable()?.Dispose(); + + Service.GetNullable()?.Dispose(); + + Service.GetNullable()?.Dispose(); + } + + /// + /// Dispose Dalamud subsystems. + /// + public void Dispose() + { + try { - this.DisposePlugins(); - Thread.Sleep(100); + if (!this.hasDisposedPlugins) + { + this.DisposePlugins(); + Thread.Sleep(100); + } + + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + + this.unloadSignal?.Dispose(); + + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + + SerilogEventSink.Instance.LogLine -= SerilogOnLogLine; + + this.processMonoHook?.Dispose(); + + Log.Debug("Dalamud::Dispose() OK!"); + } + catch (Exception ex) + { + Log.Error(ex, "Dalamud::Dispose() failed."); + } + } + + /// + /// Replace the built-in exception handler with a debug one. + /// + internal void ReplaceExceptionHandler() + { + var releaseSig = "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??"; + var releaseFilter = Service.Get().ScanText(releaseSig); + Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}"); + + var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter); + Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter); + } + + private static void SerilogOnLogLine(object? sender, (string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception) e) + { + if (e.Exception == null) + return; + + Troubleshooting.LogException(e.Exception, e.Line); + } + + /// + /// Patch method for the class Process.Handle. This patch facilitates fixing Reloaded so that it + /// uses pseudo-handles to access memory, to prevent permission errors. + /// It should never be called manually. + /// + /// A delegate that acts as the original method. + /// The equivalent of `this`. + /// A pseudo-handle for the current process, or the result from the original method. + private static IntPtr ProcessHandlePatch(Func orig, Process self) + { + var result = orig(self); + + if (self.Id == Environment.ProcessId) + { + result = (IntPtr)0xFFFFFFFF; } - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - - this.unloadSignal?.Dispose(); - - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - - SerilogEventSink.Instance.LogLine -= SerilogOnLogLine; - - this.processMonoHook?.Dispose(); - - Log.Debug("Dalamud::Dispose() OK!"); + // Log.Verbose($"Process.Handle // {self.ProcessName} // {result:X}"); + return result; } - catch (Exception ex) + + private void ApplyProcessPatch() { - Log.Error(ex, "Dalamud::Dispose() failed."); + var targetType = typeof(Process); + + var handleTarget = targetType.GetProperty(nameof(Process.Handle)).GetGetMethod(); + var handlePatch = typeof(Dalamud).GetMethod(nameof(Dalamud.ProcessHandlePatch), BindingFlags.NonPublic | BindingFlags.Static); + this.processMonoHook = new MonoMod.RuntimeDetour.Hook(handleTarget, handlePatch); } } - - /// - /// Replace the built-in exception handler with a debug one. - /// - internal void ReplaceExceptionHandler() - { - var releaseSig = "40 55 53 56 48 8D AC 24 ?? ?? ?? ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 85 ?? ?? ?? ?? 48 83 3D ?? ?? ?? ?? ??"; - var releaseFilter = Service.Get().ScanText(releaseSig); - Log.Debug($"SE debug filter at {releaseFilter.ToInt64():X}"); - - var oldFilter = NativeFunctions.SetUnhandledExceptionFilter(releaseFilter); - Log.Debug("Reset ExceptionFilter, old: {0}", oldFilter); - } - - private static void SerilogOnLogLine(object? sender, (string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception) e) - { - if (e.Exception == null) - return; - - Troubleshooting.LogException(e.Exception, e.Line); - } - - /// - /// Patch method for the class Process.Handle. This patch facilitates fixing Reloaded so that it - /// uses pseudo-handles to access memory, to prevent permission errors. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// A pseudo-handle for the current process, or the result from the original method. - private static IntPtr ProcessHandlePatch(Func orig, Process self) - { - var result = orig(self); - - if (self.Id == Environment.ProcessId) - { - result = (IntPtr)0xFFFFFFFF; - } - - // Log.Verbose($"Process.Handle // {self.ProcessName} // {result:X}"); - return result; - } - - private void ApplyProcessPatch() - { - var targetType = typeof(Process); - - var handleTarget = targetType.GetProperty(nameof(Process.Handle)).GetGetMethod(); - var handlePatch = typeof(Dalamud).GetMethod(nameof(Dalamud.ProcessHandlePatch), BindingFlags.NonPublic | BindingFlags.Static); - this.processMonoHook = new MonoMod.RuntimeDetour.Hook(handleTarget, handlePatch); - } } diff --git a/Dalamud/DalamudStartInfo.cs b/Dalamud/DalamudStartInfo.cs index fbdcd3588..891a57dca 100644 --- a/Dalamud/DalamudStartInfo.cs +++ b/Dalamud/DalamudStartInfo.cs @@ -3,57 +3,58 @@ using System; using Dalamud.Game; using Newtonsoft.Json; -namespace Dalamud; - -/// -/// Struct containing information needed to initialize Dalamud. -/// -[Serializable] -public record DalamudStartInfo +namespace Dalamud { /// - /// Gets or sets the working directory of the XIVLauncher installations. + /// Struct containing information needed to initialize Dalamud. /// - public string WorkingDirectory { get; set; } + [Serializable] + public record DalamudStartInfo + { + /// + /// Gets or sets the working directory of the XIVLauncher installations. + /// + public string WorkingDirectory { get; set; } - /// - /// Gets the path to the configuration file. - /// - public string ConfigurationPath { get; init; } + /// + /// Gets the path to the configuration file. + /// + public string ConfigurationPath { get; init; } - /// - /// Gets the path to the directory for installed plugins. - /// - public string PluginDirectory { get; init; } + /// + /// Gets the path to the directory for installed plugins. + /// + public string PluginDirectory { get; init; } - /// - /// Gets the path to the directory for developer plugins. - /// - public string DefaultPluginDirectory { get; init; } + /// + /// Gets the path to the directory for developer plugins. + /// + public string DefaultPluginDirectory { get; init; } - /// - /// Gets the path to core Dalamud assets. - /// - public string AssetDirectory { get; init; } + /// + /// Gets the path to core Dalamud assets. + /// + public string AssetDirectory { get; init; } - /// - /// Gets the language of the game client. - /// - public ClientLanguage Language { get; init; } + /// + /// Gets the language of the game client. + /// + public ClientLanguage Language { get; init; } - /// - /// Gets the current game version code. - /// - [JsonConverter(typeof(GameVersionConverter))] - public GameVersion GameVersion { get; init; } + /// + /// Gets the current game version code. + /// + [JsonConverter(typeof(GameVersionConverter))] + public GameVersion GameVersion { get; init; } - /// - /// Gets a value indicating whether or not market board information should be uploaded by default. - /// - public bool OptOutMbCollection { get; init; } + /// + /// Gets a value indicating whether or not market board information should be uploaded by default. + /// + public bool OptOutMbCollection { get; init; } - /// - /// Gets a value that specifies how much to wait before a new Dalamud session. - /// - public int DelayInitializeMs { get; init; } = 0; + /// + /// Gets a value that specifies how much to wait before a new Dalamud session. + /// + public int DelayInitializeMs { get; init; } = 0; + } } diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index 61f701055..a78f0eec3 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -18,329 +18,330 @@ using Lumina.Excel; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Data; - -/// -/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class DataManager : IDisposable +namespace Dalamud.Data { - private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; - - private Thread luminaResourceThread; - private CancellationTokenSource luminaCancellationTokenSource; - /// - /// Initializes a new instance of the class. + /// This class provides data for Dalamud-internal features, but can also be used by plugins if needed. /// - internal DataManager() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class DataManager : IDisposable { - this.Language = Service.Get().Language; + private const string IconFileFormat = "ui/icon/{0:D3}000/{1}{2:D6}.tex"; - // Set up default values so plugins do not null-reference when data is being loaded. - this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary()); - } + private Thread luminaResourceThread; + private CancellationTokenSource luminaCancellationTokenSource; - /// - /// Gets the current game client language. - /// - public ClientLanguage Language { get; private set; } - - /// - /// Gets the OpCodes sent by the server to the client. - /// - public ReadOnlyDictionary ServerOpCodes { get; private set; } - - /// - /// Gets the OpCodes sent by the client to the server. - /// - [UsedImplicitly] - public ReadOnlyDictionary ClientOpCodes { get; private set; } - - /// - /// Gets a object which gives access to any excel/game data. - /// - public GameData GameData { get; private set; } - - /// - /// Gets an object which gives access to any of the game's sheet data. - /// - public ExcelModule Excel => this.GameData?.Excel; - - /// - /// Gets a value indicating whether Game Data is ready to be read. - /// - public bool IsDataReady { get; private set; } - - #region Lumina Wrappers - - /// - /// Get an with the given Excel sheet row type. - /// - /// The excel sheet type to get. - /// The , giving access to game rows. - public ExcelSheet? GetExcelSheet() where T : ExcelRow - { - return this.Excel.GetSheet(); - } - - /// - /// Get an with the given Excel sheet row type with a specified language. - /// - /// Language of the sheet to get. - /// The excel sheet type to get. - /// The , giving access to game rows. - public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow - { - return this.Excel.GetSheet(language.ToLumina()); - } - - /// - /// Get a with the given path. - /// - /// The path inside of the game files. - /// The of the file. - public FileResource? GetFile(string path) - { - return this.GetFile(path); - } - - /// - /// Get a with the given path, of the given type. - /// - /// The type of resource. - /// The path inside of the game files. - /// The of the file. - public T? GetFile(string path) where T : FileResource - { - var filePath = GameData.ParseFilePath(path); - if (filePath == null) - return default; - return this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile(filePath.Category, filePath) : default; - } - - /// - /// Check if the file with the given path exists within the game's index files. - /// - /// The path inside of the game files. - /// True if the file exists. - public bool FileExists(string path) - { - return this.GameData.FileExists(path); - } - - /// - /// Get a containing the icon with the given ID. - /// - /// The icon ID. - /// The containing the icon. - public TexFile? GetIcon(uint iconId) - { - return this.GetIcon(this.Language, iconId); - } - - /// - /// Get a containing the icon with the given ID, of the given quality. - /// - /// A value indicating whether the icon should be HQ. - /// The icon ID. - /// The containing the icon. - public TexFile? GetIcon(bool isHq, uint iconId) - { - var type = isHq ? "hq/" : string.Empty; - return this.GetIcon(type, iconId); - } - - /// - /// Get a containing the icon with the given ID, of the given language. - /// - /// The requested language. - /// The icon ID. - /// The containing the icon. - public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId) - { - var type = iconLanguage switch + /// + /// Initializes a new instance of the class. + /// + internal DataManager() { - ClientLanguage.Japanese => "ja/", - ClientLanguage.English => "en/", - ClientLanguage.German => "de/", - ClientLanguage.French => "fr/", - _ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"), - }; + this.Language = Service.Get().Language; - return this.GetIcon(type, iconId); - } + // Set up default values so plugins do not null-reference when data is being loaded. + this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary()); + } - /// - /// Get a containing the icon with the given ID, of the given type. - /// - /// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon). - /// The icon ID. - /// The containing the icon. - public TexFile? GetIcon(string type, uint iconId) - { - type ??= string.Empty; - if (type.Length > 0 && !type.EndsWith("/")) - type += "/"; + /// + /// Gets the current game client language. + /// + public ClientLanguage Language { get; private set; } - var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId); - var file = this.GetFile(filePath); + /// + /// Gets the OpCodes sent by the server to the client. + /// + public ReadOnlyDictionary ServerOpCodes { get; private set; } - if (type == string.Empty || file != default) - return file; + /// + /// Gets the OpCodes sent by the client to the server. + /// + [UsedImplicitly] + public ReadOnlyDictionary ClientOpCodes { get; private set; } - // Couldn't get specific type, try for generic version. - filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId); - file = this.GetFile(filePath); - return file; - } + /// + /// Gets a object which gives access to any excel/game data. + /// + public GameData GameData { get; private set; } - /// - /// Get a containing the HQ icon with the given ID. - /// - /// The icon ID. - /// The containing the icon. - public TexFile? GetHqIcon(uint iconId) - => this.GetIcon(true, iconId); + /// + /// Gets an object which gives access to any of the game's sheet data. + /// + public ExcelModule Excel => this.GameData?.Excel; - /// - /// Get the passed as a drawable ImGui TextureWrap. - /// - /// The Lumina . - /// A that can be used to draw the texture. - public TextureWrap? GetImGuiTexture(TexFile? tex) - { - return tex == null ? null : Service.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4); - } + /// + /// Gets a value indicating whether Game Data is ready to be read. + /// + public bool IsDataReady { get; private set; } - /// - /// Get the passed texture path as a drawable ImGui TextureWrap. - /// - /// The internal path to the texture. - /// A that can be used to draw the texture. - public TextureWrap? GetImGuiTexture(string path) - => this.GetImGuiTexture(this.GetFile(path)); + #region Lumina Wrappers - /// - /// Get a containing the icon with the given ID. - /// - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureIcon(uint iconId) - => this.GetImGuiTexture(this.GetIcon(iconId)); - - /// - /// Get a containing the icon with the given ID, of the given quality. - /// - /// A value indicating whether the icon should be HQ. - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId) - => this.GetImGuiTexture(this.GetIcon(isHq, iconId)); - - /// - /// Get a containing the icon with the given ID, of the given language. - /// - /// The requested language. - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId) - => this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId)); - - /// - /// Get a containing the icon with the given ID, of the given type. - /// - /// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon). - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureIcon(string type, uint iconId) - => this.GetImGuiTexture(this.GetIcon(type, iconId)); - - /// - /// Get a containing the HQ icon with the given ID. - /// - /// The icon ID. - /// The containing the icon. - public TextureWrap? GetImGuiTextureHqIcon(uint iconId) - => this.GetImGuiTexture(this.GetHqIcon(iconId)); - - #endregion - - /// - /// Dispose this DataManager. - /// - public void Dispose() - { - this.luminaCancellationTokenSource.Cancel(); - } - - /// - /// Initialize this data manager. - /// - /// The directory to load data from. - internal void Initialize(string baseDir) - { - try + /// + /// Get an with the given Excel sheet row type. + /// + /// The excel sheet type to get. + /// The , giving access to game rows. + public ExcelSheet? GetExcelSheet() where T : ExcelRow { - Log.Verbose("Starting data load..."); + return this.Excel.GetSheet(); + } - var zoneOpCodeDict = JsonConvert.DeserializeObject>( - File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json"))); - this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict); + /// + /// Get an with the given Excel sheet row type with a specified language. + /// + /// Language of the sheet to get. + /// The excel sheet type to get. + /// The , giving access to game rows. + public ExcelSheet? GetExcelSheet(ClientLanguage language) where T : ExcelRow + { + return this.Excel.GetSheet(language.ToLumina()); + } - Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count); + /// + /// Get a with the given path. + /// + /// The path inside of the game files. + /// The of the file. + public FileResource? GetFile(string path) + { + return this.GetFile(path); + } - var clientOpCodeDict = JsonConvert.DeserializeObject>( - File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json"))); - this.ClientOpCodes = new ReadOnlyDictionary(clientOpCodeDict); + /// + /// Get a with the given path, of the given type. + /// + /// The type of resource. + /// The path inside of the game files. + /// The of the file. + public T? GetFile(string path) where T : FileResource + { + var filePath = GameData.ParseFilePath(path); + if (filePath == null) + return default; + return this.GameData.Repositories.TryGetValue(filePath.Repository, out var repository) ? repository.GetFile(filePath.Category, filePath) : default; + } - Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count); + /// + /// Check if the file with the given path exists within the game's index files. + /// + /// The path inside of the game files. + /// True if the file exists. + public bool FileExists(string path) + { + return this.GameData.FileExists(path); + } - var luminaOptions = new LuminaOptions + /// + /// Get a containing the icon with the given ID. + /// + /// The icon ID. + /// The containing the icon. + public TexFile? GetIcon(uint iconId) + { + return this.GetIcon(this.Language, iconId); + } + + /// + /// Get a containing the icon with the given ID, of the given quality. + /// + /// A value indicating whether the icon should be HQ. + /// The icon ID. + /// The containing the icon. + public TexFile? GetIcon(bool isHq, uint iconId) + { + var type = isHq ? "hq/" : string.Empty; + return this.GetIcon(type, iconId); + } + + /// + /// Get a containing the icon with the given ID, of the given language. + /// + /// The requested language. + /// The icon ID. + /// The containing the icon. + public TexFile? GetIcon(ClientLanguage iconLanguage, uint iconId) + { + var type = iconLanguage switch { - CacheFileResources = true, + ClientLanguage.Japanese => "ja/", + ClientLanguage.English => "en/", + ClientLanguage.German => "de/", + ClientLanguage.French => "fr/", + _ => throw new ArgumentOutOfRangeException(nameof(iconLanguage), $"Unknown Language: {iconLanguage}"), + }; + + return this.GetIcon(type, iconId); + } + + /// + /// Get a containing the icon with the given ID, of the given type. + /// + /// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon). + /// The icon ID. + /// The containing the icon. + public TexFile? GetIcon(string type, uint iconId) + { + type ??= string.Empty; + if (type.Length > 0 && !type.EndsWith("/")) + type += "/"; + + var filePath = string.Format(IconFileFormat, iconId / 1000, type, iconId); + var file = this.GetFile(filePath); + + if (type == string.Empty || file != default) + return file; + + // Couldn't get specific type, try for generic version. + filePath = string.Format(IconFileFormat, iconId / 1000, string.Empty, iconId); + file = this.GetFile(filePath); + return file; + } + + /// + /// Get a containing the HQ icon with the given ID. + /// + /// The icon ID. + /// The containing the icon. + public TexFile? GetHqIcon(uint iconId) + => this.GetIcon(true, iconId); + + /// + /// Get the passed as a drawable ImGui TextureWrap. + /// + /// The Lumina . + /// A that can be used to draw the texture. + public TextureWrap? GetImGuiTexture(TexFile? tex) + { + return tex == null ? null : Service.Get().LoadImageRaw(tex.GetRgbaImageData(), tex.Header.Width, tex.Header.Height, 4); + } + + /// + /// Get the passed texture path as a drawable ImGui TextureWrap. + /// + /// The internal path to the texture. + /// A that can be used to draw the texture. + public TextureWrap? GetImGuiTexture(string path) + => this.GetImGuiTexture(this.GetFile(path)); + + /// + /// Get a containing the icon with the given ID. + /// + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureIcon(uint iconId) + => this.GetImGuiTexture(this.GetIcon(iconId)); + + /// + /// Get a containing the icon with the given ID, of the given quality. + /// + /// A value indicating whether the icon should be HQ. + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureIcon(bool isHq, uint iconId) + => this.GetImGuiTexture(this.GetIcon(isHq, iconId)); + + /// + /// Get a containing the icon with the given ID, of the given language. + /// + /// The requested language. + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureIcon(ClientLanguage iconLanguage, uint iconId) + => this.GetImGuiTexture(this.GetIcon(iconLanguage, iconId)); + + /// + /// Get a containing the icon with the given ID, of the given type. + /// + /// The type of the icon (e.g. 'hq' to get the HQ variant of an item icon). + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureIcon(string type, uint iconId) + => this.GetImGuiTexture(this.GetIcon(type, iconId)); + + /// + /// Get a containing the HQ icon with the given ID. + /// + /// The icon ID. + /// The containing the icon. + public TextureWrap? GetImGuiTextureHqIcon(uint iconId) + => this.GetImGuiTexture(this.GetHqIcon(iconId)); + + #endregion + + /// + /// Dispose this DataManager. + /// + public void Dispose() + { + this.luminaCancellationTokenSource.Cancel(); + } + + /// + /// Initialize this data manager. + /// + /// The directory to load data from. + internal void Initialize(string baseDir) + { + try + { + Log.Verbose("Starting data load..."); + + var zoneOpCodeDict = JsonConvert.DeserializeObject>( + File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json"))); + this.ServerOpCodes = new ReadOnlyDictionary(zoneOpCodeDict); + + Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count); + + var clientOpCodeDict = JsonConvert.DeserializeObject>( + File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json"))); + this.ClientOpCodes = new ReadOnlyDictionary(clientOpCodeDict); + + Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count); + + var luminaOptions = new LuminaOptions + { + CacheFileResources = true, #if DEBUG - PanicOnSheetChecksumMismatch = true, + PanicOnSheetChecksumMismatch = true, #else PanicOnSheetChecksumMismatch = false, #endif - DefaultExcelLanguage = this.Language.ToLumina(), - }; + DefaultExcelLanguage = this.Language.ToLumina(), + }; - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) - { - this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions); - } - - Log.Information("Lumina is ready: {0}", this.GameData.DataPath); - - this.IsDataReady = true; - - this.luminaCancellationTokenSource = new(); - - var luminaCancellationToken = this.luminaCancellationTokenSource.Token; - this.luminaResourceThread = new(() => - { - while (!luminaCancellationToken.IsCancellationRequested) + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) { - if (this.GameData.FileHandleManager.HasPendingFileLoads) - { - this.GameData.ProcessFileHandleQueue(); - } - else - { - Thread.Sleep(5); - } + this.GameData = new GameData(Path.Combine(Path.GetDirectoryName(processModule.FileName), "sqpack"), luminaOptions); } - }); - this.luminaResourceThread.Start(); - } - catch (Exception ex) - { - Log.Error(ex, "Could not download data."); + + Log.Information("Lumina is ready: {0}", this.GameData.DataPath); + + this.IsDataReady = true; + + this.luminaCancellationTokenSource = new(); + + var luminaCancellationToken = this.luminaCancellationTokenSource.Token; + this.luminaResourceThread = new(() => + { + while (!luminaCancellationToken.IsCancellationRequested) + { + if (this.GameData.FileHandleManager.HasPendingFileLoads) + { + this.GameData.ProcessFileHandleQueue(); + } + else + { + Thread.Sleep(5); + } + } + }); + this.luminaResourceThread.Start(); + } + catch (Exception ex) + { + Log.Error(ex, "Could not download data."); + } } } } diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index e630d3ee5..e9df6be92 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -21,261 +21,262 @@ using Serilog.Events; using static Dalamud.NativeFunctions; -namespace Dalamud; - -/// -/// The main entrypoint for the Dalamud system. -/// -public sealed class EntryPoint +namespace Dalamud { /// - /// A delegate used during initialization of the CLR from Dalamud.Boot. + /// The main entrypoint for the Dalamud system. /// - /// Pointer to a serialized data. - public delegate void InitDelegate(IntPtr infoPtr); - - /// - /// Initialize Dalamud. - /// - /// Pointer to a serialized data. - public static void Initialize(IntPtr infoPtr) + public sealed class EntryPoint { - var infoStr = Marshal.PtrToStringUTF8(infoPtr); - var info = JsonConvert.DeserializeObject(infoStr); + /// + /// A delegate used during initialization of the CLR from Dalamud.Boot. + /// + /// Pointer to a serialized data. + public delegate void InitDelegate(IntPtr infoPtr); - new Thread(() => RunThread(info)).Start(); - } - - /// - /// Initialize all Dalamud subsystems and start running on the main thread. - /// - /// The containing information needed to initialize Dalamud. - private static void RunThread(DalamudStartInfo info) - { - if (EnvironmentConfiguration.DalamudWaitForDebugger) + /// + /// Initialize Dalamud. + /// + /// Pointer to a serialized data. + public static void Initialize(IntPtr infoPtr) { - while (!Debugger.IsAttached) - { - Thread.Sleep(100); - } + var infoStr = Marshal.PtrToStringUTF8(infoPtr); + var info = JsonConvert.DeserializeObject(infoStr); + + new Thread(() => RunThread(info)).Start(); } - // Setup logger - var levelSwitch = InitLogging(info.WorkingDirectory); + /// + /// Initialize all Dalamud subsystems and start running on the main thread. + /// + /// The containing information needed to initialize Dalamud. + private static void RunThread(DalamudStartInfo info) + { + if (EnvironmentConfiguration.DalamudWaitForDebugger) + { + while (!Debugger.IsAttached) + { + Thread.Sleep(100); + } + } - // Load configuration first to get some early persistent state, like log level - var configuration = DalamudConfiguration.Load(info.ConfigurationPath); + // Setup logger + var levelSwitch = InitLogging(info.WorkingDirectory); - // Set the appropriate logging level from the configuration + // Load configuration first to get some early persistent state, like log level + var configuration = DalamudConfiguration.Load(info.ConfigurationPath); + + // Set the appropriate logging level from the configuration #if !DEBUG levelSwitch.MinimumLevel = configuration.LogLevel; #endif - // Log any unhandled exception. - AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; - TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; + // Log any unhandled exception. + AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; - var finishSignal = new ManualResetEvent(false); + var finishSignal = new ManualResetEvent(false); - try - { - if (info.DelayInitializeMs > 0) + try { - Log.Information(string.Format("Waiting for {0}ms before starting a session.", info.DelayInitializeMs)); - Thread.Sleep(info.DelayInitializeMs); + if (info.DelayInitializeMs > 0) + { + Log.Information(string.Format("Waiting for {0}ms before starting a session.", info.DelayInitializeMs)); + Thread.Sleep(info.DelayInitializeMs); + } + + Log.Information(new string('-', 80)); + Log.Information("Initializing a session.."); + + // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls; + + if (!Util.IsLinux()) + InitSymbolHandler(info); + + var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration); + Log.Information("Starting a session.."); + + // Run session + dalamud.LoadTier1(); + dalamud.WaitForUnload(); + + dalamud.Dispose(); } + catch (Exception ex) + { + Log.Fatal(ex, "Unhandled exception on main thread."); + } + finally + { + TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException; + AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException; - Log.Information(new string('-', 80)); - Log.Information("Initializing a session.."); + Log.Information("Session has ended."); + Log.CloseAndFlush(); - // This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls; - - if (!Util.IsLinux()) - InitSymbolHandler(info); - - var dalamud = new Dalamud(info, levelSwitch, finishSignal, configuration); - Log.Information("Starting a session.."); - - // Run session - dalamud.LoadTier1(); - dalamud.WaitForUnload(); - - dalamud.Dispose(); + finishSignal.Set(); + } } - catch (Exception ex) + + private static void InitSymbolHandler(DalamudStartInfo info) { - Log.Fatal(ex, "Unhandled exception on main thread."); + try + { + if (string.IsNullOrEmpty(info.AssetDirectory)) + return; + + var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb"); + var searchPath = $".;{symbolPath}"; + + // Remove any existing Symbol Handler and Init a new one with our search path added + SymCleanup(GetCurrentProcess()); + + if (!SymInitialize(GetCurrentProcess(), searchPath, true)) + throw new Win32Exception(); + } + catch (Exception ex) + { + Log.Error(ex, "SymbolHandler Initialize Failed."); + } } - finally + + private static LoggingLevelSwitch InitLogging(string baseDirectory) { - TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException; - AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException; - - Log.Information("Session has ended."); - Log.CloseAndFlush(); - - finishSignal.Set(); - } - } - - private static void InitSymbolHandler(DalamudStartInfo info) - { - try - { - if (string.IsNullOrEmpty(info.AssetDirectory)) - return; - - var symbolPath = Path.Combine(info.AssetDirectory, "UIRes", "pdb"); - var searchPath = $".;{symbolPath}"; - - // Remove any existing Symbol Handler and Init a new one with our search path added - SymCleanup(GetCurrentProcess()); - - if (!SymInitialize(GetCurrentProcess(), searchPath, true)) - throw new Win32Exception(); - } - catch (Exception ex) - { - Log.Error(ex, "SymbolHandler Initialize Failed."); - } - } - - private static LoggingLevelSwitch InitLogging(string baseDirectory) - { #if DEBUG - var logPath = Path.Combine(baseDirectory, "dalamud.log"); - var oldPath = Path.Combine(baseDirectory, "dalamud.log.old"); + var logPath = Path.Combine(baseDirectory, "dalamud.log"); + var oldPath = Path.Combine(baseDirectory, "dalamud.log.old"); #else var logPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log"); var oldPath = Path.Combine(baseDirectory, "..", "..", "..", "dalamud.log.old"); #endif - CullLogFile(logPath, oldPath, 1 * 1024 * 1024); - CullLogFile(oldPath, null, 10 * 1024 * 1024); + CullLogFile(logPath, oldPath, 1 * 1024 * 1024); + CullLogFile(oldPath, null, 10 * 1024 * 1024); - var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose); - Log.Logger = new LoggerConfiguration() - .WriteTo.Async(a => a.File(logPath)) - .WriteTo.Sink(SerilogEventSink.Instance) - .MinimumLevel.ControlledBy(levelSwitch) - .CreateLogger(); + var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose); + Log.Logger = new LoggerConfiguration() + .WriteTo.Async(a => a.File(logPath)) + .WriteTo.Sink(SerilogEventSink.Instance) + .MinimumLevel.ControlledBy(levelSwitch) + .CreateLogger(); - return levelSwitch; - } + return levelSwitch; + } - private static void CullLogFile(string logPath, string? oldPath, int cullingFileSize) - { - try + private static void CullLogFile(string logPath, string? oldPath, int cullingFileSize) { - var bufferSize = 4096; - - var logFile = new FileInfo(logPath); - - if (!logFile.Exists) - logFile.Create(); - - if (logFile.Length <= cullingFileSize) - return; - - var amountToCull = logFile.Length - cullingFileSize; - - if (amountToCull < bufferSize) - return; - - if (oldPath != null) + try { - var oldFile = new FileInfo(oldPath); + var bufferSize = 4096; - if (!oldFile.Exists) - oldFile.Create().Close(); + var logFile = new FileInfo(logPath); - using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); - using var writer = new BinaryWriter(oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite)); + if (!logFile.Exists) + logFile.Create(); - var read = -1; - var total = 0; - var buffer = new byte[bufferSize]; - while (read != 0 && total < amountToCull) + if (logFile.Length <= cullingFileSize) + return; + + var amountToCull = logFile.Length - cullingFileSize; + + if (amountToCull < bufferSize) + return; + + if (oldPath != null) { - read = reader.Read(buffer, 0, buffer.Length); - writer.Write(buffer, 0, read); - total += read; + var oldFile = new FileInfo(oldPath); + + if (!oldFile.Exists) + oldFile.Create().Close(); + + using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); + using var writer = new BinaryWriter(oldFile.Open(FileMode.Append, FileAccess.Write, FileShare.ReadWrite)); + + var read = -1; + var total = 0; + var buffer = new byte[bufferSize]; + while (read != 0 && total < amountToCull) + { + read = reader.Read(buffer, 0, buffer.Length); + writer.Write(buffer, 0, read); + total += read; + } + } + + { + using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); + using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite)); + + reader.BaseStream.Seek(amountToCull, SeekOrigin.Begin); + + var read = -1; + var total = 0; + var buffer = new byte[bufferSize]; + while (read != 0) + { + read = reader.Read(buffer, 0, buffer.Length); + writer.Write(buffer, 0, read); + total += read; + } + + writer.BaseStream.SetLength(total); } } - + catch (Exception ex) { - using var reader = new BinaryReader(logFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); - using var writer = new BinaryWriter(logFile.Open(FileMode.Open, FileAccess.Write, FileShare.ReadWrite)); + Log.Error(ex, "Log cull failed"); - reader.BaseStream.Seek(amountToCull, SeekOrigin.Begin); - - var read = -1; - var total = 0; - var buffer = new byte[bufferSize]; - while (read != 0) - { - read = reader.Read(buffer, 0, buffer.Length); - writer.Write(buffer, 0, read); - total += read; - } - - writer.BaseStream.SetLength(total); + /* + var caption = "XIVLauncher Error"; + var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}"; + _ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok); + */ } } - catch (Exception ex) + + private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) { - Log.Error(ex, "Log cull failed"); + switch (args.ExceptionObject) + { + case Exception ex: + Log.Fatal(ex, "Unhandled exception on AppDomain"); + Troubleshooting.LogException(ex, "DalamudUnhandled"); - /* - var caption = "XIVLauncher Error"; - var message = $"Log cull threw an exception: {ex.Message}\n{ex.StackTrace ?? string.Empty}"; - _ = MessageBoxW(IntPtr.Zero, message, caption, MessageBoxType.IconError | MessageBoxType.Ok); - */ + var info = "Further information could not be obtained"; + if (ex.TargetSite != null && ex.TargetSite.DeclaringType != null) + { + info = $"{ex.TargetSite.DeclaringType.Assembly.GetName().Name}, {ex.TargetSite.DeclaringType.FullName}::{ex.TargetSite.Name}"; + } + + const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal; + var result = MessageBoxW( + Process.GetCurrentProcess().MainWindowHandle, + $"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\nType: {ex.GetType().Name}\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\nDo you want to disable all plugins the next time you start the game?", + "Dalamud", + flags); + + if (result == (int)User32.MessageBoxResult.IDYES) + { + Log.Information("User chose to disable plugins on next launch..."); + var config = Service.Get(); + config.PluginSafeMode = true; + config.Save(); + } + + Environment.Exit(-1); + + break; + default: + Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject); + break; + } } - } - private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) - { - switch (args.ExceptionObject) + private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args) { - case Exception ex: - Log.Fatal(ex, "Unhandled exception on AppDomain"); - Troubleshooting.LogException(ex, "DalamudUnhandled"); - - var info = "Further information could not be obtained"; - if (ex.TargetSite != null && ex.TargetSite.DeclaringType != null) - { - info = $"{ex.TargetSite.DeclaringType.Assembly.GetName().Name}, {ex.TargetSite.DeclaringType.FullName}::{ex.TargetSite.Name}"; - } - - const MessageBoxType flags = NativeFunctions.MessageBoxType.YesNo | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal; - var result = MessageBoxW( - Process.GetCurrentProcess().MainWindowHandle, - $"An internal error in a Dalamud plugin occurred.\nThe game must close.\n\nType: {ex.GetType().Name}\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\nDo you want to disable all plugins the next time you start the game?", - "Dalamud", - flags); - - if (result == (int)User32.MessageBoxResult.IDYES) - { - Log.Information("User chose to disable plugins on next launch..."); - var config = Service.Get(); - config.PluginSafeMode = true; - config.Save(); - } - - Environment.Exit(-1); - - break; - default: - Log.Fatal("Unhandled SEH object on AppDomain: {Object}", args.ExceptionObject); - break; + if (!args.Observed) + Log.Error(args.Exception, "Unobserved exception in Task."); } } - - private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args) - { - if (!args.Observed) - Log.Error(args.Exception, "Unobserved exception in Task."); - } } diff --git a/Dalamud/Game/BaseAddressResolver.cs b/Dalamud/Game/BaseAddressResolver.cs index ab1502376..4528b138e 100644 --- a/Dalamud/Game/BaseAddressResolver.cs +++ b/Dalamud/Game/BaseAddressResolver.cs @@ -3,111 +3,112 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -namespace Dalamud.Game; - -/// -/// Base memory address resolver. -/// -public abstract class BaseAddressResolver +namespace Dalamud.Game { /// - /// Gets a list of memory addresses that were found, to list in /xldata. + /// Base memory address resolver. /// - public static Dictionary> DebugScannedValues { get; } = new(); - - /// - /// Gets or sets a value indicating whether the resolver has successfully run or . - /// - protected bool IsResolved { get; set; } - - /// - /// Setup the resolver, calling the appopriate method based on the process architecture. - /// - public void Setup() + public abstract class BaseAddressResolver { - var scanner = Service.Get(); - this.Setup(scanner); - } + /// + /// Gets a list of memory addresses that were found, to list in /xldata. + /// + public static Dictionary> DebugScannedValues { get; } = new(); - /// - /// Setup the resolver, calling the appopriate method based on the process architecture. - /// - /// The SigScanner instance. - public void Setup(SigScanner scanner) - { - // Because C# don't allow to call virtual function while in ctor - // we have to do this shit :\ + /// + /// Gets or sets a value indicating whether the resolver has successfully run or . + /// + protected bool IsResolved { get; set; } - if (this.IsResolved) + /// + /// Setup the resolver, calling the appopriate method based on the process architecture. + /// + public void Setup() { - return; + var scanner = Service.Get(); + this.Setup(scanner); } - if (scanner.Is32BitProcess) + /// + /// Setup the resolver, calling the appopriate method based on the process architecture. + /// + /// The SigScanner instance. + public void Setup(SigScanner scanner) { - this.Setup32Bit(scanner); - } - else - { - this.Setup64Bit(scanner); + // Because C# don't allow to call virtual function while in ctor + // we have to do this shit :\ + + if (this.IsResolved) + { + return; + } + + if (scanner.Is32BitProcess) + { + this.Setup32Bit(scanner); + } + else + { + this.Setup64Bit(scanner); + } + + this.SetupInternal(scanner); + + var className = this.GetType().Name; + DebugScannedValues[className] = new List<(string, IntPtr)>(); + + foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) + { + DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this))); + } + + this.IsResolved = true; } - this.SetupInternal(scanner); - - var className = this.GetType().Name; - DebugScannedValues[className] = new List<(string, IntPtr)>(); - - foreach (var property in this.GetType().GetProperties().Where(x => x.PropertyType == typeof(IntPtr))) + /// + /// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer. + /// + /// The delegate to marshal the function pointer to. + /// The address of the virtual table. + /// The offset from address to the vtable pointer. + /// The vfunc index. + /// A delegate function pointer that can be invoked. + public T GetVirtualFunction(IntPtr address, int vtableOffset, int count) where T : class { - DebugScannedValues[className].Add((property.Name, (IntPtr)property.GetValue(this))); + // Get vtable + var vtable = Marshal.ReadIntPtr(address, vtableOffset); + + // Get an address to the function + var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count); + + return Marshal.GetDelegateForFunctionPointer(functionAddress); } - this.IsResolved = true; - } + /// + /// Setup the resolver by finding any necessary memory addresses. + /// + /// The SigScanner instance. + protected virtual void Setup32Bit(SigScanner scanner) + { + throw new NotSupportedException("32 bit version is not supported."); + } - /// - /// Fetch vfunc N from a pointer to the vtable and return a delegate function pointer. - /// - /// The delegate to marshal the function pointer to. - /// The address of the virtual table. - /// The offset from address to the vtable pointer. - /// The vfunc index. - /// A delegate function pointer that can be invoked. - public T GetVirtualFunction(IntPtr address, int vtableOffset, int count) where T : class - { - // Get vtable - var vtable = Marshal.ReadIntPtr(address, vtableOffset); + /// + /// Setup the resolver by finding any necessary memory addresses. + /// + /// The SigScanner instance. + protected virtual void Setup64Bit(SigScanner scanner) + { + throw new NotSupportedException("64 bit version is not supported."); + } - // Get an address to the function - var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count); - - return Marshal.GetDelegateForFunctionPointer(functionAddress); - } - - /// - /// Setup the resolver by finding any necessary memory addresses. - /// - /// The SigScanner instance. - protected virtual void Setup32Bit(SigScanner scanner) - { - throw new NotSupportedException("32 bit version is not supported."); - } - - /// - /// Setup the resolver by finding any necessary memory addresses. - /// - /// The SigScanner instance. - protected virtual void Setup64Bit(SigScanner scanner) - { - throw new NotSupportedException("64 bit version is not supported."); - } - - /// - /// Setup the resolver by finding any necessary memory addresses. - /// - /// The SigScanner instance. - protected virtual void SetupInternal(SigScanner scanner) - { - // Do nothing + /// + /// Setup the resolver by finding any necessary memory addresses. + /// + /// The SigScanner instance. + protected virtual void SetupInternal(SigScanner scanner) + { + // Do nothing + } } } diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index 0da9f6dcf..5465fef97 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -21,298 +21,298 @@ using Dalamud.Plugin.Internal; using Dalamud.Utility; using Serilog; -namespace Dalamud.Game; - -/// -/// Chat events and public helper functions. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public class ChatHandlers +namespace Dalamud.Game { - // private static readonly Dictionary UnicodeToDiscordEmojiDict = new() - // { - // { "", "<:ffxive071:585847382210642069>" }, - // { "", "<:ffxive083:585848592699490329>" }, - // }; - - // private readonly Dictionary handledChatTypeColors = new() - // { - // { XivChatType.CrossParty, Color.DodgerBlue }, - // { XivChatType.Party, Color.DodgerBlue }, - // { XivChatType.FreeCompany, Color.DeepSkyBlue }, - // { XivChatType.CrossLinkShell1, Color.ForestGreen }, - // { XivChatType.CrossLinkShell2, Color.ForestGreen }, - // { XivChatType.CrossLinkShell3, Color.ForestGreen }, - // { XivChatType.CrossLinkShell4, Color.ForestGreen }, - // { XivChatType.CrossLinkShell5, Color.ForestGreen }, - // { XivChatType.CrossLinkShell6, Color.ForestGreen }, - // { XivChatType.CrossLinkShell7, Color.ForestGreen }, - // { XivChatType.CrossLinkShell8, Color.ForestGreen }, - // { XivChatType.Ls1, Color.ForestGreen }, - // { XivChatType.Ls2, Color.ForestGreen }, - // { XivChatType.Ls3, Color.ForestGreen }, - // { XivChatType.Ls4, Color.ForestGreen }, - // { XivChatType.Ls5, Color.ForestGreen }, - // { XivChatType.Ls6, Color.ForestGreen }, - // { XivChatType.Ls7, Color.ForestGreen }, - // { XivChatType.Ls8, Color.ForestGreen }, - // { XivChatType.TellIncoming, Color.HotPink }, - // { XivChatType.PvPTeam, Color.SandyBrown }, - // { XivChatType.Urgent, Color.DarkViolet }, - // { XivChatType.NoviceNetwork, Color.SaddleBrown }, - // { XivChatType.Echo, Color.Gray }, - // }; - - private readonly Regex rmtRegex = new( - @"4KGOLD|We have sufficient stock|VPK\.OM|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|Off Code( *):|offers Fantasia", - RegexOptions.Compiled); - - private readonly Dictionary retainerSaleRegexes = new() + /// + /// Chat events and public helper functions. + /// + [PluginInterface] + [InterfaceVersion("1.0")] + public class ChatHandlers { + // private static readonly Dictionary UnicodeToDiscordEmojiDict = new() + // { + // { "", "<:ffxive071:585847382210642069>" }, + // { "", "<:ffxive083:585848592699490329>" }, + // }; + + // private readonly Dictionary handledChatTypeColors = new() + // { + // { XivChatType.CrossParty, Color.DodgerBlue }, + // { XivChatType.Party, Color.DodgerBlue }, + // { XivChatType.FreeCompany, Color.DeepSkyBlue }, + // { XivChatType.CrossLinkShell1, Color.ForestGreen }, + // { XivChatType.CrossLinkShell2, Color.ForestGreen }, + // { XivChatType.CrossLinkShell3, Color.ForestGreen }, + // { XivChatType.CrossLinkShell4, Color.ForestGreen }, + // { XivChatType.CrossLinkShell5, Color.ForestGreen }, + // { XivChatType.CrossLinkShell6, Color.ForestGreen }, + // { XivChatType.CrossLinkShell7, Color.ForestGreen }, + // { XivChatType.CrossLinkShell8, Color.ForestGreen }, + // { XivChatType.Ls1, Color.ForestGreen }, + // { XivChatType.Ls2, Color.ForestGreen }, + // { XivChatType.Ls3, Color.ForestGreen }, + // { XivChatType.Ls4, Color.ForestGreen }, + // { XivChatType.Ls5, Color.ForestGreen }, + // { XivChatType.Ls6, Color.ForestGreen }, + // { XivChatType.Ls7, Color.ForestGreen }, + // { XivChatType.Ls8, Color.ForestGreen }, + // { XivChatType.TellIncoming, Color.HotPink }, + // { XivChatType.PvPTeam, Color.SandyBrown }, + // { XivChatType.Urgent, Color.DarkViolet }, + // { XivChatType.NoviceNetwork, Color.SaddleBrown }, + // { XivChatType.Echo, Color.Gray }, + // }; + + private readonly Regex rmtRegex = new( + @"4KGOLD|We have sufficient stock|VPK\.OM|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans|4KGOLD\.COM|Cheapest Gil with|pvp and bank on google|Selling Cheap GIL|ff14mogstation\.com|Cheap Gil 1000k|gilsforyou|server 1000K =|gils_selling|E A S Y\.C O M|bonus code|mins delivery guarantee|Sell cheap|Salegm\.com|cheap Mog|Off Code:|FF14Mog.com|使用する5%オ|Off Code( *):|offers Fantasia", + RegexOptions.Compiled); + + private readonly Dictionary retainerSaleRegexes = new() { - ClientLanguage.Japanese, - new Regex[] { + ClientLanguage.Japanese, + new Regex[] + { new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)×(?[\d,.]+)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.English, - new Regex[] + } + }, { + ClientLanguage.English, + new Regex[] + { new Regex(@"^(?.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.German, - new Regex[] + } + }, { + ClientLanguage.German, + new Regex[] + { new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) für (?[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled), new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) verkauft und (?[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.French, - new Regex[] + } + }, { + ClientLanguage.French, + new Regex[] + { new Regex(@"^Un servant a vendu (?.+) pour (?[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled), - } - }, - }; + } + }, + }; - private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled); + private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled); - private readonly DalamudLinkPayload openInstallerWindowLink; + private readonly DalamudLinkPayload openInstallerWindowLink; - private bool hasSeenLoadingMsg; - private bool hasAutoUpdatedPlugins; + private bool hasSeenLoadingMsg; + private bool hasAutoUpdatedPlugins; - /// - /// Initializes a new instance of the class. - /// - internal ChatHandlers() - { - var chatGui = Service.Get(); - - chatGui.CheckMessageHandled += this.OnCheckMessageHandled; - chatGui.ChatMessage += this.OnChatMessage; - - this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) => + /// + /// Initializes a new instance of the class. + /// + internal ChatHandlers() { - Service.Get().OpenPluginInstaller(); - }); - } + var chatGui = Service.Get(); - /// - /// Gets the last URL seen in chat. - /// - public string? LastLink { get; private set; } + chatGui.CheckMessageHandled += this.OnCheckMessageHandled; + chatGui.ChatMessage += this.OnChatMessage; - /// - /// Convert a TextPayload to SeString and wrap in italics payloads. - /// - /// Text to convert. - /// SeString payload of italicized text. - public static SeString MakeItalics(string text) - => MakeItalics(new TextPayload(text)); - - /// - /// Convert a TextPayload to SeString and wrap in italics payloads. - /// - /// Text to convert. - /// SeString payload of italicized text. - public static SeString MakeItalics(TextPayload text) - => new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff); - - private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled) - { - var configuration = Service.Get(); - - var textVal = message.TextValue; - - if (!configuration.DisableRmtFiltering) - { - var matched = this.rmtRegex.IsMatch(textVal); - if (matched) + this.openInstallerWindowLink = chatGui.AddChatLinkHandler("Dalamud", 1001, (i, m) => { - // This seems to be a RMT ad - let's not show it - Log.Debug("Handled RMT ad: " + message.TextValue); + Service.Get().OpenPluginInstaller(); + }); + } + + /// + /// Gets the last URL seen in chat. + /// + public string? LastLink { get; private set; } + + /// + /// Convert a TextPayload to SeString and wrap in italics payloads. + /// + /// Text to convert. + /// SeString payload of italicized text. + public static SeString MakeItalics(string text) + => MakeItalics(new TextPayload(text)); + + /// + /// Convert a TextPayload to SeString and wrap in italics payloads. + /// + /// Text to convert. + /// SeString payload of italicized text. + public static SeString MakeItalics(TextPayload text) + => new(EmphasisItalicPayload.ItalicsOn, text, EmphasisItalicPayload.ItalicsOff); + + private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled) + { + var configuration = Service.Get(); + + var textVal = message.TextValue; + + if (!configuration.DisableRmtFiltering) + { + var matched = this.rmtRegex.IsMatch(textVal); + if (matched) + { + // This seems to be a RMT ad - let's not show it + Log.Debug("Handled RMT ad: " + message.TextValue); + isHandled = true; + return; + } + } + + if (configuration.BadWords != null && + configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) + { + // This seems to be in the user block list - let's not show it + Log.Debug("Blocklist triggered"); isHandled = true; return; } } - if (configuration.BadWords != null && - configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x))) + private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) { - // This seems to be in the user block list - let's not show it - Log.Debug("Blocklist triggered"); - isHandled = true; - return; - } - } + var startInfo = Service.Get(); + var clientState = Service.Get(); - private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) - { - var startInfo = Service.Get(); - var clientState = Service.Get(); + if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) + this.PrintWelcomeMessage(); - if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) - this.PrintWelcomeMessage(); + // For injections while logged in + if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg) + this.PrintWelcomeMessage(); - // For injections while logged in - if (clientState.LocalPlayer != null && clientState.TerritoryType == 0 && !this.hasSeenLoadingMsg) - this.PrintWelcomeMessage(); - - if (!this.hasAutoUpdatedPlugins) - this.AutoUpdatePlugins(); + if (!this.hasAutoUpdatedPlugins) + this.AutoUpdatePlugins(); #if !DEBUG && false if (!this.hasSeenLoadingMsg) return; #endif - if (type == XivChatType.RetainerSale) - { - foreach (var regex in this.retainerSaleRegexes[startInfo.Language]) + if (type == XivChatType.RetainerSale) { - var matchInfo = regex.Match(message.TextValue); - - // we no longer really need to do/validate the item matching since we read the id from the byte array - // but we'd be checking the main match anyway - var itemInfo = matchInfo.Groups["item"]; - if (!itemInfo.Success) - continue; - - var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload; - if (itemLink == default) + foreach (var regex in this.retainerSaleRegexes[startInfo.Language]) { - Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode())); + var matchInfo = regex.Match(message.TextValue); + + // we no longer really need to do/validate the item matching since we read the id from the byte array + // but we'd be checking the main match anyway + var itemInfo = matchInfo.Groups["item"]; + if (!itemInfo.Success) + continue; + + var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload; + if (itemLink == default) + { + Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode())); + break; + } + + Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}"); + + var valueInfo = matchInfo.Groups["value"]; + // not sure if using a culture here would work correctly, so just strip symbols instead + if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue)) + continue; + + // Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ)); break; } - - Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}"); - - var valueInfo = matchInfo.Groups["value"]; - // not sure if using a culture here would work correctly, so just strip symbols instead - if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue)) - continue; - - // Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ)); - break; - } - } - - var messageCopy = message; - var senderCopy = sender; - - var linkMatch = this.urlRegex.Match(message.TextValue); - if (linkMatch.Value.Length > 0) - this.LastLink = linkMatch.Value; - } - - private void PrintWelcomeMessage() - { - var chatGui = Service.Get(); - var configuration = Service.Get(); - var pluginManager = Service.Get(); - var dalamudInterface = Service.Get(); - - var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); - - chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion) - + string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count)); - - if (configuration.PrintPluginsWelcomeMsg) - { - foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name)) - { - chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion)); - } - } - - if (string.IsNullOrEmpty(configuration.LastVersion) || !assemblyVersion.StartsWith(configuration.LastVersion)) - { - chatGui.PrintChat(new XivChatEntry - { - Message = Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog."), - Type = XivChatType.Notice, - }); - - if (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor))) - { - dalamudInterface.OpenChangelogWindow(); - configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor; } - configuration.LastVersion = assemblyVersion; - configuration.Save(); + var messageCopy = message; + var senderCopy = sender; + + var linkMatch = this.urlRegex.Match(message.TextValue); + if (linkMatch.Value.Length > 0) + this.LastLink = linkMatch.Value; } - this.hasSeenLoadingMsg = true; - } - - private void AutoUpdatePlugins() - { - var chatGui = Service.Get(); - var configuration = Service.Get(); - var pluginManager = Service.Get(); - var notifications = Service.Get(); - - if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0) + private void PrintWelcomeMessage() { - // Plugins aren't ready yet. - return; - } + var chatGui = Service.Get(); + var configuration = Service.Get(); + var pluginManager = Service.Get(); + var dalamudInterface = Service.Get(); - this.hasAutoUpdatedPlugins = true; + var assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); - Task.Run(() => pluginManager.UpdatePluginsAsync(!configuration.AutoUpdatePlugins)).ContinueWith(task => - { - if (task.IsFaulted) + chatGui.Print(string.Format(Loc.Localize("DalamudWelcome", "Dalamud vD{0} loaded."), assemblyVersion) + + string.Format(Loc.Localize("PluginsWelcome", " {0} plugin(s) loaded."), pluginManager.InstalledPlugins.Count)); + + if (configuration.PrintPluginsWelcomeMsg) { - Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates.")); + foreach (var plugin in pluginManager.InstalledPlugins.OrderBy(plugin => plugin.Name)) + { + chatGui.Print(string.Format(Loc.Localize("DalamudPluginLoaded", " 》 {0} v{1} loaded."), plugin.Name, plugin.Manifest.AssemblyVersion)); + } + } + + if (string.IsNullOrEmpty(configuration.LastVersion) || !assemblyVersion.StartsWith(configuration.LastVersion)) + { + chatGui.PrintChat(new XivChatEntry + { + Message = Loc.Localize("DalamudUpdated", "The In-Game addon has been updated or was reinstalled successfully! Please check the discord for a full changelog."), + Type = XivChatType.Notice, + }); + + if (string.IsNullOrEmpty(configuration.LastChangelogMajorMinor) || (!ChangelogWindow.WarrantsChangelogForMajorMinor.StartsWith(configuration.LastChangelogMajorMinor) && assemblyVersion.StartsWith(ChangelogWindow.WarrantsChangelogForMajorMinor))) + { + dalamudInterface.OpenChangelogWindow(); + configuration.LastChangelogMajorMinor = ChangelogWindow.WarrantsChangelogForMajorMinor; + } + + configuration.LastVersion = assemblyVersion; + configuration.Save(); + } + + this.hasSeenLoadingMsg = true; + } + + private void AutoUpdatePlugins() + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + var pluginManager = Service.Get(); + var notifications = Service.Get(); + + if (!pluginManager.ReposReady || pluginManager.InstalledPlugins.Count == 0 || pluginManager.AvailablePlugins.Count == 0) + { + // Plugins aren't ready yet. return; } - var updatedPlugins = task.Result; - if (updatedPlugins != null && updatedPlugins.Any()) - { - if (configuration.AutoUpdatePlugins) - { - pluginManager.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:")); - notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info); - } - else - { - var data = Service.Get(); + this.hasAutoUpdatedPlugins = true; - chatGui.PrintChat(new XivChatEntry + Task.Run(() => pluginManager.UpdatePluginsAsync(!configuration.AutoUpdatePlugins)).ContinueWith(task => + { + if (task.IsFaulted) + { + Log.Error(task.Exception, Loc.Localize("DalamudPluginUpdateCheckFail", "Could not check for plugin updates.")); + return; + } + + var updatedPlugins = task.Result; + if (updatedPlugins != null && updatedPlugins.Any()) + { + if (configuration.AutoUpdatePlugins) { - Message = new SeString(new List() - { + pluginManager.PrintUpdatedPlugins(updatedPlugins, Loc.Localize("DalamudPluginAutoUpdate", "Auto-update:")); + notifications.AddNotification(Loc.Localize("NotificationUpdatedPlugins", "{0} of your plugins were updated.").Format(updatedPlugins.Count), Loc.Localize("NotificationAutoUpdate", "Auto-Update"), NotificationType.Info); + } + else + { + var data = Service.Get(); + + chatGui.PrintChat(new XivChatEntry + { + Message = new SeString(new List() + { new TextPayload(Loc.Localize("DalamudPluginUpdateRequired", "One or more of your plugins needs to be updated. Please use the /xlplugins command in-game to update them!")), new TextPayload(" ["), new UIForegroundPayload(500), @@ -321,11 +321,12 @@ public class ChatHandlers RawPayload.LinkTerminator, new UIForegroundPayload(0), new TextPayload("]"), - }), - Type = XivChatType.Urgent, - }); + }), + Type = XivChatType.Urgent, + }); + } } - } - }); + }); + } } } diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index 9af104e80..21a31d868 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -7,179 +7,180 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Buddy; - -/// -/// This collection represents the buddies present in your squadron or trust party. -/// It does not include the local player. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed partial class BuddyList +namespace Dalamud.Game.ClientState.Buddy { - private const uint InvalidObjectID = 0xE0000000; - - private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. + /// This collection represents the buddies present in your squadron or trust party. + /// It does not include the local player. /// - /// Client state address resolver. - internal BuddyList(ClientStateAddressResolver addressResolver) + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed partial class BuddyList { - this.address = addressResolver; + private const uint InvalidObjectID = 0xE0000000; - Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}"); - } + private readonly ClientStateAddressResolver address; - /// - /// Gets the amount of battle buddies the local player has. - /// - public int Length - { - get + /// + /// Initializes a new instance of the class. + /// + /// Client state address resolver. + internal BuddyList(ClientStateAddressResolver addressResolver) { - var i = 0; - for (; i < 3; i++) + this.address = addressResolver; + + Log.Verbose($"Buddy list address 0x{this.address.BuddyList.ToInt64():X}"); + } + + /// + /// Gets the amount of battle buddies the local player has. + /// + public int Length + { + get { - var addr = this.GetBattleBuddyMemberAddress(i); - var member = this.CreateBuddyMemberReference(addr); - if (member == null) - break; + var i = 0; + for (; i < 3; i++) + { + var addr = this.GetBattleBuddyMemberAddress(i); + var member = this.CreateBuddyMemberReference(addr); + if (member == null) + break; + } + + return i; } - - return i; } - } - /// - /// Gets a value indicating whether the local player's companion is present. - /// - public bool CompanionBuddyPresent => this.CompanionBuddy != null; + /// + /// Gets a value indicating whether the local player's companion is present. + /// + public bool CompanionBuddyPresent => this.CompanionBuddy != null; - /// - /// Gets a value indicating whether the local player's pet is present. - /// - public bool PetBuddyPresent => this.PetBuddy != null; + /// + /// Gets a value indicating whether the local player's pet is present. + /// + public bool PetBuddyPresent => this.PetBuddy != null; - /// - /// Gets the active companion buddy. - /// - public BuddyMember? CompanionBuddy - { - get + /// + /// Gets the active companion buddy. + /// + public BuddyMember? CompanionBuddy { - var addr = this.GetCompanionBuddyMemberAddress(); - return this.CreateBuddyMemberReference(addr); + get + { + var addr = this.GetCompanionBuddyMemberAddress(); + return this.CreateBuddyMemberReference(addr); + } } - } - /// - /// Gets the active pet buddy. - /// - public BuddyMember? PetBuddy - { - get + /// + /// Gets the active pet buddy. + /// + public BuddyMember? PetBuddy { - var addr = this.GetPetBuddyMemberAddress(); - return this.CreateBuddyMemberReference(addr); + get + { + var addr = this.GetPetBuddyMemberAddress(); + return this.CreateBuddyMemberReference(addr); + } } - } - /// - /// Gets the address of the buddy list. - /// - internal IntPtr BuddyListAddress => this.address.BuddyList; + /// + /// Gets the address of the buddy list. + /// + internal IntPtr BuddyListAddress => this.address.BuddyList; - private static int BuddyMemberSize { get; } = Marshal.SizeOf(); + private static int BuddyMemberSize { get; } = Marshal.SizeOf(); - private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress; + private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress; - /// - /// Gets a battle buddy at the specified spawn index. - /// - /// Spawn index. - /// A at the specified spawn index. - public BuddyMember? this[int index] - { - get + /// + /// Gets a battle buddy at the specified spawn index. + /// + /// Spawn index. + /// A at the specified spawn index. + public BuddyMember? this[int index] { - var address = this.GetBattleBuddyMemberAddress(index); - return this.CreateBuddyMemberReference(address); + get + { + var address = this.GetBattleBuddyMemberAddress(index); + return this.CreateBuddyMemberReference(address); + } + } + + /// + /// Gets the address of the companion buddy. + /// + /// The memory address of the companion buddy. + public unsafe IntPtr GetCompanionBuddyMemberAddress() + { + return (IntPtr)(&this.BuddyListStruct->Companion); + } + + /// + /// Gets the address of the pet buddy. + /// + /// The memory address of the pet buddy. + public unsafe IntPtr GetPetBuddyMemberAddress() + { + return (IntPtr)(&this.BuddyListStruct->Pet); + } + + /// + /// Gets the address of the battle buddy at the specified index of the buddy list. + /// + /// The index of the battle buddy. + /// The memory address of the battle buddy. + public unsafe IntPtr GetBattleBuddyMemberAddress(int index) + { + if (index < 0 || index >= 3) + return IntPtr.Zero; + + return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize)); + } + + /// + /// Create a reference to a buddy. + /// + /// The address of the buddy in memory. + /// object containing the requested data. + public BuddyMember? CreateBuddyMemberReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (address == IntPtr.Zero) + return null; + + var buddy = new BuddyMember(address); + if (buddy.ObjectId == InvalidObjectID) + return null; + + return buddy; } } /// - /// Gets the address of the companion buddy. + /// This collection represents the buddies present in your squadron or trust party. /// - /// The memory address of the companion buddy. - public unsafe IntPtr GetCompanionBuddyMemberAddress() + public sealed partial class BuddyList : IReadOnlyCollection { - return (IntPtr)(&this.BuddyListStruct->Companion); - } + /// + int IReadOnlyCollection.Count => this.Length; - /// - /// Gets the address of the pet buddy. - /// - /// The memory address of the pet buddy. - public unsafe IntPtr GetPetBuddyMemberAddress() - { - return (IntPtr)(&this.BuddyListStruct->Pet); - } + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < this.Length; i++) + { + yield return this[i]; + } + } - /// - /// Gets the address of the battle buddy at the specified index of the buddy list. - /// - /// The index of the battle buddy. - /// The memory address of the battle buddy. - public unsafe IntPtr GetBattleBuddyMemberAddress(int index) - { - if (index < 0 || index >= 3) - return IntPtr.Zero; - - return (IntPtr)(this.BuddyListStruct->BattleBuddies + (index * BuddyMemberSize)); - } - - /// - /// Create a reference to a buddy. - /// - /// The address of the buddy in memory. - /// object containing the requested data. - public BuddyMember? CreateBuddyMemberReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - var buddy = new BuddyMember(address); - if (buddy.ObjectId == InvalidObjectID) - return null; - - return buddy; + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } } - -/// -/// This collection represents the buddies present in your squadron or trust party. -/// -public sealed partial class BuddyList : IReadOnlyCollection -{ - /// - int IReadOnlyCollection.Count => this.Length; - - /// - public IEnumerator GetEnumerator() - { - for (var i = 0; i < this.Length; i++) - { - yield return this[i]; - } - } - - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); -} diff --git a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs index 4d16a69d1..0029d20db 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyMember.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyMember.cs @@ -4,69 +4,70 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Resolvers; -namespace Dalamud.Game.ClientState.Buddy; - -/// -/// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties. -/// -public unsafe class BuddyMember +namespace Dalamud.Game.ClientState.Buddy { /// - /// Initializes a new instance of the class. + /// This class represents a buddy such as the chocobo companion, summoned pets, squadron groups and trust parties. /// - /// Buddy address. - internal BuddyMember(IntPtr address) + public unsafe class BuddyMember { - this.Address = address; + /// + /// Initializes a new instance of the class. + /// + /// Buddy address. + internal BuddyMember(IntPtr address) + { + this.Address = address; + } + + /// + /// Gets the address of the buddy in memory. + /// + public IntPtr Address { get; } + + /// + /// Gets the object ID of this buddy. + /// + public uint ObjectId => this.Struct->ObjectID; + + /// + /// Gets the actor associated with this buddy. + /// + /// + /// This iterates the actor table, it should be used with care. + /// + public GameObject? GameObject => Service.Get().SearchById(this.ObjectId); + + /// + /// Gets the current health of this buddy. + /// + public uint CurrentHP => this.Struct->CurrentHealth; + + /// + /// Gets the maximum health of this buddy. + /// + public uint MaxHP => this.Struct->MaxHealth; + + /// + /// Gets the data ID of this buddy. + /// + public uint DataID => this.Struct->DataID; + + /// + /// Gets the Mount data related to this buddy. It should only be used with companion buddies. + /// + public ExcelResolver MountData => new(this.DataID); + + /// + /// Gets the Pet data related to this buddy. It should only be used with pet buddies. + /// + public ExcelResolver PetData => new(this.DataID); + + /// + /// Gets the Trust data related to this buddy. It should only be used with battle buddies. + /// + public ExcelResolver TrustData => new(this.DataID); + + private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address; } - - /// - /// Gets the address of the buddy in memory. - /// - public IntPtr Address { get; } - - /// - /// Gets the object ID of this buddy. - /// - public uint ObjectId => this.Struct->ObjectID; - - /// - /// Gets the actor associated with this buddy. - /// - /// - /// This iterates the actor table, it should be used with care. - /// - public GameObject? GameObject => Service.Get().SearchById(this.ObjectId); - - /// - /// Gets the current health of this buddy. - /// - public uint CurrentHP => this.Struct->CurrentHealth; - - /// - /// Gets the maximum health of this buddy. - /// - public uint MaxHP => this.Struct->MaxHealth; - - /// - /// Gets the data ID of this buddy. - /// - public uint DataID => this.Struct->DataID; - - /// - /// Gets the Mount data related to this buddy. It should only be used with companion buddies. - /// - public ExcelResolver MountData => new(this.DataID); - - /// - /// Gets the Pet data related to this buddy. It should only be used with pet buddies. - /// - public ExcelResolver PetData => new(this.DataID); - - /// - /// Gets the Trust data related to this buddy. It should only be used with battle buddies. - /// - public ExcelResolver TrustData => new(this.DataID); - - private FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember*)this.Address; } diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 6f7fdb6f3..52b63ed9e 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -16,164 +16,165 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState; - -/// -/// This class represents the state of the game client at the time of access. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class ClientState : IDisposable +namespace Dalamud.Game.ClientState { - private readonly ClientStateAddressResolver address; - private readonly Hook setupTerritoryTypeHook; - - private bool lastConditionNone = true; - /// - /// Initializes a new instance of the class. - /// Set up client state access. + /// This class represents the state of the game client at the time of access. /// - internal ClientState() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class ClientState : IDisposable { - this.address = new ClientStateAddressResolver(); - this.address.Setup(); + private readonly ClientStateAddressResolver address; + private readonly Hook setupTerritoryTypeHook; - Log.Verbose("===== C L I E N T S T A T E ====="); + private bool lastConditionNone = true; - this.ClientLanguage = Service.Get().Language; - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Service.Set(this.address); - - Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); - - this.setupTerritoryTypeHook = new Hook(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); - - var framework = Service.Get(); - framework.Update += this.FrameworkOnOnUpdateEvent; - - var networkHandlers = Service.Get(); - networkHandlers.CfPop += this.NetworkHandlersOnCfPop; - } - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType); - - /// - /// Event that gets fired when the current Territory changes. - /// - public event EventHandler TerritoryChanged; - - /// - /// Event that fires when a character is logging in. - /// - public event EventHandler Login; - - /// - /// Event that fires when a character is logging out. - /// - public event EventHandler Logout; - - /// - /// Event that gets fired when a duty is ready. - /// - public event EventHandler CfPop; - - /// - /// Gets the language of the client. - /// - public ClientLanguage ClientLanguage { get; } - - /// - /// Gets the current Territory the player resides in. - /// - public ushort TerritoryType { get; private set; } - - /// - /// Gets the local player character, if one is present. - /// - public PlayerCharacter? LocalPlayer => Service.Get()[0] as PlayerCharacter; - - /// - /// Gets the content ID of the local character. - /// - public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId); - - /// - /// Gets a value indicating whether a character is logged in. - /// - public bool IsLoggedIn { get; private set; } - - /// - /// Enable this module. - /// - public void Enable() - { - Service.Get().Enable(); - Service.Get().Enable(); - this.setupTerritoryTypeHook.Enable(); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - this.setupTerritoryTypeHook.Dispose(); - Service.Get().Dispose(); - Service.Get().Dispose(); - Service.Get().Update -= this.FrameworkOnOnUpdateEvent; - Service.Get().CfPop -= this.NetworkHandlersOnCfPop; - } - - private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) - { - this.TerritoryType = terriType; - this.TerritoryChanged?.Invoke(this, terriType); - - Log.Debug("TerritoryType changed: {0}", terriType); - - return this.setupTerritoryTypeHook.Original(manager, terriType); - } - - private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e) - { - this.CfPop?.Invoke(this, e); - } - - private void FrameworkOnOnUpdateEvent(Framework framework) - { - var condition = Service.Get(); - if (condition.Any() && this.lastConditionNone == true) + /// + /// Initializes a new instance of the class. + /// Set up client state access. + /// + internal ClientState() { - Log.Debug("Is login"); - this.lastConditionNone = false; - this.IsLoggedIn = true; - this.Login?.Invoke(this, null); + this.address = new ClientStateAddressResolver(); + this.address.Setup(); + + Log.Verbose("===== C L I E N T S T A T E ====="); + + this.ClientLanguage = Service.Get().Language; + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Service.Set(this.address); + + Log.Verbose($"SetupTerritoryType address 0x{this.address.SetupTerritoryType.ToInt64():X}"); + + this.setupTerritoryTypeHook = new Hook(this.address.SetupTerritoryType, this.SetupTerritoryTypeDetour); + + var framework = Service.Get(); + framework.Update += this.FrameworkOnOnUpdateEvent; + + var networkHandlers = Service.Get(); + networkHandlers.CfPop += this.NetworkHandlersOnCfPop; } - if (!condition.Any() && this.lastConditionNone == false) + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType); + + /// + /// Event that gets fired when the current Territory changes. + /// + public event EventHandler TerritoryChanged; + + /// + /// Event that fires when a character is logging in. + /// + public event EventHandler Login; + + /// + /// Event that fires when a character is logging out. + /// + public event EventHandler Logout; + + /// + /// Event that gets fired when a duty is ready. + /// + public event EventHandler CfPop; + + /// + /// Gets the language of the client. + /// + public ClientLanguage ClientLanguage { get; } + + /// + /// Gets the current Territory the player resides in. + /// + public ushort TerritoryType { get; private set; } + + /// + /// Gets the local player character, if one is present. + /// + public PlayerCharacter? LocalPlayer => Service.Get()[0] as PlayerCharacter; + + /// + /// Gets the content ID of the local character. + /// + public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId); + + /// + /// Gets a value indicating whether a character is logged in. + /// + public bool IsLoggedIn { get; private set; } + + /// + /// Enable this module. + /// + public void Enable() { - Log.Debug("Is logout"); - this.lastConditionNone = true; - this.IsLoggedIn = false; - this.Logout?.Invoke(this, null); + Service.Get().Enable(); + Service.Get().Enable(); + this.setupTerritoryTypeHook.Enable(); + } + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + this.setupTerritoryTypeHook.Dispose(); + Service.Get().Dispose(); + Service.Get().Dispose(); + Service.Get().Update -= this.FrameworkOnOnUpdateEvent; + Service.Get().CfPop -= this.NetworkHandlersOnCfPop; + } + + private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType) + { + this.TerritoryType = terriType; + this.TerritoryChanged?.Invoke(this, terriType); + + Log.Debug("TerritoryType changed: {0}", terriType); + + return this.setupTerritoryTypeHook.Original(manager, terriType); + } + + private void NetworkHandlersOnCfPop(object sender, Lumina.Excel.GeneratedSheets.ContentFinderCondition e) + { + this.CfPop?.Invoke(this, e); + } + + private void FrameworkOnOnUpdateEvent(Framework framework) + { + var condition = Service.Get(); + if (condition.Any() && this.lastConditionNone == true) + { + Log.Debug("Is login"); + this.lastConditionNone = false; + this.IsLoggedIn = true; + this.Login?.Invoke(this, null); + } + + if (!condition.Any() && this.lastConditionNone == false) + { + Log.Debug("Is logout"); + this.lastConditionNone = true; + this.IsLoggedIn = false; + this.Logout?.Invoke(this, null); + } } } } diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 1be46668d..11b77e2df 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -1,113 +1,114 @@ using System; -namespace Dalamud.Game.ClientState; - -/// -/// Client state memory address resolver. -/// -public sealed class ClientStateAddressResolver : BaseAddressResolver +namespace Dalamud.Game.ClientState { - // Static offsets - /// - /// Gets the address of the actor table. + /// Client state memory address resolver. /// - public IntPtr ObjectTable { get; private set; } - - /// - /// Gets the address of the buddy list. - /// - public IntPtr BuddyList { get; private set; } - - /// - /// Gets the address of a pointer to the fate table. - /// - /// - /// This is a static address to a pointer, not the address of the table itself. - /// - public IntPtr FateTablePtr { get; private set; } - - /// - /// Gets the address of the Group Manager. - /// - public IntPtr GroupManager { get; private set; } - - /// - /// Gets the address of the local content id. - /// - public IntPtr LocalContentId { get; private set; } - - /// - /// Gets the address of job gauge data. - /// - public IntPtr JobGaugeData { get; private set; } - - /// - /// Gets the address of the keyboard state. - /// - public IntPtr KeyboardState { get; private set; } - - /// - /// Gets the address of the keyboard state index array which translates the VK enumeration to the key state. - /// - public IntPtr KeyboardStateIndexArray { get; private set; } - - /// - /// Gets the address of the target manager. - /// - public IntPtr TargetManager { get; private set; } - - /// - /// Gets the address of the condition flag array. - /// - public IntPtr ConditionFlags { get; private set; } - - // Functions - - /// - /// Gets the address of the method which sets the territory type. - /// - public IntPtr SetupTerritoryType { get; private set; } - - /// - /// Gets the address of the method which polls the gamepads for data. - /// Called every frame, even when `Enable Gamepad` is off in the settings. - /// - public IntPtr GamepadPoll { get; private set; } - - /// - /// Scan for and setup any configured address pointers. - /// - /// The signature scanner to facilitate setup. - protected override void Setup64Bit(SigScanner sig) + public sealed class ClientStateAddressResolver : BaseAddressResolver { - // We don't need those anymore, but maybe someone else will - let's leave them here for good measure - // ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148; - // SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??"); + // Static offsets - this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83"); + /// + /// Gets the address of the actor table. + /// + public IntPtr ObjectTable { get; private set; } - this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04"); + /// + /// Gets the address of the buddy list. + /// + public IntPtr BuddyList { get; private set; } - this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??"); + /// + /// Gets the address of a pointer to the fate table. + /// + /// + /// This is a static address to a pointer, not the address of the table itself. + /// + public IntPtr FateTablePtr { get; private set; } - this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50"); + /// + /// Gets the address of the Group Manager. + /// + public IntPtr GroupManager { get; private set; } - this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07"); - this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x8; + /// + /// Gets the address of the local content id. + /// + public IntPtr LocalContentId { get; private set; } - this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??"); + /// + /// Gets the address of job gauge data. + /// + public IntPtr JobGaugeData { get; private set; } - // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used. - // lea rcx, ds:1DB9F74h[rax*4] KeyboardState - // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray - this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; - this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4; + /// + /// Gets the address of the keyboard state. + /// + public IntPtr KeyboardState { get; private set; } - this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30"); + /// + /// Gets the address of the keyboard state index array which translates the VK enumeration to the key state. + /// + public IntPtr KeyboardStateIndexArray { get; private set; } - this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB"); + /// + /// Gets the address of the target manager. + /// + public IntPtr TargetManager { get; private set; } - this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"); + /// + /// Gets the address of the condition flag array. + /// + public IntPtr ConditionFlags { get; private set; } + + // Functions + + /// + /// Gets the address of the method which sets the territory type. + /// + public IntPtr SetupTerritoryType { get; private set; } + + /// + /// Gets the address of the method which polls the gamepads for data. + /// Called every frame, even when `Enable Gamepad` is off in the settings. + /// + public IntPtr GamepadPoll { get; private set; } + + /// + /// Scan for and setup any configured address pointers. + /// + /// The signature scanner to facilitate setup. + protected override void Setup64Bit(SigScanner sig) + { + // We don't need those anymore, but maybe someone else will - let's leave them here for good measure + // ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148; + // SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??"); + + this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83"); + + this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04"); + + this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F9 44 0F B7 41 ??"); + + this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 76 50"); + + this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07"); + this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 0D ?? ?? ?? ?? 48 85 C9 74 43") + 0x8; + + this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??"); + + // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used. + // lea rcx, ds:1DB9F74h[rax*4] KeyboardState + // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray + this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; + this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4; + + this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30"); + + this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB"); + + this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B"); + } } } diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs index 22aadff7a..597f4725b 100644 --- a/Dalamud/Game/ClientState/Conditions/Condition.cs +++ b/Dalamud/Game/ClientState/Conditions/Condition.cs @@ -4,154 +4,155 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Conditions; - -/// -/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed partial class Condition +namespace Dalamud.Game.ClientState.Conditions { /// - /// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has. + /// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. /// - public const int MaxConditionEntries = 100; - - private readonly bool[] cache = new bool[MaxConditionEntries]; - - /// - /// Initializes a new instance of the class. - /// - /// The ClientStateAddressResolver instance. - internal Condition(ClientStateAddressResolver resolver) + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed partial class Condition { - this.Address = resolver.ConditionFlags; - } + /// + /// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has. + /// + public const int MaxConditionEntries = 100; - /// - /// A delegate type used with the event. - /// - /// The changed condition. - /// The value the condition is set to. - public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value); + private readonly bool[] cache = new bool[MaxConditionEntries]; - /// - /// Event that gets fired when a condition is set. - /// Should only get fired for actual changes, so the previous value will always be !value. - /// - public event ConditionChangeDelegate? ConditionChange; - - /// - /// Gets the condition array base pointer. - /// - public IntPtr Address { get; private set; } - - /// - /// Check the value of a specific condition/state flag. - /// - /// The condition flag to check. - public unsafe bool this[int flag] - { - get + /// + /// Initializes a new instance of the class. + /// + /// The ClientStateAddressResolver instance. + internal Condition(ClientStateAddressResolver resolver) { - if (flag < 0 || flag >= MaxConditionEntries) - return false; - - return *(bool*)(this.Address + flag); - } - } - - /// - public unsafe bool this[ConditionFlag flag] - => this[(int)flag]; - - /// - /// Check if any condition flags are set. - /// - /// Whether any single flag is set. - public bool Any() - { - for (var i = 0; i < MaxConditionEntries; i++) - { - var cond = this[i]; - - if (cond) - return true; + this.Address = resolver.ConditionFlags; } - return false; - } + /// + /// A delegate type used with the event. + /// + /// The changed condition. + /// The value the condition is set to. + public delegate void ConditionChangeDelegate(ConditionFlag flag, bool value); - /// - /// Enables the hooks of the Condition class function. - /// - public void Enable() - { - // Initialization - for (var i = 0; i < MaxConditionEntries; i++) - this.cache[i] = this[i]; + /// + /// Event that gets fired when a condition is set. + /// Should only get fired for actual changes, so the previous value will always be !value. + /// + public event ConditionChangeDelegate? ConditionChange; - Service.Get().Update += this.FrameworkUpdate; - } + /// + /// Gets the condition array base pointer. + /// + public IntPtr Address { get; private set; } - private void FrameworkUpdate(Framework framework) - { - for (var i = 0; i < MaxConditionEntries; i++) + /// + /// Check the value of a specific condition/state flag. + /// + /// The condition flag to check. + public unsafe bool this[int flag] { - var value = this[i]; - - if (value != this.cache[i]) + get { - this.cache[i] = value; + if (flag < 0 || flag >= MaxConditionEntries) + return false; - try + return *(bool*)(this.Address + flag); + } + } + + /// + public unsafe bool this[ConditionFlag flag] + => this[(int)flag]; + + /// + /// Check if any condition flags are set. + /// + /// Whether any single flag is set. + public bool Any() + { + for (var i = 0; i < MaxConditionEntries; i++) + { + var cond = this[i]; + + if (cond) + return true; + } + + return false; + } + + /// + /// Enables the hooks of the Condition class function. + /// + public void Enable() + { + // Initialization + for (var i = 0; i < MaxConditionEntries; i++) + this.cache[i] = this[i]; + + Service.Get().Update += this.FrameworkUpdate; + } + + private void FrameworkUpdate(Framework framework) + { + for (var i = 0; i < MaxConditionEntries; i++) + { + var value = this[i]; + + if (value != this.cache[i]) { - this.ConditionChange?.Invoke((ConditionFlag)i, value); - } - catch (Exception ex) - { - Log.Error(ex, $"While invoking {nameof(this.ConditionChange)}, an exception was thrown."); + this.cache[i] = value; + + try + { + this.ConditionChange?.Invoke((ConditionFlag)i, value); + } + catch (Exception ex) + { + Log.Error(ex, $"While invoking {nameof(this.ConditionChange)}, an exception was thrown."); + } } } } } -} - -/// -/// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. -/// -public sealed partial class Condition : IDisposable -{ - private bool isDisposed; /// - /// Finalizes an instance of the class. + /// Provides access to conditions (generally player state). You can check whether a player is in combat, mounted, etc. /// - ~Condition() + public sealed partial class Condition : IDisposable { - this.Dispose(false); - } + private bool isDisposed; - /// - /// Disposes this instance, alongside its hooks. - /// - public void Dispose() - { - GC.SuppressFinalize(this); - this.Dispose(true); - } - - private void Dispose(bool disposing) - { - if (this.isDisposed) - return; - - if (disposing) + /// + /// Finalizes an instance of the class. + /// + ~Condition() { - Service.Get().Update -= this.FrameworkUpdate; + this.Dispose(false); } - this.isDisposed = true; + /// + /// Disposes this instance, alongside its hooks. + /// + public void Dispose() + { + GC.SuppressFinalize(this); + this.Dispose(true); + } + + private void Dispose(bool disposing) + { + if (this.isDisposed) + return; + + if (disposing) + { + Service.Get().Update -= this.FrameworkUpdate; + } + + this.isDisposed = true; + } } } diff --git a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs index 214f5f7a9..75c295ed0 100644 --- a/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs +++ b/Dalamud/Game/ClientState/Conditions/ConditionFlag.cs @@ -1,452 +1,453 @@ -namespace Dalamud.Game.ClientState.Conditions; - -/// -/// Possible state flags (or conditions as they're called internally) that can be set on the local client. -/// -/// These come from LogMessage (somewhere) and directly map to each state field managed by the client. As of 5.25, it maps to -/// LogMessage row 7700 and onwards, which can be checked by looking at the Condition sheet and looking at what column 2 maps to. -/// -public enum ConditionFlag +namespace Dalamud.Game.ClientState.Conditions { /// - /// Unused. - /// - None = 0, - - /// - /// Unable to execute command under normal conditions. - /// - NormalConditions = 1, - - /// - /// Unable to execute command while unconscious. - /// - Unconscious = 2, - - /// - /// Unable to execute command during an emote. - /// - Emoting = 3, - - /// - /// Unable to execute command while mounted. - /// - Mounted = 4, - - /// - /// Unable to execute command while crafting. - /// - Crafting = 5, - - /// - /// Unable to execute command while gathering. - /// - Gathering = 6, - - /// - /// Unable to execute command while melding materia. - /// - MeldingMateria = 7, - - /// - /// Unable to execute command while operating a siege machine. - /// - OperatingSiegeMachine = 8, - - /// - /// Unable to execute command while carrying an object. - /// - CarryingObject = 9, - - /// - /// Unable to execute command while mounted. - /// - Mounted2 = 10, - - /// - /// Unable to execute command while in that position. - /// - InThatPosition = 11, - - /// - /// Unable to execute command while chocobo racing. - /// - ChocoboRacing = 12, - - /// - /// Unable to execute command while playing a mini-game. - /// - PlayingMiniGame = 13, - - /// - /// Unable to execute command while playing Lord of Verminion. - /// - PlayingLordOfVerminion = 14, - - /// - /// Unable to execute command while participating in a custom match. - /// - ParticipatingInCustomMatch = 15, - - /// - /// Unable to execute command while performing. - /// - Performing = 16, - - // Unknown17 = 17, - // Unknown18 = 18, - // Unknown19 = 19, - // Unknown20 = 20, - // Unknown21 = 21, - // Unknown22 = 22, - // Unknown23 = 23, - // Unknown24 = 24, - - /// - /// Unable to execute command while occupied. - /// - Occupied = 25, - - /// - /// Unable to execute command during combat. - /// - InCombat = 26, - - /// - /// Unable to execute command while casting. - /// - Casting = 27, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction = 28, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction2 = 29, - - /// - /// Unable to execute command while occupied. - /// - Occupied30 = 30, - - /// - /// Unable to execute command while occupied. - /// - // todo: not sure if this is used for other event states/??? - OccupiedInEvent = 31, - - /// - /// Unable to execute command while occupied. - /// - OccupiedInQuestEvent = 32, - - /// - /// Unable to execute command while occupied. - /// - Occupied33 = 33, - - /// - /// Unable to execute command while bound by duty. - /// - BoundByDuty = 34, - - /// - /// Unable to execute command while occupied. - /// - OccupiedInCutSceneEvent = 35, - - /// - /// Unable to execute command while in a dueling area. - /// - InDuelingArea = 36, - - /// - /// Unable to execute command while a trade is open. - /// - TradeOpen = 37, - - /// - /// Unable to execute command while occupied. - /// - Occupied38 = 38, - - /// - /// Unable to execute command while occupied. - /// - Occupied39 = 39, - - /// - /// Unable to execute command while crafting. - /// - Crafting40 = 40, - - /// - /// Unable to execute command while preparing to craft. - /// - PreparingToCraft = 41, - - /// - /// Unable to execute command while gathering. - /// - Gathering42 = 42, - - /// - /// Unable to execute command while fishing. - /// - Fishing = 43, - - // Unknown44 = 44, - - /// - /// Unable to execute command while between areas. - /// - BetweenAreas = 45, - - /// - /// Unable to execute command while stealthed. - /// - Stealthed = 46, - - // Unknown47 = 47, - - /// - /// Unable to execute command while jumping. - /// - Jumping = 48, - - /// - /// Unable to execute command while auto-run is active. - /// - AutorunActive = 49, - - /// - /// Unable to execute command while occupied. - /// - // todo: used for other shits? - OccupiedSummoningBell = 50, - - /// - /// Unable to execute command while between areas. - /// - BetweenAreas51 = 51, - - /// - /// Unable to execute command due to system error. - /// - SystemError = 52, - - /// - /// Unable to execute command while logging out. - /// - LoggingOut = 53, - - /// - /// Unable to execute command at this location. - /// - ConditionLocation = 54, - - /// - /// Unable to execute command while waiting for duty. - /// - WaitingForDuty = 55, - - /// - /// Unable to execute command while bound by duty. - /// - BoundByDuty56 = 56, - - /// - /// Unable to execute command at this time. - /// - Unknown57 = 57, - - /// - /// Unable to execute command while watching a cutscene. - /// - WatchingCutscene = 58, - - /// - /// Unable to execute command while waiting for Duty Finder. - /// - WaitingForDutyFinder = 59, - - /// - /// Unable to execute command while creating a character. - /// - CreatingCharacter = 60, - - /// - /// Unable to execute command while jumping. - /// - Jumping61 = 61, - - /// - /// Unable to execute command while the PvP display is active. - /// - PvPDisplayActive = 62, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction63 = 63, - - /// - /// Unable to execute command while mounting. - /// - Mounting = 64, - - /// - /// Unable to execute command while carrying an item. - /// - CarryingItem = 65, - - /// - /// Unable to execute command while using the Party Finder. - /// - UsingPartyFinder = 66, - - /// - /// Unable to execute command while using housing functions. - /// - UsingHousingFunctions = 67, - - /// - /// Unable to execute command while transformed. - /// - Transformed = 68, - - /// - /// Unable to execute command while on the free trial. - /// - OnFreeTrial = 69, - - /// - /// Unable to execute command while being moved. - /// - BeingMoved = 70, - - /// - /// Unable to execute command while mounting. - /// - Mounting71 = 71, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction72 = 72, - - /// - /// Unable to execute command while suffering status affliction. - /// - SufferingStatusAffliction73 = 73, - - /// - /// Unable to execute command while registering for a race or match. - /// - RegisteringForRaceOrMatch = 74, - - /// - /// Unable to execute command while waiting for a race or match. - /// - WaitingForRaceOrMatch = 75, - - /// - /// Unable to execute command while waiting for a Triple Triad match. - /// - WaitingForTripleTriadMatch = 76, - - /// - /// Unable to execute command while in flight. - /// - InFlight = 77, - - /// - /// Unable to execute command while watching a cutscene. - /// - WatchingCutscene78 = 78, - - /// - /// Unable to execute command while delving into a deep dungeon. - /// - InDeepDungeon = 79, - - /// - /// Unable to execute command while swimming. - /// - Swimming = 80, - - /// - /// Unable to execute command while diving. - /// - Diving = 81, - - /// - /// Unable to execute command while registering for a Triple Triad match. - /// - RegisteringForTripleTriadMatch = 82, - - /// - /// Unable to execute command while waiting for a Triple Triad match. - /// - WaitingForTripleTriadMatch83 = 83, - - /// - /// Unable to execute command while participating in a cross-world party or alliance. - /// - ParticipatingInCrossWorldPartyOrAlliance = 84, - - // Unknown85 = 85, - - /// - /// Unable to execute command while playing duty record. - /// - DutyRecorderPlayback = 86, - - /// - /// Unable to execute command while casting. - /// - Casting87 = 87, - - /// - /// Unable to execute command in this state. - /// - InThisState88 = 88, - - /// - /// Unable to execute command in this state. - /// - InThisState89 = 89, - - /// - /// Unable to execute command while role-playing. - /// - RolePlaying = 90, - - /// - /// Unable to execute command while bound by duty. - /// - BoundToDuty97 = 91, - - /// - /// Unable to execute command while readying to visit another World. - /// - ReadyingVisitOtherWorld = 92, - - /// - /// Unable to execute command while waiting to visit another World. - /// - WaitingToVisitOtherWorld = 93, - - /// - /// Unable to execute command while using a parasol. - /// - UsingParasol = 94, - - /// - /// Unable to execute command while bound by duty. - /// - BoundByDuty95 = 95, + /// Possible state flags (or conditions as they're called internally) that can be set on the local client. + /// + /// These come from LogMessage (somewhere) and directly map to each state field managed by the client. As of 5.25, it maps to + /// LogMessage row 7700 and onwards, which can be checked by looking at the Condition sheet and looking at what column 2 maps to. + /// + public enum ConditionFlag + { + /// + /// Unused. + /// + None = 0, + + /// + /// Unable to execute command under normal conditions. + /// + NormalConditions = 1, + + /// + /// Unable to execute command while unconscious. + /// + Unconscious = 2, + + /// + /// Unable to execute command during an emote. + /// + Emoting = 3, + + /// + /// Unable to execute command while mounted. + /// + Mounted = 4, + + /// + /// Unable to execute command while crafting. + /// + Crafting = 5, + + /// + /// Unable to execute command while gathering. + /// + Gathering = 6, + + /// + /// Unable to execute command while melding materia. + /// + MeldingMateria = 7, + + /// + /// Unable to execute command while operating a siege machine. + /// + OperatingSiegeMachine = 8, + + /// + /// Unable to execute command while carrying an object. + /// + CarryingObject = 9, + + /// + /// Unable to execute command while mounted. + /// + Mounted2 = 10, + + /// + /// Unable to execute command while in that position. + /// + InThatPosition = 11, + + /// + /// Unable to execute command while chocobo racing. + /// + ChocoboRacing = 12, + + /// + /// Unable to execute command while playing a mini-game. + /// + PlayingMiniGame = 13, + + /// + /// Unable to execute command while playing Lord of Verminion. + /// + PlayingLordOfVerminion = 14, + + /// + /// Unable to execute command while participating in a custom match. + /// + ParticipatingInCustomMatch = 15, + + /// + /// Unable to execute command while performing. + /// + Performing = 16, + + // Unknown17 = 17, + // Unknown18 = 18, + // Unknown19 = 19, + // Unknown20 = 20, + // Unknown21 = 21, + // Unknown22 = 22, + // Unknown23 = 23, + // Unknown24 = 24, + + /// + /// Unable to execute command while occupied. + /// + Occupied = 25, + + /// + /// Unable to execute command during combat. + /// + InCombat = 26, + + /// + /// Unable to execute command while casting. + /// + Casting = 27, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction = 28, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction2 = 29, + + /// + /// Unable to execute command while occupied. + /// + Occupied30 = 30, + + /// + /// Unable to execute command while occupied. + /// + // todo: not sure if this is used for other event states/??? + OccupiedInEvent = 31, + + /// + /// Unable to execute command while occupied. + /// + OccupiedInQuestEvent = 32, + + /// + /// Unable to execute command while occupied. + /// + Occupied33 = 33, + + /// + /// Unable to execute command while bound by duty. + /// + BoundByDuty = 34, + + /// + /// Unable to execute command while occupied. + /// + OccupiedInCutSceneEvent = 35, + + /// + /// Unable to execute command while in a dueling area. + /// + InDuelingArea = 36, + + /// + /// Unable to execute command while a trade is open. + /// + TradeOpen = 37, + + /// + /// Unable to execute command while occupied. + /// + Occupied38 = 38, + + /// + /// Unable to execute command while occupied. + /// + Occupied39 = 39, + + /// + /// Unable to execute command while crafting. + /// + Crafting40 = 40, + + /// + /// Unable to execute command while preparing to craft. + /// + PreparingToCraft = 41, + + /// + /// Unable to execute command while gathering. + /// + Gathering42 = 42, + + /// + /// Unable to execute command while fishing. + /// + Fishing = 43, + + // Unknown44 = 44, + + /// + /// Unable to execute command while between areas. + /// + BetweenAreas = 45, + + /// + /// Unable to execute command while stealthed. + /// + Stealthed = 46, + + // Unknown47 = 47, + + /// + /// Unable to execute command while jumping. + /// + Jumping = 48, + + /// + /// Unable to execute command while auto-run is active. + /// + AutorunActive = 49, + + /// + /// Unable to execute command while occupied. + /// + // todo: used for other shits? + OccupiedSummoningBell = 50, + + /// + /// Unable to execute command while between areas. + /// + BetweenAreas51 = 51, + + /// + /// Unable to execute command due to system error. + /// + SystemError = 52, + + /// + /// Unable to execute command while logging out. + /// + LoggingOut = 53, + + /// + /// Unable to execute command at this location. + /// + ConditionLocation = 54, + + /// + /// Unable to execute command while waiting for duty. + /// + WaitingForDuty = 55, + + /// + /// Unable to execute command while bound by duty. + /// + BoundByDuty56 = 56, + + /// + /// Unable to execute command at this time. + /// + Unknown57 = 57, + + /// + /// Unable to execute command while watching a cutscene. + /// + WatchingCutscene = 58, + + /// + /// Unable to execute command while waiting for Duty Finder. + /// + WaitingForDutyFinder = 59, + + /// + /// Unable to execute command while creating a character. + /// + CreatingCharacter = 60, + + /// + /// Unable to execute command while jumping. + /// + Jumping61 = 61, + + /// + /// Unable to execute command while the PvP display is active. + /// + PvPDisplayActive = 62, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction63 = 63, + + /// + /// Unable to execute command while mounting. + /// + Mounting = 64, + + /// + /// Unable to execute command while carrying an item. + /// + CarryingItem = 65, + + /// + /// Unable to execute command while using the Party Finder. + /// + UsingPartyFinder = 66, + + /// + /// Unable to execute command while using housing functions. + /// + UsingHousingFunctions = 67, + + /// + /// Unable to execute command while transformed. + /// + Transformed = 68, + + /// + /// Unable to execute command while on the free trial. + /// + OnFreeTrial = 69, + + /// + /// Unable to execute command while being moved. + /// + BeingMoved = 70, + + /// + /// Unable to execute command while mounting. + /// + Mounting71 = 71, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction72 = 72, + + /// + /// Unable to execute command while suffering status affliction. + /// + SufferingStatusAffliction73 = 73, + + /// + /// Unable to execute command while registering for a race or match. + /// + RegisteringForRaceOrMatch = 74, + + /// + /// Unable to execute command while waiting for a race or match. + /// + WaitingForRaceOrMatch = 75, + + /// + /// Unable to execute command while waiting for a Triple Triad match. + /// + WaitingForTripleTriadMatch = 76, + + /// + /// Unable to execute command while in flight. + /// + InFlight = 77, + + /// + /// Unable to execute command while watching a cutscene. + /// + WatchingCutscene78 = 78, + + /// + /// Unable to execute command while delving into a deep dungeon. + /// + InDeepDungeon = 79, + + /// + /// Unable to execute command while swimming. + /// + Swimming = 80, + + /// + /// Unable to execute command while diving. + /// + Diving = 81, + + /// + /// Unable to execute command while registering for a Triple Triad match. + /// + RegisteringForTripleTriadMatch = 82, + + /// + /// Unable to execute command while waiting for a Triple Triad match. + /// + WaitingForTripleTriadMatch83 = 83, + + /// + /// Unable to execute command while participating in a cross-world party or alliance. + /// + ParticipatingInCrossWorldPartyOrAlliance = 84, + + // Unknown85 = 85, + + /// + /// Unable to execute command while playing duty record. + /// + DutyRecorderPlayback = 86, + + /// + /// Unable to execute command while casting. + /// + Casting87 = 87, + + /// + /// Unable to execute command in this state. + /// + InThisState88 = 88, + + /// + /// Unable to execute command in this state. + /// + InThisState89 = 89, + + /// + /// Unable to execute command while role-playing. + /// + RolePlaying = 90, + + /// + /// Unable to execute command while bound by duty. + /// + BoundToDuty97 = 91, + + /// + /// Unable to execute command while readying to visit another World. + /// + ReadyingVisitOtherWorld = 92, + + /// + /// Unable to execute command while waiting to visit another World. + /// + WaitingToVisitOtherWorld = 93, + + /// + /// Unable to execute command while using a parasol. + /// + UsingParasol = 94, + + /// + /// Unable to execute command while bound by duty. + /// + BoundByDuty95 = 95, + } } diff --git a/Dalamud/Game/ClientState/Fates/Fate.cs b/Dalamud/Game/ClientState/Fates/Fate.cs index 560b32e60..5f9a4cb95 100644 --- a/Dalamud/Game/ClientState/Fates/Fate.cs +++ b/Dalamud/Game/ClientState/Fates/Fate.cs @@ -6,130 +6,131 @@ using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -namespace Dalamud.Game.ClientState.Fates; - -/// -/// This class represents an FFXIV Fate. -/// -public unsafe partial class Fate : IEquatable +namespace Dalamud.Game.ClientState.Fates { /// - /// Initializes a new instance of the class. + /// This class represents an FFXIV Fate. /// - /// The address of this fate in memory. - internal Fate(IntPtr address) + public unsafe partial class Fate : IEquatable { - this.Address = address; + /// + /// Initializes a new instance of the class. + /// + /// The address of this fate in memory. + internal Fate(IntPtr address) + { + this.Address = address; + } + + /// + /// Gets the address of this Fate in memory. + /// + public IntPtr Address { get; } + + private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address; + + public static bool operator ==(Fate fate1, Fate fate2) + { + if (fate1 is null || fate2 is null) + return Equals(fate1, fate2); + + return fate1.Equals(fate2); + } + + public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2); + + /// + /// Gets a value indicating whether this Fate is still valid in memory. + /// + /// The fate to check. + /// True or false. + public static bool IsValid(Fate fate) + { + var clientState = Service.Get(); + + if (fate == null) + return false; + + if (clientState.LocalContentId == 0) + return false; + + return true; + } + + /// + /// Gets a value indicating whether this actor is still valid in memory. + /// + /// True or false. + public bool IsValid() => IsValid(this); + + /// + bool IEquatable.Equals(Fate other) => this.FateId == other?.FateId; + + /// + public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as Fate); + + /// + public override int GetHashCode() => this.FateId.GetHashCode(); } /// - /// Gets the address of this Fate in memory. + /// This class represents an FFXIV Fate. /// - public IntPtr Address { get; } - - private FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateContext*)this.Address; - - public static bool operator ==(Fate fate1, Fate fate2) + public unsafe partial class Fate { - if (fate1 is null || fate2 is null) - return Equals(fate1, fate2); + /// + /// Gets the Fate ID of this . + /// + public ushort FateId => this.Struct->FateId; - return fate1.Equals(fate2); + /// + /// Gets game data linked to this Fate. + /// + public Lumina.Excel.GeneratedSheets.Fate GameData => Service.Get().GetExcelSheet().GetRow(this.FateId); + + /// + /// Gets the time this started. + /// + public int StartTimeEpoch => this.Struct->StartTimeEpoch; + + /// + /// Gets how long this will run. + /// + public short Duration => this.Struct->Duration; + + /// + /// Gets the remaining time in seconds for this . + /// + public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds(); + + /// + /// Gets the displayname of this . + /// + public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name); + + /// + /// Gets the state of this (Running, Ended, Failed, Preparation, WaitingForEnd). + /// + public FateState State => (FateState)this.Struct->State; + + /// + /// Gets the progress amount of this . + /// + public byte Progress => this.Struct->Progress; + + /// + /// Gets the level of this . + /// + public byte Level => this.Struct->Level; + + /// + /// Gets the position of this . + /// + public Vector3 Position => this.Struct->Location; + + /// + /// Gets the territory this is located in. + /// + public ExcelResolver TerritoryType => new(this.Struct->TerritoryID); } - - public static bool operator !=(Fate fate1, Fate fate2) => !(fate1 == fate2); - - /// - /// Gets a value indicating whether this Fate is still valid in memory. - /// - /// The fate to check. - /// True or false. - public static bool IsValid(Fate fate) - { - var clientState = Service.Get(); - - if (fate == null) - return false; - - if (clientState.LocalContentId == 0) - return false; - - return true; - } - - /// - /// Gets a value indicating whether this actor is still valid in memory. - /// - /// True or false. - public bool IsValid() => IsValid(this); - - /// - bool IEquatable.Equals(Fate other) => this.FateId == other?.FateId; - - /// - public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as Fate); - - /// - public override int GetHashCode() => this.FateId.GetHashCode(); -} - -/// -/// This class represents an FFXIV Fate. -/// -public unsafe partial class Fate -{ - /// - /// Gets the Fate ID of this . - /// - public ushort FateId => this.Struct->FateId; - - /// - /// Gets game data linked to this Fate. - /// - public Lumina.Excel.GeneratedSheets.Fate GameData => Service.Get().GetExcelSheet().GetRow(this.FateId); - - /// - /// Gets the time this started. - /// - public int StartTimeEpoch => this.Struct->StartTimeEpoch; - - /// - /// Gets how long this will run. - /// - public short Duration => this.Struct->Duration; - - /// - /// Gets the remaining time in seconds for this . - /// - public long TimeRemaining => this.StartTimeEpoch + this.Duration - DateTimeOffset.Now.ToUnixTimeSeconds(); - - /// - /// Gets the displayname of this . - /// - public SeString Name => MemoryHelper.ReadSeString(&this.Struct->Name); - - /// - /// Gets the state of this (Running, Ended, Failed, Preparation, WaitingForEnd). - /// - public FateState State => (FateState)this.Struct->State; - - /// - /// Gets the progress amount of this . - /// - public byte Progress => this.Struct->Progress; - - /// - /// Gets the level of this . - /// - public byte Level => this.Struct->Level; - - /// - /// Gets the position of this . - /// - public Vector3 Position => this.Struct->Location; - - /// - /// Gets the territory this is located in. - /// - public ExcelResolver TerritoryType => new(this.Struct->TerritoryID); } diff --git a/Dalamud/Game/ClientState/Fates/FateState.cs b/Dalamud/Game/ClientState/Fates/FateState.cs index 8f2ef85cc..c7a789231 100644 --- a/Dalamud/Game/ClientState/Fates/FateState.cs +++ b/Dalamud/Game/ClientState/Fates/FateState.cs @@ -1,32 +1,33 @@ -namespace Dalamud.Game.ClientState.Fates; - -/// -/// This represents the state of a single Fate. -/// -public enum FateState : byte +namespace Dalamud.Game.ClientState.Fates { /// - /// The Fate is active. + /// This represents the state of a single Fate. /// - Running = 0x02, + public enum FateState : byte + { + /// + /// The Fate is active. + /// + Running = 0x02, - /// - /// The Fate has ended. - /// - Ended = 0x04, + /// + /// The Fate has ended. + /// + Ended = 0x04, - /// - /// The player failed the Fate. - /// - Failed = 0x05, + /// + /// The player failed the Fate. + /// + Failed = 0x05, - /// - /// The Fate is preparing to run. - /// - Preparation = 0x07, + /// + /// The Fate is preparing to run. + /// + Preparation = 0x07, - /// - /// The Fate is preparing to end. - /// - WaitingForEnd = 0x08, + /// + /// The Fate is preparing to end. + /// + WaitingForEnd = 0x08, + } } diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs index 96d8339fa..ff0361db9 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -6,142 +6,143 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Fates; - -/// -/// This collection represents the currently available Fate events. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed partial class FateTable +namespace Dalamud.Game.ClientState.Fates { - private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. + /// This collection represents the currently available Fate events. /// - /// Client state address resolver. - internal FateTable(ClientStateAddressResolver addressResolver) + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed partial class FateTable { - this.address = addressResolver; + private readonly ClientStateAddressResolver address; - Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}"); - } - - /// - /// Gets the address of the Fate table. - /// - public IntPtr Address => this.address.FateTablePtr; - - /// - /// Gets the amount of currently active Fates. - /// - public unsafe int Length - { - get + /// + /// Initializes a new instance of the class. + /// + /// Client state address resolver. + internal FateTable(ClientStateAddressResolver addressResolver) { - var fateTable = this.FateTableAddress; - if (fateTable == IntPtr.Zero) - return 0; + this.address = addressResolver; - // Sonar used this to check if the table was safe to read - var check = Struct->Unk80.ToInt64(); - if (check == 0) - return 0; - - var start = Struct->FirstFatePtr.ToInt64(); - var end = Struct->LastFatePtr.ToInt64(); - if (start == 0 || end == 0) - return 0; - - return (int)((end - start) / 8); + Log.Verbose($"Fate table address 0x{this.address.FateTablePtr.ToInt64():X}"); } - } - /// - /// Gets the address of the Fate table. - /// - internal unsafe IntPtr FateTableAddress - { - get + /// + /// Gets the address of the Fate table. + /// + public IntPtr Address => this.address.FateTablePtr; + + /// + /// Gets the amount of currently active Fates. + /// + public unsafe int Length { - if (this.address.FateTablePtr == IntPtr.Zero) + get + { + var fateTable = this.FateTableAddress; + if (fateTable == IntPtr.Zero) + return 0; + + // Sonar used this to check if the table was safe to read + var check = Struct->Unk80.ToInt64(); + if (check == 0) + return 0; + + var start = Struct->FirstFatePtr.ToInt64(); + var end = Struct->LastFatePtr.ToInt64(); + if (start == 0 || end == 0) + return 0; + + return (int)((end - start) / 8); + } + } + + /// + /// Gets the address of the Fate table. + /// + internal unsafe IntPtr FateTableAddress + { + get + { + if (this.address.FateTablePtr == IntPtr.Zero) + return IntPtr.Zero; + + return *(IntPtr*)this.address.FateTablePtr; + } + } + + private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress; + + /// + /// Get an actor at the specified spawn index. + /// + /// Spawn index. + /// A at the specified spawn index. + public Fate? this[int index] + { + get + { + var address = this.GetFateAddress(index); + return this.CreateFateReference(address); + } + } + + /// + /// Gets the address of the Fate at the specified index of the fate table. + /// + /// The index of the Fate. + /// The memory address of the Fate. + public unsafe IntPtr GetFateAddress(int index) + { + if (index >= this.Length) return IntPtr.Zero; - return *(IntPtr*)this.address.FateTablePtr; + var fateTable = this.FateTableAddress; + if (fateTable == IntPtr.Zero) + return IntPtr.Zero; + + var firstFate = this.Struct->FirstFatePtr; + return *(IntPtr*)(firstFate + (8 * index)); } - } - private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress; - - /// - /// Get an actor at the specified spawn index. - /// - /// Spawn index. - /// A at the specified spawn index. - public Fate? this[int index] - { - get + /// + /// Create a reference to a FFXIV actor. + /// + /// The offset of the actor in memory. + /// object containing requested data. + public Fate? CreateFateReference(IntPtr offset) { - var address = this.GetFateAddress(index); - return this.CreateFateReference(address); + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (offset == IntPtr.Zero) + return null; + + return new Fate(offset); } } /// - /// Gets the address of the Fate at the specified index of the fate table. + /// This collection represents the currently available Fate events. /// - /// The index of the Fate. - /// The memory address of the Fate. - public unsafe IntPtr GetFateAddress(int index) + public sealed partial class FateTable : IReadOnlyCollection { - if (index >= this.Length) - return IntPtr.Zero; + /// + int IReadOnlyCollection.Count => this.Length; - var fateTable = this.FateTableAddress; - if (fateTable == IntPtr.Zero) - return IntPtr.Zero; + /// + public IEnumerator GetEnumerator() + { + for (var i = 0; i < this.Length; i++) + { + yield return this[i]; + } + } - var firstFate = this.Struct->FirstFatePtr; - return *(IntPtr*)(firstFate + (8 * index)); - } - - /// - /// Create a reference to a FFXIV actor. - /// - /// The offset of the actor in memory. - /// object containing requested data. - public Fate? CreateFateReference(IntPtr offset) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (offset == IntPtr.Zero) - return null; - - return new Fate(offset); + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } } - -/// -/// This collection represents the currently available Fate events. -/// -public sealed partial class FateTable : IReadOnlyCollection -{ - /// - int IReadOnlyCollection.Count => this.Length; - - /// - public IEnumerator GetEnumerator() - { - for (var i = 0; i < this.Length; i++) - { - yield return this[i]; - } - } - - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); -} diff --git a/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs b/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs index a73f72857..7813803c8 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadButtons.cs @@ -1,95 +1,96 @@ using System; -namespace Dalamud.Game.ClientState.GamePad; - -/// -/// Bitmask of the Button ushort used by the game. -/// -[Flags] -public enum GamepadButtons : ushort +namespace Dalamud.Game.ClientState.GamePad { /// - /// No buttons pressed. + /// Bitmask of the Button ushort used by the game. /// - None = 0, + [Flags] + public enum GamepadButtons : ushort + { + /// + /// No buttons pressed. + /// + None = 0, - /// - /// Digipad up. - /// - DpadUp = 0x0001, + /// + /// Digipad up. + /// + DpadUp = 0x0001, - /// - /// Digipad down. - /// - DpadDown = 0x0002, + /// + /// Digipad down. + /// + DpadDown = 0x0002, - /// - /// Digipad left. - /// - DpadLeft = 0x0004, + /// + /// Digipad left. + /// + DpadLeft = 0x0004, - /// - /// Digipad right. - /// - DpadRight = 0x0008, + /// + /// Digipad right. + /// + DpadRight = 0x0008, - /// - /// North action button. Triangle on PS, Y on Xbox. - /// - North = 0x0010, + /// + /// North action button. Triangle on PS, Y on Xbox. + /// + North = 0x0010, - /// - /// South action button. Cross on PS, A on Xbox. - /// - South = 0x0020, + /// + /// South action button. Cross on PS, A on Xbox. + /// + South = 0x0020, - /// - /// West action button. Square on PS, X on Xbos. - /// - West = 0x0040, + /// + /// West action button. Square on PS, X on Xbos. + /// + West = 0x0040, - /// - /// East action button. Circle on PS, B on Xbox. - /// - East = 0x0080, + /// + /// East action button. Circle on PS, B on Xbox. + /// + East = 0x0080, - /// - /// First button on left shoulder side. - /// - L1 = 0x0100, + /// + /// First button on left shoulder side. + /// + L1 = 0x0100, - /// - /// Second button on left shoulder side. Analog input lost in this bitmask. - /// - L2 = 0x0200, + /// + /// Second button on left shoulder side. Analog input lost in this bitmask. + /// + L2 = 0x0200, - /// - /// Press on left analogue stick. - /// - L3 = 0x0400, + /// + /// Press on left analogue stick. + /// + L3 = 0x0400, - /// - /// First button on right shoulder. - /// - R1 = 0x0800, + /// + /// First button on right shoulder. + /// + R1 = 0x0800, - /// - /// Second button on right shoulder. Analog input lost in this bitmask. - /// - R2 = 0x1000, + /// + /// Second button on right shoulder. Analog input lost in this bitmask. + /// + R2 = 0x1000, - /// - /// Press on right analogue stick. - /// - R3 = 0x2000, + /// + /// Press on right analogue stick. + /// + R3 = 0x2000, - /// - /// Button on the right inner side of the controller. Options on PS, Start on Xbox. - /// - Start = 0x8000, + /// + /// Button on the right inner side of the controller. Options on PS, Start on Xbox. + /// + Start = 0x8000, - /// - /// Button on the left inner side of the controller. ??? on PS, Back on Xbox. - /// - Select = 0x4000, + /// + /// Button on the left inner side of the controller. ??? on PS, Back on Xbox. + /// + Select = 0x4000, + } } diff --git a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs index 32439cd08..d6d46a0cc 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadInput.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadInput.cs @@ -1,75 +1,76 @@ using System.Runtime.InteropServices; -namespace Dalamud.Game.ClientState.GamePad; - -/// -/// Struct which gets populated by polling the gamepads. -/// -/// Has an array of gamepads, among many other things (here not mapped). -/// All we really care about is the final data which the game uses to determine input. -/// -/// The size is definitely bigger than only the following fields but I do not know how big. -/// -[StructLayout(LayoutKind.Explicit)] -public struct GamepadInput +namespace Dalamud.Game.ClientState.GamePad { /// - /// Left analogue stick's horizontal value, -99 for left, 99 for right. + /// Struct which gets populated by polling the gamepads. + /// + /// Has an array of gamepads, among many other things (here not mapped). + /// All we really care about is the final data which the game uses to determine input. + /// + /// The size is definitely bigger than only the following fields but I do not know how big. /// - [FieldOffset(0x88)] - public int LeftStickX; + [StructLayout(LayoutKind.Explicit)] + public struct GamepadInput + { + /// + /// Left analogue stick's horizontal value, -99 for left, 99 for right. + /// + [FieldOffset(0x88)] + public int LeftStickX; - /// - /// Left analogue stick's vertical value, -99 for down, 99 for up. - /// - [FieldOffset(0x8C)] - public int LeftStickY; + /// + /// Left analogue stick's vertical value, -99 for down, 99 for up. + /// + [FieldOffset(0x8C)] + public int LeftStickY; - /// - /// Right analogue stick's horizontal value, -99 for left, 99 for right. - /// - [FieldOffset(0x90)] - public int RightStickX; + /// + /// Right analogue stick's horizontal value, -99 for left, 99 for right. + /// + [FieldOffset(0x90)] + public int RightStickX; - /// - /// Right analogue stick's vertical value, -99 for down, 99 for up. - /// - [FieldOffset(0x94)] - public int RightStickY; + /// + /// Right analogue stick's vertical value, -99 for down, 99 for up. + /// + [FieldOffset(0x94)] + public int RightStickY; - /// - /// Raw input, set the whole time while a button is held. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x98)] - public ushort ButtonsRaw; + /// + /// Raw input, set the whole time while a button is held. See for the mapping. + /// + /// + /// This is a bitfield. + /// + [FieldOffset(0x98)] + public ushort ButtonsRaw; - /// - /// Button pressed, set once when the button is pressed. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0x9C)] - public ushort ButtonsPressed; + /// + /// Button pressed, set once when the button is pressed. See for the mapping. + /// + /// + /// This is a bitfield. + /// + [FieldOffset(0x9C)] + public ushort ButtonsPressed; - /// - /// Button released input, set once right after the button is not hold anymore. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0xA0)] - public ushort ButtonsReleased; + /// + /// Button released input, set once right after the button is not hold anymore. See for the mapping. + /// + /// + /// This is a bitfield. + /// + [FieldOffset(0xA0)] + public ushort ButtonsReleased; - /// - /// Repeatedly emits the held button input in fixed intervals. See for the mapping. - /// - /// - /// This is a bitfield. - /// - [FieldOffset(0xA4)] - public ushort ButtonsRepeat; + /// + /// Repeatedly emits the held button input in fixed intervals. See for the mapping. + /// + /// + /// This is a bitfield. + /// + [FieldOffset(0xA4)] + public ushort ButtonsRepeat; + } } diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs index 7a316d5af..8759673b2 100644 --- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs +++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs @@ -4,257 +4,258 @@ using Dalamud.Hooking; using ImGuiNET; using Serilog; -namespace Dalamud.Game.ClientState.GamePad; - -/// -/// Exposes the game gamepad state to dalamud. -/// -/// Will block game's gamepad input if is set. -/// -public unsafe class GamepadState : IDisposable +namespace Dalamud.Game.ClientState.GamePad { - private readonly Hook gamepadPoll; - - private bool isDisposed; - - private int leftStickX; - private int leftStickY; - private int rightStickX; - private int rightStickY; - /// - /// Initializes a new instance of the class. + /// Exposes the game gamepad state to dalamud. + /// + /// Will block game's gamepad input if is set. /// - /// Resolver knowing the pointer to the GamepadPoll function. - public GamepadState(ClientStateAddressResolver resolver) + public unsafe class GamepadState : IDisposable { - Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}"); - this.gamepadPoll = new Hook(resolver.GamepadPoll, this.GamepadPollDetour); - } + private readonly Hook gamepadPoll; - /// - /// Finalizes an instance of the class. - /// - ~GamepadState() - { - this.Dispose(false); - } + private bool isDisposed; - private delegate int ControllerPoll(IntPtr controllerInput); + private int leftStickX; + private int leftStickY; + private int rightStickX; + private int rightStickY; - /// - /// Gets the pointer to the current instance of the GamepadInput struct. - /// - public IntPtr GamepadInputAddress { get; private set; } - - /// - /// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). - /// - public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0; - - /// - /// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). - /// - public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0; - - /// - /// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). - /// - public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0; - - /// - /// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). - /// - public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0; - - /// - /// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). - /// - public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0; - - /// - /// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). - /// - public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0; - - /// - /// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). - /// - public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0; - - /// - /// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). - /// - public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0; - - /// - /// Gets buttons pressed bitmask, set once when the button is pressed. See for the mapping. - /// - /// Exposed internally for Debug Data window. - /// - internal ushort ButtonsPressed { get; private set; } - - /// - /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping. - /// - /// Exposed internally for Debug Data window. - /// - internal ushort ButtonsRaw { get; private set; } - - /// - /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping. - /// - /// Exposed internally for Debug Data window. - /// - internal ushort ButtonsReleased { get; private set; } - - /// - /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping. - /// - /// Exposed internally for Debug Data window. - /// - internal ushort ButtonsRepeat { get; private set; } - - /// - /// Gets or sets a value indicating whether detour should block gamepad input for game. - /// - /// Ideally, we would use - /// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0 - /// but this has a race condition during load with the detour which sets up ImGui - /// and throws if our detour gets called before the other. - /// - internal bool NavEnableGamepad { get; set; } - - /// - /// Gets whether has been pressed. - /// - /// Only true on first frame of the press. - /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. - /// - /// The button to check for. - /// 1 if pressed, 0 otherwise. - public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0; - - /// - /// Gets whether is being pressed. - /// - /// True in intervals if button is held down. - /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. - /// - /// The button to check for. - /// 1 if still pressed during interval, 0 otherwise or in between intervals. - public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0; - - /// - /// Gets whether has been released. - /// - /// Only true the frame after release. - /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. - /// - /// The button to check for. - /// 1 if released, 0 otherwise. - public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0; - - /// - /// Gets the raw state of . - /// - /// Is set the entire time a button is pressed down. - /// - /// The button to check for. - /// 1 the whole time button is pressed, 0 otherwise. - public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0; - - /// - /// Enables the hook of the GamepadPoll function. - /// - public void Enable() - { - this.gamepadPoll.Enable(); - } - - /// - /// Disposes this instance, alongside its hooks. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - private int GamepadPollDetour(IntPtr gamepadInput) - { - var original = this.gamepadPoll.Original(gamepadInput); - try + /// + /// Initializes a new instance of the class. + /// + /// Resolver knowing the pointer to the GamepadPoll function. + public GamepadState(ClientStateAddressResolver resolver) { - this.GamepadInputAddress = gamepadInput; - var input = (GamepadInput*)gamepadInput; - this.leftStickX = input->LeftStickX; - this.leftStickY = input->LeftStickY; - this.rightStickX = input->RightStickX; - this.rightStickY = input->RightStickY; - this.ButtonsRaw = input->ButtonsRaw; - this.ButtonsPressed = input->ButtonsPressed; - this.ButtonsReleased = input->ButtonsReleased; - this.ButtonsRepeat = input->ButtonsRepeat; + Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}"); + this.gamepadPoll = new Hook(resolver.GamepadPoll, this.GamepadPollDetour); + } - if (this.NavEnableGamepad) + /// + /// Finalizes an instance of the class. + /// + ~GamepadState() + { + this.Dispose(false); + } + + private delegate int ControllerPoll(IntPtr controllerInput); + + /// + /// Gets the pointer to the current instance of the GamepadInput struct. + /// + public IntPtr GamepadInputAddress { get; private set; } + + /// + /// Gets the state of the left analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickLeft => this.leftStickX < 0 ? -this.leftStickX / 100f : 0; + + /// + /// Gets the state of the left analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickRight => this.leftStickX > 0 ? this.leftStickX / 100f : 0; + + /// + /// Gets the state of the left analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickUp => this.leftStickY > 0 ? this.leftStickY / 100f : 0; + + /// + /// Gets the state of the left analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). + /// + public float LeftStickDown => this.leftStickY < 0 ? -this.leftStickY / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the left direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickLeft => this.rightStickX < 0 ? -this.rightStickX / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the right direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickRight => this.rightStickX > 0 ? this.rightStickX / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the up direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickUp => this.rightStickY > 0 ? this.rightStickY / 100f : 0; + + /// + /// Gets the state of the right analogue stick in the down direction between 0 (not tilted) and 1 (max tilt). + /// + public float RightStickDown => this.rightStickY < 0 ? -this.rightStickY / 100f : 0; + + /// + /// Gets buttons pressed bitmask, set once when the button is pressed. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsPressed { get; private set; } + + /// + /// Gets raw button bitmask, set the whole time while a button is held. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsRaw { get; private set; } + + /// + /// Gets button released bitmask, set once right after the button is not hold anymore. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsReleased { get; private set; } + + /// + /// Gets button repeat bitmask, emits the held button input in fixed intervals. See for the mapping. + /// + /// Exposed internally for Debug Data window. + /// + internal ushort ButtonsRepeat { get; private set; } + + /// + /// Gets or sets a value indicating whether detour should block gamepad input for game. + /// + /// Ideally, we would use + /// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0 + /// but this has a race condition during load with the detour which sets up ImGui + /// and throws if our detour gets called before the other. + /// + internal bool NavEnableGamepad { get; set; } + + /// + /// Gets whether has been pressed. + /// + /// Only true on first frame of the press. + /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. + /// + /// The button to check for. + /// 1 if pressed, 0 otherwise. + public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0; + + /// + /// Gets whether is being pressed. + /// + /// True in intervals if button is held down. + /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. + /// + /// The button to check for. + /// 1 if still pressed during interval, 0 otherwise or in between intervals. + public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0; + + /// + /// Gets whether has been released. + /// + /// Only true the frame after release. + /// If ImGuiConfigFlags.NavEnableGamepad is set, this is unreliable. + /// + /// The button to check for. + /// 1 if released, 0 otherwise. + public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0; + + /// + /// Gets the raw state of . + /// + /// Is set the entire time a button is pressed down. + /// + /// The button to check for. + /// 1 the whole time button is pressed, 0 otherwise. + public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0; + + /// + /// Enables the hook of the GamepadPoll function. + /// + public void Enable() + { + this.gamepadPoll.Enable(); + } + + /// + /// Disposes this instance, alongside its hooks. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + private int GamepadPollDetour(IntPtr gamepadInput) + { + var original = this.gamepadPoll.Original(gamepadInput); + try { - input->LeftStickX = 0; - input->LeftStickY = 0; - input->RightStickX = 0; - input->RightStickY = 0; + this.GamepadInputAddress = gamepadInput; + var input = (GamepadInput*)gamepadInput; + this.leftStickX = input->LeftStickX; + this.leftStickY = input->LeftStickY; + this.rightStickX = input->RightStickX; + this.rightStickY = input->RightStickY; + this.ButtonsRaw = input->ButtonsRaw; + this.ButtonsPressed = input->ButtonsPressed; + this.ButtonsReleased = input->ButtonsReleased; + this.ButtonsRepeat = input->ButtonsRepeat; - // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` - // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). - // It does block, however, all input to the game. - // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2 - // and the digipad (in some situations, but thankfully not in menus) functional. - // We can either: - // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or - // (b) ignore it as so far it seems only a 'visual' error - // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input, - // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them - // because of the other blocked input) - // `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on - // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set. - // This is debatable. - // ImGui itself does not care either way as it uses the Raw values and does its own state handling. - const ushort deletionMask = (ushort)(~GamepadButtons.L2 - & ~GamepadButtons.R2 - & ~GamepadButtons.DpadDown - & ~GamepadButtons.DpadLeft - & ~GamepadButtons.DpadUp - & ~GamepadButtons.DpadRight); - input->ButtonsRaw &= deletionMask; - input->ButtonsPressed = 0; - input->ButtonsReleased = 0; - input->ButtonsRepeat = 0; - return 0; + if (this.NavEnableGamepad) + { + input->LeftStickX = 0; + input->LeftStickY = 0; + input->RightStickX = 0; + input->RightStickY = 0; + + // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` + // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). + // It does block, however, all input to the game. + // Leaving `ButtonsRaw` as it is and only zeroing the other leaves e.g. long-hold L2/R2 + // and the digipad (in some situations, but thankfully not in menus) functional. + // We can either: + // (a) Explicitly only set L2/R2/Digipad to 0 (and destroy their `ButtonPressed` field) => Needs to be documented, or + // (b) ignore it as so far it seems only a 'visual' error + // (L2/R2 being held down activates CrossHotBar but activating an ability is impossible because of the others blocked input, + // Digipad is ignored in menus but without any menu's one still switches target or party members, but cannot interact with them + // because of the other blocked input) + // `ButtonPressed` is pretty useful but its hella confusing to the user, so we do (a) and advise plugins do not rely on + // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set. + // This is debatable. + // ImGui itself does not care either way as it uses the Raw values and does its own state handling. + const ushort deletionMask = (ushort)(~GamepadButtons.L2 + & ~GamepadButtons.R2 + & ~GamepadButtons.DpadDown + & ~GamepadButtons.DpadLeft + & ~GamepadButtons.DpadUp + & ~GamepadButtons.DpadRight); + input->ButtonsRaw &= deletionMask; + input->ButtonsPressed = 0; + input->ButtonsReleased = 0; + input->ButtonsRepeat = 0; + return 0; + } + + // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the + // original, zero or do the work adjusting the bits. + return original; + } + catch (Exception e) + { + Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!"); + + // NOTE (Chiv) Explicitly deactivate on error + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; + return original; + } + } + + private void Dispose(bool disposing) + { + if (this.isDisposed) return; + if (disposing) + { + this.gamepadPoll?.Disable(); + this.gamepadPoll?.Dispose(); } - // NOTE (Chiv) Not so sure about the return value, does not seem to matter if we return the - // original, zero or do the work adjusting the bits. - return original; + this.isDisposed = true; } - catch (Exception e) - { - Log.Error(e, $"Gamepad Poll detour critical error! Gamepad navigation will not work!"); - - // NOTE (Chiv) Explicitly deactivate on error - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; - return original; - } - } - - private void Dispose(bool disposing) - { - if (this.isDisposed) return; - if (disposing) - { - this.gamepadPoll?.Disable(); - this.gamepadPoll?.Dispose(); - } - - this.isDisposed = true; } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/BOTDState.cs b/Dalamud/Game/ClientState/JobGauge/Enums/BOTDState.cs index 37bc167f2..0c32755fd 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/BOTDState.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/BOTDState.cs @@ -1,22 +1,23 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums; - -/// -/// DRG Blood of the Dragon state types. -/// -public enum BOTDState : byte +namespace Dalamud.Game.ClientState.JobGauge.Enums { /// - /// Inactive type. + /// DRG Blood of the Dragon state types. /// - NONE = 0, + public enum BOTDState : byte + { + /// + /// Inactive type. + /// + NONE = 0, - /// - /// Blood of the Dragon is active. - /// - BOTD = 1, + /// + /// Blood of the Dragon is active. + /// + BOTD = 1, - /// - /// Life of the Dragon is active. - /// - LOTD = 2, + /// + /// Life of the Dragon is active. + /// + LOTD = 2, + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs index 1b367a93e..02e064b12 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/CardType.cs @@ -1,52 +1,53 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums; - -/// -/// AST Arcanum (card) types. -/// -public enum CardType : byte +namespace Dalamud.Game.ClientState.JobGauge.Enums { /// - /// No card. + /// AST Arcanum (card) types. /// - NONE = 0, + public enum CardType : byte + { + /// + /// No card. + /// + NONE = 0, - /// - /// The Balance card. - /// - BALANCE = 1, + /// + /// The Balance card. + /// + BALANCE = 1, - /// - /// The Bole card. - /// - BOLE = 2, + /// + /// The Bole card. + /// + BOLE = 2, - /// - /// The Arrow card. - /// - ARROW = 3, + /// + /// The Arrow card. + /// + ARROW = 3, - /// - /// The Spear card. - /// - SPEAR = 4, + /// + /// The Spear card. + /// + SPEAR = 4, - /// - /// The Ewer card. - /// - EWER = 5, + /// + /// The Ewer card. + /// + EWER = 5, - /// - /// The Spire card. - /// - SPIRE = 6, + /// + /// The Spire card. + /// + SPIRE = 6, - /// - /// The Lord of Crowns card. - /// - LORD = 0x70, + /// + /// The Lord of Crowns card. + /// + LORD = 0x70, - /// - /// The Lady of Crowns card. - /// - LADY = 0x80, + /// + /// The Lady of Crowns card. + /// + LADY = 0x80, + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs index b674d11b8..9083aad8a 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/DismissedFairy.cs @@ -1,17 +1,18 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums; - -/// -/// SCH Dismissed fairy types. -/// -public enum DismissedFairy : byte +namespace Dalamud.Game.ClientState.JobGauge.Enums { /// - /// Dismissed fairy is Eos. + /// SCH Dismissed fairy types. /// - EOS = 6, + public enum DismissedFairy : byte + { + /// + /// Dismissed fairy is Eos. + /// + EOS = 6, - /// - /// Dismissed fairy is Selene. - /// - SELENE = 7, + /// + /// Dismissed fairy is Selene. + /// + SELENE = 7, + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs index 31ee6dac1..71449645f 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Mudras.cs @@ -1,22 +1,23 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums; - -/// -/// NIN Mudra types. -/// -public enum Mudras : byte +namespace Dalamud.Game.ClientState.JobGauge.Enums { /// - /// Ten mudra. + /// NIN Mudra types. /// - TEN = 1, + public enum Mudras : byte + { + /// + /// Ten mudra. + /// + TEN = 1, - /// - /// Chi mudra. - /// - CHI = 2, + /// + /// Chi mudra. + /// + CHI = 2, - /// - /// Jin mudra. - /// - JIN = 3, + /// + /// Jin mudra. + /// + JIN = 3, + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs index 768810b56..343cbfd8e 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/PetGlam.cs @@ -1,27 +1,28 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums; - -/// -/// SMN summoned pet glam types. -/// -public enum PetGlam : byte +namespace Dalamud.Game.ClientState.JobGauge.Enums { /// - /// No pet glam. + /// SMN summoned pet glam types. /// - NONE = 0, + public enum PetGlam : byte + { + /// + /// No pet glam. + /// + NONE = 0, - /// - /// Emerald carbuncle pet glam. - /// - EMERALD = 1, + /// + /// Emerald carbuncle pet glam. + /// + EMERALD = 1, - /// - /// Topaz carbuncle pet glam. - /// - TOPAZ = 2, + /// + /// Topaz carbuncle pet glam. + /// + TOPAZ = 2, - /// - /// Ruby carbuncle pet glam. - /// - RUBY = 3, + /// + /// Ruby carbuncle pet glam. + /// + RUBY = 3, + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs index eacef91fc..04440dcdc 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SealType.cs @@ -1,27 +1,28 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums; - -/// -/// AST Divination seal types. -/// -public enum SealType : byte +namespace Dalamud.Game.ClientState.JobGauge.Enums { /// - /// No seal. + /// AST Divination seal types. /// - NONE = 0, + public enum SealType : byte + { + /// + /// No seal. + /// + NONE = 0, - /// - /// Sun seal. - /// - SUN = 1, + /// + /// Sun seal. + /// + SUN = 1, - /// - /// Moon seal. - /// - MOON = 2, + /// + /// Moon seal. + /// + MOON = 2, - /// - /// Celestial seal. - /// - CELESTIAL = 3, + /// + /// Celestial seal. + /// + CELESTIAL = 3, + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs index bdd98b750..de9e01fc4 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Sen.cs @@ -1,30 +1,31 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Enums; - -/// -/// Samurai Sen types. -/// -[Flags] -public enum Sen : byte +namespace Dalamud.Game.ClientState.JobGauge.Enums { /// - /// No Sen. + /// Samurai Sen types. /// - NONE = 0, + [Flags] + public enum Sen : byte + { + /// + /// No Sen. + /// + NONE = 0, - /// - /// Setsu Sen type. - /// - SETSU = 1 << 0, + /// + /// Setsu Sen type. + /// + SETSU = 1 << 0, - /// - /// Getsu Sen type. - /// - GETSU = 1 << 1, + /// + /// Getsu Sen type. + /// + GETSU = 1 << 1, - /// - /// Ka Sen type. - /// - KA = 1 << 2, + /// + /// Ka Sen type. + /// + KA = 1 << 2, + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs index ce144741f..25ecf66cc 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/Song.cs @@ -1,27 +1,28 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums; - -/// -/// BRD Song types. -/// -public enum Song : byte +namespace Dalamud.Game.ClientState.JobGauge.Enums { /// - /// No song is active type. + /// BRD Song types. /// - NONE = 0, + public enum Song : byte + { + /// + /// No song is active type. + /// + NONE = 0, - /// - /// Mage's Ballad type. - /// - MAGE = 5, + /// + /// Mage's Ballad type. + /// + MAGE = 5, - /// - /// Army's Paeon type. - /// - ARMY = 10, + /// + /// Army's Paeon type. + /// + ARMY = 10, - /// - /// The Wanderer's Minuet type. - /// - WANDERER = 15, + /// + /// The Wanderer's Minuet type. + /// + WANDERER = 15, + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs index e72521043..d0b1e933f 100644 --- a/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs +++ b/Dalamud/Game/ClientState/JobGauge/Enums/SummonPet.cs @@ -1,27 +1,28 @@ -namespace Dalamud.Game.ClientState.JobGauge.Enums; - -/// -/// SMN summoned pet types. -/// -public enum SummonPet : byte +namespace Dalamud.Game.ClientState.JobGauge.Enums { /// - /// No pet. + /// SMN summoned pet types. /// - NONE = 0, + public enum SummonPet : byte + { + /// + /// No pet. + /// + NONE = 0, - /// - /// The summoned pet Ifrit. - /// - IFRIT = 3, + /// + /// The summoned pet Ifrit. + /// + IFRIT = 3, - /// - /// The summoned pet Titan. - /// - TITAN = 4, + /// + /// The summoned pet Titan. + /// + TITAN = 4, - /// - /// The summoned pet Garuda. - /// - GARUDA = 5, + /// + /// The summoned pet Garuda. + /// + GARUDA = 5, + } } diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index 9fbbd51ed..958f78b1b 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -7,47 +7,48 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.JobGauge; - -/// -/// This class converts in-memory Job gauge data to structs. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public class JobGauges +namespace Dalamud.Game.ClientState.JobGauge { - private Dictionary cache = new(); - /// - /// Initializes a new instance of the class. + /// This class converts in-memory Job gauge data to structs. /// - /// Address resolver with the JobGauge memory location(s). - public JobGauges(ClientStateAddressResolver addressResolver) + [PluginInterface] + [InterfaceVersion("1.0")] + public class JobGauges { - this.Address = addressResolver.JobGaugeData; + private Dictionary cache = new(); - Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}"); - } - - /// - /// Gets the address of the JobGauge data. - /// - public IntPtr Address { get; } - - /// - /// Get the JobGauge for a given job. - /// - /// A JobGauge struct from ClientState.Structs.JobGauge. - /// A JobGauge. - public T Get() where T : JobGaugeBase - { - // This is cached to mitigate the effects of using activator for instantiation. - // Since the gauge itself reads from live memory, there isn't much downside to doing this. - if (!this.cache.TryGetValue(typeof(T), out var gauge)) + /// + /// Initializes a new instance of the class. + /// + /// Address resolver with the JobGauge memory location(s). + public JobGauges(ClientStateAddressResolver addressResolver) { - gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null); + this.Address = addressResolver.JobGaugeData; + + Log.Verbose($"JobGaugeData address 0x{this.Address.ToInt64():X}"); } - return (T)gauge; + /// + /// Gets the address of the JobGauge data. + /// + public IntPtr Address { get; } + + /// + /// Get the JobGauge for a given job. + /// + /// A JobGauge struct from ClientState.Structs.JobGauge. + /// A JobGauge. + public T Get() where T : JobGaugeBase + { + // This is cached to mitigate the effects of using activator for instantiation. + // Since the gauge itself reads from live memory, there isn't much downside to doing this. + if (!this.cache.TryGetValue(typeof(T), out var gauge)) + { + gauge = this.cache[typeof(T)] = (T)Activator.CreateInstance(typeof(T), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { this.Address }, null); + } + + return (T)gauge; + } } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs index b1fd996b8..45175344f 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/ASTGauge.cs @@ -2,38 +2,39 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory AST job gauge. -/// -public unsafe class ASTGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory AST job gauge. /// - /// Address of the job gauge. - internal ASTGauge(IntPtr address) - : base(address) + public unsafe class ASTGauge : JobGaugeBase { - } + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal ASTGauge(IntPtr address) + : base(address) + { + } - /// - /// Gets the currently drawn . - /// - /// Currently drawn . - public CardType DrawnCard => (CardType)this.Struct->Card; + /// + /// Gets the currently drawn . + /// + /// Currently drawn . + public CardType DrawnCard => (CardType)this.Struct->Card; - /// - /// Check if a is currently active on the divination gauge. - /// - /// The to check for. - /// If the given Seal is currently divined. - public unsafe bool ContainsSeal(SealType seal) - { - if (this.Struct->Seals[0] == (byte)seal) return true; - if (this.Struct->Seals[1] == (byte)seal) return true; - if (this.Struct->Seals[2] == (byte)seal) return true; - return false; + /// + /// Check if a is currently active on the divination gauge. + /// + /// The to check for. + /// If the given Seal is currently divined. + public unsafe bool ContainsSeal(SealType seal) + { + if (this.Struct->Seals[0] == (byte)seal) return true; + if (this.Struct->Seals[1] == (byte)seal) return true; + if (this.Struct->Seals[2] == (byte)seal) return true; + return false; + } } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs index fef699e2b..0e5516b7a 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BLMGauge.cs @@ -1,66 +1,67 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory BLM job gauge. -/// -public unsafe class BLMGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory BLM job gauge. /// - /// Address of the job gauge. - internal BLMGauge(IntPtr address) - : base(address) + public unsafe class BLMGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal BLMGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the time remaining for the Enochian time in milliseconds. + /// + public short EnochianTimer => this.Struct->EnochianTimer; + + /// + /// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds. + /// + public short ElementTimeRemaining => this.Struct->ElementTimeRemaining; + + /// + /// Gets the number of Polyglot stacks remaining. + /// + public byte PolyglotStacks => this.Struct->PolyglotStacks; + + /// + /// Gets the number of Umbral Hearts remaining. + /// + public byte UmbralHearts => this.Struct->UmbralHearts; + + /// + /// Gets the amount of Umbral Ice stacks. + /// + public byte UmbralIceStacks => (byte)(this.InUmbralIce ? -this.Struct->ElementStance : 0); + + /// + /// Gets the amount of Astral Fire stacks. + /// + public byte AstralFireStacks => (byte)(this.InAstralFire ? this.Struct->ElementStance : 0); + + /// + /// Gets a value indicating whether if the player is in Umbral Ice. + /// + /// true or false. + public bool InUmbralIce => this.Struct->ElementStance < 0; + + /// + /// Gets a value indicating whether if the player is in Astral fire. + /// + /// true or false. + public bool InAstralFire => this.Struct->ElementStance > 0; + + /// + /// Gets a value indicating whether if Enochian is active. + /// + /// true or false. + public bool IsEnochianActive => this.Struct->Enochian != 0; } - - /// - /// Gets the time remaining for the Enochian time in milliseconds. - /// - public short EnochianTimer => this.Struct->EnochianTimer; - - /// - /// Gets the time remaining for Astral Fire or Umbral Ice in milliseconds. - /// - public short ElementTimeRemaining => this.Struct->ElementTimeRemaining; - - /// - /// Gets the number of Polyglot stacks remaining. - /// - public byte PolyglotStacks => this.Struct->PolyglotStacks; - - /// - /// Gets the number of Umbral Hearts remaining. - /// - public byte UmbralHearts => this.Struct->UmbralHearts; - - /// - /// Gets the amount of Umbral Ice stacks. - /// - public byte UmbralIceStacks => (byte)(this.InUmbralIce ? -this.Struct->ElementStance : 0); - - /// - /// Gets the amount of Astral Fire stacks. - /// - public byte AstralFireStacks => (byte)(this.InAstralFire ? this.Struct->ElementStance : 0); - - /// - /// Gets a value indicating whether if the player is in Umbral Ice. - /// - /// true or false. - public bool InUmbralIce => this.Struct->ElementStance < 0; - - /// - /// Gets a value indicating whether if the player is in Astral fire. - /// - /// true or false. - public bool InAstralFire => this.Struct->ElementStance > 0; - - /// - /// Gets a value indicating whether if Enochian is active. - /// - /// true or false. - public bool IsEnochianActive => this.Struct->Enochian != 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs index d361bfd4d..ed814d055 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/BRDGauge.cs @@ -2,39 +2,40 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory BRD job gauge. -/// -public unsafe class BRDGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory BRD job gauge. /// - /// Address of the job gauge. - internal BRDGauge(IntPtr address) - : base(address) + public unsafe class BRDGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal BRDGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the current song timer in milliseconds. + /// + public short SongTimer => this.Struct->SongTimer; + + /// + /// Gets the amount of Repertoire accumulated. + /// + public byte Repertoire => this.Struct->Repertoire; + + /// + /// Gets the amount of Soul Voice accumulated. + /// + public byte SoulVoice => this.Struct->SoulVoice; + + /// + /// Gets the type of song that is active. + /// + public Song Song => (Song)this.Struct->Song; } - - /// - /// Gets the current song timer in milliseconds. - /// - public short SongTimer => this.Struct->SongTimer; - - /// - /// Gets the amount of Repertoire accumulated. - /// - public byte Repertoire => this.Struct->Repertoire; - - /// - /// Gets the amount of Soul Voice accumulated. - /// - public byte SoulVoice => this.Struct->SoulVoice; - - /// - /// Gets the type of song that is active. - /// - public Song Song => (Song)this.Struct->Song; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs index 455c5bca0..7ae2b7bca 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/DNCGauge.cs @@ -1,59 +1,60 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory DNC job gauge. -/// -public unsafe class DNCGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory DNC job gauge. /// - /// Address of the job gauge. - internal DNCGauge(IntPtr address) - : base(address) + public unsafe class DNCGauge : JobGaugeBase { - } - - /// - /// Gets the number of feathers available. - /// - public byte Feathers => this.Struct->Feathers; - - /// - /// Gets the amount of Espirit available. - /// - public byte Esprit => this.Struct->Esprit; - - /// - /// Gets the number of steps completed for the current dance. - /// - public byte CompletedSteps => this.Struct->StepIndex; - - /// - /// Gets all the steps in the current dance. - /// - public unsafe uint[] Steps - { - get + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal DNCGauge(IntPtr address) + : base(address) { - var arr = new uint[4]; - for (var i = 0; i < 4; i++) - arr[i] = this.Struct->DanceSteps[i] + 15999u - 1; - return arr; } + + /// + /// Gets the number of feathers available. + /// + public byte Feathers => this.Struct->Feathers; + + /// + /// Gets the amount of Espirit available. + /// + public byte Esprit => this.Struct->Esprit; + + /// + /// Gets the number of steps completed for the current dance. + /// + public byte CompletedSteps => this.Struct->StepIndex; + + /// + /// Gets all the steps in the current dance. + /// + public unsafe uint[] Steps + { + get + { + var arr = new uint[4]; + for (var i = 0; i < 4; i++) + arr[i] = this.Struct->DanceSteps[i] + 15999u - 1; + return arr; + } + } + + /// + /// Gets the next step in the current dance. + /// + /// The next dance step action ID. + public uint NextStep => 15999u + this.Struct->DanceSteps[this.Struct->StepIndex] - 1; + + /// + /// Gets a value indicating whether the player is dancing or not. + /// + /// true or false. + public bool IsDancing => this.Struct->DanceSteps[0] != 0; } - - /// - /// Gets the next step in the current dance. - /// - /// The next dance step action ID. - public uint NextStep => 15999u + this.Struct->DanceSteps[this.Struct->StepIndex] - 1; - - /// - /// Gets a value indicating whether the player is dancing or not. - /// - /// true or false. - public bool IsDancing => this.Struct->DanceSteps[0] != 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs index 20bd0c488..dd89b069a 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/DRGGauge.cs @@ -2,34 +2,35 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory DRG job gauge. -/// -public unsafe class DRGGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory DRG job gauge. /// - /// Address of the job gauge. - internal DRGGauge(IntPtr address) - : base(address) + public unsafe class DRGGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal DRGGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the time remaining for Blood of the Dragon in milliseconds. + /// + public short BOTDTimer => this.Struct->BotdTimer; + + /// + /// Gets the current state of Blood of the Dragon. + /// + public BOTDState BOTDState => (BOTDState)this.Struct->BotdState; + + /// + /// Gets the count of eyes opened during Blood of the Dragon. + /// + public byte EyeCount => this.Struct->EyeCount; } - - /// - /// Gets the time remaining for Blood of the Dragon in milliseconds. - /// - public short BOTDTimer => this.Struct->BotdTimer; - - /// - /// Gets the current state of Blood of the Dragon. - /// - public BOTDState BOTDState => (BOTDState)this.Struct->BotdState; - - /// - /// Gets the count of eyes opened during Blood of the Dragon. - /// - public byte EyeCount => this.Struct->EyeCount; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs index 25a245ac6..d903c0f68 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/DRKGauge.cs @@ -1,39 +1,40 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory DRK job gauge. -/// -public unsafe class DRKGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory DRK job gauge. /// - /// Address of the job gauge. - internal DRKGauge(IntPtr address) - : base(address) + public unsafe class DRKGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal DRKGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the amount of blood accumulated. + /// + public byte Blood => this.Struct->Blood; + + /// + /// Gets the Darkside time remaining in milliseconds. + /// + public ushort DarksideTimeRemaining => this.Struct->DarksideTimer; + + /// + /// Gets the Shadow time remaining in milliseconds. + /// + public ushort ShadowTimeRemaining => this.Struct->ShadowTimer; + + /// + /// Gets a value indicating whether the player has Dark Arts or not. + /// + /// true or false. + public bool HasDarkArts => this.Struct->DarkArtsState > 0; } - - /// - /// Gets the amount of blood accumulated. - /// - public byte Blood => this.Struct->Blood; - - /// - /// Gets the Darkside time remaining in milliseconds. - /// - public ushort DarksideTimeRemaining => this.Struct->DarksideTimer; - - /// - /// Gets the Shadow time remaining in milliseconds. - /// - public ushort ShadowTimeRemaining => this.Struct->ShadowTimer; - - /// - /// Gets a value indicating whether the player has Dark Arts or not. - /// - /// true or false. - public bool HasDarkArts => this.Struct->DarkArtsState > 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs index 4acc9a712..dc934d8f4 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/GNBGauge.cs @@ -1,33 +1,34 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory GNB job gauge. -/// -public unsafe class GNBGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory GNB job gauge. /// - /// Address of the job gauge. - internal GNBGauge(IntPtr address) - : base(address) + public unsafe class GNBGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal GNBGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the amount of ammo available. + /// + public byte Ammo => this.Struct->Ammo; + + /// + /// Gets the max combo time of the Gnashing Fang combo. + /// + public short MaxTimerDuration => this.Struct->MaxTimerDuration; + + /// + /// Gets the current step of the Gnashing Fang combo. + /// + public byte AmmoComboStep => this.Struct->AmmoComboStep; } - - /// - /// Gets the amount of ammo available. - /// - public byte Ammo => this.Struct->Ammo; - - /// - /// Gets the max combo time of the Gnashing Fang combo. - /// - public short MaxTimerDuration => this.Struct->MaxTimerDuration; - - /// - /// Gets the current step of the Gnashing Fang combo. - /// - public byte AmmoComboStep => this.Struct->AmmoComboStep; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase.cs b/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase.cs index 09280cf7c..0160a9a87 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase.cs @@ -1,23 +1,24 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// Base job gauge class. -/// -public abstract unsafe class JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// Base job gauge class. /// - /// Address of the job gauge. - internal JobGaugeBase(IntPtr address) + public abstract unsafe class JobGaugeBase { - this.Address = address; - } + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal JobGaugeBase(IntPtr address) + { + this.Address = address; + } - /// - /// Gets the address of this job gauge in memory. - /// - public IntPtr Address { get; } + /// + /// Gets the address of this job gauge in memory. + /// + public IntPtr Address { get; } + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase{T}.cs b/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase{T}.cs index dbe107d2c..0dc494d8b 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase{T}.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/JobGaugeBase{T}.cs @@ -1,24 +1,25 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// Base job gauge class. -/// -/// The underlying FFXIVClientStructs type. -public unsafe class JobGaugeBase : JobGaugeBase where T : unmanaged +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// Base job gauge class. /// - /// Address of the job gauge. - internal JobGaugeBase(IntPtr address) - : base(address) + /// The underlying FFXIVClientStructs type. + public unsafe class JobGaugeBase : JobGaugeBase where T : unmanaged { - } + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal JobGaugeBase(IntPtr address) + : base(address) + { + } - /// - /// Gets an unsafe struct pointer of this job gauge. - /// - private protected T* Struct => (T*)this.Address; + /// + /// Gets an unsafe struct pointer of this job gauge. + /// + private protected T* Struct => (T*)this.Address; + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/MCHGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/MCHGauge.cs index 76e3b00c4..1971137a5 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/MCHGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/MCHGauge.cs @@ -1,55 +1,56 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory MCH job gauge. -/// -public unsafe class MCHGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory MCH job gauge. /// - /// Address of the job gauge. - internal MCHGauge(IntPtr address) - : base(address) + public unsafe class MCHGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal MCHGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the time time remaining for Overheat in milliseconds. + /// + public short OverheatTimeRemaining => this.Struct->OverheatTimeRemaining; + + /// + /// Gets the time remaining for the Rook or Queen in milliseconds. + /// + public short SummonTimeRemaining => this.Struct->SummonTimeRemaining; + + /// + /// Gets the current Heat level. + /// + public byte Heat => this.Struct->Heat; + + /// + /// Gets the current Battery level. + /// + public byte Battery => this.Struct->Battery; + + /// + /// Gets the battery level of the last summon (robot). + /// + public byte LastSummonBatteryPower => this.Struct->LastSummonBatteryPower; + + /// + /// Gets a value indicating whether the player is currently Overheated. + /// + /// true or false. + public bool IsOverheated => (this.Struct->TimerActive & 1) != 0; + + /// + /// Gets a value indicating whether the player has an active Robot. + /// + /// true or false. + public bool IsRobotActive => (this.Struct->TimerActive & 2) != 0; } - - /// - /// Gets the time time remaining for Overheat in milliseconds. - /// - public short OverheatTimeRemaining => this.Struct->OverheatTimeRemaining; - - /// - /// Gets the time remaining for the Rook or Queen in milliseconds. - /// - public short SummonTimeRemaining => this.Struct->SummonTimeRemaining; - - /// - /// Gets the current Heat level. - /// - public byte Heat => this.Struct->Heat; - - /// - /// Gets the current Battery level. - /// - public byte Battery => this.Struct->Battery; - - /// - /// Gets the battery level of the last summon (robot). - /// - public byte LastSummonBatteryPower => this.Struct->LastSummonBatteryPower; - - /// - /// Gets a value indicating whether the player is currently Overheated. - /// - /// true or false. - public bool IsOverheated => (this.Struct->TimerActive & 1) != 0; - - /// - /// Gets a value indicating whether the player has an active Robot. - /// - /// true or false. - public bool IsRobotActive => (this.Struct->TimerActive & 2) != 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs index 1a84afb74..8de421541 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/MNKGauge.cs @@ -1,23 +1,24 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory MNK job gauge. -/// -public unsafe class MNKGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory MNK job gauge. /// - /// Address of the job gauge. - internal MNKGauge(IntPtr address) - : base(address) + public unsafe class MNKGauge : JobGaugeBase { - } + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal MNKGauge(IntPtr address) + : base(address) + { + } - /// - /// Gets the number of Chakra available. - /// - public byte Chakra => this.Struct->Chakra; + /// + /// Gets the number of Chakra available. + /// + public byte Chakra => this.Struct->Chakra; + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs index 4e8dc7b65..40bf64d4a 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/NINGauge.cs @@ -1,33 +1,34 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory NIN job gauge. -/// -public unsafe class NINGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory NIN job gauge. /// - /// The address of the gauge. - internal NINGauge(IntPtr address) - : base(address) + public unsafe class NINGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// The address of the gauge. + internal NINGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the time left on Huton in milliseconds. + /// + public int HutonTimer => this.Struct->HutonTimer; + + /// + /// Gets the amount of Ninki available. + /// + public byte Ninki => this.Struct->Ninki; + + /// + /// Gets the number of times Huton has been cast manually. + /// + public byte HutonManualCasts => this.Struct->HutonManualCasts; } - - /// - /// Gets the time left on Huton in milliseconds. - /// - public int HutonTimer => this.Struct->HutonTimer; - - /// - /// Gets the amount of Ninki available. - /// - public byte Ninki => this.Struct->Ninki; - - /// - /// Gets the number of times Huton has been cast manually. - /// - public byte HutonManualCasts => this.Struct->HutonManualCasts; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/PLDGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/PLDGauge.cs index 8998200c2..d610515e1 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/PLDGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/PLDGauge.cs @@ -1,23 +1,24 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory PLD job gauge. -/// -public unsafe class PLDGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory PLD job gauge. /// - /// Address of the job gauge. - internal PLDGauge(IntPtr address) - : base(address) + public unsafe class PLDGauge : JobGaugeBase { - } + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal PLDGauge(IntPtr address) + : base(address) + { + } - /// - /// Gets the current level of the Oath gauge. - /// - public byte OathGauge => this.Struct->OathGauge; + /// + /// Gets the current level of the Oath gauge. + /// + public byte OathGauge => this.Struct->OathGauge; + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs index 43171a3d7..63c8b6a20 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/RDMGauge.cs @@ -1,28 +1,29 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory RDM job gauge. -/// -public unsafe class RDMGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory RDM job gauge. /// - /// Address of the job gauge. - internal RDMGauge(IntPtr address) - : base(address) + public unsafe class RDMGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal RDMGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the level of the White gauge. + /// + public byte WhiteMana => this.Struct->WhiteMana; + + /// + /// Gets the level of the Black gauge. + /// + public byte BlackMana => this.Struct->BlackMana; } - - /// - /// Gets the level of the White gauge. - /// - public byte WhiteMana => this.Struct->WhiteMana; - - /// - /// Gets the level of the Black gauge. - /// - public byte BlackMana => this.Struct->BlackMana; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs index a006086f4..64559e4e3 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SAMGauge.cs @@ -2,52 +2,53 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory SAM job gauge. -/// -public unsafe class SAMGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory SAM job gauge. /// - /// Address of the job gauge. - internal SAMGauge(IntPtr address) - : base(address) + public unsafe class SAMGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal SAMGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the current amount of Kenki available. + /// + public byte Kenki => this.Struct->Kenki; + + /// + /// Gets the amount of Meditation stacks. + /// + public byte MeditationStacks => this.Struct->MeditationStacks; + + /// + /// Gets the active Sen. + /// + public Sen Sen => (Sen)this.Struct->SenFlags; + + /// + /// Gets a value indicating whether the Setsu Sen is active. + /// + /// true or false. + public bool HasSetsu => (this.Sen & Sen.SETSU) != 0; + + /// + /// Gets a value indicating whether the Getsu Sen is active. + /// + /// true or false. + public bool HasGetsu => (this.Sen & Sen.GETSU) != 0; + + /// + /// Gets a value indicating whether the Ka Sen is active. + /// + /// true or false. + public bool HasKa => (this.Sen & Sen.KA) != 0; } - - /// - /// Gets the current amount of Kenki available. - /// - public byte Kenki => this.Struct->Kenki; - - /// - /// Gets the amount of Meditation stacks. - /// - public byte MeditationStacks => this.Struct->MeditationStacks; - - /// - /// Gets the active Sen. - /// - public Sen Sen => (Sen)this.Struct->SenFlags; - - /// - /// Gets a value indicating whether the Setsu Sen is active. - /// - /// true or false. - public bool HasSetsu => (this.Sen & Sen.SETSU) != 0; - - /// - /// Gets a value indicating whether the Getsu Sen is active. - /// - /// true or false. - public bool HasGetsu => (this.Sen & Sen.GETSU) != 0; - - /// - /// Gets a value indicating whether the Ka Sen is active. - /// - /// true or false. - public bool HasKa => (this.Sen & Sen.KA) != 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SCHGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SCHGauge.cs index d3f5669fa..f72e7047b 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SCHGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SCHGauge.cs @@ -2,39 +2,40 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory SCH job gauge. -/// -public unsafe class SCHGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory SCH job gauge. /// - /// Address of the job gauge. - internal SCHGauge(IntPtr address) - : base(address) + public unsafe class SCHGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal SCHGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the amount of Aetherflow stacks available. + /// + public byte Aetherflow => this.Struct->Aetherflow; + + /// + /// Gets the current level of the Fairy Gauge. + /// + public byte FairyGauge => this.Struct->FairyGauge; + + /// + /// Gets the Seraph time remaiSCHg in milliseconds. + /// + public short SeraphTimer => this.Struct->SeraphTimer; + + /// + /// Gets the last dismissed fairy. + /// + public DismissedFairy DismissedFairy => (DismissedFairy)this.Struct->DismissedFairy; } - - /// - /// Gets the amount of Aetherflow stacks available. - /// - public byte Aetherflow => this.Struct->Aetherflow; - - /// - /// Gets the current level of the Fairy Gauge. - /// - public byte FairyGauge => this.Struct->FairyGauge; - - /// - /// Gets the Seraph time remaiSCHg in milliseconds. - /// - public short SeraphTimer => this.Struct->SeraphTimer; - - /// - /// Gets the last dismissed fairy. - /// - public DismissedFairy DismissedFairy => (DismissedFairy)this.Struct->DismissedFairy; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs index f0f8b33dc..5074f0bc9 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/SMNGauge.cs @@ -2,58 +2,59 @@ using System; using Dalamud.Game.ClientState.JobGauge.Enums; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory SMN job gauge. -/// -public unsafe class SMNGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory SMN job gauge. /// - /// Address of the job gauge. - internal SMNGauge(IntPtr address) - : base(address) + public unsafe class SMNGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal SMNGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the time remaining for the current summon. + /// + public short TimerRemaining => this.Struct->TimerRemaining; + + /// + /// Gets the summon that will return after the current summon expires. + /// + public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon; + + /// + /// Gets the summon glam for the . + /// + public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam; + + /// + /// Gets the current aether flags. + /// Use the summon accessors instead. + /// + public byte AetherFlags => this.Struct->AetherFlags; + + /// + /// Gets a value indicating whether if Phoenix is ready to be summoned. + /// + /// true or false. + public bool IsPhoenixReady => (this.AetherFlags & 0x10) > 0; + + /// + /// Gets a value indicating whether Bahamut is ready to be summoned. + /// + /// true or false. + public bool IsBahamutReady => (this.AetherFlags & 8) > 0; + + /// + /// Gets a value indicating whether there are any Aetherflow stacks available. + /// + /// true or false. + public bool HasAetherflowStacks => (this.AetherFlags & 3) > 0; } - - /// - /// Gets the time remaining for the current summon. - /// - public short TimerRemaining => this.Struct->TimerRemaining; - - /// - /// Gets the summon that will return after the current summon expires. - /// - public SummonPet ReturnSummon => (SummonPet)this.Struct->ReturnSummon; - - /// - /// Gets the summon glam for the . - /// - public PetGlam ReturnSummonGlam => (PetGlam)this.Struct->ReturnSummonGlam; - - /// - /// Gets the current aether flags. - /// Use the summon accessors instead. - /// - public byte AetherFlags => this.Struct->AetherFlags; - - /// - /// Gets a value indicating whether if Phoenix is ready to be summoned. - /// - /// true or false. - public bool IsPhoenixReady => (this.AetherFlags & 0x10) > 0; - - /// - /// Gets a value indicating whether Bahamut is ready to be summoned. - /// - /// true or false. - public bool IsBahamutReady => (this.AetherFlags & 8) > 0; - - /// - /// Gets a value indicating whether there are any Aetherflow stacks available. - /// - /// true or false. - public bool HasAetherflowStacks => (this.AetherFlags & 3) > 0; } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/WARGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/WARGauge.cs index 2a50e9d24..484ac83a8 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/WARGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/WARGauge.cs @@ -1,23 +1,24 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory WAR job gauge. -/// -public unsafe class WARGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory WAR job gauge. /// - /// Address of the job gauge. - internal WARGauge(IntPtr address) - : base(address) + public unsafe class WARGauge : JobGaugeBase { - } + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal WARGauge(IntPtr address) + : base(address) + { + } - /// - /// Gets the amount of wrath in the Beast gauge. - /// - public byte BeastGauge => this.Struct->BeastGauge; + /// + /// Gets the amount of wrath in the Beast gauge. + /// + public byte BeastGauge => this.Struct->BeastGauge; + } } diff --git a/Dalamud/Game/ClientState/JobGauge/Types/WHMGauge.cs b/Dalamud/Game/ClientState/JobGauge/Types/WHMGauge.cs index afe19f59d..f3933a5aa 100644 --- a/Dalamud/Game/ClientState/JobGauge/Types/WHMGauge.cs +++ b/Dalamud/Game/ClientState/JobGauge/Types/WHMGauge.cs @@ -1,33 +1,34 @@ using System; -namespace Dalamud.Game.ClientState.JobGauge.Types; - -/// -/// In-memory WHM job gauge. -/// -public unsafe class WHMGauge : JobGaugeBase +namespace Dalamud.Game.ClientState.JobGauge.Types { /// - /// Initializes a new instance of the class. + /// In-memory WHM job gauge. /// - /// Address of the job gauge. - internal WHMGauge(IntPtr address) - : base(address) + public unsafe class WHMGauge : JobGaugeBase { + /// + /// Initializes a new instance of the class. + /// + /// Address of the job gauge. + internal WHMGauge(IntPtr address) + : base(address) + { + } + + /// + /// Gets the time to next lily in milliseconds. + /// + public short LilyTimer => this.Struct->LilyTimer; + + /// + /// Gets the number of Lilies. + /// + public byte Lily => this.Struct->Lily; + + /// + /// Gets the number of times the blood lily has been nourished. + /// + public byte BloodLily => this.Struct->BloodLily; } - - /// - /// Gets the time to next lily in milliseconds. - /// - public short LilyTimer => this.Struct->LilyTimer; - - /// - /// Gets the number of Lilies. - /// - public byte Lily => this.Struct->Lily; - - /// - /// Gets the number of times the blood lily has been nourished. - /// - public byte BloodLily => this.Struct->BloodLily; } diff --git a/Dalamud/Game/ClientState/Keys/KeyState.cs b/Dalamud/Game/ClientState/Keys/KeyState.cs index eb99397f6..0476eee89 100644 --- a/Dalamud/Game/ClientState/Keys/KeyState.cs +++ b/Dalamud/Game/ClientState/Keys/KeyState.cs @@ -6,154 +6,155 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Keys; - -/// -/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode. -/// -/// -/// The stored key state is actually a combination field, however the below ephemeral states are consumed each frame. Setting -/// the value may be mildly useful, however retrieving the value is largely pointless. In testing, it wasn't possible without -/// setting the statue manually. -/// index & 0 = key pressed. -/// index & 1 = key down (ephemeral). -/// index & 2 = key up (ephemeral). -/// index & 3 = short key press (ephemeral). -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public class KeyState +namespace Dalamud.Game.ClientState.Keys { - // The array is accessed in a way that this limit doesn't appear to exist - // but there is other state data past this point, and keys beyond here aren't - // generally valid for most things anyway - private const int MaxKeyCode = 0xF0; - private readonly IntPtr bufferBase; - private readonly IntPtr indexBase; - private VirtualKey[] validVirtualKeyCache = null; - /// - /// Initializes a new instance of the class. + /// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode. /// - /// The ClientStateAddressResolver instance. - public KeyState(ClientStateAddressResolver addressResolver) + /// + /// The stored key state is actually a combination field, however the below ephemeral states are consumed each frame. Setting + /// the value may be mildly useful, however retrieving the value is largely pointless. In testing, it wasn't possible without + /// setting the statue manually. + /// index & 0 = key pressed. + /// index & 1 = key down (ephemeral). + /// index & 2 = key up (ephemeral). + /// index & 3 = short key press (ephemeral). + /// + [PluginInterface] + [InterfaceVersion("1.0")] + public class KeyState { - var moduleBaseAddress = Service.Get().Module.BaseAddress; + // The array is accessed in a way that this limit doesn't appear to exist + // but there is other state data past this point, and keys beyond here aren't + // generally valid for most things anyway + private const int MaxKeyCode = 0xF0; + private readonly IntPtr bufferBase; + private readonly IntPtr indexBase; + private VirtualKey[] validVirtualKeyCache = null; - this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); - this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray); - - Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}"); - } - - /// - /// Get or set the key-pressed state for a given vkCode. - /// - /// The virtual key to change. - /// Whether the specified key is currently pressed. - /// If the vkCode is not valid. Refer to or . - /// If the set value is non-zero. - public unsafe bool this[int vkCode] - { - get => this.GetRawValue(vkCode) != 0; - set => this.SetRawValue(vkCode, value ? 1 : 0); - } - - /// - public bool this[VirtualKey vkCode] - { - get => this[(int)vkCode]; - set => this[(int)vkCode] = value; - } - - /// - /// Gets the value in the index array. - /// - /// The virtual key to change. - /// The raw value stored in the index array. - /// If the vkCode is not valid. Refer to or . - public int GetRawValue(int vkCode) - => this.GetRefValue(vkCode); - - /// - public int GetRawValue(VirtualKey vkCode) - => this.GetRawValue((int)vkCode); - - /// - /// Sets the value in the index array. - /// - /// The virtual key to change. - /// The raw value to set in the index array. - /// If the vkCode is not valid. Refer to or . - /// If the set value is non-zero. - public void SetRawValue(int vkCode, int value) - { - if (value != 0) - throw new ArgumentOutOfRangeException(nameof(value), "Dalamud does not support pressing keys, only preventing them via zero or False. If you have a valid use-case for this, please contact the dev team."); - - this.GetRefValue(vkCode) = value; - } - - /// - public void SetRawValue(VirtualKey vkCode, int value) - => this.SetRawValue((int)vkCode, value); - - /// - /// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game. - /// - /// Virtual key code. - /// If the code is valid. - public bool IsVirtualKeyValid(int vkCode) - => this.ConvertVirtualKey(vkCode) != 0; - - /// - public bool IsVirtualKeyValid(VirtualKey vkCode) - => this.IsVirtualKeyValid((int)vkCode); - - /// - /// Gets an array of virtual keys the game considers valid input. - /// - /// An array of valid virtual keys. - public VirtualKey[] GetValidVirtualKeys() - => this.validVirtualKeyCache ??= Enum.GetValues().Where(vk => this.IsVirtualKeyValid(vk)).ToArray(); - - /// - /// Clears the pressed state for all keys. - /// - public void ClearAll() - { - foreach (var vk in this.GetValidVirtualKeys()) + /// + /// Initializes a new instance of the class. + /// + /// The ClientStateAddressResolver instance. + public KeyState(ClientStateAddressResolver addressResolver) { - this[vk] = false; + var moduleBaseAddress = Service.Get().Module.BaseAddress; + + this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); + this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray); + + Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}"); + } + + /// + /// Get or set the key-pressed state for a given vkCode. + /// + /// The virtual key to change. + /// Whether the specified key is currently pressed. + /// If the vkCode is not valid. Refer to or . + /// If the set value is non-zero. + public unsafe bool this[int vkCode] + { + get => this.GetRawValue(vkCode) != 0; + set => this.SetRawValue(vkCode, value ? 1 : 0); + } + + /// + public bool this[VirtualKey vkCode] + { + get => this[(int)vkCode]; + set => this[(int)vkCode] = value; + } + + /// + /// Gets the value in the index array. + /// + /// The virtual key to change. + /// The raw value stored in the index array. + /// If the vkCode is not valid. Refer to or . + public int GetRawValue(int vkCode) + => this.GetRefValue(vkCode); + + /// + public int GetRawValue(VirtualKey vkCode) + => this.GetRawValue((int)vkCode); + + /// + /// Sets the value in the index array. + /// + /// The virtual key to change. + /// The raw value to set in the index array. + /// If the vkCode is not valid. Refer to or . + /// If the set value is non-zero. + public void SetRawValue(int vkCode, int value) + { + if (value != 0) + throw new ArgumentOutOfRangeException(nameof(value), "Dalamud does not support pressing keys, only preventing them via zero or False. If you have a valid use-case for this, please contact the dev team."); + + this.GetRefValue(vkCode) = value; + } + + /// + public void SetRawValue(VirtualKey vkCode, int value) + => this.SetRawValue((int)vkCode, value); + + /// + /// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game. + /// + /// Virtual key code. + /// If the code is valid. + public bool IsVirtualKeyValid(int vkCode) + => this.ConvertVirtualKey(vkCode) != 0; + + /// + public bool IsVirtualKeyValid(VirtualKey vkCode) + => this.IsVirtualKeyValid((int)vkCode); + + /// + /// Gets an array of virtual keys the game considers valid input. + /// + /// An array of valid virtual keys. + public VirtualKey[] GetValidVirtualKeys() + => this.validVirtualKeyCache ??= Enum.GetValues().Where(vk => this.IsVirtualKeyValid(vk)).ToArray(); + + /// + /// Clears the pressed state for all keys. + /// + public void ClearAll() + { + foreach (var vk in this.GetValidVirtualKeys()) + { + this[vk] = false; + } + } + + /// + /// Converts a virtual key into the equivalent value that the game uses. + /// Valid values are non-zero. + /// + /// Virtual key. + /// Converted value. + private unsafe byte ConvertVirtualKey(int vkCode) + { + if (vkCode <= 0 || vkCode >= MaxKeyCode) + return 0; + + return *(byte*)(this.indexBase + vkCode); + } + + /// + /// Gets the raw value from the key state array. + /// + /// Virtual key code. + /// A reference to the indexed array. + private unsafe ref int GetRefValue(int vkCode) + { + vkCode = this.ConvertVirtualKey(vkCode); + + if (vkCode == 0) + throw new ArgumentException($"Keycode state is only valid for certain values. Reference GetValidVirtualKeys for help."); + + return ref *(int*)(this.bufferBase + (4 * vkCode)); } } - - /// - /// Converts a virtual key into the equivalent value that the game uses. - /// Valid values are non-zero. - /// - /// Virtual key. - /// Converted value. - private unsafe byte ConvertVirtualKey(int vkCode) - { - if (vkCode <= 0 || vkCode >= MaxKeyCode) - return 0; - - return *(byte*)(this.indexBase + vkCode); - } - - /// - /// Gets the raw value from the key state array. - /// - /// Virtual key code. - /// A reference to the indexed array. - private unsafe ref int GetRefValue(int vkCode) - { - vkCode = this.ConvertVirtualKey(vkCode); - - if (vkCode == 0) - throw new ArgumentException($"Keycode state is only valid for certain values. Reference GetValidVirtualKeys for help."); - - return ref *(int*)(this.bufferBase + (4 * vkCode)); - } } diff --git a/Dalamud/Game/ClientState/Keys/VirtualKey.cs b/Dalamud/Game/ClientState/Keys/VirtualKey.cs index 998c3307c..ab9436822 100644 --- a/Dalamud/Game/ClientState/Keys/VirtualKey.cs +++ b/Dalamud/Game/ClientState/Keys/VirtualKey.cs @@ -1,1043 +1,1044 @@ -namespace Dalamud.Game.ClientState.Keys; - -/// -/// Virtual-key codes. -/// -/// -/// Defined in winuser.h from Windows SDK v6.1. -/// -public enum VirtualKey : ushort +namespace Dalamud.Game.ClientState.Keys { /// - /// This is an addendum to use on functions in which you have to pass a zero value to represent no key code. - /// - NO_KEY = 0, - - /// - /// Left mouse button. - /// - LBUTTON = 1, - - /// - /// Right mouse button. - /// - RBUTTON = 2, - - /// - /// Control-break processing. - /// - CANCEL = 3, - - /// - /// Middle mouse button (three-button mouse). + /// Virtual-key codes. /// /// - /// NOT contiguous with L and R buttons. + /// Defined in winuser.h from Windows SDK v6.1. /// - MBUTTON = 4, - - /// - /// X1 mouse button. - /// - /// - /// NOT contiguous with L and R buttons. - /// - XBUTTON1 = 5, - - /// - /// X2 mouse button. - /// - /// - /// NOT contiguous with L and R buttons. - /// - XBUTTON2 = 6, - - /// - /// BACKSPACE key. - /// - BACK = 8, - - /// - /// TAB key. - /// - TAB = 9, - - /// - /// CLEAR key. - /// - CLEAR = 12, - - /// - /// RETURN key. - /// - RETURN = 13, - - /// - /// SHIFT key. - /// - SHIFT = 16, - - /// - /// CONTROL key. - /// - CONTROL = 17, - - /// - /// ALT key. - /// - MENU = 18, - - /// - /// PAUSE key. - /// - PAUSE = 19, - - /// - /// CAPS LOCK key. - /// - CAPITAL = 20, - - /// - /// IME Kana mode. - /// - KANA = 21, - - /// - /// IME Hanguel mode (maintained for compatibility; use User32.VirtualKey.HANGUL). - /// - HANGEUL = KANA, - - /// - /// IME Hangul mode. - /// - HANGUL = KANA, - - /// - /// IME Junja mode. - /// - JUNJA = 23, - - /// - /// IME final mode. - /// - FINAL = 24, - - /// - /// IME Hanja mode. - /// - HANJA = 25, - - /// - /// IME Kanji mode. - /// - KANJI = HANJA, - - /// - /// ESC key. - /// - ESCAPE = 27, - - /// - /// IME convert. - /// - CONVERT = 28, - - /// - /// IME nonconvert. - /// - NONCONVERT = 29, - - /// - /// IME accept. - /// - ACCEPT = 30, - - /// - /// IME mode change request. - /// - MODECHANGE = 31, - - /// - /// SPACEBAR. - /// - SPACE = 32, - - /// - /// PAGE UP key. - /// - PRIOR = 33, - - /// - /// PAGE DOWN key. - /// - NEXT = 34, - - /// - /// END key. - /// - END = 35, - - /// - /// HOME key. - /// - HOME = 36, - - /// - /// LEFT ARROW key. - /// - LEFT = 37, - - /// - /// UP ARROW key. - /// - UP = 38, - - /// - /// RIGHT ARROW key. - /// - RIGHT = 39, - - /// - /// DOWN ARROW key. - /// - DOWN = 40, - - /// - /// SELECT key. - /// - SELECT = 41, - - /// - /// PRINT key. - /// - PRINT = 42, - - /// - /// EXECUTE key. - /// - EXECUTE = 43, - - /// - /// PRINT SCREEN key. - /// - SNAPSHOT = 44, - - /// - /// INS key. - /// - INSERT = 45, - - /// - /// DEL key. - /// - DELETE = 46, - - /// - /// HELP key. - /// - HELP = 47, - - /// - /// 0 key. - /// - KEY_0 = 48, - - /// - /// 1 key. - /// - KEY_1 = 49, - - /// - /// 2 key. - /// - KEY_2 = 50, - - /// - /// 3 key. - /// - KEY_3 = 51, - - /// - /// 4 key. - /// - KEY_4 = 52, - - /// - /// 5 key. - /// - KEY_5 = 53, - - /// - /// 6 key. - /// - KEY_6 = 54, - - /// - /// 7 key. - /// - KEY_7 = 55, - - /// - /// 8 key. - /// - KEY_8 = 56, - - /// - /// 9 key. - /// - KEY_9 = 57, - - /// - /// A key. - /// - A = 65, - - /// - /// B key. - /// - B = 66, - - /// - /// C key. - /// - C = 67, - - /// - /// D key. - /// - D = 68, - - /// - /// E key. - /// - E = 69, - - /// - /// F key. - /// - F = 70, - - /// - /// G key. - /// - G = 71, - - /// - /// H key. - /// - H = 72, - - /// - /// I key. - /// - I = 73, - - /// - /// J key. - /// - J = 74, - - /// - /// K key. - /// - K = 75, - - /// - /// L key. - /// - L = 76, - - /// - /// M key. - /// - M = 77, - - /// - /// N key. - /// - N = 78, - - /// - /// O key. - /// - O = 79, - - /// - /// P key. - /// - P = 80, - - /// - /// Q key. - /// - Q = 81, - - /// - /// R key. - /// - R = 82, - - /// - /// S key. - /// - S = 83, - - /// - /// T key. - /// - T = 84, - - /// - /// U key. - /// - U = 85, - - /// - /// V key. - /// - V = 86, - - /// - /// W key. - /// - W = 87, - - /// - /// X key. - /// - X = 88, - - /// - /// Y key. - /// - Y = 89, - - /// - /// Z key. - /// - Z = 90, - - /// - /// Left Windows key (Natural keyboard). - /// - LWIN = 91, - - /// - /// Right Windows key (Natural keyboard). - /// - RWIN = 92, - - /// - /// Applications key (Natural keyboard). - /// - APPS = 93, - - /// - /// Computer Sleep key. - /// - SLEEP = 95, - - /// - /// Numeric keypad 0 key. - /// - NUMPAD0 = 96, - - /// - /// Numeric keypad 1 key. - /// - NUMPAD1 = 97, - - /// - /// Numeric keypad 2 key. - /// - NUMPAD2 = 98, - - /// - /// Numeric keypad 3 key. - /// - NUMPAD3 = 99, - - /// - /// Numeric keypad 4 key. - /// - NUMPAD4 = 100, - - /// - /// Numeric keypad 5 key. - /// - NUMPAD5 = 101, - - /// - /// Numeric keypad 6 key. - /// - NUMPAD6 = 102, - - /// - /// Numeric keypad 7 key. - /// - NUMPAD7 = 103, - - /// - /// Numeric keypad 8 key. - /// - NUMPAD8 = 104, - - /// - /// Numeric keypad 9 key. - /// - NUMPAD9 = 105, - - /// - /// Multiply key. - /// - MULTIPLY = 106, - - /// - /// Add key. - /// - ADD = 107, - - /// - /// Separator key. - /// - SEPARATOR = 108, - - /// - /// Subtract key. - /// - SUBTRACT = 109, - - /// - /// Decimal key. - /// - DECIMAL = 110, - - /// - /// Divide key. - /// - DIVIDE = 111, - - /// - /// F1 Key. - /// - F1 = 112, - - /// - /// F2 Key. - /// - F2 = 113, - - /// - /// F3 Key. - /// - F3 = 114, - - /// - /// F4 Key. - /// - F4 = 115, - - /// - /// F5 Key. - /// - F5 = 116, - - /// - /// F6 Key. - /// - F6 = 117, - - /// - /// F7 Key. - /// - F7 = 118, - - /// - /// F8 Key. - /// - F8 = 119, - - /// - /// F9 Key. - /// - F9 = 120, - - /// - /// F10 Key. - /// - F10 = 121, - - /// - /// F11 Key. - /// - F11 = 122, - - /// - /// F12 Key. - /// - F12 = 123, - - /// - /// F13 Key. - /// - F13 = 124, - - /// - /// F14 Key. - /// - F14 = 125, - - /// - /// F15 Key. - /// - F15 = 126, - - /// - /// F16 Key. - /// - F16 = 127, - - /// - /// F17 Key. - /// - F17 = 128, - - /// - /// F18 Key. - /// - F18 = 129, - - /// - /// F19 Key. - /// - F19 = 130, - - /// - /// F20 Key. - /// - F20 = 131, - - /// - /// F21 Key. - /// - F21 = 132, - - /// - /// F22 Key. - /// - F22 = 133, - - /// - /// F23 Key. - /// - F23 = 134, - - /// - /// F24 Key. - /// - F24 = 135, - - /// - /// NUM LOCK key. - /// - NUMLOCK = 144, - - /// - /// SCROLL LOCK key. - /// - SCROLL = 145, - - /// - /// '=' key on numpad (NEC PC-9800 kbd definitions). - /// - OEM_NEC_EQUAL = 146, - - /// - /// 'Dictionary' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_JISHO = OEM_NEC_EQUAL, - - /// - /// 'Unregister word' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_MASSHOU = 147, - - /// - /// 'Register word' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_TOUROKU = 148, - - /// - /// 'Left OYAYUBI' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_LOYA = 149, - - /// - /// 'Right OYAYUBI' key (Fujitsu/OASYS kbd definitions). - /// - OEM_FJ_ROYA = 150, - - /// - /// Left SHIFT key. - /// - /// - /// Used only as parameters to User32.GetAsyncKeyState and User32.GetKeyState. No other API or message will distinguish left and right keys in this way. - /// - LSHIFT = 160, - - /// - /// Right SHIFT key. - /// - RSHIFT = 161, - - /// - /// Left CONTROL key. - /// - LCONTROL = 162, - - /// - /// Right CONTROL key. - /// - RCONTROL = 163, - - /// - /// Left MENU key. - /// - LMENU = 164, - - /// - /// Right MENU key. - /// - RMENU = 165, - - /// - /// Browser Back key. - /// - BROWSER_BACK = 166, - - /// - /// Browser Forward key. - /// - BROWSER_FORWARD = 167, - - /// - /// Browser Refresh key. - /// - BROWSER_REFRESH = 168, - - /// - /// Browser Stop key. - /// - BROWSER_STOP = 169, - - /// - /// Browser Search key. - /// - BROWSER_SEARCH = 170, - - /// - /// Browser Favorites key. - /// - BROWSER_FAVORITES = 171, - - /// - /// Browser Start and Home key. - /// - BROWSER_HOME = 172, - - /// - /// Volume Mute key. - /// - VOLUME_MUTE = 173, - - /// - /// Volume Down key. - /// - VOLUME_DOWN = 174, - - /// - /// Volume Up key. - /// - VOLUME_UP = 175, - - /// - /// Next Track key. - /// - MEDIA_NEXT_TRACK = 176, - - /// - /// Previous Track key. - /// - MEDIA_PREV_TRACK = 177, - - /// - /// Stop Media key. - /// - MEDIA_STOP = 178, - - /// - /// Play/Pause Media key. - /// - MEDIA_PLAY_PAUSE = 179, - - /// - /// Start Mail key. - /// - LAUNCH_MAIL = 180, - - /// - /// Select Media key. - /// - LAUNCH_MEDIA_SELECT = 181, - - /// - /// Start Application 1 key. - /// - LAUNCH_APP1 = 182, - - /// - /// Start Application 2 key. - /// - LAUNCH_APP2 = 183, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the ';:' key. - /// - OEM_1 = 186, - - /// - /// For any country/region, the '+' key. - /// - OEM_PLUS = 187, - - /// - /// For any country/region, the ',' key. - /// - OEM_COMMA = 188, - - /// - /// For any country/region, the '-' key. - /// - OEM_MINUS = 189, - - /// - /// For any country/region, the '.' key. - /// - OEM_PERIOD = 190, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '/?' key. - /// - OEM_2 = 191, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '`~' key. - /// - OEM_3 = 192, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '[{' key. - /// - OEM_4 = 219, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the '\|' key. - /// - OEM_5 = 220, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the ']}' key. - /// - OEM_6 = 221, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - /// - /// For the US standard keyboard, the 'single-quote/double-quote' (''"') key. - /// - OEM_7 = 222, - - /// - /// Used for miscellaneous characters; it can vary by keyboard.. - /// - OEM_8 = 223, - - /// - /// OEM specific. - /// - /// - /// 'AX' key on Japanese AX kbd. - /// - OEM_AX = 225, - - /// - /// Either the angle bracket ("<>") key or the backslash ("\|") key on the RT 102-key keyboard. - /// - OEM_102 = 226, - - /// - /// OEM specific. - /// - /// - /// Help key on ICO. - /// - ICO_HELP = 227, - - /// - /// OEM specific. - /// - /// - /// 00 key on ICO. - /// - ICO_00 = 228, - - /// - /// IME PROCESS key. - /// - PROCESSKEY = 229, - - /// - /// OEM specific. - /// - /// - /// Clear key on ICO. - /// - ICO_CLEAR = 230, - - /// - /// Used to pass Unicode characters as if they were keystrokes. The PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods.. - /// - /// - /// For more information, see Remark in User32.KEYBDINPUT, User32.SendInput, User32.WindowMessage.WM_KEYDOWN, and User32.WindowMessage.WM_KEYUP. - /// - PACKET = 231, - - /// - /// Nokia/Ericsson definition. - /// - OEM_RESET = 233, - - /// - /// Nokia/Ericsson definition. - /// - OEM_JUMP = 234, - - /// - /// Nokia/Ericsson definition. - /// - OEM_PA1 = 235, - - /// - /// Nokia/Ericsson definition. - /// - OEM_PA2 = 236, - - /// - /// Nokia/Ericsson definition. - /// - OEM_PA3 = 237, - - /// - /// Nokia/Ericsson definition. - /// - OEM_WSCTRL = 238, - - /// - /// Nokia/Ericsson definition. - /// - OEM_CUSEL = 239, - - /// - /// Nokia/Ericsson definition. - /// - OEM_ATTN = 240, - - /// - /// Nokia/Ericsson definition. - /// - OEM_FINISH = 241, - - /// - /// Nokia/Ericsson definition. - /// - OEM_COPY = 242, - - /// - /// Nokia/Ericsson definition. - /// - OEM_AUTO = 243, - - /// - /// Nokia/Ericsson definition. - /// - OEM_ENLW = 244, - - /// - /// Nokia/Ericsson definition. - /// - OEM_BACKTAB = 245, - - /// - /// Attn key. - /// - ATTN = 246, - - /// - /// CrSel key. - /// - CRSEL = 247, - - /// - /// ExSel key. - /// - EXSEL = 248, - - /// - /// Erase EOF key. - /// - EREOF = 249, - - /// - /// Play key. - /// - PLAY = 250, - - /// - /// Zoom key. - /// - ZOOM = 251, - - /// - /// Reserved constant by Windows headers definition. - /// - NONAME = 252, - - /// - /// PA1 key. - /// - PA1 = 253, - - /// - /// Clear key. - /// - OEM_CLEAR = 254, + public enum VirtualKey : ushort + { + /// + /// This is an addendum to use on functions in which you have to pass a zero value to represent no key code. + /// + NO_KEY = 0, + + /// + /// Left mouse button. + /// + LBUTTON = 1, + + /// + /// Right mouse button. + /// + RBUTTON = 2, + + /// + /// Control-break processing. + /// + CANCEL = 3, + + /// + /// Middle mouse button (three-button mouse). + /// + /// + /// NOT contiguous with L and R buttons. + /// + MBUTTON = 4, + + /// + /// X1 mouse button. + /// + /// + /// NOT contiguous with L and R buttons. + /// + XBUTTON1 = 5, + + /// + /// X2 mouse button. + /// + /// + /// NOT contiguous with L and R buttons. + /// + XBUTTON2 = 6, + + /// + /// BACKSPACE key. + /// + BACK = 8, + + /// + /// TAB key. + /// + TAB = 9, + + /// + /// CLEAR key. + /// + CLEAR = 12, + + /// + /// RETURN key. + /// + RETURN = 13, + + /// + /// SHIFT key. + /// + SHIFT = 16, + + /// + /// CONTROL key. + /// + CONTROL = 17, + + /// + /// ALT key. + /// + MENU = 18, + + /// + /// PAUSE key. + /// + PAUSE = 19, + + /// + /// CAPS LOCK key. + /// + CAPITAL = 20, + + /// + /// IME Kana mode. + /// + KANA = 21, + + /// + /// IME Hanguel mode (maintained for compatibility; use User32.VirtualKey.HANGUL). + /// + HANGEUL = KANA, + + /// + /// IME Hangul mode. + /// + HANGUL = KANA, + + /// + /// IME Junja mode. + /// + JUNJA = 23, + + /// + /// IME final mode. + /// + FINAL = 24, + + /// + /// IME Hanja mode. + /// + HANJA = 25, + + /// + /// IME Kanji mode. + /// + KANJI = HANJA, + + /// + /// ESC key. + /// + ESCAPE = 27, + + /// + /// IME convert. + /// + CONVERT = 28, + + /// + /// IME nonconvert. + /// + NONCONVERT = 29, + + /// + /// IME accept. + /// + ACCEPT = 30, + + /// + /// IME mode change request. + /// + MODECHANGE = 31, + + /// + /// SPACEBAR. + /// + SPACE = 32, + + /// + /// PAGE UP key. + /// + PRIOR = 33, + + /// + /// PAGE DOWN key. + /// + NEXT = 34, + + /// + /// END key. + /// + END = 35, + + /// + /// HOME key. + /// + HOME = 36, + + /// + /// LEFT ARROW key. + /// + LEFT = 37, + + /// + /// UP ARROW key. + /// + UP = 38, + + /// + /// RIGHT ARROW key. + /// + RIGHT = 39, + + /// + /// DOWN ARROW key. + /// + DOWN = 40, + + /// + /// SELECT key. + /// + SELECT = 41, + + /// + /// PRINT key. + /// + PRINT = 42, + + /// + /// EXECUTE key. + /// + EXECUTE = 43, + + /// + /// PRINT SCREEN key. + /// + SNAPSHOT = 44, + + /// + /// INS key. + /// + INSERT = 45, + + /// + /// DEL key. + /// + DELETE = 46, + + /// + /// HELP key. + /// + HELP = 47, + + /// + /// 0 key. + /// + KEY_0 = 48, + + /// + /// 1 key. + /// + KEY_1 = 49, + + /// + /// 2 key. + /// + KEY_2 = 50, + + /// + /// 3 key. + /// + KEY_3 = 51, + + /// + /// 4 key. + /// + KEY_4 = 52, + + /// + /// 5 key. + /// + KEY_5 = 53, + + /// + /// 6 key. + /// + KEY_6 = 54, + + /// + /// 7 key. + /// + KEY_7 = 55, + + /// + /// 8 key. + /// + KEY_8 = 56, + + /// + /// 9 key. + /// + KEY_9 = 57, + + /// + /// A key. + /// + A = 65, + + /// + /// B key. + /// + B = 66, + + /// + /// C key. + /// + C = 67, + + /// + /// D key. + /// + D = 68, + + /// + /// E key. + /// + E = 69, + + /// + /// F key. + /// + F = 70, + + /// + /// G key. + /// + G = 71, + + /// + /// H key. + /// + H = 72, + + /// + /// I key. + /// + I = 73, + + /// + /// J key. + /// + J = 74, + + /// + /// K key. + /// + K = 75, + + /// + /// L key. + /// + L = 76, + + /// + /// M key. + /// + M = 77, + + /// + /// N key. + /// + N = 78, + + /// + /// O key. + /// + O = 79, + + /// + /// P key. + /// + P = 80, + + /// + /// Q key. + /// + Q = 81, + + /// + /// R key. + /// + R = 82, + + /// + /// S key. + /// + S = 83, + + /// + /// T key. + /// + T = 84, + + /// + /// U key. + /// + U = 85, + + /// + /// V key. + /// + V = 86, + + /// + /// W key. + /// + W = 87, + + /// + /// X key. + /// + X = 88, + + /// + /// Y key. + /// + Y = 89, + + /// + /// Z key. + /// + Z = 90, + + /// + /// Left Windows key (Natural keyboard). + /// + LWIN = 91, + + /// + /// Right Windows key (Natural keyboard). + /// + RWIN = 92, + + /// + /// Applications key (Natural keyboard). + /// + APPS = 93, + + /// + /// Computer Sleep key. + /// + SLEEP = 95, + + /// + /// Numeric keypad 0 key. + /// + NUMPAD0 = 96, + + /// + /// Numeric keypad 1 key. + /// + NUMPAD1 = 97, + + /// + /// Numeric keypad 2 key. + /// + NUMPAD2 = 98, + + /// + /// Numeric keypad 3 key. + /// + NUMPAD3 = 99, + + /// + /// Numeric keypad 4 key. + /// + NUMPAD4 = 100, + + /// + /// Numeric keypad 5 key. + /// + NUMPAD5 = 101, + + /// + /// Numeric keypad 6 key. + /// + NUMPAD6 = 102, + + /// + /// Numeric keypad 7 key. + /// + NUMPAD7 = 103, + + /// + /// Numeric keypad 8 key. + /// + NUMPAD8 = 104, + + /// + /// Numeric keypad 9 key. + /// + NUMPAD9 = 105, + + /// + /// Multiply key. + /// + MULTIPLY = 106, + + /// + /// Add key. + /// + ADD = 107, + + /// + /// Separator key. + /// + SEPARATOR = 108, + + /// + /// Subtract key. + /// + SUBTRACT = 109, + + /// + /// Decimal key. + /// + DECIMAL = 110, + + /// + /// Divide key. + /// + DIVIDE = 111, + + /// + /// F1 Key. + /// + F1 = 112, + + /// + /// F2 Key. + /// + F2 = 113, + + /// + /// F3 Key. + /// + F3 = 114, + + /// + /// F4 Key. + /// + F4 = 115, + + /// + /// F5 Key. + /// + F5 = 116, + + /// + /// F6 Key. + /// + F6 = 117, + + /// + /// F7 Key. + /// + F7 = 118, + + /// + /// F8 Key. + /// + F8 = 119, + + /// + /// F9 Key. + /// + F9 = 120, + + /// + /// F10 Key. + /// + F10 = 121, + + /// + /// F11 Key. + /// + F11 = 122, + + /// + /// F12 Key. + /// + F12 = 123, + + /// + /// F13 Key. + /// + F13 = 124, + + /// + /// F14 Key. + /// + F14 = 125, + + /// + /// F15 Key. + /// + F15 = 126, + + /// + /// F16 Key. + /// + F16 = 127, + + /// + /// F17 Key. + /// + F17 = 128, + + /// + /// F18 Key. + /// + F18 = 129, + + /// + /// F19 Key. + /// + F19 = 130, + + /// + /// F20 Key. + /// + F20 = 131, + + /// + /// F21 Key. + /// + F21 = 132, + + /// + /// F22 Key. + /// + F22 = 133, + + /// + /// F23 Key. + /// + F23 = 134, + + /// + /// F24 Key. + /// + F24 = 135, + + /// + /// NUM LOCK key. + /// + NUMLOCK = 144, + + /// + /// SCROLL LOCK key. + /// + SCROLL = 145, + + /// + /// '=' key on numpad (NEC PC-9800 kbd definitions). + /// + OEM_NEC_EQUAL = 146, + + /// + /// 'Dictionary' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_JISHO = OEM_NEC_EQUAL, + + /// + /// 'Unregister word' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_MASSHOU = 147, + + /// + /// 'Register word' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_TOUROKU = 148, + + /// + /// 'Left OYAYUBI' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_LOYA = 149, + + /// + /// 'Right OYAYUBI' key (Fujitsu/OASYS kbd definitions). + /// + OEM_FJ_ROYA = 150, + + /// + /// Left SHIFT key. + /// + /// + /// Used only as parameters to User32.GetAsyncKeyState and User32.GetKeyState. No other API or message will distinguish left and right keys in this way. + /// + LSHIFT = 160, + + /// + /// Right SHIFT key. + /// + RSHIFT = 161, + + /// + /// Left CONTROL key. + /// + LCONTROL = 162, + + /// + /// Right CONTROL key. + /// + RCONTROL = 163, + + /// + /// Left MENU key. + /// + LMENU = 164, + + /// + /// Right MENU key. + /// + RMENU = 165, + + /// + /// Browser Back key. + /// + BROWSER_BACK = 166, + + /// + /// Browser Forward key. + /// + BROWSER_FORWARD = 167, + + /// + /// Browser Refresh key. + /// + BROWSER_REFRESH = 168, + + /// + /// Browser Stop key. + /// + BROWSER_STOP = 169, + + /// + /// Browser Search key. + /// + BROWSER_SEARCH = 170, + + /// + /// Browser Favorites key. + /// + BROWSER_FAVORITES = 171, + + /// + /// Browser Start and Home key. + /// + BROWSER_HOME = 172, + + /// + /// Volume Mute key. + /// + VOLUME_MUTE = 173, + + /// + /// Volume Down key. + /// + VOLUME_DOWN = 174, + + /// + /// Volume Up key. + /// + VOLUME_UP = 175, + + /// + /// Next Track key. + /// + MEDIA_NEXT_TRACK = 176, + + /// + /// Previous Track key. + /// + MEDIA_PREV_TRACK = 177, + + /// + /// Stop Media key. + /// + MEDIA_STOP = 178, + + /// + /// Play/Pause Media key. + /// + MEDIA_PLAY_PAUSE = 179, + + /// + /// Start Mail key. + /// + LAUNCH_MAIL = 180, + + /// + /// Select Media key. + /// + LAUNCH_MEDIA_SELECT = 181, + + /// + /// Start Application 1 key. + /// + LAUNCH_APP1 = 182, + + /// + /// Start Application 2 key. + /// + LAUNCH_APP2 = 183, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the ';:' key. + /// + OEM_1 = 186, + + /// + /// For any country/region, the '+' key. + /// + OEM_PLUS = 187, + + /// + /// For any country/region, the ',' key. + /// + OEM_COMMA = 188, + + /// + /// For any country/region, the '-' key. + /// + OEM_MINUS = 189, + + /// + /// For any country/region, the '.' key. + /// + OEM_PERIOD = 190, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '/?' key. + /// + OEM_2 = 191, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '`~' key. + /// + OEM_3 = 192, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '[{' key. + /// + OEM_4 = 219, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the '\|' key. + /// + OEM_5 = 220, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the ']}' key. + /// + OEM_6 = 221, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + /// + /// For the US standard keyboard, the 'single-quote/double-quote' (''"') key. + /// + OEM_7 = 222, + + /// + /// Used for miscellaneous characters; it can vary by keyboard.. + /// + OEM_8 = 223, + + /// + /// OEM specific. + /// + /// + /// 'AX' key on Japanese AX kbd. + /// + OEM_AX = 225, + + /// + /// Either the angle bracket ("<>") key or the backslash ("\|") key on the RT 102-key keyboard. + /// + OEM_102 = 226, + + /// + /// OEM specific. + /// + /// + /// Help key on ICO. + /// + ICO_HELP = 227, + + /// + /// OEM specific. + /// + /// + /// 00 key on ICO. + /// + ICO_00 = 228, + + /// + /// IME PROCESS key. + /// + PROCESSKEY = 229, + + /// + /// OEM specific. + /// + /// + /// Clear key on ICO. + /// + ICO_CLEAR = 230, + + /// + /// Used to pass Unicode characters as if they were keystrokes. The PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods.. + /// + /// + /// For more information, see Remark in User32.KEYBDINPUT, User32.SendInput, User32.WindowMessage.WM_KEYDOWN, and User32.WindowMessage.WM_KEYUP. + /// + PACKET = 231, + + /// + /// Nokia/Ericsson definition. + /// + OEM_RESET = 233, + + /// + /// Nokia/Ericsson definition. + /// + OEM_JUMP = 234, + + /// + /// Nokia/Ericsson definition. + /// + OEM_PA1 = 235, + + /// + /// Nokia/Ericsson definition. + /// + OEM_PA2 = 236, + + /// + /// Nokia/Ericsson definition. + /// + OEM_PA3 = 237, + + /// + /// Nokia/Ericsson definition. + /// + OEM_WSCTRL = 238, + + /// + /// Nokia/Ericsson definition. + /// + OEM_CUSEL = 239, + + /// + /// Nokia/Ericsson definition. + /// + OEM_ATTN = 240, + + /// + /// Nokia/Ericsson definition. + /// + OEM_FINISH = 241, + + /// + /// Nokia/Ericsson definition. + /// + OEM_COPY = 242, + + /// + /// Nokia/Ericsson definition. + /// + OEM_AUTO = 243, + + /// + /// Nokia/Ericsson definition. + /// + OEM_ENLW = 244, + + /// + /// Nokia/Ericsson definition. + /// + OEM_BACKTAB = 245, + + /// + /// Attn key. + /// + ATTN = 246, + + /// + /// CrSel key. + /// + CRSEL = 247, + + /// + /// ExSel key. + /// + EXSEL = 248, + + /// + /// Erase EOF key. + /// + EREOF = 249, + + /// + /// Play key. + /// + PLAY = 250, + + /// + /// Zoom key. + /// + ZOOM = 251, + + /// + /// Reserved constant by Windows headers definition. + /// + NONAME = 252, + + /// + /// PA1 key. + /// + PA1 = 253, + + /// + /// Clear key. + /// + OEM_CLEAR = 254, + } } diff --git a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs index dd6057d36..489891c45 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/BattleNpcSubKind.cs @@ -1,27 +1,28 @@ -namespace Dalamud.Game.ClientState.Objects.Enums; - -/// -/// An Enum describing possible BattleNpc kinds. -/// -public enum BattleNpcSubKind : byte +namespace Dalamud.Game.ClientState.Objects.Enums { /// - /// Invalid BattleNpc. + /// An Enum describing possible BattleNpc kinds. /// - None = 0, + public enum BattleNpcSubKind : byte + { + /// + /// Invalid BattleNpc. + /// + None = 0, - /// - /// BattleNpc representing a Pet. - /// - Pet = 2, + /// + /// BattleNpc representing a Pet. + /// + Pet = 2, - /// - /// BattleNpc representing a Chocobo. - /// - Chocobo = 3, + /// + /// BattleNpc representing a Chocobo. + /// + Chocobo = 3, - /// - /// BattleNpc representing a standard enemy. - /// - Enemy = 5, + /// + /// BattleNpc representing a standard enemy. + /// + Enemy = 5, + } } diff --git a/Dalamud/Game/ClientState/Objects/Enums/CustomizeIndex.cs b/Dalamud/Game/ClientState/Objects/Enums/CustomizeIndex.cs index 299583fd3..b1827c0c5 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/CustomizeIndex.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/CustomizeIndex.cs @@ -1,138 +1,139 @@ -namespace Dalamud.Game.ClientState.Objects.Enums; - -/// -/// This enum describes the indices of the Customize array. -/// -// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire) -public enum CustomizeIndex +namespace Dalamud.Game.ClientState.Objects.Enums { /// - /// The race of the character. + /// This enum describes the indices of the Customize array. /// - Race = 0x00, + // TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire) + public enum CustomizeIndex + { + /// + /// The race of the character. + /// + Race = 0x00, - /// - /// The gender of the character. - /// - Gender = 0x01, + /// + /// The gender of the character. + /// + Gender = 0x01, - /// - /// The tribe of the character. - /// - Tribe = 0x04, + /// + /// The tribe of the character. + /// + Tribe = 0x04, - /// - /// The height of the character. - /// - Height = 0x03, + /// + /// The height of the character. + /// + Height = 0x03, - /// - /// The model type of the character. - /// - ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features + /// + /// The model type of the character. + /// + ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features - /// - /// The face type of the character. - /// - FaceType = 0x05, + /// + /// The face type of the character. + /// + FaceType = 0x05, - /// - /// The hair of the character. - /// - HairStyle = 0x06, + /// + /// The hair of the character. + /// + HairStyle = 0x06, - /// - /// Whether or not the character has hair highlights. - /// - HasHighlights = 0x07, // negative to enable, positive to disable + /// + /// Whether or not the character has hair highlights. + /// + HasHighlights = 0x07, // negative to enable, positive to disable - /// - /// The skin color of the character. - /// - SkinColor = 0x08, + /// + /// The skin color of the character. + /// + SkinColor = 0x08, - /// - /// The eye color of the character. - /// - EyeColor = 0x09, // color of character's right eye + /// + /// The eye color of the character. + /// + EyeColor = 0x09, // color of character's right eye - /// - /// The hair color of the character. - /// - HairColor = 0x0A, // main color + /// + /// The hair color of the character. + /// + HairColor = 0x0A, // main color - /// - /// The highlights hair color of the character. - /// - HairColor2 = 0x0B, // highlights color + /// + /// The highlights hair color of the character. + /// + HairColor2 = 0x0B, // highlights color - /// - /// The face features of the character. - /// - FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small + /// + /// The face features of the character. + /// + FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small - /// - /// The color of the face features of the character. - /// - FaceFeaturesColor = 0x0D, + /// + /// The color of the face features of the character. + /// + FaceFeaturesColor = 0x0D, - /// - /// The eyebrows of the character. - /// - Eyebrows = 0x0E, + /// + /// The eyebrows of the character. + /// + Eyebrows = 0x0E, - /// - /// The 2nd eye color of the character. - /// - EyeColor2 = 0x0F, // color of character's left eye + /// + /// The 2nd eye color of the character. + /// + EyeColor2 = 0x0F, // color of character's left eye - /// - /// The eye shape of the character. - /// - EyeShape = 0x10, + /// + /// The eye shape of the character. + /// + EyeShape = 0x10, - /// - /// The nose shape of the character. - /// - NoseShape = 0x11, + /// + /// The nose shape of the character. + /// + NoseShape = 0x11, - /// - /// The jaw shape of the character. - /// - JawShape = 0x12, + /// + /// The jaw shape of the character. + /// + JawShape = 0x12, - /// - /// The lip style of the character. - /// - LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour) + /// + /// The lip style of the character. + /// + LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour) - /// - /// The lip color of the character. - /// - LipColor = 0x14, + /// + /// The lip color of the character. + /// + LipColor = 0x14, - /// - /// The race feature size of the character. - /// - RaceFeatureSize = 0x15, + /// + /// The race feature size of the character. + /// + RaceFeatureSize = 0x15, - /// - /// The race feature type of the character. - /// - RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client + /// + /// The race feature type of the character. + /// + RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client - /// - /// The bust size of the character. - /// - BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference + /// + /// The bust size of the character. + /// + BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference - /// - /// The face paint of the character. - /// - Facepaint = 0x18, + /// + /// The face paint of the character. + /// + Facepaint = 0x18, - /// - /// The face paint color of the character. - /// - FacepaintColor = 0x19, + /// + /// The face paint color of the character. + /// + FacepaintColor = 0x19, + } } diff --git a/Dalamud/Game/ClientState/Objects/Enums/ObjectKind.cs b/Dalamud/Game/ClientState/Objects/Enums/ObjectKind.cs index 6bfb80863..038bce143 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/ObjectKind.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/ObjectKind.cs @@ -1,82 +1,83 @@ -namespace Dalamud.Game.ClientState.Objects.Enums; - -/// -/// Enum describing possible entity kinds. -/// -public enum ObjectKind : byte +namespace Dalamud.Game.ClientState.Objects.Enums { /// - /// Invalid character. + /// Enum describing possible entity kinds. /// - None = 0x00, + public enum ObjectKind : byte + { + /// + /// Invalid character. + /// + None = 0x00, - /// - /// Objects representing player characters. - /// - Player = 0x01, + /// + /// Objects representing player characters. + /// + Player = 0x01, - /// - /// Objects representing battle NPCs. - /// - BattleNpc = 0x02, + /// + /// Objects representing battle NPCs. + /// + BattleNpc = 0x02, - /// - /// Objects representing event NPCs. - /// - EventNpc = 0x03, + /// + /// Objects representing event NPCs. + /// + EventNpc = 0x03, - /// - /// Objects representing treasures. - /// - Treasure = 0x04, + /// + /// Objects representing treasures. + /// + Treasure = 0x04, - /// - /// Objects representing aetherytes. - /// - Aetheryte = 0x05, + /// + /// Objects representing aetherytes. + /// + Aetheryte = 0x05, - /// - /// Objects representing gathering points. - /// - GatheringPoint = 0x06, + /// + /// Objects representing gathering points. + /// + GatheringPoint = 0x06, - /// - /// Objects representing event objects. - /// - EventObj = 0x07, + /// + /// Objects representing event objects. + /// + EventObj = 0x07, - /// - /// Objects representing mounts. - /// - MountType = 0x08, + /// + /// Objects representing mounts. + /// + MountType = 0x08, - /// - /// Objects representing minions. - /// - Companion = 0x09, // Minion + /// + /// Objects representing minions. + /// + Companion = 0x09, // Minion - /// - /// Objects representing retainers. - /// - Retainer = 0x0A, + /// + /// Objects representing retainers. + /// + Retainer = 0x0A, - /// - /// Objects representing area objects. - /// - Area = 0x0B, + /// + /// Objects representing area objects. + /// + Area = 0x0B, - /// - /// Objects representing housing objects. - /// - Housing = 0x0C, + /// + /// Objects representing housing objects. + /// + Housing = 0x0C, - /// - /// Objects representing cutscene objects. - /// - Cutscene = 0x0D, + /// + /// Objects representing cutscene objects. + /// + Cutscene = 0x0D, - /// - /// Objects representing card stand objects. - /// - CardStand = 0x0E, + /// + /// Objects representing card stand objects. + /// + CardStand = 0x0E, + } } diff --git a/Dalamud/Game/ClientState/Objects/Enums/StatusFlags.cs b/Dalamud/Game/ClientState/Objects/Enums/StatusFlags.cs index 2fed2a655..43587c66b 100644 --- a/Dalamud/Game/ClientState/Objects/Enums/StatusFlags.cs +++ b/Dalamud/Game/ClientState/Objects/Enums/StatusFlags.cs @@ -1,55 +1,56 @@ using System; -namespace Dalamud.Game.ClientState.Objects.Enums; - -/// -/// Enum describing possible status flags. -/// -[Flags] -public enum StatusFlags : byte +namespace Dalamud.Game.ClientState.Objects.Enums { /// - /// No status flags set. + /// Enum describing possible status flags. /// - None = 0, + [Flags] + public enum StatusFlags : byte + { + /// + /// No status flags set. + /// + None = 0, - /// - /// Hostile character. - /// - Hostile = 1, + /// + /// Hostile character. + /// + Hostile = 1, - /// - /// Character in combat. - /// - InCombat = 2, + /// + /// Character in combat. + /// + InCombat = 2, - /// - /// Character weapon is out. - /// - WeaponOut = 4, + /// + /// Character weapon is out. + /// + WeaponOut = 4, - /// - /// Character offhand is out. - /// - OffhandOut = 8, + /// + /// Character offhand is out. + /// + OffhandOut = 8, - /// - /// Character is a party member. - /// - PartyMember = 16, + /// + /// Character is a party member. + /// + PartyMember = 16, - /// - /// Character is a alliance member. - /// - AllianceMember = 32, + /// + /// Character is a alliance member. + /// + AllianceMember = 32, - /// - /// Character is in friend list. - /// - Friend = 64, + /// + /// Character is in friend list. + /// + Friend = 64, - /// - /// Character is casting. - /// - IsCasting = 128, + /// + /// Character is casting. + /// + IsCasting = 128, + } } diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index 83e1c77f5..df045be1c 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -9,139 +9,140 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Objects; - -/// -/// This collection represents the currently spawned FFXIV game objects. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed partial class ObjectTable +namespace Dalamud.Game.ClientState.Objects { - private const int ObjectTableLength = 424; - - private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. + /// This collection represents the currently spawned FFXIV game objects. /// - /// Client state address resolver. - internal ObjectTable(ClientStateAddressResolver addressResolver) + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed partial class ObjectTable { - this.address = addressResolver; + private const int ObjectTableLength = 424; - Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}"); - } + private readonly ClientStateAddressResolver address; - /// - /// Gets the address of the object table. - /// - public IntPtr Address => this.address.ObjectTable; - - /// - /// Gets the length of the object table. - /// - public int Length => ObjectTableLength; - - /// - /// Get an object at the specified spawn index. - /// - /// Spawn index. - /// An at the specified spawn index. - public GameObject? this[int index] - { - get + /// + /// Initializes a new instance of the class. + /// + /// Client state address resolver. + internal ObjectTable(ClientStateAddressResolver addressResolver) { - var address = this.GetObjectAddress(index); - return this.CreateObjectReference(address); + this.address = addressResolver; + + Log.Verbose($"Object table address 0x{this.address.ObjectTable.ToInt64():X}"); + } + + /// + /// Gets the address of the object table. + /// + public IntPtr Address => this.address.ObjectTable; + + /// + /// Gets the length of the object table. + /// + public int Length => ObjectTableLength; + + /// + /// Get an object at the specified spawn index. + /// + /// Spawn index. + /// An at the specified spawn index. + public GameObject? this[int index] + { + get + { + var address = this.GetObjectAddress(index); + return this.CreateObjectReference(address); + } + } + + /// + /// Search for a game object by their Object ID. + /// + /// Object ID to find. + /// A game object or null. + public GameObject? SearchById(uint objectId) + { + if (objectId is GameObject.InvalidGameObjectId or 0) + return null; + + foreach (var obj in this) + { + if (obj == null) + continue; + + if (obj.ObjectId == objectId) + return obj; + } + + return null; + } + + /// + /// Gets the address of the game object at the specified index of the object table. + /// + /// The index of the object. + /// The memory address of the object. + public unsafe IntPtr GetObjectAddress(int index) + { + if (index < 0 || index >= ObjectTableLength) + return IntPtr.Zero; + + return *(IntPtr*)(this.address.ObjectTable + (8 * index)); + } + + /// + /// Create a reference to an FFXIV game object. + /// + /// The address of the object in memory. + /// object or inheritor containing the requested data. + public unsafe GameObject? CreateObjectReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (address == IntPtr.Zero) + return null; + + var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)address; + var objKind = (ObjectKind)obj->ObjectKind; + return objKind switch + { + ObjectKind.Player => new PlayerCharacter(address), + ObjectKind.BattleNpc => new BattleNpc(address), + ObjectKind.EventObj => new EventObj(address), + ObjectKind.Companion => new Npc(address), + _ => new GameObject(address), + }; } } /// - /// Search for a game object by their Object ID. + /// This collection represents the currently spawned FFXIV game objects. /// - /// Object ID to find. - /// A game object or null. - public GameObject? SearchById(uint objectId) + public sealed partial class ObjectTable : IReadOnlyCollection { - if (objectId is GameObject.InvalidGameObjectId or 0) - return null; + /// + int IReadOnlyCollection.Count => this.Length; - foreach (var obj in this) + /// + public IEnumerator GetEnumerator() { - if (obj == null) - continue; + for (var i = 0; i < ObjectTableLength; i++) + { + var obj = this[i]; - if (obj.ObjectId == objectId) - return obj; + if (obj == null) + continue; + + yield return obj; + } } - return null; - } - - /// - /// Gets the address of the game object at the specified index of the object table. - /// - /// The index of the object. - /// The memory address of the object. - public unsafe IntPtr GetObjectAddress(int index) - { - if (index < 0 || index >= ObjectTableLength) - return IntPtr.Zero; - - return *(IntPtr*)(this.address.ObjectTable + (8 * index)); - } - - /// - /// Create a reference to an FFXIV game object. - /// - /// The address of the object in memory. - /// object or inheritor containing the requested data. - public unsafe GameObject? CreateObjectReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)address; - var objKind = (ObjectKind)obj->ObjectKind; - return objKind switch - { - ObjectKind.Player => new PlayerCharacter(address), - ObjectKind.BattleNpc => new BattleNpc(address), - ObjectKind.EventObj => new EventObj(address), - ObjectKind.Companion => new Npc(address), - _ => new GameObject(address), - }; + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } } - -/// -/// This collection represents the currently spawned FFXIV game objects. -/// -public sealed partial class ObjectTable : IReadOnlyCollection -{ - /// - int IReadOnlyCollection.Count => this.Length; - - /// - public IEnumerator GetEnumerator() - { - for (var i = 0; i < ObjectTableLength; i++) - { - var obj = this[i]; - - if (obj == null) - continue; - - yield return obj; - } - } - - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); -} diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/BattleNpc.cs b/Dalamud/Game/ClientState/Objects/SubKinds/BattleNpc.cs index cbdc44a4f..42bf16f15 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/BattleNpc.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/BattleNpc.cs @@ -2,28 +2,29 @@ using System; using Dalamud.Game.ClientState.Objects.Enums; -namespace Dalamud.Game.ClientState.Objects.Types; - -/// -/// This class represents a battle NPC. -/// -public unsafe class BattleNpc : BattleChara +namespace Dalamud.Game.ClientState.Objects.Types { /// - /// Initializes a new instance of the class. - /// Set up a new BattleNpc with the provided memory representation. + /// This class represents a battle NPC. /// - /// The address of this actor in memory. - internal BattleNpc(IntPtr address) - : base(address) + public unsafe class BattleNpc : BattleChara { + /// + /// Initializes a new instance of the class. + /// Set up a new BattleNpc with the provided memory representation. + /// + /// The address of this actor in memory. + internal BattleNpc(IntPtr address) + : base(address) + { + } + + /// + /// Gets the BattleNpc of this BattleNpc. + /// + public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind; + + /// + public override uint TargetObjectId => this.Struct->Character.TargetObjectID; } - - /// - /// Gets the BattleNpc of this BattleNpc. - /// - public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind)this.Struct->Character.GameObject.SubKind; - - /// - public override uint TargetObjectId => this.Struct->Character.TargetObjectID; } diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/EventObj.cs b/Dalamud/Game/ClientState/Objects/SubKinds/EventObj.cs index 9d977ec95..61710f32b 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/EventObj.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/EventObj.cs @@ -2,20 +2,21 @@ using System; using Dalamud.Game.ClientState.Objects.Types; -namespace Dalamud.Game.ClientState.Objects.SubKinds; - -/// -/// This class represents an EventObj. -/// -public unsafe class EventObj : GameObject +namespace Dalamud.Game.ClientState.Objects.SubKinds { /// - /// Initializes a new instance of the class. - /// Set up a new EventObj with the provided memory representation. + /// This class represents an EventObj. /// - /// The address of this event object in memory. - internal EventObj(IntPtr address) - : base(address) + public unsafe class EventObj : GameObject { + /// + /// Initializes a new instance of the class. + /// Set up a new EventObj with the provided memory representation. + /// + /// The address of this event object in memory. + internal EventObj(IntPtr address) + : base(address) + { + } } } diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/Npc.cs b/Dalamud/Game/ClientState/Objects/SubKinds/Npc.cs index 802e647ff..3d9ff17b9 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/Npc.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/Npc.cs @@ -2,20 +2,21 @@ using System; using Dalamud.Game.ClientState.Objects.Types; -namespace Dalamud.Game.ClientState.Objects.SubKinds; - -/// -/// This class represents a NPC. -/// -public unsafe class Npc : Character +namespace Dalamud.Game.ClientState.Objects.SubKinds { /// - /// Initializes a new instance of the class. - /// Set up a new NPC with the provided memory representation. + /// This class represents a NPC. /// - /// The address of this actor in memory. - internal Npc(IntPtr address) - : base(address) + public unsafe class Npc : Character { + /// + /// Initializes a new instance of the class. + /// Set up a new NPC with the provided memory representation. + /// + /// The address of this actor in memory. + internal Npc(IntPtr address) + : base(address) + { + } } } diff --git a/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs b/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs index e998ae5e4..121dae753 100644 --- a/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs +++ b/Dalamud/Game/ClientState/Objects/SubKinds/PlayerCharacter.cs @@ -3,35 +3,36 @@ using System; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Resolvers; -namespace Dalamud.Game.ClientState.Objects.SubKinds; - -/// -/// This class represents a player character. -/// -public unsafe class PlayerCharacter : BattleChara +namespace Dalamud.Game.ClientState.Objects.SubKinds { /// - /// Initializes a new instance of the class. - /// This represents a player character. + /// This class represents a player character. /// - /// The address of this actor in memory. - internal PlayerCharacter(IntPtr address) - : base(address) + public unsafe class PlayerCharacter : BattleChara { + /// + /// Initializes a new instance of the class. + /// This represents a player character. + /// + /// The address of this actor in memory. + internal PlayerCharacter(IntPtr address) + : base(address) + { + } + + /// + /// Gets the current world of the character. + /// + public ExcelResolver CurrentWorld => new(this.Struct->Character.CurrentWorld); + + /// + /// Gets the home world of the character. + /// + public ExcelResolver HomeWorld => new(this.Struct->Character.HomeWorld); + + /// + /// Gets the target actor ID of the PlayerCharacter. + /// + public override uint TargetObjectId => this.Struct->Character.PlayerTargetObjectID; } - - /// - /// Gets the current world of the character. - /// - public ExcelResolver CurrentWorld => new(this.Struct->Character.CurrentWorld); - - /// - /// Gets the home world of the character. - /// - public ExcelResolver HomeWorld => new(this.Struct->Character.HomeWorld); - - /// - /// Gets the target actor ID of the PlayerCharacter. - /// - public override uint TargetObjectId => this.Struct->Character.PlayerTargetObjectID; } diff --git a/Dalamud/Game/ClientState/Objects/TargetManager.cs b/Dalamud/Game/ClientState/Objects/TargetManager.cs index ea0eff382..d9372674c 100644 --- a/Dalamud/Game/ClientState/Objects/TargetManager.cs +++ b/Dalamud/Game/ClientState/Objects/TargetManager.cs @@ -4,160 +4,161 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.IoC; using Dalamud.IoC.Internal; -namespace Dalamud.Game.ClientState.Objects; - -/// -/// Get and set various kinds of targets for the player. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed unsafe class TargetManager +namespace Dalamud.Game.ClientState.Objects { - private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. + /// Get and set various kinds of targets for the player. /// - /// The ClientStateAddressResolver instance. - internal TargetManager(ClientStateAddressResolver addressResolver) + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed unsafe class TargetManager { - this.address = addressResolver; + private readonly ClientStateAddressResolver address; + + /// + /// Initializes a new instance of the class. + /// + /// The ClientStateAddressResolver instance. + internal TargetManager(ClientStateAddressResolver addressResolver) + { + this.address = addressResolver; + } + + /// + /// Gets the address of the target manager. + /// + public IntPtr Address => this.address.TargetManager; + + /// + /// Gets or sets the current target. + /// + public GameObject? Target + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->Target); + set => this.SetTarget(value); + } + + /// + /// Gets or sets the mouseover target. + /// + public GameObject? MouseOverTarget + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->MouseOverTarget); + set => this.SetMouseOverTarget(value); + } + + /// + /// Gets or sets the focus target. + /// + public GameObject? FocusTarget + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->FocusTarget); + set => this.SetFocusTarget(value); + } + + /// + /// Gets or sets the previous target. + /// + public GameObject? PreviousTarget + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->PreviousTarget); + set => this.SetPreviousTarget(value); + } + + /// + /// Gets or sets the soft target. + /// + public GameObject? SoftTarget + { + get => Service.Get().CreateObjectReference((IntPtr)Struct->SoftTarget); + set => this.SetSoftTarget(value); + } + + private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address; + + /// + /// Sets the current target. + /// + /// Actor to target. + public void SetTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the mouseover target. + /// + /// Actor to target. + public void SetMouseOverTarget(GameObject? actor) => this.SetMouseOverTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the focus target. + /// + /// Actor to target. + public void SetFocusTarget(GameObject? actor) => this.SetFocusTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the previous target. + /// + /// Actor to target. + public void SetPreviousTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the soft target. + /// + /// Actor to target. + public void SetSoftTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); + + /// + /// Sets the current target. + /// + /// Actor (address) to target. + public void SetTarget(IntPtr actorAddress) => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Sets the mouseover target. + /// + /// Actor (address) to target. + public void SetMouseOverTarget(IntPtr actorAddress) => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Sets the focus target. + /// + /// Actor (address) to target. + public void SetFocusTarget(IntPtr actorAddress) => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Sets the previous target. + /// + /// Actor (address) to target. + public void SetPreviousTarget(IntPtr actorAddress) => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Sets the soft target. + /// + /// Actor (address) to target. + public void SetSoftTarget(IntPtr actorAddress) => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; + + /// + /// Clears the current target. + /// + public void ClearTarget() => this.SetTarget(IntPtr.Zero); + + /// + /// Clears the mouseover target. + /// + public void ClearMouseOverTarget() => this.SetMouseOverTarget(IntPtr.Zero); + + /// + /// Clears the focus target. + /// + public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero); + + /// + /// Clears the previous target. + /// + public void ClearPreviousTarget() => this.SetPreviousTarget(IntPtr.Zero); + + /// + /// Clears the soft target. + /// + public void ClearSoftTarget() => this.SetSoftTarget(IntPtr.Zero); } - - /// - /// Gets the address of the target manager. - /// - public IntPtr Address => this.address.TargetManager; - - /// - /// Gets or sets the current target. - /// - public GameObject? Target - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->Target); - set => this.SetTarget(value); - } - - /// - /// Gets or sets the mouseover target. - /// - public GameObject? MouseOverTarget - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->MouseOverTarget); - set => this.SetMouseOverTarget(value); - } - - /// - /// Gets or sets the focus target. - /// - public GameObject? FocusTarget - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->FocusTarget); - set => this.SetFocusTarget(value); - } - - /// - /// Gets or sets the previous target. - /// - public GameObject? PreviousTarget - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->PreviousTarget); - set => this.SetPreviousTarget(value); - } - - /// - /// Gets or sets the soft target. - /// - public GameObject? SoftTarget - { - get => Service.Get().CreateObjectReference((IntPtr)Struct->SoftTarget); - set => this.SetSoftTarget(value); - } - - private FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Control.TargetSystem*)this.Address; - - /// - /// Sets the current target. - /// - /// Actor to target. - public void SetTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the mouseover target. - /// - /// Actor to target. - public void SetMouseOverTarget(GameObject? actor) => this.SetMouseOverTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the focus target. - /// - /// Actor to target. - public void SetFocusTarget(GameObject? actor) => this.SetFocusTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the previous target. - /// - /// Actor to target. - public void SetPreviousTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the soft target. - /// - /// Actor to target. - public void SetSoftTarget(GameObject? actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero); - - /// - /// Sets the current target. - /// - /// Actor (address) to target. - public void SetTarget(IntPtr actorAddress) => Struct->Target = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Sets the mouseover target. - /// - /// Actor (address) to target. - public void SetMouseOverTarget(IntPtr actorAddress) => Struct->MouseOverTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Sets the focus target. - /// - /// Actor (address) to target. - public void SetFocusTarget(IntPtr actorAddress) => Struct->FocusTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Sets the previous target. - /// - /// Actor (address) to target. - public void SetPreviousTarget(IntPtr actorAddress) => Struct->PreviousTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Sets the soft target. - /// - /// Actor (address) to target. - public void SetSoftTarget(IntPtr actorAddress) => Struct->SoftTarget = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)actorAddress; - - /// - /// Clears the current target. - /// - public void ClearTarget() => this.SetTarget(IntPtr.Zero); - - /// - /// Clears the mouseover target. - /// - public void ClearMouseOverTarget() => this.SetMouseOverTarget(IntPtr.Zero); - - /// - /// Clears the focus target. - /// - public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero); - - /// - /// Clears the previous target. - /// - public void ClearPreviousTarget() => this.SetPreviousTarget(IntPtr.Zero); - - /// - /// Clears the soft target. - /// - public void ClearSoftTarget() => this.SetSoftTarget(IntPtr.Zero); } diff --git a/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs b/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs index 8db830491..633b5eb38 100644 --- a/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs +++ b/Dalamud/Game/ClientState/Objects/Types/BattleChara.cs @@ -2,65 +2,66 @@ using System; using Dalamud.Game.ClientState.Statuses; -namespace Dalamud.Game.ClientState.Objects.Types; - -/// -/// This class represents the battle characters. -/// -public unsafe class BattleChara : Character +namespace Dalamud.Game.ClientState.Objects.Types { /// - /// Initializes a new instance of the class. - /// This represents a battle character. + /// This class represents the battle characters. /// - /// The address of this character in memory. - internal BattleChara(IntPtr address) - : base(address) + public unsafe class BattleChara : Character { + /// + /// Initializes a new instance of the class. + /// This represents a battle character. + /// + /// The address of this character in memory. + internal BattleChara(IntPtr address) + : base(address) + { + } + + /// + /// Gets the current status effects. + /// + public StatusList StatusList => new(&this.Struct->StatusManager); + + /// + /// Gets a value indicating whether the chara is currently casting. + /// + public bool IsCasting => this.Struct->SpellCastInfo.IsCasting > 0; + + /// + /// Gets a value indicating whether the cast is interruptible. + /// + public bool IsCastInterruptible => this.Struct->SpellCastInfo.Interruptible > 0; + + /// + /// Gets the spell action type of the spell being cast by the actor. + /// + public byte CastActionType => (byte)this.Struct->SpellCastInfo.ActionType; + + /// + /// Gets the spell action ID of the spell being cast by the actor. + /// + public uint CastActionId => this.Struct->SpellCastInfo.ActionID; + + /// + /// Gets the object ID of the target currently being cast at by the chara. + /// + public uint CastTargetObjectId => this.Struct->SpellCastInfo.CastTargetID; + + /// + /// Gets the current casting time of the spell being cast by the chara. + /// + public float CurrentCastTime => this.Struct->SpellCastInfo.CurrentCastTime; + + /// + /// Gets the total casting time of the spell being cast by the chara. + /// + public float TotalCastTime => this.Struct->SpellCastInfo.TotalCastTime; + + /// + /// Gets the underlying structure. + /// + private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address; } - - /// - /// Gets the current status effects. - /// - public StatusList StatusList => new(&this.Struct->StatusManager); - - /// - /// Gets a value indicating whether the chara is currently casting. - /// - public bool IsCasting => this.Struct->SpellCastInfo.IsCasting > 0; - - /// - /// Gets a value indicating whether the cast is interruptible. - /// - public bool IsCastInterruptible => this.Struct->SpellCastInfo.Interruptible > 0; - - /// - /// Gets the spell action type of the spell being cast by the actor. - /// - public byte CastActionType => (byte)this.Struct->SpellCastInfo.ActionType; - - /// - /// Gets the spell action ID of the spell being cast by the actor. - /// - public uint CastActionId => this.Struct->SpellCastInfo.ActionID; - - /// - /// Gets the object ID of the target currently being cast at by the chara. - /// - public uint CastTargetObjectId => this.Struct->SpellCastInfo.CastTargetID; - - /// - /// Gets the current casting time of the spell being cast by the chara. - /// - public float CurrentCastTime => this.Struct->SpellCastInfo.CurrentCastTime; - - /// - /// Gets the total casting time of the spell being cast by the chara. - /// - public float TotalCastTime => this.Struct->SpellCastInfo.TotalCastTime; - - /// - /// Gets the underlying structure. - /// - private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)this.Address; } diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs index 77d2c3e57..ff412d8e5 100644 --- a/Dalamud/Game/ClientState/Objects/Types/Character.cs +++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs @@ -5,101 +5,102 @@ using Dalamud.Game.ClientState.Resolvers; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -namespace Dalamud.Game.ClientState.Objects.Types; - -/// -/// This class represents the base for non-static entities. -/// -public unsafe class Character : GameObject +namespace Dalamud.Game.ClientState.Objects.Types { /// - /// Initializes a new instance of the class. - /// This represents a non-static entity. + /// This class represents the base for non-static entities. /// - /// The address of this character in memory. - internal Character(IntPtr address) - : base(address) + public unsafe class Character : GameObject { + /// + /// Initializes a new instance of the class. + /// This represents a non-static entity. + /// + /// The address of this character in memory. + internal Character(IntPtr address) + : base(address) + { + } + + /// + /// Gets the current HP of this Chara. + /// + public uint CurrentHp => this.Struct->Health; + + /// + /// Gets the maximum HP of this Chara. + /// + public uint MaxHp => this.Struct->MaxHealth; + + /// + /// Gets the current MP of this Chara. + /// + public uint CurrentMp => this.Struct->Mana; + + /// + /// Gets the maximum MP of this Chara. + /// + public uint MaxMp => this.Struct->MaxMana; + + /// + /// Gets the current GP of this Chara. + /// + public uint CurrentGp => this.Struct->GatheringPoints; + + /// + /// Gets the maximum GP of this Chara. + /// + public uint MaxGp => this.Struct->MaxGatheringPoints; + + /// + /// Gets the current CP of this Chara. + /// + public uint CurrentCp => this.Struct->CraftingPoints; + + /// + /// Gets the maximum CP of this Chara. + /// + public uint MaxCp => this.Struct->MaxCraftingPoints; + + /// + /// Gets the ClassJob of this Chara. + /// + public ExcelResolver ClassJob => new(this.Struct->ClassJob); + + /// + /// Gets the level of this Chara. + /// + public byte Level => this.Struct->Level; + + /// + /// Gets a byte array describing the visual appearance of this Chara. + /// Indexed by . + /// + public byte[] Customize => MemoryHelper.Read((IntPtr)this.Struct->CustomizeData, 28); + + /// + /// Gets the Free Company tag of this chara. + /// + public SeString CompanyTag => MemoryHelper.ReadSeString((IntPtr)this.Struct->FreeCompanyTag, 6); + + /// + /// Gets the target object ID of the character. + /// + public override uint TargetObjectId => this.Struct->TargetObjectID; + + /// + /// Gets the name ID of the character. + /// + public uint NameId => this.Struct->NameID; + + /// + /// Gets the status flags. + /// + public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags; + + /// + /// Gets the underlying structure. + /// + private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address; } - - /// - /// Gets the current HP of this Chara. - /// - public uint CurrentHp => this.Struct->Health; - - /// - /// Gets the maximum HP of this Chara. - /// - public uint MaxHp => this.Struct->MaxHealth; - - /// - /// Gets the current MP of this Chara. - /// - public uint CurrentMp => this.Struct->Mana; - - /// - /// Gets the maximum MP of this Chara. - /// - public uint MaxMp => this.Struct->MaxMana; - - /// - /// Gets the current GP of this Chara. - /// - public uint CurrentGp => this.Struct->GatheringPoints; - - /// - /// Gets the maximum GP of this Chara. - /// - public uint MaxGp => this.Struct->MaxGatheringPoints; - - /// - /// Gets the current CP of this Chara. - /// - public uint CurrentCp => this.Struct->CraftingPoints; - - /// - /// Gets the maximum CP of this Chara. - /// - public uint MaxCp => this.Struct->MaxCraftingPoints; - - /// - /// Gets the ClassJob of this Chara. - /// - public ExcelResolver ClassJob => new(this.Struct->ClassJob); - - /// - /// Gets the level of this Chara. - /// - public byte Level => this.Struct->Level; - - /// - /// Gets a byte array describing the visual appearance of this Chara. - /// Indexed by . - /// - public byte[] Customize => MemoryHelper.Read((IntPtr)this.Struct->CustomizeData, 28); - - /// - /// Gets the Free Company tag of this chara. - /// - public SeString CompanyTag => MemoryHelper.ReadSeString((IntPtr)this.Struct->FreeCompanyTag, 6); - - /// - /// Gets the target object ID of the character. - /// - public override uint TargetObjectId => this.Struct->TargetObjectID; - - /// - /// Gets the name ID of the character. - /// - public uint NameId => this.Struct->NameID; - - /// - /// Gets the status flags. - /// - public StatusFlags StatusFlags => (StatusFlags)this.Struct->StatusFlags; - - /// - /// Gets the underlying structure. - /// - private protected new FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)this.Address; } diff --git a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs index 199522b9a..6cd84e92b 100644 --- a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs +++ b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs @@ -5,170 +5,171 @@ using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; -namespace Dalamud.Game.ClientState.Objects.Types; - -/// -/// This class represents a GameObject in FFXIV. -/// -public unsafe partial class GameObject : IEquatable +namespace Dalamud.Game.ClientState.Objects.Types { /// - /// IDs of non-networked GameObjects. + /// This class represents a GameObject in FFXIV. /// - public const uint InvalidGameObjectId = 0xE0000000; - - /// - /// Initializes a new instance of the class. - /// - /// The address of this game object in memory. - internal GameObject(IntPtr address) + public unsafe partial class GameObject : IEquatable { - this.Address = address; + /// + /// IDs of non-networked GameObjects. + /// + public const uint InvalidGameObjectId = 0xE0000000; + + /// + /// Initializes a new instance of the class. + /// + /// The address of this game object in memory. + internal GameObject(IntPtr address) + { + this.Address = address; + } + + /// + /// Gets the address of the game object in memory. + /// + public IntPtr Address { get; } + + /// + /// Gets the Dalamud instance. + /// + private protected Dalamud Dalamud { get; } + + /// + /// This allows you to if (obj) {...} to check for validity. + /// + /// The actor to check. + /// True or false. + public static implicit operator bool(GameObject? gameObject) => IsValid(gameObject); + + public static bool operator ==(GameObject? gameObject1, GameObject? gameObject2) + { + // Using == results in a stack overflow. + if (gameObject1 is null || gameObject2 is null) + return Equals(gameObject1, gameObject2); + + return gameObject1.Equals(gameObject2); + } + + public static bool operator !=(GameObject? actor1, GameObject? actor2) => !(actor1 == actor2); + + /// + /// Gets a value indicating whether this actor is still valid in memory. + /// + /// The actor to check. + /// True or false. + public static bool IsValid(GameObject? actor) + { + var clientState = Service.Get(); + + if (actor is null) + return false; + + if (clientState.LocalContentId == 0) + return false; + + return true; + } + + /// + /// Gets a value indicating whether this actor is still valid in memory. + /// + /// True or false. + public bool IsValid() => IsValid(this); + + /// + bool IEquatable.Equals(GameObject other) => this.ObjectId == other?.ObjectId; + + /// + public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as GameObject); + + /// + public override int GetHashCode() => this.ObjectId.GetHashCode(); } /// - /// Gets the address of the game object in memory. + /// This class represents a basic actor (GameObject) in FFXIV. /// - public IntPtr Address { get; } - - /// - /// Gets the Dalamud instance. - /// - private protected Dalamud Dalamud { get; } - - /// - /// This allows you to if (obj) {...} to check for validity. - /// - /// The actor to check. - /// True or false. - public static implicit operator bool(GameObject? gameObject) => IsValid(gameObject); - - public static bool operator ==(GameObject? gameObject1, GameObject? gameObject2) + public unsafe partial class GameObject { - // Using == results in a stack overflow. - if (gameObject1 is null || gameObject2 is null) - return Equals(gameObject1, gameObject2); + /// + /// Gets the name of this . + /// + public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 64); - return gameObject1.Equals(gameObject2); + /// + /// Gets the object ID of this . + /// + public uint ObjectId => this.Struct->ObjectID; + + /// + /// Gets the data ID for linking to other respective game data. + /// + public uint DataId => this.Struct->DataID; + + /// + /// Gets the ID of this GameObject's owner. + /// + public uint OwnerId => this.Struct->OwnerID; + + /// + /// Gets the entity kind of this . + /// See the ObjectKind enum for possible values. + /// + public ObjectKind ObjectKind => (ObjectKind)this.Struct->ObjectKind; + + /// + /// Gets the sub kind of this Actor. + /// + public byte SubKind => this.Struct->SubKind; + + /// + /// Gets the X distance from the local player in yalms. + /// + public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX; + + /// + /// Gets the Y distance from the local player in yalms. + /// + public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ; + + /// + /// Gets the position of this . + /// + public Vector3 Position => new(this.Struct->Position.X, this.Struct->Position.Y, this.Struct->Position.Z); + + /// + /// Gets the rotation of this . + /// This ranges from -pi to pi radians. + /// + public float Rotation => this.Struct->Rotation; + + /// + /// Gets the hitbox radius of this . + /// + public float HitboxRadius => this.Struct->HitboxRadius; + + /// + /// Gets the current target of the game object. + /// + public virtual uint TargetObjectId => 0; + + /// + /// Gets the target object of the game object. + /// + /// + /// This iterates the actor table, it should be used with care. + /// + // TODO: Fix for non-networked GameObjects + public virtual GameObject? TargetObject => Service.Get().SearchById(this.TargetObjectId); + + /// + /// Gets the underlying structure. + /// + private protected FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address; + + /// + public override string ToString() => $"{this.ObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}"; } - - public static bool operator !=(GameObject? actor1, GameObject? actor2) => !(actor1 == actor2); - - /// - /// Gets a value indicating whether this actor is still valid in memory. - /// - /// The actor to check. - /// True or false. - public static bool IsValid(GameObject? actor) - { - var clientState = Service.Get(); - - if (actor is null) - return false; - - if (clientState.LocalContentId == 0) - return false; - - return true; - } - - /// - /// Gets a value indicating whether this actor is still valid in memory. - /// - /// True or false. - public bool IsValid() => IsValid(this); - - /// - bool IEquatable.Equals(GameObject other) => this.ObjectId == other?.ObjectId; - - /// - public override bool Equals(object obj) => ((IEquatable)this).Equals(obj as GameObject); - - /// - public override int GetHashCode() => this.ObjectId.GetHashCode(); -} - -/// -/// This class represents a basic actor (GameObject) in FFXIV. -/// -public unsafe partial class GameObject -{ - /// - /// Gets the name of this . - /// - public SeString Name => MemoryHelper.ReadSeString((IntPtr)this.Struct->Name, 64); - - /// - /// Gets the object ID of this . - /// - public uint ObjectId => this.Struct->ObjectID; - - /// - /// Gets the data ID for linking to other respective game data. - /// - public uint DataId => this.Struct->DataID; - - /// - /// Gets the ID of this GameObject's owner. - /// - public uint OwnerId => this.Struct->OwnerID; - - /// - /// Gets the entity kind of this . - /// See the ObjectKind enum for possible values. - /// - public ObjectKind ObjectKind => (ObjectKind)this.Struct->ObjectKind; - - /// - /// Gets the sub kind of this Actor. - /// - public byte SubKind => this.Struct->SubKind; - - /// - /// Gets the X distance from the local player in yalms. - /// - public byte YalmDistanceX => this.Struct->YalmDistanceFromPlayerX; - - /// - /// Gets the Y distance from the local player in yalms. - /// - public byte YalmDistanceZ => this.Struct->YalmDistanceFromPlayerZ; - - /// - /// Gets the position of this . - /// - public Vector3 Position => new(this.Struct->Position.X, this.Struct->Position.Y, this.Struct->Position.Z); - - /// - /// Gets the rotation of this . - /// This ranges from -pi to pi radians. - /// - public float Rotation => this.Struct->Rotation; - - /// - /// Gets the hitbox radius of this . - /// - public float HitboxRadius => this.Struct->HitboxRadius; - - /// - /// Gets the current target of the game object. - /// - public virtual uint TargetObjectId => 0; - - /// - /// Gets the target object of the game object. - /// - /// - /// This iterates the actor table, it should be used with care. - /// - // TODO: Fix for non-networked GameObjects - public virtual GameObject? TargetObject => Service.Get().SearchById(this.TargetObjectId); - - /// - /// Gets the underlying structure. - /// - private protected FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)this.Address; - - /// - public override string ToString() => $"{this.ObjectId:X}({this.Name.TextValue} - {this.ObjectKind}) at {this.Address:X}"; } diff --git a/Dalamud/Game/ClientState/Party/PartyList.cs b/Dalamud/Game/ClientState/Party/PartyList.cs index 2b9de5dd8..bd303401f 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -7,177 +7,178 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.ClientState.Party; - -/// -/// This collection represents the actors present in your party or alliance. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed unsafe partial class PartyList +namespace Dalamud.Game.ClientState.Party { - private const int GroupLength = 8; - private const int AllianceLength = 20; - - private readonly ClientStateAddressResolver address; - /// - /// Initializes a new instance of the class. + /// This collection represents the actors present in your party or alliance. /// - /// Client state address resolver. - internal PartyList(ClientStateAddressResolver addressResolver) + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed unsafe partial class PartyList { - this.address = addressResolver; + private const int GroupLength = 8; + private const int AllianceLength = 20; - Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}"); - } + private readonly ClientStateAddressResolver address; - /// - /// Gets the amount of party members the local player has. - /// - public int Length => this.GroupManagerStruct->MemberCount; - - /// - /// Gets the index of the party leader. - /// - public uint PartyLeaderIndex => this.GroupManagerStruct->PartyLeaderIndex; - - /// - /// Gets a value indicating whether this group is an alliance. - /// - public bool IsAlliance => this.GroupManagerStruct->IsAlliance; - - /// - /// Gets the address of the Group Manager. - /// - public IntPtr GroupManagerAddress => this.address.GroupManager; - - /// - /// Gets the address of the party list within the group manager. - /// - public IntPtr GroupListAddress => (IntPtr)GroupManagerStruct->PartyMembers; - - /// - /// Gets the address of the alliance member list within the group manager. - /// - public IntPtr AllianceListAddress => (IntPtr)this.GroupManagerStruct->AllianceMembers; - - private static int PartyMemberSize { get; } = Marshal.SizeOf(); - - private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress; - - /// - /// Get a party member at the specified spawn index. - /// - /// Spawn index. - /// A at the specified spawn index. - public PartyMember? this[int index] - { - get + /// + /// Initializes a new instance of the class. + /// + /// Client state address resolver. + internal PartyList(ClientStateAddressResolver addressResolver) { - // Normally using Length results in a recursion crash, however we know the party size via ptr. - if (index < 0 || index >= this.Length) + this.address = addressResolver; + + Log.Verbose($"Group manager address 0x{this.address.GroupManager.ToInt64():X}"); + } + + /// + /// Gets the amount of party members the local player has. + /// + public int Length => this.GroupManagerStruct->MemberCount; + + /// + /// Gets the index of the party leader. + /// + public uint PartyLeaderIndex => this.GroupManagerStruct->PartyLeaderIndex; + + /// + /// Gets a value indicating whether this group is an alliance. + /// + public bool IsAlliance => this.GroupManagerStruct->IsAlliance; + + /// + /// Gets the address of the Group Manager. + /// + public IntPtr GroupManagerAddress => this.address.GroupManager; + + /// + /// Gets the address of the party list within the group manager. + /// + public IntPtr GroupListAddress => (IntPtr)GroupManagerStruct->PartyMembers; + + /// + /// Gets the address of the alliance member list within the group manager. + /// + public IntPtr AllianceListAddress => (IntPtr)this.GroupManagerStruct->AllianceMembers; + + private static int PartyMemberSize { get; } = Marshal.SizeOf(); + + private FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager* GroupManagerStruct => (FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager*)this.GroupManagerAddress; + + /// + /// Get a party member at the specified spawn index. + /// + /// Spawn index. + /// A at the specified spawn index. + public PartyMember? this[int index] + { + get + { + // Normally using Length results in a recursion crash, however we know the party size via ptr. + if (index < 0 || index >= this.Length) + return null; + + if (this.Length > GroupLength) + { + var addr = this.GetAllianceMemberAddress(index); + return this.CreateAllianceMemberReference(addr); + } + else + { + var addr = this.GetPartyMemberAddress(index); + return this.CreatePartyMemberReference(addr); + } + } + } + + /// + /// Gets the address of the party member at the specified index of the party list. + /// + /// The index of the party member. + /// The memory address of the party member. + public IntPtr GetPartyMemberAddress(int index) + { + if (index < 0 || index >= GroupLength) + return IntPtr.Zero; + + return this.GroupListAddress + (index * PartyMemberSize); + } + + /// + /// Create a reference to an FFXIV party member. + /// + /// The address of the party member in memory. + /// The party member object containing the requested data. + public PartyMember? CreatePartyMemberReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) return null; - if (this.Length > GroupLength) - { - var addr = this.GetAllianceMemberAddress(index); - return this.CreateAllianceMemberReference(addr); - } - else - { - var addr = this.GetPartyMemberAddress(index); - return this.CreatePartyMemberReference(addr); - } + if (address == IntPtr.Zero) + return null; + + return new PartyMember(address); } - } - /// - /// Gets the address of the party member at the specified index of the party list. - /// - /// The index of the party member. - /// The memory address of the party member. - public IntPtr GetPartyMemberAddress(int index) - { - if (index < 0 || index >= GroupLength) - return IntPtr.Zero; - - return this.GroupListAddress + (index * PartyMemberSize); - } - - /// - /// Create a reference to an FFXIV party member. - /// - /// The address of the party member in memory. - /// The party member object containing the requested data. - public PartyMember? CreatePartyMemberReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - return new PartyMember(address); - } - - /// - /// Gets the address of the alliance member at the specified index of the alliance list. - /// - /// The index of the alliance member. - /// The memory address of the alliance member. - public IntPtr GetAllianceMemberAddress(int index) - { - if (index < 0 || index >= AllianceLength) - return IntPtr.Zero; - - return this.AllianceListAddress + (index * PartyMemberSize); - } - - /// - /// Create a reference to an FFXIV alliance member. - /// - /// The address of the alliance member in memory. - /// The party member object containing the requested data. - public PartyMember? CreateAllianceMemberReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - return new PartyMember(address); - } -} - -/// -/// This collection represents the party members present in your party or alliance. -/// -public sealed partial class PartyList : IReadOnlyCollection -{ - /// - int IReadOnlyCollection.Count => this.Length; - - /// - public IEnumerator GetEnumerator() - { - // Normally using Length results in a recursion crash, however we know the party size via ptr. - for (var i = 0; i < this.Length; i++) + /// + /// Gets the address of the alliance member at the specified index of the alliance list. + /// + /// The index of the alliance member. + /// The memory address of the alliance member. + public IntPtr GetAllianceMemberAddress(int index) { - var member = this[i]; + if (index < 0 || index >= AllianceLength) + return IntPtr.Zero; - if (member == null) - break; + return this.AllianceListAddress + (index * PartyMemberSize); + } - yield return member; + /// + /// Create a reference to an FFXIV alliance member. + /// + /// The address of the alliance member in memory. + /// The party member object containing the requested data. + public PartyMember? CreateAllianceMemberReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (address == IntPtr.Zero) + return null; + + return new PartyMember(address); } } - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + /// + /// This collection represents the party members present in your party or alliance. + /// + public sealed partial class PartyList : IReadOnlyCollection + { + /// + int IReadOnlyCollection.Count => this.Length; + + /// + public IEnumerator GetEnumerator() + { + // Normally using Length results in a recursion crash, however we know the party size via ptr. + for (var i = 0; i < this.Length; i++) + { + var member = this[i]; + + if (member == null) + break; + + yield return member; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } } diff --git a/Dalamud/Game/ClientState/Party/PartyMember.cs b/Dalamud/Game/ClientState/Party/PartyMember.cs index 889e8479a..9d70592f7 100644 --- a/Dalamud/Game/ClientState/Party/PartyMember.cs +++ b/Dalamud/Game/ClientState/Party/PartyMember.cs @@ -9,104 +9,105 @@ using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory; using JetBrains.Annotations; -namespace Dalamud.Game.ClientState.Party; - -/// -/// This class represents a party member in the group manager. -/// -public unsafe class PartyMember +namespace Dalamud.Game.ClientState.Party { /// - /// Initializes a new instance of the class. + /// This class represents a party member in the group manager. /// - /// Address of the party member. - internal PartyMember(IntPtr address) + public unsafe class PartyMember { - this.Address = address; + /// + /// Initializes a new instance of the class. + /// + /// Address of the party member. + internal PartyMember(IntPtr address) + { + this.Address = address; + } + + /// + /// Gets the address of this party member in memory. + /// + public IntPtr Address { get; } + + /// + /// Gets a list of buffs or debuffs applied to this party member. + /// + public StatusList Statuses => new(&this.Struct->StatusManager); + + /// + /// Gets the position of the party member. + /// + public Vector3 Position => new(this.Struct->X, this.Struct->Y, this.Struct->Z); + + /// + /// Gets the content ID of the party member. + /// + public long ContentId => this.Struct->ContentID; + + /// + /// Gets the actor ID of this party member. + /// + public uint ObjectId => this.Struct->ObjectID; + + /// + /// Gets the actor associated with this buddy. + /// + /// + /// This iterates the actor table, it should be used with care. + /// + public GameObject? GameObject => Service.Get().SearchById(this.ObjectId); + + /// + /// Gets the current HP of this party member. + /// + public uint CurrentHP => this.Struct->CurrentHP; + + /// + /// Gets the maximum HP of this party member. + /// + public uint MaxHP => this.Struct->MaxHP; + + /// + /// Gets the current MP of this party member. + /// + public ushort CurrentMP => this.Struct->CurrentMP; + + /// + /// Gets the maximum MP of this party member. + /// + public ushort MaxMP => this.Struct->MaxMP; + + /// + /// Gets the territory this party member is located in. + /// + public ExcelResolver Territory => new(this.Struct->TerritoryType); + + /// + /// Gets the World this party member resides in. + /// + public ExcelResolver World => new(this.Struct->HomeWorld); + + /// + /// Gets the displayname of this party member. + /// + public SeString Name => MemoryHelper.ReadSeString((IntPtr)Struct->Name, 0x40); + + /// + /// Gets the sex of this party member. + /// + public byte Sex => this.Struct->Sex; + + /// + /// Gets the classjob of this party member. + /// + public ExcelResolver ClassJob => new(this.Struct->ClassJob); + + /// + /// Gets the level of this party member. + /// + public byte Level => this.Struct->Level; + + private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address; } - - /// - /// Gets the address of this party member in memory. - /// - public IntPtr Address { get; } - - /// - /// Gets a list of buffs or debuffs applied to this party member. - /// - public StatusList Statuses => new(&this.Struct->StatusManager); - - /// - /// Gets the position of the party member. - /// - public Vector3 Position => new(this.Struct->X, this.Struct->Y, this.Struct->Z); - - /// - /// Gets the content ID of the party member. - /// - public long ContentId => this.Struct->ContentID; - - /// - /// Gets the actor ID of this party member. - /// - public uint ObjectId => this.Struct->ObjectID; - - /// - /// Gets the actor associated with this buddy. - /// - /// - /// This iterates the actor table, it should be used with care. - /// - public GameObject? GameObject => Service.Get().SearchById(this.ObjectId); - - /// - /// Gets the current HP of this party member. - /// - public uint CurrentHP => this.Struct->CurrentHP; - - /// - /// Gets the maximum HP of this party member. - /// - public uint MaxHP => this.Struct->MaxHP; - - /// - /// Gets the current MP of this party member. - /// - public ushort CurrentMP => this.Struct->CurrentMP; - - /// - /// Gets the maximum MP of this party member. - /// - public ushort MaxMP => this.Struct->MaxMP; - - /// - /// Gets the territory this party member is located in. - /// - public ExcelResolver Territory => new(this.Struct->TerritoryType); - - /// - /// Gets the World this party member resides in. - /// - public ExcelResolver World => new(this.Struct->HomeWorld); - - /// - /// Gets the displayname of this party member. - /// - public SeString Name => MemoryHelper.ReadSeString((IntPtr)Struct->Name, 0x40); - - /// - /// Gets the sex of this party member. - /// - public byte Sex => this.Struct->Sex; - - /// - /// Gets the classjob of this party member. - /// - public ExcelResolver ClassJob => new(this.Struct->ClassJob); - - /// - /// Gets the level of this party member. - /// - public byte Level => this.Struct->Level; - - private FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Group.PartyMember*)this.Address; } diff --git a/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs b/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs index 5cc36a652..5a5af1080 100644 --- a/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs +++ b/Dalamud/Game/ClientState/Resolvers/ExcelResolver{T}.cs @@ -1,30 +1,31 @@ using Dalamud.Data; using Lumina.Excel; -namespace Dalamud.Game.ClientState.Resolvers; - -/// -/// This object resolves a rowID within an Excel sheet. -/// -/// The type of Lumina sheet to resolve. -public class ExcelResolver where T : ExcelRow +namespace Dalamud.Game.ClientState.Resolvers { /// - /// Initializes a new instance of the class. + /// This object resolves a rowID within an Excel sheet. /// - /// The ID of the classJob. - internal ExcelResolver(uint id) + /// The type of Lumina sheet to resolve. + public class ExcelResolver where T : ExcelRow { - this.Id = id; + /// + /// Initializes a new instance of the class. + /// + /// The ID of the classJob. + internal ExcelResolver(uint id) + { + this.Id = id; + } + + /// + /// Gets the ID to be resolved. + /// + public uint Id { get; } + + /// + /// Gets GameData linked to this excel row. + /// + public T GameData => Service.Get().GetExcelSheet().GetRow(this.Id); } - - /// - /// Gets the ID to be resolved. - /// - public uint Id { get; } - - /// - /// Gets GameData linked to this excel row. - /// - public T GameData => Service.Get().GetExcelSheet().GetRow(this.Id); } diff --git a/Dalamud/Game/ClientState/Statuses/Status.cs b/Dalamud/Game/ClientState/Statuses/Status.cs index 4373186a4..7dd88f8eb 100644 --- a/Dalamud/Game/ClientState/Statuses/Status.cs +++ b/Dalamud/Game/ClientState/Statuses/Status.cs @@ -4,64 +4,65 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Resolvers; -namespace Dalamud.Game.ClientState.Statuses; - -/// -/// This class represents a status effect an actor is afflicted by. -/// -public unsafe class Status +namespace Dalamud.Game.ClientState.Statuses { /// - /// Initializes a new instance of the class. + /// This class represents a status effect an actor is afflicted by. /// - /// Status address. - internal Status(IntPtr address) + public unsafe class Status { - this.Address = address; + /// + /// Initializes a new instance of the class. + /// + /// Status address. + internal Status(IntPtr address) + { + this.Address = address; + } + + /// + /// Gets the address of the status in memory. + /// + public IntPtr Address { get; } + + /// + /// Gets the status ID of this status. + /// + public uint StatusId => this.Struct->StatusID; + + /// + /// Gets the GameData associated with this status. + /// + public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver(this.Struct->StatusID).GameData; + + /// + /// Gets the parameter value of the status. + /// + public byte Param => this.Struct->Param; + + /// + /// Gets the stack count of this status. + /// + public byte StackCount => this.Struct->StackCount; + + /// + /// Gets the time remaining of this status. + /// + public float RemainingTime => this.Struct->RemainingTime; + + /// + /// Gets the source ID of this status. + /// + public uint SourceID => this.Struct->SourceID; + + /// + /// Gets the source actor associated with this status. + /// + /// + /// This iterates the actor table, it should be used with care. + /// + public GameObject? SourceObject => Service.Get().SearchById(this.SourceID); + + private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address; } - - /// - /// Gets the address of the status in memory. - /// - public IntPtr Address { get; } - - /// - /// Gets the status ID of this status. - /// - public uint StatusId => this.Struct->StatusID; - - /// - /// Gets the GameData associated with this status. - /// - public Lumina.Excel.GeneratedSheets.Status GameData => new ExcelResolver(this.Struct->StatusID).GameData; - - /// - /// Gets the parameter value of the status. - /// - public byte Param => this.Struct->Param; - - /// - /// Gets the stack count of this status. - /// - public byte StackCount => this.Struct->StackCount; - - /// - /// Gets the time remaining of this status. - /// - public float RemainingTime => this.Struct->RemainingTime; - - /// - /// Gets the source ID of this status. - /// - public uint SourceID => this.Struct->SourceID; - - /// - /// Gets the source actor associated with this status. - /// - /// - /// This iterates the actor table, it should be used with care. - /// - public GameObject? SourceObject => Service.Get().SearchById(this.SourceID); - - private FFXIVClientStructs.FFXIV.Client.Game.Status* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Status*)this.Address; } diff --git a/Dalamud/Game/ClientState/Statuses/StatusList.cs b/Dalamud/Game/ClientState/Statuses/StatusList.cs index bcff50360..591988e35 100644 --- a/Dalamud/Game/ClientState/Statuses/StatusList.cs +++ b/Dalamud/Game/ClientState/Statuses/StatusList.cs @@ -3,158 +3,159 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; -namespace Dalamud.Game.ClientState.Statuses; - -/// -/// This collection represents the status effects an actor is afflicted by. -/// -public sealed unsafe partial class StatusList +namespace Dalamud.Game.ClientState.Statuses { - private const int StatusListLength = 30; - /// - /// Initializes a new instance of the class. + /// This collection represents the status effects an actor is afflicted by. /// - /// Address of the status list. - internal StatusList(IntPtr address) + public sealed unsafe partial class StatusList { - this.Address = address; - } + private const int StatusListLength = 30; - /// - /// Initializes a new instance of the class. - /// - /// Pointer to the status list. - internal unsafe StatusList(void* pointer) - : this((IntPtr)pointer) - { - } - - /// - /// Gets the address of the status list in memory. - /// - public IntPtr Address { get; } - - /// - /// Gets the amount of status effect slots the actor has. - /// - public int Length => StatusListLength; - - private static int StatusSize { get; } = Marshal.SizeOf(); - - private FFXIVClientStructs.FFXIV.Client.Game.StatusManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.StatusManager*)this.Address; - - /// - /// Get a status effect at the specified index. - /// - /// Status Index. - /// The status at the specified index. - public Status? this[int index] - { - get + /// + /// Initializes a new instance of the class. + /// + /// Address of the status list. + internal StatusList(IntPtr address) { - if (index < 0 || index > StatusListLength) + this.Address = address; + } + + /// + /// Initializes a new instance of the class. + /// + /// Pointer to the status list. + internal unsafe StatusList(void* pointer) + : this((IntPtr)pointer) + { + } + + /// + /// Gets the address of the status list in memory. + /// + public IntPtr Address { get; } + + /// + /// Gets the amount of status effect slots the actor has. + /// + public int Length => StatusListLength; + + private static int StatusSize { get; } = Marshal.SizeOf(); + + private FFXIVClientStructs.FFXIV.Client.Game.StatusManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.StatusManager*)this.Address; + + /// + /// Get a status effect at the specified index. + /// + /// Status Index. + /// The status at the specified index. + public Status? this[int index] + { + get + { + if (index < 0 || index > StatusListLength) + return null; + + var addr = this.GetStatusAddress(index); + return CreateStatusReference(addr); + } + } + + /// + /// Create a reference to an FFXIV actor status list. + /// + /// The address of the status list in memory. + /// The status object containing the requested data. + public static StatusList? CreateStatusListReference(IntPtr address) + { + // The use case for CreateStatusListReference and CreateStatusReference to be static is so + // fake status lists can be generated. Since they aren't exposed as services, it's either + // here or somewhere else. + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) return null; - var addr = this.GetStatusAddress(index); - return CreateStatusReference(addr); + if (address == IntPtr.Zero) + return null; + + return new StatusList(address); + } + + /// + /// Create a reference to an FFXIV actor status. + /// + /// The address of the status effect in memory. + /// The status object containing the requested data. + public static Status? CreateStatusReference(IntPtr address) + { + var clientState = Service.Get(); + + if (clientState.LocalContentId == 0) + return null; + + if (address == IntPtr.Zero) + return null; + + return new Status(address); + } + + /// + /// Gets the address of the party member at the specified index of the party list. + /// + /// The index of the party member. + /// The memory address of the party member. + public IntPtr GetStatusAddress(int index) + { + if (index < 0 || index >= StatusListLength) + return IntPtr.Zero; + + return (IntPtr)(this.Struct->Status + (index * StatusSize)); } } /// - /// Create a reference to an FFXIV actor status list. + /// This collection represents the status effects an actor is afflicted by. /// - /// The address of the status list in memory. - /// The status object containing the requested data. - public static StatusList? CreateStatusListReference(IntPtr address) + public sealed partial class StatusList : IReadOnlyCollection, ICollection { - // The use case for CreateStatusListReference and CreateStatusReference to be static is so - // fake status lists can be generated. Since they aren't exposed as services, it's either - // here or somewhere else. - var clientState = Service.Get(); + /// + int IReadOnlyCollection.Count => this.Length; - if (clientState.LocalContentId == 0) - return null; + /// + int ICollection.Count => this.Length; - if (address == IntPtr.Zero) - return null; + /// + bool ICollection.IsSynchronized => false; - return new StatusList(address); - } + /// + object ICollection.SyncRoot => this; - /// - /// Create a reference to an FFXIV actor status. - /// - /// The address of the status effect in memory. - /// The status object containing the requested data. - public static Status? CreateStatusReference(IntPtr address) - { - var clientState = Service.Get(); - - if (clientState.LocalContentId == 0) - return null; - - if (address == IntPtr.Zero) - return null; - - return new Status(address); - } - - /// - /// Gets the address of the party member at the specified index of the party list. - /// - /// The index of the party member. - /// The memory address of the party member. - public IntPtr GetStatusAddress(int index) - { - if (index < 0 || index >= StatusListLength) - return IntPtr.Zero; - - return (IntPtr)(this.Struct->Status + (index * StatusSize)); - } -} - -/// -/// This collection represents the status effects an actor is afflicted by. -/// -public sealed partial class StatusList : IReadOnlyCollection, ICollection -{ - /// - int IReadOnlyCollection.Count => this.Length; - - /// - int ICollection.Count => this.Length; - - /// - bool ICollection.IsSynchronized => false; - - /// - object ICollection.SyncRoot => this; - - /// - public IEnumerator GetEnumerator() - { - for (var i = 0; i < StatusListLength; i++) + /// + public IEnumerator GetEnumerator() { - var status = this[i]; + for (var i = 0; i < StatusListLength; i++) + { + var status = this[i]; - if (status == null || status.StatusId == 0) - continue; + if (status == null || status.StatusId == 0) + continue; - yield return status; + yield return status; + } } - } - /// - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + /// + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - /// - void ICollection.CopyTo(Array array, int index) - { - for (var i = 0; i < this.Length; i++) + /// + void ICollection.CopyTo(Array array, int index) { - array.SetValue(this[i], index); - index++; + for (var i = 0; i < this.Length; i++) + { + array.SetValue(this[i], index); + index++; + } } } } diff --git a/Dalamud/Game/ClientState/Structs/StatusEffect.cs b/Dalamud/Game/ClientState/Structs/StatusEffect.cs index 2a60a7d3b..6d4c2fd77 100644 --- a/Dalamud/Game/ClientState/Structs/StatusEffect.cs +++ b/Dalamud/Game/ClientState/Structs/StatusEffect.cs @@ -1,35 +1,36 @@ using System.Runtime.InteropServices; -namespace Dalamud.Game.ClientState.Structs; - -/// -/// Native memory representation of a FFXIV status effect. -/// -[StructLayout(LayoutKind.Sequential)] -public struct StatusEffect +namespace Dalamud.Game.ClientState.Structs { /// - /// The effect ID. + /// Native memory representation of a FFXIV status effect. /// - public short EffectId; + [StructLayout(LayoutKind.Sequential)] + public struct StatusEffect + { + /// + /// The effect ID. + /// + public short EffectId; - /// - /// How many stacks are present. - /// - public byte StackCount; + /// + /// How many stacks are present. + /// + public byte StackCount; - /// - /// Additional parameters. - /// - public byte Param; + /// + /// Additional parameters. + /// + public byte Param; - /// - /// The duration remaining. - /// - public float Duration; + /// + /// The duration remaining. + /// + public float Duration; - /// - /// The ID of the actor that caused this effect. - /// - public int OwnerId; + /// + /// The ID of the actor that caused this effect. + /// + public int OwnerId; + } } diff --git a/Dalamud/Game/Command/CommandInfo.cs b/Dalamud/Game/Command/CommandInfo.cs index 9b559599a..0eb97177c 100644 --- a/Dalamud/Game/Command/CommandInfo.cs +++ b/Dalamud/Game/Command/CommandInfo.cs @@ -1,47 +1,48 @@ using System.Reflection; -namespace Dalamud.Game.Command; - -/// -/// This class describes a registered command. -/// -public sealed class CommandInfo +namespace Dalamud.Game.Command { /// - /// Initializes a new instance of the class. - /// Create a new CommandInfo with the provided handler. + /// This class describes a registered command. /// - /// The method to call when the command is run. - public CommandInfo(HandlerDelegate handler) + public sealed class CommandInfo { - this.Handler = handler; - this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name; + /// + /// Initializes a new instance of the class. + /// Create a new CommandInfo with the provided handler. + /// + /// The method to call when the command is run. + public CommandInfo(HandlerDelegate handler) + { + this.Handler = handler; + this.LoaderAssemblyName = Assembly.GetCallingAssembly()?.GetName()?.Name; + } + + /// + /// The function to be executed when the command is dispatched. + /// + /// The command itself. + /// The arguments supplied to the command, ready for parsing. + public delegate void HandlerDelegate(string command, string arguments); + + /// + /// Gets a which will be called when the command is dispatched. + /// + public HandlerDelegate Handler { get; } + + /// + /// Gets or sets the help message for this command. + /// + public string HelpMessage { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether if this command should be shown in the help output. + /// + public bool ShowInHelp { get; set; } = true; + + /// + /// Gets or sets the name of the assembly responsible for this command. + /// + internal string LoaderAssemblyName { get; set; } = string.Empty; } - - /// - /// The function to be executed when the command is dispatched. - /// - /// The command itself. - /// The arguments supplied to the command, ready for parsing. - public delegate void HandlerDelegate(string command, string arguments); - - /// - /// Gets a which will be called when the command is dispatched. - /// - public HandlerDelegate Handler { get; } - - /// - /// Gets or sets the help message for this command. - /// - public string HelpMessage { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether if this command should be shown in the help output. - /// - public bool ShowInHelp { get; set; } = true; - - /// - /// Gets or sets the name of the assembly responsible for this command. - /// - internal string LoaderAssemblyName { get; set; } = string.Empty; } diff --git a/Dalamud/Game/Command/CommandManager.cs b/Dalamud/Game/Command/CommandManager.cs index ba88f446d..b1038f294 100644 --- a/Dalamud/Game/Command/CommandManager.cs +++ b/Dalamud/Game/Command/CommandManager.cs @@ -10,166 +10,167 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.Command; - -/// -/// This class manages registered in-game slash commands. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class CommandManager +namespace Dalamud.Game.Command { - private readonly Dictionary commandMap = new(); - private readonly Regex commandRegexEn = new(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled); - private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?.+)$", RegexOptions.Compiled); - private readonly Regex commandRegexDe = new(@"^„(?.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled); - private readonly Regex commandRegexFr = new(@"^La commande texte “(?.+)” n'existe pas\.$", RegexOptions.Compiled); - private readonly Regex commandRegexCn = new(@"^“(?.+)”(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled); - private readonly Regex currentLangCommandRegex; - /// - /// Initializes a new instance of the class. + /// This class manages registered in-game slash commands. /// - internal CommandManager() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class CommandManager { - var startInfo = Service.Get(); + private readonly Dictionary commandMap = new(); + private readonly Regex commandRegexEn = new(@"^The command (?.+) does not exist\.$", RegexOptions.Compiled); + private readonly Regex commandRegexJp = new(@"^そのコマンドはありません。: (?.+)$", RegexOptions.Compiled); + private readonly Regex commandRegexDe = new(@"^„(?.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled); + private readonly Regex commandRegexFr = new(@"^La commande texte “(?.+)” n'existe pas\.$", RegexOptions.Compiled); + private readonly Regex commandRegexCn = new(@"^“(?.+)”(出现问题:该命令不存在|出現問題:該命令不存在)。$", RegexOptions.Compiled); + private readonly Regex currentLangCommandRegex; - this.currentLangCommandRegex = startInfo.Language switch + /// + /// Initializes a new instance of the class. + /// + internal CommandManager() { - ClientLanguage.Japanese => this.commandRegexJp, - ClientLanguage.English => this.commandRegexEn, - ClientLanguage.German => this.commandRegexDe, - ClientLanguage.French => this.commandRegexFr, - _ => this.currentLangCommandRegex, - }; + var startInfo = Service.Get(); - Service.Get().CheckMessageHandled += this.OnCheckMessageHandled; - } - - /// - /// Gets a read-only list of all registered commands. - /// - public ReadOnlyDictionary Commands => new(this.commandMap); - - /// - /// Process a command in full. - /// - /// The full command string. - /// True if the command was found and dispatched. - public bool ProcessCommand(string content) - { - string command; - string argument; - - var separatorPosition = content.IndexOf(' '); - if (separatorPosition == -1 || separatorPosition + 1 >= content.Length) - { - // If no space was found or ends with the space. Process them as a no argument - if (separatorPosition + 1 >= content.Length) + this.currentLangCommandRegex = startInfo.Language switch { - // Remove the trailing space - command = content.Substring(0, separatorPosition); + ClientLanguage.Japanese => this.commandRegexJp, + ClientLanguage.English => this.commandRegexEn, + ClientLanguage.German => this.commandRegexDe, + ClientLanguage.French => this.commandRegexFr, + _ => this.currentLangCommandRegex, + }; + + Service.Get().CheckMessageHandled += this.OnCheckMessageHandled; + } + + /// + /// Gets a read-only list of all registered commands. + /// + public ReadOnlyDictionary Commands => new(this.commandMap); + + /// + /// Process a command in full. + /// + /// The full command string. + /// True if the command was found and dispatched. + public bool ProcessCommand(string content) + { + string command; + string argument; + + var separatorPosition = content.IndexOf(' '); + if (separatorPosition == -1 || separatorPosition + 1 >= content.Length) + { + // If no space was found or ends with the space. Process them as a no argument + if (separatorPosition + 1 >= content.Length) + { + // Remove the trailing space + command = content.Substring(0, separatorPosition); + } + else + { + command = content; + } + + argument = string.Empty; } else { - command = content; + // e.g.) + // /testcommand arg1 + // => Total of 17 chars + // => command: 0-12 (12 chars) + // => argument: 13-17 (4 chars) + // => content.IndexOf(' ') == 12 + command = content.Substring(0, separatorPosition); + + var argStart = separatorPosition + 1; + argument = content[argStart..]; } - argument = string.Empty; - } - else - { - // e.g.) - // /testcommand arg1 - // => Total of 17 chars - // => command: 0-12 (12 chars) - // => argument: 13-17 (4 chars) - // => content.IndexOf(' ') == 12 - command = content.Substring(0, separatorPosition); + if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found. + return false; - var argStart = separatorPosition + 1; - argument = content[argStart..]; - } - - if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found. - return false; - - this.DispatchCommand(command, argument, handler); - return true; - } - - /// - /// Dispatch the handling of a command. - /// - /// The command to dispatch. - /// The provided arguments. - /// A object describing this command. - public void DispatchCommand(string command, string argument, CommandInfo info) - { - try - { - info.Handler(command, argument); - } - catch (Exception ex) - { - Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument); - } - } - - /// - /// Add a command handler, which you can use to add your own custom commands to the in-game chat. - /// - /// The command to register. - /// A object describing the command. - /// If adding was successful. - public bool AddHandler(string command, CommandInfo info) - { - if (info == null) - throw new ArgumentNullException(nameof(info), "Command handler is null."); - - try - { - this.commandMap.Add(command, info); + this.DispatchCommand(command, argument, handler); return true; } - catch (ArgumentException) - { - Log.Error("Command {CommandName} is already registered.", command); - return false; - } - } - /// - /// Remove a command from the command handlers. - /// - /// The command to remove. - /// If the removal was successful. - public bool RemoveHandler(string command) - { - return this.commandMap.Remove(command); - } - - private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) - { - if (type == XivChatType.ErrorMessage && senderId == 0) + /// + /// Dispatch the handling of a command. + /// + /// The command to dispatch. + /// The provided arguments. + /// A object describing this command. + public void DispatchCommand(string command, string argument, CommandInfo info) { - var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"]; - if (cmdMatch.Success) + try { - // Yes, it's a chat command. - var command = cmdMatch.Value; - if (this.ProcessCommand(command)) isHandled = true; + info.Handler(command, argument); } - else + catch (Exception ex) { - // Always match for china, since they patch in language files without changing the ClientLanguage. - cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"]; + Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command, argument); + } + } + + /// + /// Add a command handler, which you can use to add your own custom commands to the in-game chat. + /// + /// The command to register. + /// A object describing the command. + /// If adding was successful. + public bool AddHandler(string command, CommandInfo info) + { + if (info == null) + throw new ArgumentNullException(nameof(info), "Command handler is null."); + + try + { + this.commandMap.Add(command, info); + return true; + } + catch (ArgumentException) + { + Log.Error("Command {CommandName} is already registered.", command); + return false; + } + } + + /// + /// Remove a command from the command handlers. + /// + /// The command to remove. + /// If the removal was successful. + public bool RemoveHandler(string command) + { + return this.commandMap.Remove(command); + } + + private void OnCheckMessageHandled(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled) + { + if (type == XivChatType.ErrorMessage && senderId == 0) + { + var cmdMatch = this.currentLangCommandRegex.Match(message.TextValue).Groups["command"]; if (cmdMatch.Success) { - // Yes, it's a Chinese fallback chat command. + // Yes, it's a chat command. var command = cmdMatch.Value; if (this.ProcessCommand(command)) isHandled = true; } + else + { + // Always match for china, since they patch in language files without changing the ClientLanguage. + cmdMatch = this.commandRegexCn.Match(message.TextValue).Groups["command"]; + if (cmdMatch.Success) + { + // Yes, it's a Chinese fallback chat command. + var command = cmdMatch.Value; + if (this.ProcessCommand(command)) isHandled = true; + } + } } } } diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs index 1e49a348e..38a978e1f 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -16,300 +16,301 @@ using Dalamud.IoC.Internal; using Dalamud.Utility; using Serilog; -namespace Dalamud.Game; - -/// -/// This class represents the Framework of the native game client and grants access to various subsystems. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class Framework : IDisposable +namespace Dalamud.Game { - private static Stopwatch statsStopwatch = new(); - private Stopwatch updateStopwatch = new(); - - private bool tier2Initialized = false; - private bool tier3Initialized = false; - private bool tierInitError = false; - - private Hook updateHook; - private Hook destroyHook; - private Hook realDestroyHook; - /// - /// Initializes a new instance of the class. + /// This class represents the Framework of the native game client and grants access to various subsystems. /// - internal Framework() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class Framework : IDisposable { - this.Address = new FrameworkAddressResolver(); - this.Address.Setup(); + private static Stopwatch statsStopwatch = new(); + private Stopwatch updateStopwatch = new(); - Log.Verbose($"Framework address 0x{this.Address.BaseAddress.ToInt64():X}"); - if (this.Address.BaseAddress == IntPtr.Zero) + private bool tier2Initialized = false; + private bool tier3Initialized = false; + private bool tierInitError = false; + + private Hook updateHook; + private Hook destroyHook; + private Hook realDestroyHook; + + /// + /// Initializes a new instance of the class. + /// + internal Framework() { - throw new InvalidOperationException("Framework is not initalized yet."); + this.Address = new FrameworkAddressResolver(); + this.Address.Setup(); + + Log.Verbose($"Framework address 0x{this.Address.BaseAddress.ToInt64():X}"); + if (this.Address.BaseAddress == IntPtr.Zero) + { + throw new InvalidOperationException("Framework is not initalized yet."); + } + + // Hook virtual functions + this.HookVTable(); } - // Hook virtual functions - this.HookVTable(); - } + /// + /// A delegate type used with the event. + /// + /// The Framework instance. + public delegate void OnUpdateDelegate(Framework framework); - /// - /// A delegate type used with the event. - /// - /// The Framework instance. - public delegate void OnUpdateDelegate(Framework framework); + /// + /// A delegate type used during the native Framework::destroy. + /// + /// The native Framework address. + /// A value indicating if the call was successful. + public delegate bool OnRealDestroyDelegate(IntPtr framework); - /// - /// A delegate type used during the native Framework::destroy. - /// - /// The native Framework address. - /// A value indicating if the call was successful. - public delegate bool OnRealDestroyDelegate(IntPtr framework); + /// + /// A delegate type used during the native Framework::free. + /// + /// The native Framework address. + public delegate IntPtr OnDestroyDelegate(); - /// - /// A delegate type used during the native Framework::free. - /// - /// The native Framework address. - public delegate IntPtr OnDestroyDelegate(); + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate bool OnUpdateDetour(IntPtr framework); - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate bool OnUpdateDetour(IntPtr framework); + private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate - private delegate IntPtr OnDestroyDetour(); // OnDestroyDelegate + /// + /// Event that gets fired every time the game framework updates. + /// + public event OnUpdateDelegate Update; - /// - /// Event that gets fired every time the game framework updates. - /// - public event OnUpdateDelegate Update; + /// + /// Gets or sets a value indicating whether the collection of stats is enabled. + /// + public static bool StatsEnabled { get; set; } - /// - /// Gets or sets a value indicating whether the collection of stats is enabled. - /// - public static bool StatsEnabled { get; set; } + /// + /// Gets the stats history mapping. + /// + public static Dictionary> StatsHistory { get; } = new(); - /// - /// Gets the stats history mapping. - /// - public static Dictionary> StatsHistory { get; } = new(); + /// + /// Gets a raw pointer to the instance of Client::Framework. + /// + public FrameworkAddressResolver Address { get; } - /// - /// Gets a raw pointer to the instance of Client::Framework. - /// - public FrameworkAddressResolver Address { get; } + /// + /// Gets the last time that the Framework Update event was triggered. + /// + public DateTime LastUpdate { get; private set; } = DateTime.MinValue; - /// - /// Gets the last time that the Framework Update event was triggered. - /// - public DateTime LastUpdate { get; private set; } = DateTime.MinValue; + /// + /// Gets the last time in UTC that the Framework Update event was triggered. + /// + public DateTime LastUpdateUTC { get; private set; } = DateTime.MinValue; - /// - /// Gets the last time in UTC that the Framework Update event was triggered. - /// - public DateTime LastUpdateUTC { get; private set; } = DateTime.MinValue; + /// + /// Gets the delta between the last Framework Update and the currently executing one. + /// + public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero; - /// - /// Gets the delta between the last Framework Update and the currently executing one. - /// - public TimeSpan UpdateDelta { get; private set; } = TimeSpan.Zero; + /// + /// Gets or sets a value indicating whether to dispatch update events. + /// + internal bool DispatchUpdateEvents { get; set; } = true; - /// - /// Gets or sets a value indicating whether to dispatch update events. - /// - internal bool DispatchUpdateEvents { get; set; } = true; - - /// - /// Enable this module. - /// - public void Enable() - { - Service.Set(); - Service.Get().Enable(); - Service.Get().Enable(); - - this.updateHook.Enable(); - this.destroyHook.Enable(); - this.realDestroyHook.Enable(); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - Service.GetNullable()?.Dispose(); - Service.GetNullable()?.Dispose(); - - this.updateHook?.Disable(); - this.destroyHook?.Disable(); - this.realDestroyHook?.Disable(); - Thread.Sleep(500); - - this.updateHook?.Dispose(); - this.destroyHook?.Dispose(); - this.realDestroyHook?.Dispose(); - - this.updateStopwatch.Reset(); - statsStopwatch.Reset(); - } - - private void HookVTable() - { - var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress); - // Virtual function layout: - // .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor - // .rdata:00000001411F1FE8 dq offset Xiv__Framework__init - // .rdata:00000001411F1FF0 dq offset Xiv__Framework__destroy - // .rdata:00000001411F1FF8 dq offset Xiv__Framework__free - // .rdata:00000001411F2000 dq offset Xiv__Framework__update - - var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4); - this.updateHook = new Hook(pUpdate, this.HandleFrameworkUpdate); - - var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3); - this.destroyHook = new Hook(pDestroy, this.HandleFrameworkDestroy); - - var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2); - this.realDestroyHook = new Hook(pRealDestroy, this.HandleRealDestroy); - } - - private bool HandleFrameworkUpdate(IntPtr framework) - { - // If any of the tier loads failed, just go to the original code. - if (this.tierInitError) - goto original; - - var dalamud = Service.Get(); - - // If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously - if (!this.tier2Initialized) + /// + /// Enable this module. + /// + public void Enable() { - this.tier2Initialized = dalamud.LoadTier2(); + Service.Set(); + Service.Get().Enable(); + Service.Get().Enable(); + + this.updateHook.Enable(); + this.destroyHook.Enable(); + this.realDestroyHook.Enable(); + } + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + Service.GetNullable()?.Dispose(); + Service.GetNullable()?.Dispose(); + + this.updateHook?.Disable(); + this.destroyHook?.Disable(); + this.realDestroyHook?.Disable(); + Thread.Sleep(500); + + this.updateHook?.Dispose(); + this.destroyHook?.Dispose(); + this.realDestroyHook?.Dispose(); + + this.updateStopwatch.Reset(); + statsStopwatch.Reset(); + } + + private void HookVTable() + { + var vtable = Marshal.ReadIntPtr(this.Address.BaseAddress); + // Virtual function layout: + // .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor + // .rdata:00000001411F1FE8 dq offset Xiv__Framework__init + // .rdata:00000001411F1FF0 dq offset Xiv__Framework__destroy + // .rdata:00000001411F1FF8 dq offset Xiv__Framework__free + // .rdata:00000001411F2000 dq offset Xiv__Framework__update + + var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4); + this.updateHook = new Hook(pUpdate, this.HandleFrameworkUpdate); + + var pDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 3); + this.destroyHook = new Hook(pDestroy, this.HandleFrameworkDestroy); + + var pRealDestroy = Marshal.ReadIntPtr(vtable, IntPtr.Size * 2); + this.realDestroyHook = new Hook(pRealDestroy, this.HandleRealDestroy); + } + + private bool HandleFrameworkUpdate(IntPtr framework) + { + // If any of the tier loads failed, just go to the original code. + if (this.tierInitError) + goto original; + + var dalamud = Service.Get(); + + // If this is the first time we are running this loop, we need to init Dalamud subsystems synchronously if (!this.tier2Initialized) - this.tierInitError = true; + { + this.tier2Initialized = dalamud.LoadTier2(); + if (!this.tier2Initialized) + this.tierInitError = true; - goto original; - } + goto original; + } - // Plugins expect the interface to be available and ready, so we need to wait with plugins until we have init'd ImGui - if (!this.tier3Initialized && Service.GetNullable()?.IsReady == true) - { - this.tier3Initialized = dalamud.LoadTier3(); - if (!this.tier3Initialized) - this.tierInitError = true; + // Plugins expect the interface to be available and ready, so we need to wait with plugins until we have init'd ImGui + if (!this.tier3Initialized && Service.GetNullable()?.IsReady == true) + { + this.tier3Initialized = dalamud.LoadTier3(); + if (!this.tier3Initialized) + this.tierInitError = true; - goto original; - } - - try - { - Service.Get().UpdateQueue(); - Service.Get().UpdateQueue(); - Service.Get().UpdateQueue(); - } - catch (Exception ex) - { - Log.Error(ex, "Exception while handling Framework::Update hook."); - } - - if (this.DispatchUpdateEvents) - { - this.updateStopwatch.Stop(); - this.UpdateDelta = TimeSpan.FromMilliseconds(this.updateStopwatch.ElapsedMilliseconds); - this.updateStopwatch.Restart(); - - this.LastUpdate = DateTime.Now; - this.LastUpdateUTC = DateTime.UtcNow; + goto original; + } try { - if (StatsEnabled && this.Update != null) - { - // Stat Tracking for Framework Updates - var invokeList = this.Update.GetInvocationList(); - var notUpdated = StatsHistory.Keys.ToList(); - - // Individually invoke OnUpdate handlers and time them. - foreach (var d in invokeList) - { - statsStopwatch.Restart(); - d.Method.Invoke(d.Target, new object[] { this }); - statsStopwatch.Stop(); - - var key = $"{d.Target}::{d.Method.Name}"; - if (notUpdated.Contains(key)) - notUpdated.Remove(key); - - if (!StatsHistory.ContainsKey(key)) - StatsHistory.Add(key, new List()); - - StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds); - - if (StatsHistory[key].Count > 1000) - { - StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000); - } - } - - // Cleanup handlers that are no longer being called - foreach (var key in notUpdated) - { - if (StatsHistory[key].Count > 0) - { - StatsHistory[key].RemoveAt(0); - } - else - { - StatsHistory.Remove(key); - } - } - } - else - { - this.Update?.Invoke(this); - } + Service.Get().UpdateQueue(); + Service.Get().UpdateQueue(); + Service.Get().UpdateQueue(); } catch (Exception ex) { - Log.Error(ex, "Exception while dispatching Framework::Update event."); + Log.Error(ex, "Exception while handling Framework::Update hook."); } + + if (this.DispatchUpdateEvents) + { + this.updateStopwatch.Stop(); + this.UpdateDelta = TimeSpan.FromMilliseconds(this.updateStopwatch.ElapsedMilliseconds); + this.updateStopwatch.Restart(); + + this.LastUpdate = DateTime.Now; + this.LastUpdateUTC = DateTime.UtcNow; + + try + { + if (StatsEnabled && this.Update != null) + { + // Stat Tracking for Framework Updates + var invokeList = this.Update.GetInvocationList(); + var notUpdated = StatsHistory.Keys.ToList(); + + // Individually invoke OnUpdate handlers and time them. + foreach (var d in invokeList) + { + statsStopwatch.Restart(); + d.Method.Invoke(d.Target, new object[] { this }); + statsStopwatch.Stop(); + + var key = $"{d.Target}::{d.Method.Name}"; + if (notUpdated.Contains(key)) + notUpdated.Remove(key); + + if (!StatsHistory.ContainsKey(key)) + StatsHistory.Add(key, new List()); + + StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds); + + if (StatsHistory[key].Count > 1000) + { + StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000); + } + } + + // Cleanup handlers that are no longer being called + foreach (var key in notUpdated) + { + if (StatsHistory[key].Count > 0) + { + StatsHistory[key].RemoveAt(0); + } + else + { + StatsHistory.Remove(key); + } + } + } + else + { + this.Update?.Invoke(this); + } + } + catch (Exception ex) + { + Log.Error(ex, "Exception while dispatching Framework::Update event."); + } + } + + original: + return this.updateHook.Original(framework); } - original: - return this.updateHook.Original(framework); - } - - private bool HandleRealDestroy(IntPtr framework) - { - if (this.DispatchUpdateEvents) + private bool HandleRealDestroy(IntPtr framework) { - Log.Information("Framework::Destroy!"); + if (this.DispatchUpdateEvents) + { + Log.Information("Framework::Destroy!"); + + var dalamud = Service.Get(); + dalamud.DisposePlugins(); + + Log.Information("Framework::Destroy OK!"); + } + + this.DispatchUpdateEvents = false; + + return this.realDestroyHook.Original(framework); + } + + private IntPtr HandleFrameworkDestroy() + { + Log.Information("Framework::Free!"); + + // Store the pointer to the original trampoline location + var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original); var dalamud = Service.Get(); - dalamud.DisposePlugins(); + dalamud.Unload(); + dalamud.WaitForUnloadFinish(); - Log.Information("Framework::Destroy OK!"); + Log.Information("Framework::Free OK!"); + + // Return the original trampoline location to cleanly exit + return originalPtr; } - - this.DispatchUpdateEvents = false; - - return this.realDestroyHook.Original(framework); - } - - private IntPtr HandleFrameworkDestroy() - { - Log.Information("Framework::Free!"); - - // Store the pointer to the original trampoline location - var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original); - - var dalamud = Service.Get(); - dalamud.Unload(); - dalamud.WaitForUnloadFinish(); - - Log.Information("Framework::Free OK!"); - - // Return the original trampoline location to cleanly exit - return originalPtr; } } diff --git a/Dalamud/Game/FrameworkAddressResolver.cs b/Dalamud/Game/FrameworkAddressResolver.cs index 6dea4f579..7bcae5045 100644 --- a/Dalamud/Game/FrameworkAddressResolver.cs +++ b/Dalamud/Game/FrameworkAddressResolver.cs @@ -1,54 +1,57 @@ using System; using System.Runtime.InteropServices; -namespace Dalamud.Game; +using Dalamud.Game.Internal; -/// -/// The address resolver for the class. -/// -public sealed class FrameworkAddressResolver : BaseAddressResolver +namespace Dalamud.Game { /// - /// Gets the base address native Framework class. + /// The address resolver for the class. /// - public IntPtr BaseAddress { get; private set; } - - /// - /// Gets the address for the native GuiManager class. - /// - public IntPtr GuiManager { get; private set; } - - /// - /// Gets the address for the native ScriptManager class. - /// - public IntPtr ScriptManager { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) + public sealed class FrameworkAddressResolver : BaseAddressResolver { - this.SetupFramework(sig); + /// + /// Gets the base address native Framework class. + /// + public IntPtr BaseAddress { get; private set; } - // Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h] - // Xiv__Framework__GetGuiManager+F 000 retn - this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08); + /// + /// Gets the address for the native GuiManager class. + /// + public IntPtr GuiManager { get; private set; } - // Called from Framework::Init - this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here - } + /// + /// Gets the address for the native ScriptManager class. + /// + public IntPtr ScriptManager { get; private set; } - private void SetupFramework(SigScanner scanner) - { - // Dissasembly of part of the .dtor - // 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0 - // 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130 - // 00007FF701AD666A | 48 8D ?? ?? ?? 00 00 | LEA RCX,QWORD PTR DS:[RBX + 2C38] - // 00007FF701AD6671 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E2A7D0 - // 00007FF701AD6676 | 48 8D ?? ?? ?? ?? ?? | LEA RAX,QWORD PTR DS:[7FF702C31F80 - var fwDtor = scanner.ScanText("48 C7 05 ?? ?? ?? ?? 00 00 00 00 E8 ?? ?? ?? ?? 48 8D ?? ?? ?? 00 00 E8 ?? ?? ?? ?? 48 8D"); - var fwOffset = Marshal.ReadInt32(fwDtor + 3); - var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset); + /// + protected override void Setup64Bit(SigScanner sig) + { + this.SetupFramework(sig); - // Framework does not change once initialized in startup so don't bother to deref again and again. - this.BaseAddress = Marshal.ReadIntPtr(pFramework); + // Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h] + // Xiv__Framework__GetGuiManager+F 000 retn + this.GuiManager = Marshal.ReadIntPtr(this.BaseAddress, 0x2C08); + + // Called from Framework::Init + this.ScriptManager = this.BaseAddress + 0x2C68; // note that no deref here + } + + private void SetupFramework(SigScanner scanner) + { + // Dissasembly of part of the .dtor + // 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0 + // 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130 + // 00007FF701AD666A | 48 8D ?? ?? ?? 00 00 | LEA RCX,QWORD PTR DS:[RBX + 2C38] + // 00007FF701AD6671 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E2A7D0 + // 00007FF701AD6676 | 48 8D ?? ?? ?? ?? ?? | LEA RAX,QWORD PTR DS:[7FF702C31F80 + var fwDtor = scanner.ScanText("48 C7 05 ?? ?? ?? ?? 00 00 00 00 E8 ?? ?? ?? ?? 48 8D ?? ?? ?? 00 00 E8 ?? ?? ?? ?? 48 8D"); + var fwOffset = Marshal.ReadInt32(fwDtor + 3); + var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset); + + // Framework does not change once initialized in startup so don't bother to deref again and again. + this.BaseAddress = Marshal.ReadIntPtr(pFramework); + } } } diff --git a/Dalamud/Game/GameVersion.cs b/Dalamud/Game/GameVersion.cs index 53b55fd67..a93e0bff2 100644 --- a/Dalamud/Game/GameVersion.cs +++ b/Dalamud/Game/GameVersion.cs @@ -5,404 +5,405 @@ using System.Text; using Newtonsoft.Json; -namespace Dalamud.Game; - -/// -/// A GameVersion object contains give hierarchical numeric components: year, month, -/// day, major and minor. All components may be unspecified, which is represented -/// internally as a -1. By definition, an unspecified component matches anything -/// (both unspecified and specified), and an unspecified component is "less than" any -/// specified component. It will also equal the string "any" if all components are -/// unspecified. The value can be retrieved from the ffxivgame.ver file in your game -/// installation directory. -/// -[Serializable] -public sealed class GameVersion : ICloneable, IComparable, IComparable, IEquatable +namespace Dalamud.Game { - private static readonly GameVersion AnyVersion = new(); - /// - /// Initializes a new instance of the class. + /// A GameVersion object contains give hierarchical numeric components: year, month, + /// day, major and minor. All components may be unspecified, which is represented + /// internally as a -1. By definition, an unspecified component matches anything + /// (both unspecified and specified), and an unspecified component is "less than" any + /// specified component. It will also equal the string "any" if all components are + /// unspecified. The value can be retrieved from the ffxivgame.ver file in your game + /// installation directory. /// - /// Version string to parse. - [JsonConstructor] - public GameVersion(string version) + [Serializable] + public sealed class GameVersion : ICloneable, IComparable, IComparable, IEquatable { - var ver = Parse(version); - this.Year = ver.Year; - this.Month = ver.Month; - this.Day = ver.Day; - this.Major = ver.Major; - this.Minor = ver.Minor; - } + private static readonly GameVersion AnyVersion = new(); - /// - /// Initializes a new instance of the class. - /// - /// The year. - /// The month. - /// The day. - /// The major version. - /// The minor version. - public GameVersion(int year, int month, int day, int major, int minor) - { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); - - if ((this.Month = month) < 0) - throw new ArgumentOutOfRangeException(nameof(month)); - - if ((this.Day = day) < 0) - throw new ArgumentOutOfRangeException(nameof(day)); - - if ((this.Major = major) < 0) - throw new ArgumentOutOfRangeException(nameof(major)); - - if ((this.Minor = minor) < 0) - throw new ArgumentOutOfRangeException(nameof(minor)); - } - - /// - /// Initializes a new instance of the class. - /// - /// The year. - /// The month. - /// The day. - /// The major version. - public GameVersion(int year, int month, int day, int major) - { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); - - if ((this.Month = month) < 0) - throw new ArgumentOutOfRangeException(nameof(month)); - - if ((this.Day = day) < 0) - throw new ArgumentOutOfRangeException(nameof(day)); - - if ((this.Major = major) < 0) - throw new ArgumentOutOfRangeException(nameof(major)); - } - - /// - /// Initializes a new instance of the class. - /// - /// The year. - /// The month. - /// The day. - public GameVersion(int year, int month, int day) - { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); - - if ((this.Month = month) < 0) - throw new ArgumentOutOfRangeException(nameof(month)); - - if ((this.Day = day) < 0) - throw new ArgumentOutOfRangeException(nameof(day)); - } - - /// - /// Initializes a new instance of the class. - /// - /// The year. - /// The month. - public GameVersion(int year, int month) - { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); - - if ((this.Month = month) < 0) - throw new ArgumentOutOfRangeException(nameof(month)); - } - - /// - /// Initializes a new instance of the class. - /// - /// The year. - public GameVersion(int year) - { - if ((this.Year = year) < 0) - throw new ArgumentOutOfRangeException(nameof(year)); - } - - /// - /// Initializes a new instance of the class. - /// - public GameVersion() - { - } - - /// - /// Gets the default "any" game version. - /// - public static GameVersion Any => AnyVersion; - - /// - /// Gets the year component. - /// - public int Year { get; } = -1; - - /// - /// Gets the month component. - /// - public int Month { get; } = -1; - - /// - /// Gets the day component. - /// - public int Day { get; } = -1; - - /// - /// Gets the major version component. - /// - public int Major { get; } = -1; - - /// - /// Gets the minor version component. - /// - public int Minor { get; } = -1; - - public static implicit operator GameVersion(string ver) - { - return Parse(ver); - } - - public static bool operator ==(GameVersion v1, GameVersion v2) - { - if (v1 is null) + /// + /// Initializes a new instance of the class. + /// + /// Version string to parse. + [JsonConstructor] + public GameVersion(string version) { - return v2 is null; + var ver = Parse(version); + this.Year = ver.Year; + this.Month = ver.Month; + this.Day = ver.Day; + this.Major = ver.Major; + this.Minor = ver.Minor; } - return v1.Equals(v2); - } - - public static bool operator !=(GameVersion v1, GameVersion v2) - { - return !(v1 == v2); - } - - public static bool operator <(GameVersion v1, GameVersion v2) - { - if (v1 is null) - throw new ArgumentNullException(nameof(v1)); - - return v1.CompareTo(v2) < 0; - } - - public static bool operator <=(GameVersion v1, GameVersion v2) - { - if (v1 is null) - throw new ArgumentNullException(nameof(v1)); - - return v1.CompareTo(v2) <= 0; - } - - public static bool operator >(GameVersion v1, GameVersion v2) - { - return v2 < v1; - } - - public static bool operator >=(GameVersion v1, GameVersion v2) - { - return v2 <= v1; - } - - public static GameVersion operator +(GameVersion v1, TimeSpan v2) - { - if (v1 == null) - throw new ArgumentNullException(nameof(v1)); - - if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1) - return v1; - - var date = new DateTime(v1.Year, v1.Month, v1.Day) + v2; - - return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor); - } - - public static GameVersion operator -(GameVersion v1, TimeSpan v2) - { - if (v1 == null) - throw new ArgumentNullException(nameof(v1)); - - if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1) - return v1; - - var date = new DateTime(v1.Year, v1.Month, v1.Day) - v2; - - return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor); - } - - /// - /// Parse a version string. YYYY.MM.DD.majr.minr or "any". - /// - /// Input to parse. - /// GameVersion object. - public static GameVersion Parse(string input) - { - if (input == null) - throw new ArgumentNullException(nameof(input)); - - if (input.ToLower(CultureInfo.InvariantCulture) == "any") - return new GameVersion(); - - var parts = input.Split('.'); - var tplParts = parts.Select(p => + /// + /// Initializes a new instance of the class. + /// + /// The year. + /// The month. + /// The day. + /// The major version. + /// The minor version. + public GameVersion(int year, int month, int day, int major, int minor) { - var result = int.TryParse(p, out var value); - return (result, value); - }).ToArray(); + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); - if (tplParts.Any(t => !t.result)) - throw new FormatException("Bad formatting"); + if ((this.Month = month) < 0) + throw new ArgumentOutOfRangeException(nameof(month)); - var intParts = tplParts.Select(t => t.value).ToArray(); - var len = intParts.Length; + if ((this.Day = day) < 0) + throw new ArgumentOutOfRangeException(nameof(day)); - if (len == 1) - return new GameVersion(intParts[0]); - else if (len == 2) - return new GameVersion(intParts[0], intParts[1]); - else if (len == 3) - return new GameVersion(intParts[0], intParts[1], intParts[2]); - else if (len == 4) - return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3]); - else if (len == 5) - return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3], intParts[4]); - else - throw new ArgumentException("Too many parts"); - } + if ((this.Major = major) < 0) + throw new ArgumentOutOfRangeException(nameof(major)); - /// - /// Try to parse a version string. YYYY.MM.DD.majr.minr or "any". - /// - /// Input to parse. - /// GameVersion object. - /// Success or failure. - public static bool TryParse(string input, out GameVersion result) - { - try - { - result = Parse(input); - return true; + if ((this.Minor = minor) < 0) + throw new ArgumentOutOfRangeException(nameof(minor)); } - catch + + /// + /// Initializes a new instance of the class. + /// + /// The year. + /// The month. + /// The day. + /// The major version. + public GameVersion(int year, int month, int day, int major) { - result = null; - return false; + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); + + if ((this.Month = month) < 0) + throw new ArgumentOutOfRangeException(nameof(month)); + + if ((this.Day = day) < 0) + throw new ArgumentOutOfRangeException(nameof(day)); + + if ((this.Major = major) < 0) + throw new ArgumentOutOfRangeException(nameof(major)); } - } - /// - public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor); - - /// - public int CompareTo(object obj) - { - if (obj == null) - return 1; - - if (obj is GameVersion value) + /// + /// Initializes a new instance of the class. + /// + /// The year. + /// The month. + /// The day. + public GameVersion(int year, int month, int day) { - return this.CompareTo(value); + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); + + if ((this.Month = month) < 0) + throw new ArgumentOutOfRangeException(nameof(month)); + + if ((this.Day = day) < 0) + throw new ArgumentOutOfRangeException(nameof(day)); } - else + + /// + /// Initializes a new instance of the class. + /// + /// The year. + /// The month. + public GameVersion(int year, int month) { - throw new ArgumentException("Argument must be a GameVersion"); + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); + + if ((this.Month = month) < 0) + throw new ArgumentOutOfRangeException(nameof(month)); } - } - /// - public int CompareTo(GameVersion value) - { - if (value == null) - return 1; + /// + /// Initializes a new instance of the class. + /// + /// The year. + public GameVersion(int year) + { + if ((this.Year = year) < 0) + throw new ArgumentOutOfRangeException(nameof(year)); + } + + /// + /// Initializes a new instance of the class. + /// + public GameVersion() + { + } + + /// + /// Gets the default "any" game version. + /// + public static GameVersion Any => AnyVersion; + + /// + /// Gets the year component. + /// + public int Year { get; } = -1; + + /// + /// Gets the month component. + /// + public int Month { get; } = -1; + + /// + /// Gets the day component. + /// + public int Day { get; } = -1; + + /// + /// Gets the major version component. + /// + public int Major { get; } = -1; + + /// + /// Gets the minor version component. + /// + public int Minor { get; } = -1; + + public static implicit operator GameVersion(string ver) + { + return Parse(ver); + } + + public static bool operator ==(GameVersion v1, GameVersion v2) + { + if (v1 is null) + { + return v2 is null; + } + + return v1.Equals(v2); + } + + public static bool operator !=(GameVersion v1, GameVersion v2) + { + return !(v1 == v2); + } + + public static bool operator <(GameVersion v1, GameVersion v2) + { + if (v1 is null) + throw new ArgumentNullException(nameof(v1)); + + return v1.CompareTo(v2) < 0; + } + + public static bool operator <=(GameVersion v1, GameVersion v2) + { + if (v1 is null) + throw new ArgumentNullException(nameof(v1)); + + return v1.CompareTo(v2) <= 0; + } + + public static bool operator >(GameVersion v1, GameVersion v2) + { + return v2 < v1; + } + + public static bool operator >=(GameVersion v1, GameVersion v2) + { + return v2 <= v1; + } + + public static GameVersion operator +(GameVersion v1, TimeSpan v2) + { + if (v1 == null) + throw new ArgumentNullException(nameof(v1)); + + if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1) + return v1; + + var date = new DateTime(v1.Year, v1.Month, v1.Day) + v2; + + return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor); + } + + public static GameVersion operator -(GameVersion v1, TimeSpan v2) + { + if (v1 == null) + throw new ArgumentNullException(nameof(v1)); + + if (v1.Year == -1 || v1.Month == -1 || v1.Day == -1) + return v1; + + var date = new DateTime(v1.Year, v1.Month, v1.Day) - v2; + + return new GameVersion(date.Year, date.Month, date.Day, v1.Major, v1.Minor); + } + + /// + /// Parse a version string. YYYY.MM.DD.majr.minr or "any". + /// + /// Input to parse. + /// GameVersion object. + public static GameVersion Parse(string input) + { + if (input == null) + throw new ArgumentNullException(nameof(input)); + + if (input.ToLower(CultureInfo.InvariantCulture) == "any") + return new GameVersion(); + + var parts = input.Split('.'); + var tplParts = parts.Select(p => + { + var result = int.TryParse(p, out var value); + return (result, value); + }).ToArray(); + + if (tplParts.Any(t => !t.result)) + throw new FormatException("Bad formatting"); + + var intParts = tplParts.Select(t => t.value).ToArray(); + var len = intParts.Length; + + if (len == 1) + return new GameVersion(intParts[0]); + else if (len == 2) + return new GameVersion(intParts[0], intParts[1]); + else if (len == 3) + return new GameVersion(intParts[0], intParts[1], intParts[2]); + else if (len == 4) + return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3]); + else if (len == 5) + return new GameVersion(intParts[0], intParts[1], intParts[2], intParts[3], intParts[4]); + else + throw new ArgumentException("Too many parts"); + } + + /// + /// Try to parse a version string. YYYY.MM.DD.majr.minr or "any". + /// + /// Input to parse. + /// GameVersion object. + /// Success or failure. + public static bool TryParse(string input, out GameVersion result) + { + try + { + result = Parse(input); + return true; + } + catch + { + result = null; + return false; + } + } + + /// + public object Clone() => new GameVersion(this.Year, this.Month, this.Day, this.Major, this.Minor); + + /// + public int CompareTo(object obj) + { + if (obj == null) + return 1; + + if (obj is GameVersion value) + { + return this.CompareTo(value); + } + else + { + throw new ArgumentException("Argument must be a GameVersion"); + } + } + + /// + public int CompareTo(GameVersion value) + { + if (value == null) + return 1; + + if (this == value) + return 0; + + if (this == AnyVersion) + return 1; + + if (value == AnyVersion) + return -1; + + if (this.Year != value.Year) + return this.Year > value.Year ? 1 : -1; + + if (this.Month != value.Month) + return this.Month > value.Month ? 1 : -1; + + if (this.Day != value.Day) + return this.Day > value.Day ? 1 : -1; + + if (this.Major != value.Major) + return this.Major > value.Major ? 1 : -1; + + if (this.Minor != value.Minor) + return this.Minor > value.Minor ? 1 : -1; - if (this == value) return 0; - - if (this == AnyVersion) - return 1; - - if (value == AnyVersion) - return -1; - - if (this.Year != value.Year) - return this.Year > value.Year ? 1 : -1; - - if (this.Month != value.Month) - return this.Month > value.Month ? 1 : -1; - - if (this.Day != value.Day) - return this.Day > value.Day ? 1 : -1; - - if (this.Major != value.Major) - return this.Major > value.Major ? 1 : -1; - - if (this.Minor != value.Minor) - return this.Minor > value.Minor ? 1 : -1; - - return 0; - } - - /// - public override bool Equals(object obj) - { - if (obj is not GameVersion value) - return false; - - return this.Equals(value); - } - - /// - public bool Equals(GameVersion value) - { - if (value == null) - { - return false; } - return - (this.Year == value.Year) && - (this.Month == value.Month) && - (this.Day == value.Day) && - (this.Major == value.Major) && - (this.Minor == value.Minor); - } + /// + public override bool Equals(object obj) + { + if (obj is not GameVersion value) + return false; - /// - public override int GetHashCode() - { - var accumulator = 0; + return this.Equals(value); + } - // This might be horribly wrong, but it isn't used heavily. - accumulator |= this.Year.GetHashCode(); - accumulator |= this.Month.GetHashCode(); - accumulator |= this.Day.GetHashCode(); - accumulator |= this.Major.GetHashCode(); - accumulator |= this.Minor.GetHashCode(); + /// + public bool Equals(GameVersion value) + { + if (value == null) + { + return false; + } - return accumulator; - } + return + (this.Year == value.Year) && + (this.Month == value.Month) && + (this.Day == value.Day) && + (this.Major == value.Major) && + (this.Minor == value.Minor); + } - /// - public override string ToString() - { - if (this.Year == -1 && - this.Month == -1 && - this.Day == -1 && - this.Major == -1 && - this.Minor == -1) - return "any"; + /// + public override int GetHashCode() + { + var accumulator = 0; - return new StringBuilder() - .Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year)) - .Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month)) - .Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day)) - .Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major)) - .Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor)) - .ToString(); + // This might be horribly wrong, but it isn't used heavily. + accumulator |= this.Year.GetHashCode(); + accumulator |= this.Month.GetHashCode(); + accumulator |= this.Day.GetHashCode(); + accumulator |= this.Major.GetHashCode(); + accumulator |= this.Minor.GetHashCode(); + + return accumulator; + } + + /// + public override string ToString() + { + if (this.Year == -1 && + this.Month == -1 && + this.Day == -1 && + this.Major == -1 && + this.Minor == -1) + return "any"; + + return new StringBuilder() + .Append(string.Format("{0:D4}.", this.Year == -1 ? 0 : this.Year)) + .Append(string.Format("{0:D2}.", this.Month == -1 ? 0 : this.Month)) + .Append(string.Format("{0:D2}.", this.Day == -1 ? 0 : this.Day)) + .Append(string.Format("{0:D4}.", this.Major == -1 ? 0 : this.Major)) + .Append(string.Format("{0:D4}", this.Minor == -1 ? 0 : this.Minor)) + .ToString(); + } } } diff --git a/Dalamud/Game/GameVersionConverter.cs b/Dalamud/Game/GameVersionConverter.cs index f307b6fb9..9058e8be9 100644 --- a/Dalamud/Game/GameVersionConverter.cs +++ b/Dalamud/Game/GameVersionConverter.cs @@ -2,78 +2,79 @@ using System; using Newtonsoft.Json; -namespace Dalamud.Game; - -/// -/// Converts a to and from a string (e.g. "2010.01.01.1234.5678"). -/// -public sealed class GameVersionConverter : JsonConverter +namespace Dalamud.Game { /// - /// Writes the JSON representation of the object. + /// Converts a to and from a string (e.g. "2010.01.01.1234.5678"). /// - /// The to write to. - /// The value. - /// The calling serializer. - public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + public sealed class GameVersionConverter : JsonConverter { - if (value == null) + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { - writer.WriteNull(); - } - else if (value is GameVersion) - { - writer.WriteValue(value.ToString()); - } - else - { - throw new JsonSerializationException("Expected GameVersion object value"); - } - } - - /// - /// Reads the JSON representation of the object. - /// - /// The to read from. - /// Type of the object. - /// The existing property value of the JSON that is being converted. - /// The calling serializer. - /// The object value. - public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) - { - return null; - } - else - { - if (reader.TokenType == JsonToken.String) + if (value == null) { - try - { - return new GameVersion((string)reader.Value!); - } - catch (Exception ex) - { - throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex); - } + writer.WriteNull(); + } + else if (value is GameVersion) + { + writer.WriteValue(value.ToString()); } else { - throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}"); + throw new JsonSerializationException("Expected GameVersion object value"); } } - } - /// - /// Determines whether this instance can convert the specified object type. - /// - /// Type of the object. - /// - /// true if this instance can convert the specified object type; otherwise, false. - /// - public override bool CanConvert(Type objectType) - { - return objectType == typeof(GameVersion); + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing property value of the JSON that is being converted. + /// The calling serializer. + /// The object value. + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + { + return null; + } + else + { + if (reader.TokenType == JsonToken.String) + { + try + { + return new GameVersion((string)reader.Value!); + } + catch (Exception ex) + { + throw new JsonSerializationException($"Error parsing GameVersion string: {reader.Value}", ex); + } + } + else + { + throw new JsonSerializationException($"Unexpected token or value when parsing GameVersion. Token: {reader.TokenType}, Value: {reader.Value}"); + } + } + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + return objectType == typeof(GameVersion); + } } } diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index bab3a83b4..0aa7bb94f 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -13,470 +13,471 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.Gui; - -/// -/// This class handles interacting with the native chat UI. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class ChatGui : IDisposable +namespace Dalamud.Game.Gui { - private readonly ChatGuiAddressResolver address; - - private readonly Queue chatQueue = new(); - private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new(); - - private readonly Hook printMessageHook; - private readonly Hook populateItemLinkHook; - private readonly Hook interactableLinkClickedHook; - - private IntPtr baseAddress = IntPtr.Zero; - /// - /// Initializes a new instance of the class. + /// This class handles interacting with the native chat UI. /// - /// The base address of the ChatManager. - internal ChatGui(IntPtr baseAddress) + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class ChatGui : IDisposable { - this.address = new ChatGuiAddressResolver(baseAddress); - this.address.Setup(); + private readonly ChatGuiAddressResolver address; - Log.Verbose($"Chat manager address 0x{this.address.BaseAddress.ToInt64():X}"); + private readonly Queue chatQueue = new(); + private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new(); - this.printMessageHook = new Hook(this.address.PrintMessage, this.HandlePrintMessageDetour); - this.populateItemLinkHook = new Hook(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); - this.interactableLinkClickedHook = new Hook(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour); - } + private readonly Hook printMessageHook; + private readonly Hook populateItemLinkHook; + private readonly Hook interactableLinkClickedHook; - /// - /// A delegate type used with the event. - /// - /// The type of chat. - /// The sender ID. - /// The sender name. - /// The message sent. - /// A value indicating whether the message was handled or should be propagated. - public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); + private IntPtr baseAddress = IntPtr.Zero; - /// - /// A delegate type used with the event. - /// - /// The type of chat. - /// The sender ID. - /// The sender name. - /// The message sent. - /// A value indicating whether the message was handled or should be propagated. - public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); - - /// - /// A delegate type used with the event. - /// - /// The type of chat. - /// The sender ID. - /// The sender name. - /// The message sent. - public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); - - /// - /// A delegate type used with the event. - /// - /// The type of chat. - /// The sender ID. - /// The sender name. - /// The message sent. - public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr); - - /// - /// Event that will be fired when a chat message is sent to chat by the game. - /// - public event OnMessageDelegate ChatMessage; - - /// - /// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true. - /// - public event OnCheckMessageHandledDelegate CheckMessageHandled; - - /// - /// Event that will be fired when a chat message is handled by Dalamud or a Plugin. - /// - public event OnMessageHandledDelegate ChatMessageHandled; - - /// - /// Event that will be fired when a chat message is not handled by Dalamud or a Plugin. - /// - public event OnMessageUnhandledDelegate ChatMessageUnhandled; - - /// - /// Gets the ID of the last linked item. - /// - public int LastLinkedItemId { get; private set; } - - /// - /// Gets the flags of the last linked item. - /// - public byte LastLinkedItemFlags { get; private set; } - - /// - /// Enables this module. - /// - public void Enable() - { - this.printMessageHook.Enable(); - this.populateItemLinkHook.Enable(); - this.interactableLinkClickedHook.Enable(); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - this.printMessageHook.Dispose(); - this.populateItemLinkHook.Dispose(); - this.interactableLinkClickedHook.Dispose(); - } - - /// - /// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue, - /// later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void PrintChat(XivChatEntry chat) - { - this.chatQueue.Enqueue(chat); - } - - /// - /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue, - /// later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void Print(string message) - { - var configuration = Service.Get(); - - // Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message); - this.PrintChat(new XivChatEntry + /// + /// Initializes a new instance of the class. + /// + /// The base address of the ChatManager. + internal ChatGui(IntPtr baseAddress) { - Message = message, - Type = configuration.GeneralChatType, - }); - } + this.address = new ChatGuiAddressResolver(baseAddress); + this.address.Setup(); - /// - /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue, - /// later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void Print(SeString message) - { - var configuration = Service.Get(); + Log.Verbose($"Chat manager address 0x{this.address.BaseAddress.ToInt64():X}"); - // Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue); - this.PrintChat(new XivChatEntry - { - Message = message, - Type = configuration.GeneralChatType, - }); - } - - /// - /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to - /// the queue, later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void PrintError(string message) - { - // Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message); - this.PrintChat(new XivChatEntry - { - Message = message, - Type = XivChatType.Urgent, - }); - } - - /// - /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to - /// the queue, later to be processed when UpdateQueue() is called. - /// - /// A message to send. - public void PrintError(SeString message) - { - // Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue); - this.PrintChat(new XivChatEntry - { - Message = message, - Type = XivChatType.Urgent, - }); - } - - /// - /// Process a chat queue. - /// - public void UpdateQueue() - { - while (this.chatQueue.Count > 0) - { - var chat = this.chatQueue.Dequeue(); - - if (this.baseAddress == IntPtr.Zero) - { - continue; - } - - var senderRaw = (chat.Name ?? string.Empty).Encode(); - using var senderOwned = Service.Get().NewString(senderRaw); - - var messageRaw = (chat.Message ?? string.Empty).Encode(); - using var messageOwned = Service.Get().NewString(messageRaw); - - this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters); - } - } - - /// - /// Create a link handler. - /// - /// The name of the plugin handling the link. - /// The ID of the command to run. - /// The command action itself. - /// A payload for handling. - internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action commandAction) - { - var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId }; - this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction); - return payload; - } - - /// - /// Remove all handlers owned by a plugin. - /// - /// The name of the plugin handling the links. - internal void RemoveChatLinkHandler(string pluginName) - { - foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName)) - { - this.dalamudLinkHandlers.Remove(handler); - } - } - - /// - /// Remove a registered link handler. - /// - /// The name of the plugin handling the link. - /// The ID of the command to be removed. - internal void RemoveChatLinkHandler(string pluginName, uint commandId) - { - if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId))) - { - this.dalamudLinkHandlers.Remove((pluginName, commandId)); - } - } - - private static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2) - { - // Copyright (c) 2008-2013 Hafthor Stefansson - // Distributed under the MIT/X11 software license - // Ref: http://www.opensource.org/licenses/mit-license.php. - // https://stackoverflow.com/a/8808245 - - if (a1 == a2) return true; - if (a1 == null || a2 == null || a1.Length != a2.Length) - return false; - fixed (byte* p1 = a1, p2 = a2) - { - byte* x1 = p1, x2 = p2; - var l = a1.Length; - for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8) - { - if (*((long*)x1) != *((long*)x2)) - return false; - } - - if ((l & 4) != 0) - { - if (*((int*)x1) != *((int*)x2)) - return false; - x1 += 4; - x2 += 4; - } - - if ((l & 2) != 0) - { - if (*((short*)x1) != *((short*)x2)) - return false; - x1 += 2; - x2 += 2; - } - - if ((l & 1) != 0) - { - if (*((byte*)x1) != *((byte*)x2)) - return false; - } - - return true; - } - } - - private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) - { - try - { - this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); - - this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8); - this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14); - - // Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}"); - } - catch (Exception ex) - { - Log.Error(ex, "Exception onPopulateItemLink hook."); - this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); - } - } - - private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter) - { - var retVal = IntPtr.Zero; - - try - { - var sender = StdString.ReadFromPointer(pSenderName); - var parsedSender = SeString.Parse(sender.RawData); - var originalSenderData = (byte[])sender.RawData.Clone(); - var oldEditedSender = parsedSender.Encode(); - var senderPtr = pSenderName; - OwnedStdString allocatedString = null; - - var message = StdString.ReadFromPointer(pMessage); - var parsedMessage = SeString.Parse(message.RawData); - var originalMessageData = (byte[])message.RawData.Clone(); - var oldEdited = parsedMessage.Encode(); - var messagePtr = pMessage; - OwnedStdString allocatedStringSender = null; - - // Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue); - - // Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}"); - - // Call events - var isHandled = false; - this.CheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); - - if (!isHandled) - { - this.ChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); - } - - var newEdited = parsedMessage.Encode(); - if (!FastByteArrayCompare(oldEdited, newEdited)) - { - Log.Verbose("SeString was edited, taking precedence over StdString edit."); - message.RawData = newEdited; - // Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}"); - } - - if (!FastByteArrayCompare(originalMessageData, message.RawData)) - { - allocatedString = Service.Get().NewString(message.RawData); - Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})"); - messagePtr = allocatedString.Address; - } - - var newEditedSender = parsedSender.Encode(); - if (!FastByteArrayCompare(oldEditedSender, newEditedSender)) - { - Log.Verbose("SeString was edited, taking precedence over StdString edit."); - sender.RawData = newEditedSender; - // Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}"); - } - - if (!FastByteArrayCompare(originalSenderData, sender.RawData)) - { - allocatedStringSender = Service.Get().NewString(sender.RawData); - Log.Debug( - $"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})"); - senderPtr = allocatedStringSender.Address; - } - - // Print the original chat if it's handled. - if (isHandled) - { - this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); - } - else - { - retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter); - this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); - } - - if (this.baseAddress == IntPtr.Zero) - this.baseAddress = manager; - - allocatedString?.Dispose(); - allocatedStringSender?.Dispose(); - } - catch (Exception ex) - { - Log.Error(ex, "Exception on OnChatMessage hook."); - retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter); + this.printMessageHook = new Hook(this.address.PrintMessage, this.HandlePrintMessageDetour); + this.populateItemLinkHook = new Hook(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); + this.interactableLinkClickedHook = new Hook(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour); } - return retVal; - } + /// + /// A delegate type used with the event. + /// + /// The type of chat. + /// The sender ID. + /// The sender name. + /// The message sent. + /// A value indicating whether the message was handled or should be propagated. + public delegate void OnMessageDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); - private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr) - { - try + /// + /// A delegate type used with the event. + /// + /// The type of chat. + /// The sender ID. + /// The sender name. + /// The message sent. + /// A value indicating whether the message was handled or should be propagated. + public delegate void OnCheckMessageHandledDelegate(XivChatType type, uint senderId, ref SeString sender, ref SeString message, ref bool isHandled); + + /// + /// A delegate type used with the event. + /// + /// The type of chat. + /// The sender ID. + /// The sender name. + /// The message sent. + public delegate void OnMessageHandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); + + /// + /// A delegate type used with the event. + /// + /// The type of chat. + /// The sender ID. + /// The sender name. + /// The message sent. + public delegate void OnMessageUnhandledDelegate(XivChatType type, uint senderId, SeString sender, SeString message); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName, IntPtr message, uint senderId, IntPtr parameter); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr); + + /// + /// Event that will be fired when a chat message is sent to chat by the game. + /// + public event OnMessageDelegate ChatMessage; + + /// + /// Event that allows you to stop messages from appearing in chat by setting the isHandled parameter to true. + /// + public event OnCheckMessageHandledDelegate CheckMessageHandled; + + /// + /// Event that will be fired when a chat message is handled by Dalamud or a Plugin. + /// + public event OnMessageHandledDelegate ChatMessageHandled; + + /// + /// Event that will be fired when a chat message is not handled by Dalamud or a Plugin. + /// + public event OnMessageUnhandledDelegate ChatMessageUnhandled; + + /// + /// Gets the ID of the last linked item. + /// + public int LastLinkedItemId { get; private set; } + + /// + /// Gets the flags of the last linked item. + /// + public byte LastLinkedItemFlags { get; private set; } + + /// + /// Enables this module. + /// + public void Enable() { - var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1); + this.printMessageHook.Enable(); + this.populateItemLinkHook.Enable(); + this.interactableLinkClickedHook.Enable(); + } - if (interactableType != Payload.EmbeddedInfoType.DalamudLink) + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + this.printMessageHook.Dispose(); + this.populateItemLinkHook.Dispose(); + this.interactableLinkClickedHook.Dispose(); + } + + /// + /// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue, + /// later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void PrintChat(XivChatEntry chat) + { + this.chatQueue.Enqueue(chat); + } + + /// + /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue, + /// later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void Print(string message) + { + var configuration = Service.Get(); + + // Log.Verbose("[CHATGUI PRINT REGULAR]{0}", message); + this.PrintChat(new XivChatEntry { - this.interactableLinkClickedHook.Original(managerPtr, messagePtr); - return; - } + Message = message, + Type = configuration.GeneralChatType, + }); + } - Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); + /// + /// Queue a chat message. While method is named as PrintChat (it calls it internally), it only add a entry to the queue, + /// later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void Print(SeString message) + { + var configuration = Service.Get(); - var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10); - var messageSize = 0; - while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++; - var payloadBytes = new byte[messageSize]; - Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize); - var seStr = SeString.Parse(payloadBytes); - var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator); - var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads; - if (payloads.Count == 0) return; - var linkPayload = payloads[0]; - if (linkPayload is DalamudLinkPayload link) + // Log.Verbose("[CHATGUI PRINT SESTRING]{0}", message.TextValue); + this.PrintChat(new XivChatEntry { - if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId))) + Message = message, + Type = configuration.GeneralChatType, + }); + } + + /// + /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to + /// the queue, later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void PrintError(string message) + { + // Log.Verbose("[CHATGUI PRINT REGULAR ERROR]{0}", message); + this.PrintChat(new XivChatEntry + { + Message = message, + Type = XivChatType.Urgent, + }); + } + + /// + /// Queue an error chat message. While method is named as PrintChat (it calls it internally), it only add a entry to + /// the queue, later to be processed when UpdateQueue() is called. + /// + /// A message to send. + public void PrintError(SeString message) + { + // Log.Verbose("[CHATGUI PRINT SESTRING ERROR]{0}", message.TextValue); + this.PrintChat(new XivChatEntry + { + Message = message, + Type = XivChatType.Urgent, + }); + } + + /// + /// Process a chat queue. + /// + public void UpdateQueue() + { + while (this.chatQueue.Count > 0) + { + var chat = this.chatQueue.Dequeue(); + + if (this.baseAddress == IntPtr.Zero) { - Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}"); - this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads)); + continue; + } + + var senderRaw = (chat.Name ?? string.Empty).Encode(); + using var senderOwned = Service.Get().NewString(senderRaw); + + var messageRaw = (chat.Message ?? string.Empty).Encode(); + using var messageOwned = Service.Get().NewString(messageRaw); + + this.HandlePrintMessageDetour(this.baseAddress, chat.Type, senderOwned.Address, messageOwned.Address, chat.SenderId, chat.Parameters); + } + } + + /// + /// Create a link handler. + /// + /// The name of the plugin handling the link. + /// The ID of the command to run. + /// The command action itself. + /// A payload for handling. + internal DalamudLinkPayload AddChatLinkHandler(string pluginName, uint commandId, Action commandAction) + { + var payload = new DalamudLinkPayload() { Plugin = pluginName, CommandId = commandId }; + this.dalamudLinkHandlers.Add((pluginName, commandId), commandAction); + return payload; + } + + /// + /// Remove all handlers owned by a plugin. + /// + /// The name of the plugin handling the links. + internal void RemoveChatLinkHandler(string pluginName) + { + foreach (var handler in this.dalamudLinkHandlers.Keys.ToList().Where(k => k.PluginName == pluginName)) + { + this.dalamudLinkHandlers.Remove(handler); + } + } + + /// + /// Remove a registered link handler. + /// + /// The name of the plugin handling the link. + /// The ID of the command to be removed. + internal void RemoveChatLinkHandler(string pluginName, uint commandId) + { + if (this.dalamudLinkHandlers.ContainsKey((pluginName, commandId))) + { + this.dalamudLinkHandlers.Remove((pluginName, commandId)); + } + } + + private static unsafe bool FastByteArrayCompare(byte[] a1, byte[] a2) + { + // Copyright (c) 2008-2013 Hafthor Stefansson + // Distributed under the MIT/X11 software license + // Ref: http://www.opensource.org/licenses/mit-license.php. + // https://stackoverflow.com/a/8808245 + + if (a1 == a2) return true; + if (a1 == null || a2 == null || a1.Length != a2.Length) + return false; + fixed (byte* p1 = a1, p2 = a2) + { + byte* x1 = p1, x2 = p2; + var l = a1.Length; + for (var i = 0; i < l / 8; i++, x1 += 8, x2 += 8) + { + if (*((long*)x1) != *((long*)x2)) + return false; + } + + if ((l & 4) != 0) + { + if (*((int*)x1) != *((int*)x2)) + return false; + x1 += 4; + x2 += 4; + } + + if ((l & 2) != 0) + { + if (*((short*)x1) != *((short*)x2)) + return false; + x1 += 2; + x2 += 2; + } + + if ((l & 1) != 0) + { + if (*((byte*)x1) != *((byte*)x2)) + return false; + } + + return true; + } + } + + private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) + { + try + { + this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); + + this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8); + this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14); + + // Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}"); + } + catch (Exception ex) + { + Log.Error(ex, "Exception onPopulateItemLink hook."); + this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr); + } + } + + private IntPtr HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage, uint senderid, IntPtr parameter) + { + var retVal = IntPtr.Zero; + + try + { + var sender = StdString.ReadFromPointer(pSenderName); + var parsedSender = SeString.Parse(sender.RawData); + var originalSenderData = (byte[])sender.RawData.Clone(); + var oldEditedSender = parsedSender.Encode(); + var senderPtr = pSenderName; + OwnedStdString allocatedString = null; + + var message = StdString.ReadFromPointer(pMessage); + var parsedMessage = SeString.Parse(message.RawData); + var originalMessageData = (byte[])message.RawData.Clone(); + var oldEdited = parsedMessage.Encode(); + var messagePtr = pMessage; + OwnedStdString allocatedStringSender = null; + + // Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue); + + // Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(message.RawData).Replace("-", " ")}] {message.Value} from {senderName.Value}"); + + // Call events + var isHandled = false; + this.CheckMessageHandled?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); + + if (!isHandled) + { + this.ChatMessage?.Invoke(chattype, senderid, ref parsedSender, ref parsedMessage, ref isHandled); + } + + var newEdited = parsedMessage.Encode(); + if (!FastByteArrayCompare(oldEdited, newEdited)) + { + Log.Verbose("SeString was edited, taking precedence over StdString edit."); + message.RawData = newEdited; + // Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}"); + } + + if (!FastByteArrayCompare(originalMessageData, message.RawData)) + { + allocatedString = Service.Get().NewString(message.RawData); + Log.Debug($"HandlePrintMessageDetour String modified: {originalMessageData}({messagePtr}) -> {message}({allocatedString.Address})"); + messagePtr = allocatedString.Address; + } + + var newEditedSender = parsedSender.Encode(); + if (!FastByteArrayCompare(oldEditedSender, newEditedSender)) + { + Log.Verbose("SeString was edited, taking precedence over StdString edit."); + sender.RawData = newEditedSender; + // Log.Debug($"\nOLD: {BitConverter.ToString(originalMessageData)}\nNEW: {BitConverter.ToString(newEdited)}"); + } + + if (!FastByteArrayCompare(originalSenderData, sender.RawData)) + { + allocatedStringSender = Service.Get().NewString(sender.RawData); + Log.Debug( + $"HandlePrintMessageDetour Sender modified: {originalSenderData}({senderPtr}) -> {sender}({allocatedStringSender.Address})"); + senderPtr = allocatedStringSender.Address; + } + + // Print the original chat if it's handled. + if (isHandled) + { + this.ChatMessageHandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); } else { - Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}"); + retVal = this.printMessageHook.Original(manager, chattype, senderPtr, messagePtr, senderid, parameter); + this.ChatMessageUnhandled?.Invoke(chattype, senderid, parsedSender, parsedMessage); + } + + if (this.baseAddress == IntPtr.Zero) + this.baseAddress = manager; + + allocatedString?.Dispose(); + allocatedStringSender?.Dispose(); + } + catch (Exception ex) + { + Log.Error(ex, "Exception on OnChatMessage hook."); + retVal = this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, parameter); + } + + return retVal; + } + + private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr) + { + try + { + var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1); + + if (interactableType != Payload.EmbeddedInfoType.DalamudLink) + { + this.interactableLinkClickedHook.Original(managerPtr, messagePtr); + return; + } + + Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); + + var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10); + var messageSize = 0; + while (Marshal.ReadByte(payloadPtr, messageSize) != 0) messageSize++; + var payloadBytes = new byte[messageSize]; + Marshal.Copy(payloadPtr, payloadBytes, 0, messageSize); + var seStr = SeString.Parse(payloadBytes); + var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator); + var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads; + if (payloads.Count == 0) return; + var linkPayload = payloads[0]; + if (linkPayload is DalamudLinkPayload link) + { + if (this.dalamudLinkHandlers.ContainsKey((link.Plugin, link.CommandId))) + { + Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}"); + this.dalamudLinkHandlers[(link.Plugin, link.CommandId)].Invoke(link.CommandId, new SeString(payloads)); + } + else + { + Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}"); + } } } - } - catch (Exception ex) - { - Log.Error(ex, "Exception on InteractableLinkClicked hook"); + catch (Exception ex) + { + Log.Error(ex, "Exception on InteractableLinkClicked hook"); + } } } } diff --git a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs index e39a70e37..07c154f1f 100644 --- a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs @@ -1,117 +1,120 @@ using System; -namespace Dalamud.Game.Gui; +using Dalamud.Game.Internal; -/// -/// The address resolver for the class. -/// -public sealed class ChatGuiAddressResolver : BaseAddressResolver +namespace Dalamud.Game.Gui { /// - /// Initializes a new instance of the class. + /// The address resolver for the class. /// - /// The base address of the native ChatManager class. - public ChatGuiAddressResolver(IntPtr baseAddress) + public sealed class ChatGuiAddressResolver : BaseAddressResolver { - this.BaseAddress = baseAddress; - } + /// + /// Initializes a new instance of the class. + /// + /// The base address of the native ChatManager class. + public ChatGuiAddressResolver(IntPtr baseAddress) + { + this.BaseAddress = baseAddress; + } - /// - /// Gets the base address of the native ChatManager class. - /// - public IntPtr BaseAddress { get; } + /// + /// Gets the base address of the native ChatManager class. + /// + public IntPtr BaseAddress { get; } - /// - /// Gets the address of the native PrintMessage method. - /// - public IntPtr PrintMessage { get; private set; } + /// + /// Gets the address of the native PrintMessage method. + /// + public IntPtr PrintMessage { get; private set; } - /// - /// Gets the address of the native PopulateItemLinkObject method. - /// - public IntPtr PopulateItemLinkObject { get; private set; } + /// + /// Gets the address of the native PopulateItemLinkObject method. + /// + public IntPtr PopulateItemLinkObject { get; private set; } - /// - /// Gets the address of the native InteractableLinkClicked method. - /// - public IntPtr InteractableLinkClicked { get; private set; } + /// + /// Gets the address of the native InteractableLinkClicked method. + /// + public IntPtr InteractableLinkClicked { get; private set; } - /* - --- for reference: 4.57 --- - .text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal) - .text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near - .text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p - .text:00000001405CD210 ; sub_140141D10+220↑p ... - .text:00000001405CD210 - .text:00000001405CD210 var_220 = qword ptr -220h - .text:00000001405CD210 var_218 = byte ptr -218h - .text:00000001405CD210 var_210 = word ptr -210h - .text:00000001405CD210 var_208 = byte ptr -208h - .text:00000001405CD210 var_200 = word ptr -200h - .text:00000001405CD210 var_1FC = dword ptr -1FCh - .text:00000001405CD210 var_1F8 = qword ptr -1F8h - .text:00000001405CD210 var_1F0 = qword ptr -1F0h - .text:00000001405CD210 var_1E8 = qword ptr -1E8h - .text:00000001405CD210 var_1E0 = dword ptr -1E0h - .text:00000001405CD210 var_1DC = word ptr -1DCh - .text:00000001405CD210 var_1DA = word ptr -1DAh - .text:00000001405CD210 var_1D8 = qword ptr -1D8h - .text:00000001405CD210 var_1D0 = byte ptr -1D0h - .text:00000001405CD210 var_1C8 = qword ptr -1C8h - .text:00000001405CD210 var_1B0 = dword ptr -1B0h - .text:00000001405CD210 var_1AC = dword ptr -1ACh - .text:00000001405CD210 var_1A8 = dword ptr -1A8h - .text:00000001405CD210 var_1A4 = dword ptr -1A4h - .text:00000001405CD210 var_1A0 = dword ptr -1A0h - .text:00000001405CD210 var_160 = dword ptr -160h - .text:00000001405CD210 var_15C = dword ptr -15Ch - .text:00000001405CD210 var_140 = dword ptr -140h - .text:00000001405CD210 var_138 = dword ptr -138h - .text:00000001405CD210 var_130 = byte ptr -130h - .text:00000001405CD210 var_C0 = byte ptr -0C0h - .text:00000001405CD210 var_50 = qword ptr -50h - .text:00000001405CD210 var_38 = qword ptr -38h - .text:00000001405CD210 var_30 = qword ptr -30h - .text:00000001405CD210 var_28 = qword ptr -28h - .text:00000001405CD210 var_20 = qword ptr -20h - .text:00000001405CD210 senderActorId = dword ptr 30h - .text:00000001405CD210 isLocal = byte ptr 38h - .text:00000001405CD210 - .text:00000001405CD210 ; __unwind { // __GSHandlerCheck - .text:00000001405CD210 push rbp - .text:00000001405CD212 push rdi - .text:00000001405CD213 push r14 - .text:00000001405CD215 push r15 - .text:00000001405CD217 lea rbp, [rsp-128h] - .text:00000001405CD21F sub rsp, 228h - .text:00000001405CD226 mov rax, cs:__security_cookie - .text:00000001405CD22D xor rax, rsp - .text:00000001405CD230 mov [rbp+140h+var_50], rax - .text:00000001405CD237 xor r10b, r10b - .text:00000001405CD23A mov [rsp+240h+var_1F8], rcx - .text:00000001405CD23F xor eax, eax - .text:00000001405CD241 mov r11, r9 - .text:00000001405CD244 mov r14, r8 - .text:00000001405CD247 mov r9d, eax - .text:00000001405CD24A movzx r15d, dx - .text:00000001405CD24E lea r8, [rcx+0C10h] - .text:00000001405CD255 mov rdi, rcx - */ + /* + --- for reference: 4.57 --- + .text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal) + .text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near + .text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p + .text:00000001405CD210 ; sub_140141D10+220↑p ... + .text:00000001405CD210 + .text:00000001405CD210 var_220 = qword ptr -220h + .text:00000001405CD210 var_218 = byte ptr -218h + .text:00000001405CD210 var_210 = word ptr -210h + .text:00000001405CD210 var_208 = byte ptr -208h + .text:00000001405CD210 var_200 = word ptr -200h + .text:00000001405CD210 var_1FC = dword ptr -1FCh + .text:00000001405CD210 var_1F8 = qword ptr -1F8h + .text:00000001405CD210 var_1F0 = qword ptr -1F0h + .text:00000001405CD210 var_1E8 = qword ptr -1E8h + .text:00000001405CD210 var_1E0 = dword ptr -1E0h + .text:00000001405CD210 var_1DC = word ptr -1DCh + .text:00000001405CD210 var_1DA = word ptr -1DAh + .text:00000001405CD210 var_1D8 = qword ptr -1D8h + .text:00000001405CD210 var_1D0 = byte ptr -1D0h + .text:00000001405CD210 var_1C8 = qword ptr -1C8h + .text:00000001405CD210 var_1B0 = dword ptr -1B0h + .text:00000001405CD210 var_1AC = dword ptr -1ACh + .text:00000001405CD210 var_1A8 = dword ptr -1A8h + .text:00000001405CD210 var_1A4 = dword ptr -1A4h + .text:00000001405CD210 var_1A0 = dword ptr -1A0h + .text:00000001405CD210 var_160 = dword ptr -160h + .text:00000001405CD210 var_15C = dword ptr -15Ch + .text:00000001405CD210 var_140 = dword ptr -140h + .text:00000001405CD210 var_138 = dword ptr -138h + .text:00000001405CD210 var_130 = byte ptr -130h + .text:00000001405CD210 var_C0 = byte ptr -0C0h + .text:00000001405CD210 var_50 = qword ptr -50h + .text:00000001405CD210 var_38 = qword ptr -38h + .text:00000001405CD210 var_30 = qword ptr -30h + .text:00000001405CD210 var_28 = qword ptr -28h + .text:00000001405CD210 var_20 = qword ptr -20h + .text:00000001405CD210 senderActorId = dword ptr 30h + .text:00000001405CD210 isLocal = byte ptr 38h + .text:00000001405CD210 + .text:00000001405CD210 ; __unwind { // __GSHandlerCheck + .text:00000001405CD210 push rbp + .text:00000001405CD212 push rdi + .text:00000001405CD213 push r14 + .text:00000001405CD215 push r15 + .text:00000001405CD217 lea rbp, [rsp-128h] + .text:00000001405CD21F sub rsp, 228h + .text:00000001405CD226 mov rax, cs:__security_cookie + .text:00000001405CD22D xor rax, rsp + .text:00000001405CD230 mov [rbp+140h+var_50], rax + .text:00000001405CD237 xor r10b, r10b + .text:00000001405CD23A mov [rsp+240h+var_1F8], rcx + .text:00000001405CD23F xor eax, eax + .text:00000001405CD241 mov r11, r9 + .text:00000001405CD244 mov r14, r8 + .text:00000001405CD247 mov r9d, eax + .text:00000001405CD24A movzx r15d, dx + .text:00000001405CD24E lea r8, [rcx+0C10h] + .text:00000001405CD255 mov rdi, rcx + */ - /// - protected override void Setup64Bit(SigScanner sig) - { - // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1??? - this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05"); - // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old + /// + protected override void Setup64Bit(SigScanner sig) + { + // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448"); LAST PART FOR 5.1??? + this.PrintMessage = sig.ScanText("40 55 53 56 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC 20 02 00 00 48 8B 05"); + // PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old - // PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33"); + // PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33"); - // PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); + // PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); - // PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0 - this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); + // PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0 + this.PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? ?? FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); - this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9; + this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 80 BB ?? ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 80 BB") + 9; + } } } diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index 02984ac37..c3191562a 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs @@ -9,302 +9,303 @@ using Dalamud.IoC.Internal; using Dalamud.Memory; using Serilog; -namespace Dalamud.Game.Gui.FlyText; - -/// -/// This class facilitates interacting with and creating native in-game "fly text". -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class FlyTextGui : IDisposable +namespace Dalamud.Game.Gui.FlyText { /// - /// The native function responsible for adding fly text to the UI. See . + /// This class facilitates interacting with and creating native in-game "fly text". /// - private readonly AddFlyTextDelegate addFlyTextNative; - - /// - /// The hook that fires when the game creates a fly text element. See . - /// - private readonly Hook createFlyTextHook; - - /// - /// Initializes a new instance of the class. - /// - internal FlyTextGui() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class FlyTextGui : IDisposable { - this.Address = new FlyTextGuiAddressResolver(); - this.Address.Setup(); + /// + /// The native function responsible for adding fly text to the UI. See . + /// + private readonly AddFlyTextDelegate addFlyTextNative; - this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText); - this.createFlyTextHook = new Hook(this.Address.CreateFlyText, this.CreateFlyTextDetour); - } + /// + /// The hook that fires when the game creates a fly text element. See . + /// + private readonly Hook createFlyTextHook; - /// - /// The delegate defining the type for the FlyText event. - /// - /// The FlyTextKind. See . - /// Value1 passed to the native flytext function. - /// Value2 passed to the native flytext function. Seems unused. - /// Text1 passed to the native flytext function. - /// Text2 passed to the native flytext function. - /// Color passed to the native flytext function. Changes flytext color. - /// Icon ID passed to the native flytext function. Only displays with select FlyTextKind. - /// The vertical offset to place the flytext at. 0 is default. Negative values result - /// in text appearing higher on the screen. This does not change where the element begins to fade. - /// Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear. - public delegate void OnFlyTextCreatedDelegate( - ref FlyTextKind kind, - ref int val1, - ref int val2, - ref SeString text1, - ref SeString text2, - ref uint color, - ref uint icon, - ref float yOffset, - ref bool handled); - - /// - /// Private delegate for the native CreateFlyText function's hook. - /// - private delegate IntPtr CreateFlyTextDelegate( - IntPtr addonFlyText, - FlyTextKind kind, - int val1, - int val2, - IntPtr text2, - uint color, - uint icon, - IntPtr text1, - float yOffset); - - /// - /// Private delegate for the native AddFlyText function pointer. - /// - private delegate void AddFlyTextDelegate( - IntPtr addonFlyText, - uint actorIndex, - uint messageMax, - IntPtr numbers, - uint offsetNum, - uint offsetNumMax, - IntPtr strings, - uint offsetStr, - uint offsetStrMax, - int unknown); - - /// - /// The FlyText event that can be subscribed to. - /// - public event OnFlyTextCreatedDelegate? FlyTextCreated; - - private Dalamud Dalamud { get; } - - private FlyTextGuiAddressResolver Address { get; } - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.createFlyTextHook.Dispose(); - } - - /// - /// Displays a fly text in-game on the local player. - /// - /// The FlyTextKind. See . - /// The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player. - /// Value1 passed to the native flytext function. - /// Value2 passed to the native flytext function. Seems unused. - /// Text1 passed to the native flytext function. - /// Text2 passed to the native flytext function. - /// Color passed to the native flytext function. Changes flytext color. - /// Icon ID passed to the native flytext function. Only displays with select FlyTextKind. - public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon) - { - // Known valid flytext region within the atk arrays - var numIndex = 28; - var strIndex = 25; - var numOffset = 147u; - var strOffset = 28u; - - // Get the UI module and flytext addon pointers - var gameGui = Service.Get(); - var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule(); - var flytext = gameGui.GetAddonByName("_FlyText", 1); - - if (ui == null || flytext == IntPtr.Zero) - return; - - // Get the number and string arrays we need - var atkArrayDataHolder = ui->RaptureAtkModule.AtkModule.AtkArrayDataHolder; - var numArray = atkArrayDataHolder._NumberArrays[numIndex]; - var strArray = atkArrayDataHolder._StringArrays[strIndex]; - - // Write the values to the arrays using a known valid flytext region - numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section - numArray->IntArray[numOffset + 1] = (int)kind; - numArray->IntArray[numOffset + 2] = unchecked((int)val1); - numArray->IntArray[numOffset + 3] = unchecked((int)val2); - numArray->IntArray[numOffset + 4] = 5; // Unknown - numArray->IntArray[numOffset + 5] = unchecked((int)color); - numArray->IntArray[numOffset + 6] = unchecked((int)icon); - numArray->IntArray[numOffset + 7] = 0; // Unknown - numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset - - fixed (byte* pText1 = text1.Encode()) + /// + /// Initializes a new instance of the class. + /// + internal FlyTextGui() { - fixed (byte* pText2 = text2.Encode()) - { - strArray->StringArray[strOffset + 0] = pText1; - strArray->StringArray[strOffset + 1] = pText2; + this.Address = new FlyTextGuiAddressResolver(); + this.Address.Setup(); - this.addFlyTextNative( - flytext, - actorIndex, - 1, - (IntPtr)numArray, - numOffset, - 9, - (IntPtr)strArray, - strOffset, - 2, - 0); - } + this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText); + this.createFlyTextHook = new Hook(this.Address.CreateFlyText, this.CreateFlyTextDetour); } - } - /// - /// Enables this module. - /// - internal void Enable() - { - this.createFlyTextHook.Enable(); - } + /// + /// The delegate defining the type for the FlyText event. + /// + /// The FlyTextKind. See . + /// Value1 passed to the native flytext function. + /// Value2 passed to the native flytext function. Seems unused. + /// Text1 passed to the native flytext function. + /// Text2 passed to the native flytext function. + /// Color passed to the native flytext function. Changes flytext color. + /// Icon ID passed to the native flytext function. Only displays with select FlyTextKind. + /// The vertical offset to place the flytext at. 0 is default. Negative values result + /// in text appearing higher on the screen. This does not change where the element begins to fade. + /// Whether this flytext has been handled. If a subscriber sets this to true, the FlyText will not appear. + public delegate void OnFlyTextCreatedDelegate( + ref FlyTextKind kind, + ref int val1, + ref int val2, + ref SeString text1, + ref SeString text2, + ref uint color, + ref uint icon, + ref float yOffset, + ref bool handled); - private static byte[] Terminate(byte[] source) - { - var terminated = new byte[source.Length + 1]; - Array.Copy(source, 0, terminated, 0, source.Length); - terminated[^1] = 0; + /// + /// Private delegate for the native CreateFlyText function's hook. + /// + private delegate IntPtr CreateFlyTextDelegate( + IntPtr addonFlyText, + FlyTextKind kind, + int val1, + int val2, + IntPtr text2, + uint color, + uint icon, + IntPtr text1, + float yOffset); - return terminated; - } + /// + /// Private delegate for the native AddFlyText function pointer. + /// + private delegate void AddFlyTextDelegate( + IntPtr addonFlyText, + uint actorIndex, + uint messageMax, + IntPtr numbers, + uint offsetNum, + uint offsetNumMax, + IntPtr strings, + uint offsetStr, + uint offsetStrMax, + int unknown); - private IntPtr CreateFlyTextDetour( - IntPtr addonFlyText, - FlyTextKind kind, - int val1, - int val2, - IntPtr text2, - uint color, - uint icon, - IntPtr text1, - float yOffset) - { - var retVal = IntPtr.Zero; - try + /// + /// The FlyText event that can be subscribed to. + /// + public event OnFlyTextCreatedDelegate? FlyTextCreated; + + private Dalamud Dalamud { get; } + + private FlyTextGuiAddressResolver Address { get; } + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() { - Log.Verbose("[FlyText] Enter CreateFlyText detour!"); + this.createFlyTextHook.Dispose(); + } - var handled = false; + /// + /// Displays a fly text in-game on the local player. + /// + /// The FlyTextKind. See . + /// The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player. + /// Value1 passed to the native flytext function. + /// Value2 passed to the native flytext function. Seems unused. + /// Text1 passed to the native flytext function. + /// Text2 passed to the native flytext function. + /// Color passed to the native flytext function. Changes flytext color. + /// Icon ID passed to the native flytext function. Only displays with select FlyTextKind. + public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon) + { + // Known valid flytext region within the atk arrays + var numIndex = 28; + var strIndex = 25; + var numOffset = 147u; + var strOffset = 28u; - var tmpKind = kind; - var tmpVal1 = val1; - var tmpVal2 = val2; - var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1); - var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2); - var tmpColor = color; - var tmpIcon = icon; - var tmpYOffset = yOffset; + // Get the UI module and flytext addon pointers + var gameGui = Service.Get(); + var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule(); + var flytext = gameGui.GetAddonByName("_FlyText", 1); - var cmpText1 = tmpText1.ToString(); - var cmpText2 = tmpText2.ToString(); + if (ui == null || flytext == IntPtr.Zero) + return; - Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " + - $"kind({kind}) val1({val1}) val2({val2}) " + - $"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " + - $"color({color:X}) icon({icon}) yOffset({yOffset})"); - Log.Verbose("[FlyText] Calling flytext events!"); - this.FlyTextCreated?.Invoke( - ref tmpKind, - ref tmpVal1, - ref tmpVal2, - ref tmpText1, - ref tmpText2, - ref tmpColor, - ref tmpIcon, - ref tmpYOffset, - ref handled); + // Get the number and string arrays we need + var atkArrayDataHolder = ui->RaptureAtkModule.AtkModule.AtkArrayDataHolder; + var numArray = atkArrayDataHolder._NumberArrays[numIndex]; + var strArray = atkArrayDataHolder._StringArrays[strIndex]; - // If handled, ignore the original call - if (handled) + // Write the values to the arrays using a known valid flytext region + numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section + numArray->IntArray[numOffset + 1] = (int)kind; + numArray->IntArray[numOffset + 2] = unchecked((int)val1); + numArray->IntArray[numOffset + 3] = unchecked((int)val2); + numArray->IntArray[numOffset + 4] = 5; // Unknown + numArray->IntArray[numOffset + 5] = unchecked((int)color); + numArray->IntArray[numOffset + 6] = unchecked((int)icon); + numArray->IntArray[numOffset + 7] = 0; // Unknown + numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset + + fixed (byte* pText1 = text1.Encode()) { - Log.Verbose("[FlyText] FlyText was handled."); - - // Returning null to AddFlyText from CreateFlyText will result - // in the operation being dropped entirely. - return IntPtr.Zero; - } - - // Check if any values have changed - var dirty = tmpKind != kind || - tmpVal1 != val1 || - tmpVal2 != val2 || - tmpText1.ToString() != cmpText1 || - tmpText2.ToString() != cmpText2 || - tmpColor != color || - tmpIcon != icon || - Math.Abs(tmpYOffset - yOffset) > float.Epsilon; - - // If not dirty, make the original call - if (!dirty) - { - Log.Verbose("[FlyText] Calling flytext with original args."); - return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset); - } - - var terminated1 = Terminate(tmpText1.Encode()); - var terminated2 = Terminate(tmpText2.Encode()); - var pText1 = Marshal.AllocHGlobal(terminated1.Length); - var pText2 = Marshal.AllocHGlobal(terminated2.Length); - Marshal.Copy(terminated1, 0, pText1, terminated1.Length); - Marshal.Copy(terminated2, 0, pText2, terminated2.Length); - Log.Verbose("[FlyText] Allocated and set strings."); - - retVal = this.createFlyTextHook.Original( - addonFlyText, - tmpKind, - tmpVal1, - tmpVal2, - pText2, - tmpColor, - tmpIcon, - pText1, - tmpYOffset); - - Log.Verbose("[FlyText] Returned from original. Delaying free task."); - - Task.Delay(2000).ContinueWith(_ => - { - try + fixed (byte* pText2 = text2.Encode()) { - Marshal.FreeHGlobal(pText1); - Marshal.FreeHGlobal(pText2); - Log.Verbose("[FlyText] Freed strings."); + strArray->StringArray[strOffset + 0] = pText1; + strArray->StringArray[strOffset + 1] = pText2; + + this.addFlyTextNative( + flytext, + actorIndex, + 1, + (IntPtr)numArray, + numOffset, + 9, + (IntPtr)strArray, + strOffset, + 2, + 0); } - catch (Exception e) - { - Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task."); - } - }); - } - catch (Exception e) - { - Log.Error(e, "Exception occurred in CreateFlyTextDetour!"); + } } - return retVal; + /// + /// Enables this module. + /// + internal void Enable() + { + this.createFlyTextHook.Enable(); + } + + private static byte[] Terminate(byte[] source) + { + var terminated = new byte[source.Length + 1]; + Array.Copy(source, 0, terminated, 0, source.Length); + terminated[^1] = 0; + + return terminated; + } + + private IntPtr CreateFlyTextDetour( + IntPtr addonFlyText, + FlyTextKind kind, + int val1, + int val2, + IntPtr text2, + uint color, + uint icon, + IntPtr text1, + float yOffset) + { + var retVal = IntPtr.Zero; + try + { + Log.Verbose("[FlyText] Enter CreateFlyText detour!"); + + var handled = false; + + var tmpKind = kind; + var tmpVal1 = val1; + var tmpVal2 = val2; + var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1); + var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2); + var tmpColor = color; + var tmpIcon = icon; + var tmpYOffset = yOffset; + + var cmpText1 = tmpText1.ToString(); + var cmpText2 = tmpText2.ToString(); + + Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " + + $"kind({kind}) val1({val1}) val2({val2}) " + + $"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " + + $"color({color:X}) icon({icon}) yOffset({yOffset})"); + Log.Verbose("[FlyText] Calling flytext events!"); + this.FlyTextCreated?.Invoke( + ref tmpKind, + ref tmpVal1, + ref tmpVal2, + ref tmpText1, + ref tmpText2, + ref tmpColor, + ref tmpIcon, + ref tmpYOffset, + ref handled); + + // If handled, ignore the original call + if (handled) + { + Log.Verbose("[FlyText] FlyText was handled."); + + // Returning null to AddFlyText from CreateFlyText will result + // in the operation being dropped entirely. + return IntPtr.Zero; + } + + // Check if any values have changed + var dirty = tmpKind != kind || + tmpVal1 != val1 || + tmpVal2 != val2 || + tmpText1.ToString() != cmpText1 || + tmpText2.ToString() != cmpText2 || + tmpColor != color || + tmpIcon != icon || + Math.Abs(tmpYOffset - yOffset) > float.Epsilon; + + // If not dirty, make the original call + if (!dirty) + { + Log.Verbose("[FlyText] Calling flytext with original args."); + return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon, text1, yOffset); + } + + var terminated1 = Terminate(tmpText1.Encode()); + var terminated2 = Terminate(tmpText2.Encode()); + var pText1 = Marshal.AllocHGlobal(terminated1.Length); + var pText2 = Marshal.AllocHGlobal(terminated2.Length); + Marshal.Copy(terminated1, 0, pText1, terminated1.Length); + Marshal.Copy(terminated2, 0, pText2, terminated2.Length); + Log.Verbose("[FlyText] Allocated and set strings."); + + retVal = this.createFlyTextHook.Original( + addonFlyText, + tmpKind, + tmpVal1, + tmpVal2, + pText2, + tmpColor, + tmpIcon, + pText1, + tmpYOffset); + + Log.Verbose("[FlyText] Returned from original. Delaying free task."); + + Task.Delay(2000).ContinueWith(_ => + { + try + { + Marshal.FreeHGlobal(pText1); + Marshal.FreeHGlobal(pText2); + Log.Verbose("[FlyText] Freed strings."); + } + catch (Exception e) + { + Log.Verbose(e, "[FlyText] Exception occurred freeing strings in task."); + } + }); + } + catch (Exception e) + { + Log.Error(e, "Exception occurred in CreateFlyTextDetour!"); + } + + return retVal; + } } } diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs b/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs index f608c7d77..ae0eb0035 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGuiAddressResolver.cs @@ -1,31 +1,32 @@ using System; -namespace Dalamud.Game.Gui.FlyText; - -/// -/// An address resolver for the class. -/// -public class FlyTextGuiAddressResolver : BaseAddressResolver +namespace Dalamud.Game.Gui.FlyText { /// - /// Gets the address of the native AddFlyText method, which occurs - /// when the game adds fly text elements to the UI. Multiple fly text - /// elements can be added in a single AddFlyText call. + /// An address resolver for the class. /// - public IntPtr AddFlyText { get; private set; } - - /// - /// Gets the address of the native CreateFlyText method, which occurs - /// when the game creates a new fly text element. This method is called - /// once per fly text element, and can be called multiple times per - /// AddFlyText call. - /// - public IntPtr CreateFlyText { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) + public class FlyTextGuiAddressResolver : BaseAddressResolver { - this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7"); - this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA"); + /// + /// Gets the address of the native AddFlyText method, which occurs + /// when the game adds fly text elements to the UI. Multiple fly text + /// elements can be added in a single AddFlyText call. + /// + public IntPtr AddFlyText { get; private set; } + + /// + /// Gets the address of the native CreateFlyText method, which occurs + /// when the game creates a new fly text element. This method is called + /// once per fly text element, and can be called multiple times per + /// AddFlyText call. + /// + public IntPtr CreateFlyText { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) + { + this.AddFlyText = sig.ScanText("E8 ?? ?? ?? ?? FF C7 41 D1 C7"); + this.CreateFlyText = sig.ScanText("48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 48 63 FA"); + } } } diff --git a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs index 30c4c5e53..2b059f168 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextKind.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextKind.cs @@ -1,282 +1,283 @@ -namespace Dalamud.Game.Gui.FlyText; - -/// -/// Enum of FlyTextKind values. Members suffixed with -/// a number seem to be a duplicate, or perform duplicate behavior. -/// -public enum FlyTextKind : int +namespace Dalamud.Game.Gui.FlyText { /// - /// Val1 in serif font, Text2 in sans-serif as subtitle. - /// Used for autos and incoming DoTs. + /// Enum of FlyTextKind values. Members suffixed with + /// a number seem to be a duplicate, or perform duplicate behavior. /// - AutoAttack = 0, + public enum FlyTextKind : int + { + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle. + /// Used for autos and incoming DoTs. + /// + AutoAttack = 0, - /// - /// Val1 in serif font, Text2 in sans-serif as subtitle. - /// Does a bounce effect on appearance. - /// - DirectHit = 1, + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle. + /// Does a bounce effect on appearance. + /// + DirectHit = 1, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. - /// Does a bigger bounce effect on appearance. - /// - CriticalHit = 2, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. + /// Does a bigger bounce effect on appearance. + /// + CriticalHit = 2, - /// - /// Val1 in even larger serif font with 2 exclamations, Text2 in - /// sans-serif as subtitle. Does a large bounce effect on appearance. - /// Does not scroll up or down the screen. - /// - CriticalDirectHit = 3, + /// + /// Val1 in even larger serif font with 2 exclamations, Text2 in + /// sans-serif as subtitle. Does a large bounce effect on appearance. + /// Does not scroll up or down the screen. + /// + CriticalDirectHit = 3, - /// - /// AutoAttack with sans-serif Text1 to the left of the Val1. - /// - NamedAttack = 4, + /// + /// AutoAttack with sans-serif Text1 to the left of the Val1. + /// + NamedAttack = 4, - /// - /// DirectHit with sans-serif Text1 to the left of the Val1. - /// - NamedDirectHit = 5, + /// + /// DirectHit with sans-serif Text1 to the left of the Val1. + /// + NamedDirectHit = 5, - /// - /// CriticalHit with sans-serif Text1 to the left of the Val1. - /// - NamedCriticalHit = 6, + /// + /// CriticalHit with sans-serif Text1 to the left of the Val1. + /// + NamedCriticalHit = 6, - /// - /// CriticalDirectHit with sans-serif Text1 to the left of the Val1. - /// - NamedCriticalDirectHit = 7, + /// + /// CriticalDirectHit with sans-serif Text1 to the left of the Val1. + /// + NamedCriticalDirectHit = 7, - /// - /// All caps, serif MISS. - /// - Miss = 8, + /// + /// All caps, serif MISS. + /// + Miss = 8, - /// - /// Sans-serif Text1 next to all caps serif MISS. - /// - NamedMiss = 9, + /// + /// Sans-serif Text1 next to all caps serif MISS. + /// + NamedMiss = 9, - /// - /// All caps serif DODGE. - /// - Dodge = 10, + /// + /// All caps serif DODGE. + /// + Dodge = 10, - /// - /// Sans-serif Text1 next to all caps serif DODGE. - /// - NamedDodge = 11, + /// + /// Sans-serif Text1 next to all caps serif DODGE. + /// + NamedDodge = 11, - /// - /// Icon next to sans-serif Text1. - /// - NamedIcon = 12, + /// + /// Icon next to sans-serif Text1. + /// + NamedIcon = 12, - /// - /// Icon next to sans-serif Text1 (2). - /// - NamedIcon2 = 13, + /// + /// Icon next to sans-serif Text1 (2). + /// + NamedIcon2 = 13, - /// - /// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle. - /// - Exp = 14, + /// + /// Serif Val1 with all caps condensed font EXP with Text2 in sans-serif as subtitle. + /// + Exp = 14, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. - /// - NamedMp = 15, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle. + /// + NamedMp = 15, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. - /// - NamedTp = 16, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle. + /// + NamedTp = 16, - /// - /// AutoAttack with sans-serif Text1 to the left of the Val1 (2). - /// - NamedAttack2 = 17, + /// + /// AutoAttack with sans-serif Text1 to the left of the Val1 (2). + /// + NamedAttack2 = 17, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2). - /// - NamedMp2 = 18, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (2). + /// + NamedMp2 = 18, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2). - /// - NamedTp2 = 19, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (2). + /// + NamedTp2 = 19, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle. - /// - NamedEp = 20, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font EP with Text2 in sans-serif as subtitle. + /// + NamedEp = 20, - /// - /// Displays nothing. - /// - None = 21, + /// + /// Displays nothing. + /// + None = 21, - /// - /// All caps serif INVULNERABLE. - /// - Invulnerable = 22, + /// + /// All caps serif INVULNERABLE. + /// + Invulnerable = 22, - /// - /// All caps sans-serif condensed font INTERRUPTED! - /// Does a large bounce effect on appearance. - /// Does not scroll up or down the screen. - /// - Interrupted = 23, + /// + /// All caps sans-serif condensed font INTERRUPTED! + /// Does a large bounce effect on appearance. + /// Does not scroll up or down the screen. + /// + Interrupted = 23, - /// - /// AutoAttack with no Text2. - /// - AutoAttackNoText = 24, + /// + /// AutoAttack with no Text2. + /// + AutoAttackNoText = 24, - /// - /// AutoAttack with no Text2 (2). - /// - AutoAttackNoText2 = 25, + /// + /// AutoAttack with no Text2 (2). + /// + AutoAttackNoText2 = 25, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2). - /// - CriticalHit2 = 26, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle. Does a bigger bounce effect on appearance (2). + /// + CriticalHit2 = 26, - /// - /// AutoAttack with no Text2 (3). - /// - AutoAttackNoText3 = 27, + /// + /// AutoAttack with no Text2 (3). + /// + AutoAttackNoText3 = 27, - /// - /// CriticalHit with sans-serif Text1 to the left of the Val1 (2). - /// - NamedCriticalHit2 = 28, + /// + /// CriticalHit with sans-serif Text1 to the left of the Val1 (2). + /// + NamedCriticalHit2 = 28, - /// - /// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1. - /// Does a jiggle effect to the right on appearance. - /// - NamedCriticalHitWithMp = 29, + /// + /// Same as NamedCriticalHit with a green (cannot change) MP in condensed font to the right of Val1. + /// Does a jiggle effect to the right on appearance. + /// + NamedCriticalHitWithMp = 29, - /// - /// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1. - /// Does a jiggle effect to the right on appearance. - /// - NamedCriticalHitWithTp = 30, + /// + /// Same as NamedCriticalHit with a yellow (cannot change) TP in condensed font to the right of Val1. + /// Does a jiggle effect to the right on appearance. + /// + NamedCriticalHitWithTp = 30, - /// - /// Same as NamedIcon with sans-serif "has no effect!" to the right. - /// - NamedIconHasNoEffect = 31, + /// + /// Same as NamedIcon with sans-serif "has no effect!" to the right. + /// + NamedIconHasNoEffect = 31, - /// - /// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration. - /// - NamedIconFaded = 32, + /// + /// Same as NamedIcon but Text1 is slightly faded. Used for buff expiration. + /// + NamedIconFaded = 32, - /// - /// Same as NamedIcon but Text1 is slightly faded (2). - /// Used for buff expiration. - /// - NamedIconFaded2 = 33, + /// + /// Same as NamedIcon but Text1 is slightly faded (2). + /// Used for buff expiration. + /// + NamedIconFaded2 = 33, - /// - /// Text1 in sans-serif font. - /// - Named = 34, + /// + /// Text1 in sans-serif font. + /// + Named = 34, - /// - /// Same as NamedIcon with sans-serif "(fully resisted)" to the right. - /// - NamedIconFullyResisted = 35, + /// + /// Same as NamedIcon with sans-serif "(fully resisted)" to the right. + /// + NamedIconFullyResisted = 35, - /// - /// All caps serif 'INCAPACITATED!'. - /// - Incapacitated = 36, + /// + /// All caps serif 'INCAPACITATED!'. + /// + Incapacitated = 36, - /// - /// Text1 with sans-serif "(fully resisted)" to the right. - /// - NamedFullyResisted = 37, + /// + /// Text1 with sans-serif "(fully resisted)" to the right. + /// + NamedFullyResisted = 37, - /// - /// Text1 with sans-serif "has no effect!" to the right. - /// - NamedHasNoEffect = 38, + /// + /// Text1 with sans-serif "has no effect!" to the right. + /// + NamedHasNoEffect = 38, - /// - /// AutoAttack with sans-serif Text1 to the left of the Val1 (3). - /// - NamedAttack3 = 39, + /// + /// AutoAttack with sans-serif Text1 to the left of the Val1 (3). + /// + NamedAttack3 = 39, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3). - /// - NamedMp3 = 40, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font MP with Text2 in sans-serif as subtitle (3). + /// + NamedMp3 = 40, - /// - /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3). - /// - NamedTp3 = 41, + /// + /// Sans-serif Text1 next to serif Val1 with all caps condensed font TP with Text2 in sans-serif as subtitle (3). + /// + NamedTp3 = 41, - /// - /// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1. - /// - NamedIconInvulnerable = 42, + /// + /// Same as NamedIcon with serif "INVULNERABLE!" beneath the Text1. + /// + NamedIconInvulnerable = 42, - /// - /// All caps serif RESIST. - /// - Resist = 43, + /// + /// All caps serif RESIST. + /// + Resist = 43, - /// - /// Same as NamedIcon but places the given icon in the item icon outline. - /// - NamedIconWithItemOutline = 44, + /// + /// Same as NamedIcon but places the given icon in the item icon outline. + /// + NamedIconWithItemOutline = 44, - /// - /// AutoAttack with no Text2 (4). - /// - AutoAttackNoText4 = 45, + /// + /// AutoAttack with no Text2 (4). + /// + AutoAttackNoText4 = 45, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3). - /// Does a bigger bounce effect on appearance. - /// - CriticalHit3 = 46, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (3). + /// Does a bigger bounce effect on appearance. + /// + CriticalHit3 = 46, - /// - /// All caps serif REFLECT. - /// - Reflect = 47, + /// + /// All caps serif REFLECT. + /// + Reflect = 47, - /// - /// All caps serif REFLECTED. - /// - Reflected = 48, + /// + /// All caps serif REFLECTED. + /// + Reflected = 48, - /// - /// Val1 in serif font, Text2 in sans-serif as subtitle (2). - /// Does a bounce effect on appearance. - /// - DirectHit2 = 49, + /// + /// Val1 in serif font, Text2 in sans-serif as subtitle (2). + /// Does a bounce effect on appearance. + /// + DirectHit2 = 49, - /// - /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4). - /// Does a bigger bounce effect on appearance. - /// - CriticalHit4 = 50, + /// + /// Val1 in larger serif font with exclamation, with Text2 in sans-serif as subtitle (4). + /// Does a bigger bounce effect on appearance. + /// + CriticalHit4 = 50, - /// - /// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2). - /// Does a large bounce effect on appearance. Does not scroll up or down the screen. - /// - CriticalDirectHit2 = 51, + /// + /// Val1 in even larger serif font with 2 exclamations, Text2 in sans-serif as subtitle (2). + /// Does a large bounce effect on appearance. Does not scroll up or down the screen. + /// + CriticalDirectHit2 = 51, + } } diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index ad1d90421..0c1ae4e51 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -14,583 +14,584 @@ using Dalamud.Utility; using ImGuiNET; using Serilog; -namespace Dalamud.Game.Gui; - -/// -/// A class handling many aspects of the in-game UI. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class GameGui : IDisposable +namespace Dalamud.Game.Gui { - private readonly GameGuiAddressResolver address; - - private readonly GetMatrixSingletonDelegate getMatrixSingleton; - private readonly ScreenToWorldNativeDelegate screenToWorldNative; - private readonly GetAgentModuleDelegate getAgentModule; - - private readonly Hook setGlobalBgmHook; - private readonly Hook handleItemHoverHook; - private readonly Hook handleItemOutHook; - private readonly Hook handleActionHoverHook; - private readonly Hook handleActionOutHook; - private readonly Hook handleImmHook; - private readonly Hook toggleUiHideHook; - - private GetUIMapObjectDelegate getUIMapObject; - private OpenMapWithFlagDelegate openMapWithFlag; - /// - /// Initializes a new instance of the class. - /// This class is responsible for many aspects of interacting with the native game UI. + /// A class handling many aspects of the in-game UI. /// - internal GameGui() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class GameGui : IDisposable { - this.address = new GameGuiAddressResolver(); - this.address.Setup(); + private readonly GameGuiAddressResolver address; - Log.Verbose("===== G A M E G U I ====="); - Log.Verbose($"GameGuiManager address 0x{this.address.BaseAddress.ToInt64():X}"); - Log.Verbose($"SetGlobalBgm address 0x{this.address.SetGlobalBgm.ToInt64():X}"); - Log.Verbose($"HandleItemHover address 0x{this.address.HandleItemHover.ToInt64():X}"); - Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}"); - Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}"); - Log.Verbose($"GetAgentModule address 0x{this.address.GetAgentModule.ToInt64():X}"); + private readonly GetMatrixSingletonDelegate getMatrixSingleton; + private readonly ScreenToWorldNativeDelegate screenToWorldNative; + private readonly GetAgentModuleDelegate getAgentModule; - Service.Set(new ChatGui(this.address.ChatManager)); - Service.Set(); - Service.Set(); - Service.Set(); + private readonly Hook setGlobalBgmHook; + private readonly Hook handleItemHoverHook; + private readonly Hook handleItemOutHook; + private readonly Hook handleActionHoverHook; + private readonly Hook handleActionOutHook; + private readonly Hook handleImmHook; + private readonly Hook toggleUiHideHook; - this.setGlobalBgmHook = new Hook(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); + private GetUIMapObjectDelegate getUIMapObject; + private OpenMapWithFlagDelegate openMapWithFlag; - this.handleItemHoverHook = new Hook(this.address.HandleItemHover, this.HandleItemHoverDetour); - this.handleItemOutHook = new Hook(this.address.HandleItemOut, this.HandleItemOutDetour); - - this.handleActionHoverHook = new Hook(this.address.HandleActionHover, this.HandleActionHoverDetour); - this.handleActionOutHook = new Hook(this.address.HandleActionOut, this.HandleActionOutDetour); - - this.handleImmHook = new Hook(this.address.HandleImm, this.HandleImmDetour); - - this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer(this.address.GetMatrixSingleton); - - this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer(this.address.ScreenToWorld); - - this.toggleUiHideHook = new Hook(this.address.ToggleUiHide, this.ToggleUiHideDetour); - - this.getAgentModule = Marshal.GetDelegateForFunctionPointer(this.address.GetAgentModule); - } - - // Marshaled delegates - - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate IntPtr GetMatrixSingletonDelegate(); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private unsafe delegate bool ScreenToWorldNativeDelegate(float* camPos, float* clipPos, float rayDistance, float* worldPos, int* unknown); - - private delegate IntPtr GetAgentModuleDelegate(IntPtr uiModule); - - // Hooked delegates - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)] - private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte); - - /// - /// Event which is fired when the game UI hiding is toggled. - /// - public event EventHandler UiHideToggled; - - /// - /// Event that is fired when the currently hovered item changes. - /// - public event EventHandler HoveredItemChanged; - - /// - /// Event that is fired when the currently hovered action changes. - /// - public event EventHandler HoveredActionChanged; - - /// - /// Gets a value indicating whether the game UI is hidden. - /// - public bool GameUiHidden { get; private set; } - - /// - /// Gets or sets the item ID that is currently hovered by the player. 0 when no item is hovered. - /// If > 1.000.000, subtract 1.000.000 and treat it as HQ. - /// - public ulong HoveredItem { get; set; } - - /// - /// Gets the action ID that is current hovered by the player. 0 when no action is hovered. - /// - public HoveredAction HoveredAction { get; } = new HoveredAction(); - - /// - /// Opens the in-game map with a flag on the location of the parameter. - /// - /// Link to the map to be opened. - /// True if there were no errors and it could open the map. - public bool OpenMapWithMapLink(MapLinkPayload mapLink) - { - var uiModule = this.GetUIModule(); - - if (uiModule == IntPtr.Zero) + /// + /// Initializes a new instance of the class. + /// This class is responsible for many aspects of interacting with the native game UI. + /// + internal GameGui() { - Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()"); - return false; + this.address = new GameGuiAddressResolver(); + this.address.Setup(); + + Log.Verbose("===== G A M E G U I ====="); + Log.Verbose($"GameGuiManager address 0x{this.address.BaseAddress.ToInt64():X}"); + Log.Verbose($"SetGlobalBgm address 0x{this.address.SetGlobalBgm.ToInt64():X}"); + Log.Verbose($"HandleItemHover address 0x{this.address.HandleItemHover.ToInt64():X}"); + Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}"); + Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}"); + Log.Verbose($"GetAgentModule address 0x{this.address.GetAgentModule.ToInt64():X}"); + + Service.Set(new ChatGui(this.address.ChatManager)); + Service.Set(); + Service.Set(); + Service.Set(); + + this.setGlobalBgmHook = new Hook(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); + + this.handleItemHoverHook = new Hook(this.address.HandleItemHover, this.HandleItemHoverDetour); + this.handleItemOutHook = new Hook(this.address.HandleItemOut, this.HandleItemOutDetour); + + this.handleActionHoverHook = new Hook(this.address.HandleActionHover, this.HandleActionHoverDetour); + this.handleActionOutHook = new Hook(this.address.HandleActionOut, this.HandleActionOutDetour); + + this.handleImmHook = new Hook(this.address.HandleImm, this.HandleImmDetour); + + this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer(this.address.GetMatrixSingleton); + + this.screenToWorldNative = Marshal.GetDelegateForFunctionPointer(this.address.ScreenToWorld); + + this.toggleUiHideHook = new Hook(this.address.ToggleUiHide, this.ToggleUiHideDetour); + + this.getAgentModule = Marshal.GetDelegateForFunctionPointer(this.address.GetAgentModule); } - this.getUIMapObject = this.address.GetVirtualFunction(uiModule, 0, 8); + // Marshaled delegates - var uiMapObjectPtr = this.getUIMapObject(uiModule); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate IntPtr GetMatrixSingletonDelegate(); - if (uiMapObjectPtr == IntPtr.Zero) + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private unsafe delegate bool ScreenToWorldNativeDelegate(float* camPos, float* clipPos, float rayDistance, float* worldPos, int* unknown); + + private delegate IntPtr GetAgentModuleDelegate(IntPtr uiModule); + + // Hooked delegates + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)] + private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte); + + /// + /// Event which is fired when the game UI hiding is toggled. + /// + public event EventHandler UiHideToggled; + + /// + /// Event that is fired when the currently hovered item changes. + /// + public event EventHandler HoveredItemChanged; + + /// + /// Event that is fired when the currently hovered action changes. + /// + public event EventHandler HoveredActionChanged; + + /// + /// Gets a value indicating whether the game UI is hidden. + /// + public bool GameUiHidden { get; private set; } + + /// + /// Gets or sets the item ID that is currently hovered by the player. 0 when no item is hovered. + /// If > 1.000.000, subtract 1.000.000 and treat it as HQ. + /// + public ulong HoveredItem { get; set; } + + /// + /// Gets the action ID that is current hovered by the player. 0 when no action is hovered. + /// + public HoveredAction HoveredAction { get; } = new HoveredAction(); + + /// + /// Opens the in-game map with a flag on the location of the parameter. + /// + /// Link to the map to be opened. + /// True if there were no errors and it could open the map. + public bool OpenMapWithMapLink(MapLinkPayload mapLink) { - Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()"); - return false; - } + var uiModule = this.GetUIModule(); - this.openMapWithFlag = this.address.GetVirtualFunction(uiMapObjectPtr, 0, 63); - - var mapLinkString = mapLink.DataString; - - Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}"); - - return this.openMapWithFlag(uiMapObjectPtr, mapLinkString); - } - - /// - /// Converts in-world coordinates to screen coordinates (upper left corner origin). - /// - /// Coordinates in the world. - /// Converted coordinates. - /// True if worldPos corresponds to a position in front of the camera. - public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) - { - // Get base object with matrices - var matrixSingleton = this.getMatrixSingleton(); - - // Read current ViewProjectionMatrix plus game window size - var viewProjectionMatrix = default(SharpDX.Matrix); - float width, height; - var windowPos = ImGuiHelpers.MainViewport.Pos; - - unsafe - { - var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer(); - - for (var i = 0; i < 16; i++, rawMatrix++) - viewProjectionMatrix[i] = *rawMatrix; - - width = *rawMatrix; - height = *(rawMatrix + 1); - } - - var worldPosDx = worldPos.ToSharpDX(); - SharpDX.Vector3.Transform(ref worldPosDx, ref viewProjectionMatrix, out SharpDX.Vector3 pCoords); - - screenPos = new Vector2(pCoords.X / pCoords.Z, pCoords.Y / pCoords.Z); - - screenPos.X = (0.5f * width * (screenPos.X + 1f)) + windowPos.X; - screenPos.Y = (0.5f * height * (1f - screenPos.Y)) + windowPos.Y; - - return pCoords.Z > 0 && - screenPos.X > windowPos.X && screenPos.X < windowPos.X + width && - screenPos.Y > windowPos.Y && screenPos.Y < windowPos.Y + height; - } - - /// - /// Converts screen coordinates to in-world coordinates via raycasting. - /// - /// Screen coordinates. - /// Converted coordinates. - /// How far to search for a collision. - /// True if successful. On false, worldPos's contents are undefined. - public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000.0f) - { - // The game is only visible in the main viewport, so if the cursor is outside - // of the game window, do not bother calculating anything - var windowPos = ImGuiHelpers.MainViewport.Pos; - var windowSize = ImGuiHelpers.MainViewport.Size; - - if (screenPos.X < windowPos.X || screenPos.X > windowPos.X + windowSize.X || - screenPos.Y < windowPos.Y || screenPos.Y > windowPos.Y + windowSize.Y) - { - worldPos = default; - return false; - } - - // Get base object with matrices - var matrixSingleton = this.getMatrixSingleton(); - - // Read current ViewProjectionMatrix plus game window size - var viewProjectionMatrix = default(SharpDX.Matrix); - float width, height; - unsafe - { - var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer(); - - for (var i = 0; i < 16; i++, rawMatrix++) - viewProjectionMatrix[i] = *rawMatrix; - - width = *rawMatrix; - height = *(rawMatrix + 1); - } - - viewProjectionMatrix.Invert(); - - var localScreenPos = new SharpDX.Vector2(screenPos.X - windowPos.X, screenPos.Y - windowPos.Y); - var screenPos3D = new SharpDX.Vector3 - { - X = (localScreenPos.X / width * 2.0f) - 1.0f, - Y = -((localScreenPos.Y / height * 2.0f) - 1.0f), - Z = 0, - }; - - SharpDX.Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPos); - - screenPos3D.Z = 1; - SharpDX.Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPosOne); - - var clipPos = camPosOne - camPos; - clipPos.Normalize(); - - bool isSuccess; - unsafe - { - var camPosArray = camPos.ToArray(); - var clipPosArray = clipPos.ToArray(); - - // This array is larger than necessary because it contains more info than we currently use - var worldPosArray = stackalloc float[32]; - - // Theory: this is some kind of flag on what type of things the ray collides with - var unknown = stackalloc int[3] + if (uiModule == IntPtr.Zero) { - 0x4000, - 0x4000, - 0x0, + Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()"); + return false; + } + + this.getUIMapObject = this.address.GetVirtualFunction(uiModule, 0, 8); + + var uiMapObjectPtr = this.getUIMapObject(uiModule); + + if (uiMapObjectPtr == IntPtr.Zero) + { + Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()"); + return false; + } + + this.openMapWithFlag = this.address.GetVirtualFunction(uiMapObjectPtr, 0, 63); + + var mapLinkString = mapLink.DataString; + + Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}"); + + return this.openMapWithFlag(uiMapObjectPtr, mapLinkString); + } + + /// + /// Converts in-world coordinates to screen coordinates (upper left corner origin). + /// + /// Coordinates in the world. + /// Converted coordinates. + /// True if worldPos corresponds to a position in front of the camera. + public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) + { + // Get base object with matrices + var matrixSingleton = this.getMatrixSingleton(); + + // Read current ViewProjectionMatrix plus game window size + var viewProjectionMatrix = default(SharpDX.Matrix); + float width, height; + var windowPos = ImGuiHelpers.MainViewport.Pos; + + unsafe + { + var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer(); + + for (var i = 0; i < 16; i++, rawMatrix++) + viewProjectionMatrix[i] = *rawMatrix; + + width = *rawMatrix; + height = *(rawMatrix + 1); + } + + var worldPosDx = worldPos.ToSharpDX(); + SharpDX.Vector3.Transform(ref worldPosDx, ref viewProjectionMatrix, out SharpDX.Vector3 pCoords); + + screenPos = new Vector2(pCoords.X / pCoords.Z, pCoords.Y / pCoords.Z); + + screenPos.X = (0.5f * width * (screenPos.X + 1f)) + windowPos.X; + screenPos.Y = (0.5f * height * (1f - screenPos.Y)) + windowPos.Y; + + return pCoords.Z > 0 && + screenPos.X > windowPos.X && screenPos.X < windowPos.X + width && + screenPos.Y > windowPos.Y && screenPos.Y < windowPos.Y + height; + } + + /// + /// Converts screen coordinates to in-world coordinates via raycasting. + /// + /// Screen coordinates. + /// Converted coordinates. + /// How far to search for a collision. + /// True if successful. On false, worldPos's contents are undefined. + public bool ScreenToWorld(Vector2 screenPos, out Vector3 worldPos, float rayDistance = 100000.0f) + { + // The game is only visible in the main viewport, so if the cursor is outside + // of the game window, do not bother calculating anything + var windowPos = ImGuiHelpers.MainViewport.Pos; + var windowSize = ImGuiHelpers.MainViewport.Size; + + if (screenPos.X < windowPos.X || screenPos.X > windowPos.X + windowSize.X || + screenPos.Y < windowPos.Y || screenPos.Y > windowPos.Y + windowSize.Y) + { + worldPos = default; + return false; + } + + // Get base object with matrices + var matrixSingleton = this.getMatrixSingleton(); + + // Read current ViewProjectionMatrix plus game window size + var viewProjectionMatrix = default(SharpDX.Matrix); + float width, height; + unsafe + { + var rawMatrix = (float*)(matrixSingleton + 0x1b4).ToPointer(); + + for (var i = 0; i < 16; i++, rawMatrix++) + viewProjectionMatrix[i] = *rawMatrix; + + width = *rawMatrix; + height = *(rawMatrix + 1); + } + + viewProjectionMatrix.Invert(); + + var localScreenPos = new SharpDX.Vector2(screenPos.X - windowPos.X, screenPos.Y - windowPos.Y); + var screenPos3D = new SharpDX.Vector3 + { + X = (localScreenPos.X / width * 2.0f) - 1.0f, + Y = -((localScreenPos.Y / height * 2.0f) - 1.0f), + Z = 0, }; - fixed (float* pCamPos = camPosArray) + SharpDX.Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPos); + + screenPos3D.Z = 1; + SharpDX.Vector3.TransformCoordinate(ref screenPos3D, ref viewProjectionMatrix, out var camPosOne); + + var clipPos = camPosOne - camPos; + clipPos.Normalize(); + + bool isSuccess; + unsafe { - fixed (float* pClipPos = clipPosArray) + var camPosArray = camPos.ToArray(); + var clipPosArray = clipPos.ToArray(); + + // This array is larger than necessary because it contains more info than we currently use + var worldPosArray = stackalloc float[32]; + + // Theory: this is some kind of flag on what type of things the ray collides with + var unknown = stackalloc int[3] { - isSuccess = this.screenToWorldNative(pCamPos, pClipPos, rayDistance, worldPosArray, unknown); + 0x4000, + 0x4000, + 0x0, + }; + + fixed (float* pCamPos = camPosArray) + { + fixed (float* pClipPos = clipPosArray) + { + isSuccess = this.screenToWorldNative(pCamPos, pClipPos, rayDistance, worldPosArray, unknown); + } } + + worldPos = new Vector3 + { + X = worldPosArray[0], + Y = worldPosArray[1], + Z = worldPosArray[2], + }; } - worldPos = new Vector3 + return isSuccess; + } + + /// + /// Gets a pointer to the game's UI module. + /// + /// IntPtr pointing to UI module. + public unsafe IntPtr GetUIModule() + { + var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance(); + if (framework == null) + return IntPtr.Zero; + + var uiModule = framework->GetUiModule(); + if (uiModule == null) + return IntPtr.Zero; + + return (IntPtr)uiModule; + } + + /// + /// Gets the pointer to the Addon with the given name and index. + /// + /// Name of addon to find. + /// Index of addon to find (1-indexed). + /// IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the addon. + public unsafe IntPtr GetAddonByName(string name, int index) + { + var atkStage = FFXIVClientStructs.FFXIV.Component.GUI.AtkStage.GetSingleton(); + if (atkStage == null) + return IntPtr.Zero; + + var unitMgr = atkStage->RaptureAtkUnitManager; + if (unitMgr == null) + return IntPtr.Zero; + + var addon = unitMgr->GetAddonByName(name, index); + if (addon == null) + return IntPtr.Zero; + + return (IntPtr)addon; + } + + /// + /// Find the agent associated with an addon, if possible. + /// + /// The addon name. + /// A pointer to the agent interface. + public IntPtr FindAgentInterface(string addonName) + { + var addon = this.GetAddonByName(addonName, 1); + return this.FindAgentInterface(addon); + } + + /// + /// Find the agent associated with an addon, if possible. + /// + /// The addon address. + /// A pointer to the agent interface. + public unsafe IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon); + + /// + /// Find the agent associated with an addon, if possible. + /// + /// The addon address. + /// A pointer to the agent interface. + public unsafe IntPtr FindAgentInterface(IntPtr addon) + { + if (addon == IntPtr.Zero) + return IntPtr.Zero; + + var uiModule = Service.Get().GetUIModule(); + if (uiModule == IntPtr.Zero) { - X = worldPosArray[0], - Y = worldPosArray[1], - Z = worldPosArray[2], - }; - } - - return isSuccess; - } - - /// - /// Gets a pointer to the game's UI module. - /// - /// IntPtr pointing to UI module. - public unsafe IntPtr GetUIModule() - { - var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance(); - if (framework == null) - return IntPtr.Zero; - - var uiModule = framework->GetUiModule(); - if (uiModule == null) - return IntPtr.Zero; - - return (IntPtr)uiModule; - } - - /// - /// Gets the pointer to the Addon with the given name and index. - /// - /// Name of addon to find. - /// Index of addon to find (1-indexed). - /// IntPtr.Zero if unable to find UI, otherwise IntPtr pointing to the start of the addon. - public unsafe IntPtr GetAddonByName(string name, int index) - { - var atkStage = FFXIVClientStructs.FFXIV.Component.GUI.AtkStage.GetSingleton(); - if (atkStage == null) - return IntPtr.Zero; - - var unitMgr = atkStage->RaptureAtkUnitManager; - if (unitMgr == null) - return IntPtr.Zero; - - var addon = unitMgr->GetAddonByName(name, index); - if (addon == null) - return IntPtr.Zero; - - return (IntPtr)addon; - } - - /// - /// Find the agent associated with an addon, if possible. - /// - /// The addon name. - /// A pointer to the agent interface. - public IntPtr FindAgentInterface(string addonName) - { - var addon = this.GetAddonByName(addonName, 1); - return this.FindAgentInterface(addon); - } - - /// - /// Find the agent associated with an addon, if possible. - /// - /// The addon address. - /// A pointer to the agent interface. - public unsafe IntPtr FindAgentInterface(void* addon) => this.FindAgentInterface((IntPtr)addon); - - /// - /// Find the agent associated with an addon, if possible. - /// - /// The addon address. - /// A pointer to the agent interface. - public unsafe IntPtr FindAgentInterface(IntPtr addon) - { - if (addon == IntPtr.Zero) - return IntPtr.Zero; - - var uiModule = Service.Get().GetUIModule(); - if (uiModule == IntPtr.Zero) - { - return IntPtr.Zero; - } - - var agentModule = this.getAgentModule(uiModule); - if (agentModule == IntPtr.Zero) - { - return IntPtr.Zero; - } - - var unitBase = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)addon; - var id = unitBase->ParentID; - if (id == 0) - id = unitBase->ID; - - if (id == 0) - return IntPtr.Zero; - - for (var i = 0; i < 380; i++) - { - var agent = Marshal.ReadIntPtr(agentModule, 0x20 + (i * 8)); - if (agent == IntPtr.Zero) - continue; - if (Marshal.ReadInt32(agent, 0x20) == id) - return agent; - } - - return IntPtr.Zero; - } - - /// - /// Set the current background music. - /// - /// The background music key. - public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); - - /// - /// Enables the hooks and submodules of this module. - /// - public void Enable() - { - Service.Get().Enable(); - Service.Get().Enable(); - Service.Get().Enable(); - Service.Get().Enable(); - this.setGlobalBgmHook.Enable(); - this.handleItemHoverHook.Enable(); - this.handleItemOutHook.Enable(); - this.handleImmHook.Enable(); - this.toggleUiHideHook.Enable(); - this.handleActionHoverHook.Enable(); - this.handleActionOutHook.Enable(); - } - - /// - /// Disables the hooks and submodules of this module. - /// - public void Dispose() - { - Service.Get().Dispose(); - Service.Get().Dispose(); - Service.Get().Dispose(); - Service.Get().Dispose(); - this.setGlobalBgmHook.Dispose(); - this.handleItemHoverHook.Dispose(); - this.handleItemOutHook.Dispose(); - this.handleImmHook.Dispose(); - this.toggleUiHideHook.Dispose(); - this.handleActionHoverHook.Dispose(); - this.handleActionOutHook.Dispose(); - } - - private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6) - { - var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6); - - Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal); - - return retVal; - } - - private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) - { - var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4); - - if (retVal.ToInt64() == 22) - { - var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138); - this.HoveredItem = itemId; - - try - { - this.HoveredItemChanged?.Invoke(this, itemId); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); + return IntPtr.Zero; } - Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X")); + var agentModule = this.getAgentModule(uiModule); + if (agentModule == IntPtr.Zero) + { + return IntPtr.Zero; + } + + var unitBase = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)addon; + var id = unitBase->ParentID; + if (id == 0) + id = unitBase->ID; + + if (id == 0) + return IntPtr.Zero; + + for (var i = 0; i < 380; i++) + { + var agent = Marshal.ReadIntPtr(agentModule, 0x20 + (i * 8)); + if (agent == IntPtr.Zero) + continue; + if (Marshal.ReadInt32(agent, 0x20) == id) + return agent; + } + + return IntPtr.Zero; } - return retVal; - } + /// + /// Set the current background music. + /// + /// The background music key. + public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0); - private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) - { - var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4); - - if (a3 != IntPtr.Zero && a4 == 1) + /// + /// Enables the hooks and submodules of this module. + /// + public void Enable() { - var a3Val = Marshal.ReadByte(a3, 0x8); + Service.Get().Enable(); + Service.Get().Enable(); + Service.Get().Enable(); + Service.Get().Enable(); + this.setGlobalBgmHook.Enable(); + this.handleItemHoverHook.Enable(); + this.handleItemOutHook.Enable(); + this.handleImmHook.Enable(); + this.toggleUiHideHook.Enable(); + this.handleActionHoverHook.Enable(); + this.handleActionOutHook.Enable(); + } - if (a3Val == 255) + /// + /// Disables the hooks and submodules of this module. + /// + public void Dispose() + { + Service.Get().Dispose(); + Service.Get().Dispose(); + Service.Get().Dispose(); + Service.Get().Dispose(); + this.setGlobalBgmHook.Dispose(); + this.handleItemHoverHook.Dispose(); + this.handleItemOutHook.Dispose(); + this.handleImmHook.Dispose(); + this.toggleUiHideHook.Dispose(); + this.handleActionHoverHook.Dispose(); + this.handleActionOutHook.Dispose(); + } + + private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6) + { + var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6); + + Log.Verbose("SetGlobalBgm: {0} {1} {2} {3} {4} {5} -> {6}", bgmKey, a2, a3, a4, a5, a6, retVal); + + return retVal; + } + + private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) + { + var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4); + + if (retVal.ToInt64() == 22) { - this.HoveredItem = 0ul; + var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138); + this.HoveredItem = itemId; try { - this.HoveredItemChanged?.Invoke(this, 0ul); + this.HoveredItemChanged?.Invoke(this, itemId); } catch (Exception e) { Log.Error(e, "Could not dispatch HoveredItemChanged event."); } - Log.Verbose("HoverItemId: 0"); + Log.Verbose("HoverItemId:{0} this:{1}", itemId, hoverState.ToInt64().ToString("X")); } + + return retVal; } - return retVal; - } - - private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5) - { - this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); - this.HoveredAction.ActionKind = actionKind; - this.HoveredAction.BaseActionID = actionId; - this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C); - try + private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4) { - this.HoveredActionChanged?.Invoke(this, this.HoveredAction); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredItemChanged event."); - } + var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4); - Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X")); - } - - private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4) - { - var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4); - - if (a3 != IntPtr.Zero && a4 == 1) - { - var a3Val = Marshal.ReadByte(a3, 0x8); - - if (a3Val == 255) + if (a3 != IntPtr.Zero && a4 == 1) { - this.HoveredAction.ActionKind = HoverActionKind.None; - this.HoveredAction.BaseActionID = 0; - this.HoveredAction.ActionID = 0; + var a3Val = Marshal.ReadByte(a3, 0x8); - try + if (a3Val == 255) { - this.HoveredActionChanged?.Invoke(this, this.HoveredAction); - } - catch (Exception e) - { - Log.Error(e, "Could not dispatch HoveredActionChanged event."); - } + this.HoveredItem = 0ul; - Log.Verbose("HoverActionId: 0"); + try + { + this.HoveredItemChanged?.Invoke(this, 0ul); + } + catch (Exception e) + { + Log.Error(e, "Could not dispatch HoveredItemChanged event."); + } + + Log.Verbose("HoverItemId: 0"); + } } + + return retVal; } - return retVal; - } - - private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte) - { - this.GameUiHidden = !this.GameUiHidden; - - try + private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5) { - this.UiHideToggled?.Invoke(this, this.GameUiHidden); + this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5); + this.HoveredAction.ActionKind = actionKind; + this.HoveredAction.BaseActionID = actionId; + this.HoveredAction.ActionID = (uint)Marshal.ReadInt32(hoverState, 0x3C); + try + { + this.HoveredActionChanged?.Invoke(this, this.HoveredAction); + } + catch (Exception e) + { + Log.Error(e, "Could not dispatch HoveredItemChanged event."); + } + + Log.Verbose("HoverActionId: {0}/{1} this:{2}", actionKind, actionId, hoverState.ToInt64().ToString("X")); } - catch (Exception ex) + + private IntPtr HandleActionOutDetour(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4) { - Log.Error(ex, "Error on OnUiHideToggled event dispatch"); + var retVal = this.handleActionOutHook.Original(agentActionDetail, a2, a3, a4); + + if (a3 != IntPtr.Zero && a4 == 1) + { + var a3Val = Marshal.ReadByte(a3, 0x8); + + if (a3Val == 255) + { + this.HoveredAction.ActionKind = HoverActionKind.None; + this.HoveredAction.BaseActionID = 0; + this.HoveredAction.ActionID = 0; + + try + { + this.HoveredActionChanged?.Invoke(this, this.HoveredAction); + } + catch (Exception e) + { + Log.Error(e, "Could not dispatch HoveredActionChanged event."); + } + + Log.Verbose("HoverActionId: 0"); + } + } + + return retVal; } - Log.Debug("UiHide toggled: {0}", this.GameUiHidden); + private IntPtr ToggleUiHideDetour(IntPtr thisPtr, byte unknownByte) + { + this.GameUiHidden = !this.GameUiHidden; - return this.toggleUiHideHook.Original(thisPtr, unknownByte); - } + try + { + this.UiHideToggled?.Invoke(this, this.GameUiHidden); + } + catch (Exception ex) + { + Log.Error(ex, "Error on OnUiHideToggled event dispatch"); + } - private char HandleImmDetour(IntPtr framework, char a2, byte a3) - { - var result = this.handleImmHook.Original(framework, a2, a3); - return ImGui.GetIO().WantTextInput - ? (char)0 - : result; + Log.Debug("UiHide toggled: {0}", this.GameUiHidden); + + return this.toggleUiHideHook.Original(thisPtr, unknownByte); + } + + private char HandleImmDetour(IntPtr framework, char a2, byte a3) + { + var result = this.handleImmHook.Original(framework, a2, a3); + return ImGui.GetIO().WantTextInput + ? (char)0 + : result; + } } } diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index 31f68b13f..122a9eea2 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -1,103 +1,104 @@ using System; using System.Runtime.InteropServices; -namespace Dalamud.Game.Gui; - -/// -/// The address resolver for the class. -/// -internal sealed class GameGuiAddressResolver : BaseAddressResolver +namespace Dalamud.Game.Gui { /// - /// Initializes a new instance of the class. + /// The address resolver for the class. /// - public GameGuiAddressResolver() + internal sealed class GameGuiAddressResolver : BaseAddressResolver { - this.BaseAddress = Service.Get().Address.BaseAddress; - } + /// + /// Initializes a new instance of the class. + /// + public GameGuiAddressResolver() + { + this.BaseAddress = Service.Get().Address.BaseAddress; + } - /// - /// Gets the base address of the native GuiManager class. - /// - public IntPtr BaseAddress { get; private set; } + /// + /// Gets the base address of the native GuiManager class. + /// + public IntPtr BaseAddress { get; private set; } - /// - /// Gets the address of the native ChatManager class. - /// - public IntPtr ChatManager { get; private set; } + /// + /// Gets the address of the native ChatManager class. + /// + public IntPtr ChatManager { get; private set; } - /// - /// Gets the address of the native SetGlobalBgm method. - /// - public IntPtr SetGlobalBgm { get; private set; } + /// + /// Gets the address of the native SetGlobalBgm method. + /// + public IntPtr SetGlobalBgm { get; private set; } - /// - /// Gets the address of the native HandleItemHover method. - /// - public IntPtr HandleItemHover { get; private set; } + /// + /// Gets the address of the native HandleItemHover method. + /// + public IntPtr HandleItemHover { get; private set; } - /// - /// Gets the address of the native HandleItemOut method. - /// - public IntPtr HandleItemOut { get; private set; } + /// + /// Gets the address of the native HandleItemOut method. + /// + public IntPtr HandleItemOut { get; private set; } - /// - /// Gets the address of the native HandleActionHover method. - /// - public IntPtr HandleActionHover { get; private set; } + /// + /// Gets the address of the native HandleActionHover method. + /// + public IntPtr HandleActionHover { get; private set; } - /// - /// Gets the address of the native HandleActionOut method. - /// - public IntPtr HandleActionOut { get; private set; } + /// + /// Gets the address of the native HandleActionOut method. + /// + public IntPtr HandleActionOut { get; private set; } - /// - /// Gets the address of the native HandleImm method. - /// - public IntPtr HandleImm { get; private set; } + /// + /// Gets the address of the native HandleImm method. + /// + public IntPtr HandleImm { get; private set; } - /// - /// Gets the address of the native GetMatrixSingleton method. - /// - public IntPtr GetMatrixSingleton { get; private set; } + /// + /// Gets the address of the native GetMatrixSingleton method. + /// + public IntPtr GetMatrixSingleton { get; private set; } - /// - /// Gets the address of the native ScreenToWorld method. - /// - public IntPtr ScreenToWorld { get; private set; } + /// + /// Gets the address of the native ScreenToWorld method. + /// + public IntPtr ScreenToWorld { get; private set; } - /// - /// Gets the address of the native ToggleUiHide method. - /// - public IntPtr ToggleUiHide { get; private set; } + /// + /// Gets the address of the native ToggleUiHide method. + /// + public IntPtr ToggleUiHide { get; private set; } - /// - /// Gets the address of the native GetAgentModule method. - /// - public IntPtr GetAgentModule { get; private set; } + /// + /// Gets the address of the native GetAgentModule method. + /// + public IntPtr GetAgentModule { get; private set; } - /// - protected override void Setup64Bit(SigScanner sig) - { - this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58"); - this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??"); - this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D"); - this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F"); - this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F"); - this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); - this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??"); - this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1"); - this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??"); + /// + protected override void Setup64Bit(SigScanner sig) + { + this.SetGlobalBgm = sig.ScanText("4C 8B 15 ?? ?? ?? ?? 4D 85 D2 74 58"); + this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??"); + this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D"); + this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F"); + this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F"); + this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09"); + this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??"); + this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1"); + this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 0F B6 B9 ?? ?? ?? ?? B8 ?? ?? ?? ??"); - var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28"); - this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size); - } + var uiModuleVtableSig = sig.GetStaticAddressFromSig("48 8D 05 ?? ?? ?? ?? 4C 89 61 28"); + this.GetAgentModule = Marshal.ReadIntPtr(uiModuleVtableSig, 34 * IntPtr.Size); + } - /// - protected override void SetupInternal(SigScanner scanner) - { - // Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h] - // Xiv__UiManager__GetChatManager+7 000 retn - this.ChatManager = this.BaseAddress + 0x13E0; + /// + protected override void SetupInternal(SigScanner scanner) + { + // Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h] + // Xiv__UiManager__GetChatManager+7 000 retn + this.ChatManager = this.BaseAddress + 0x13E0; + } } } diff --git a/Dalamud/Game/Gui/HoverActionKind.cs b/Dalamud/Game/Gui/HoverActionKind.cs index 90ff9d46c..217ea18fb 100644 --- a/Dalamud/Game/Gui/HoverActionKind.cs +++ b/Dalamud/Game/Gui/HoverActionKind.cs @@ -1,48 +1,49 @@ -namespace Dalamud.Game.Gui; - -/// -/// ActionKinds used in AgentActionDetail. -/// These describe the possible kinds of actions being hovered. -/// -public enum HoverActionKind +namespace Dalamud.Game.Gui { /// - /// No action is hovered. + /// ActionKinds used in AgentActionDetail. + /// These describe the possible kinds of actions being hovered. /// - None = 0, + public enum HoverActionKind + { + /// + /// No action is hovered. + /// + None = 0, - /// - /// A regular action is hovered. - /// - Action = 21, + /// + /// A regular action is hovered. + /// + Action = 21, - /// - /// A general action is hovered. - /// - GeneralAction = 23, + /// + /// A general action is hovered. + /// + GeneralAction = 23, - /// - /// A companion order type of action is hovered. - /// - CompanionOrder = 24, + /// + /// A companion order type of action is hovered. + /// + CompanionOrder = 24, - /// - /// A main command type of action is hovered. - /// - MainCommand = 25, + /// + /// A main command type of action is hovered. + /// + MainCommand = 25, - /// - /// An extras command type of action is hovered. - /// - ExtraCommand = 26, + /// + /// An extras command type of action is hovered. + /// + ExtraCommand = 26, - /// - /// A pet order type of action is hovered. - /// - PetOrder = 28, + /// + /// A pet order type of action is hovered. + /// + PetOrder = 28, - /// - /// A trait is hovered. - /// - Trait = 29, + /// + /// A trait is hovered. + /// + Trait = 29, + } } diff --git a/Dalamud/Game/Gui/HoveredAction.cs b/Dalamud/Game/Gui/HoveredAction.cs index dcfab5b00..1a7336610 100644 --- a/Dalamud/Game/Gui/HoveredAction.cs +++ b/Dalamud/Game/Gui/HoveredAction.cs @@ -1,22 +1,23 @@ -namespace Dalamud.Game.Gui; - -/// -/// This class represents the hotbar action currently hovered over by the cursor. -/// -public class HoveredAction +namespace Dalamud.Game.Gui { /// - /// Gets or sets the base action ID. + /// This class represents the hotbar action currently hovered over by the cursor. /// - public uint BaseActionID { get; set; } = 0; + public class HoveredAction + { + /// + /// Gets or sets the base action ID. + /// + public uint BaseActionID { get; set; } = 0; - /// - /// Gets or sets the action ID accounting for automatic upgrades. - /// - public uint ActionID { get; set; } = 0; + /// + /// Gets or sets the action ID accounting for automatic upgrades. + /// + public uint ActionID { get; set; } = 0; - /// - /// Gets or sets the type of action. - /// - public HoverActionKind ActionKind { get; set; } = HoverActionKind.None; + /// + /// Gets or sets the type of action. + /// + public HoverActionKind ActionKind { get; set; } = HoverActionKind.None; + } } diff --git a/Dalamud/Game/Gui/Internal/DalamudIME.cs b/Dalamud/Game/Gui/Internal/DalamudIME.cs index 8cd104b1a..4aafa5f3b 100644 --- a/Dalamud/Game/Gui/Internal/DalamudIME.cs +++ b/Dalamud/Game/Gui/Internal/DalamudIME.cs @@ -9,234 +9,235 @@ using ImGuiNET; using static Dalamud.NativeFunctions; -namespace Dalamud.Game.Gui.Internal; - -/// -/// This class handles IME for non-English users. -/// -internal class DalamudIME : IDisposable +namespace Dalamud.Game.Gui.Internal { - private static readonly ModuleLog Log = new("IME"); - - private IntPtr interfaceHandle; - private IntPtr wndProcPtr; - private IntPtr oldWndProcPtr; - private WndProcDelegate wndProcDelegate; - /// - /// Initializes a new instance of the class. + /// This class handles IME for non-English users. /// - internal DalamudIME() + internal class DalamudIME : IDisposable { - } + private static readonly ModuleLog Log = new("IME"); - private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam); + private IntPtr interfaceHandle; + private IntPtr wndProcPtr; + private IntPtr oldWndProcPtr; + private WndProcDelegate wndProcDelegate; - /// - /// Gets a value indicating whether the module is enabled. - /// - internal bool IsEnabled { get; private set; } - - /// - /// Gets the index of the first imm candidate in relation to the full list. - /// - internal CandidateList ImmCandNative { get; private set; } = default; - - /// - /// Gets the imm candidates. - /// - internal List ImmCand { get; private set; } = new(); - - /// - /// Gets the selected imm component. - /// - internal string ImmComp { get; private set; } = string.Empty; - - /// - public void Dispose() - { - if (this.oldWndProcPtr != IntPtr.Zero) + /// + /// Initializes a new instance of the class. + /// + internal DalamudIME() { - SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr); - this.oldWndProcPtr = IntPtr.Zero; } - } - /// - /// Enables the IME module. - /// - internal void Enable() - { - try - { - this.wndProcDelegate = this.WndProcDetour; - this.interfaceHandle = Service.Get().WindowHandlePtr; - this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate); - this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr); - this.IsEnabled = true; - Log.Information("Enabled!"); - } - catch (Exception ex) - { - Log.Information(ex, "Enable failed"); - } - } + private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam); - private void ToggleWindow(bool visible) - { - if (visible) - Service.Get().OpenIMEWindow(); - else - Service.Get().CloseIMEWindow(); - } + /// + /// Gets a value indicating whether the module is enabled. + /// + internal bool IsEnabled { get; private set; } - private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam) - { - try + /// + /// Gets the index of the first imm candidate in relation to the full list. + /// + internal CandidateList ImmCandNative { get; private set; } = default; + + /// + /// Gets the imm candidates. + /// + internal List ImmCand { get; private set; } = new(); + + /// + /// Gets the selected imm component. + /// + internal string ImmComp { get; private set; } = string.Empty; + + /// + public void Dispose() { - if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput) + if (this.oldWndProcPtr != IntPtr.Zero) { - var io = ImGui.GetIO(); - var wmsg = (WindowsMessage)msg; + SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr); + this.oldWndProcPtr = IntPtr.Zero; + } + } - switch (wmsg) + /// + /// Enables the IME module. + /// + internal void Enable() + { + try + { + this.wndProcDelegate = this.WndProcDetour; + this.interfaceHandle = Service.Get().WindowHandlePtr; + this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate); + this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr); + this.IsEnabled = true; + Log.Information("Enabled!"); + } + catch (Exception ex) + { + Log.Information(ex, "Enable failed"); + } + } + + private void ToggleWindow(bool visible) + { + if (visible) + Service.Get().OpenIMEWindow(); + else + Service.Get().CloseIMEWindow(); + } + + private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam) + { + try + { + if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput) { - case WindowsMessage.WM_IME_NOTIFY: - switch ((IMECommand)wParam) - { - case IMECommand.ChangeCandidate: - this.ToggleWindow(true); + var io = ImGui.GetIO(); + var wmsg = (WindowsMessage)msg; - if (hWnd == IntPtr.Zero) - return 0; + switch (wmsg) + { + case WindowsMessage.WM_IME_NOTIFY: + switch ((IMECommand)wParam) + { + case IMECommand.ChangeCandidate: + this.ToggleWindow(true); + if (hWnd == IntPtr.Zero) + return 0; + + var hIMC = ImmGetContext(hWnd); + if (hIMC == IntPtr.Zero) + return 0; + + var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0); + if (size == 0) + break; + + var candlistPtr = Marshal.AllocHGlobal((int)size); + size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size); + + var candlist = this.ImmCandNative = Marshal.PtrToStructure(candlistPtr); + var pageSize = candlist.PageSize; + var candCount = candlist.Count; + + if (pageSize > 0 && candCount > 1) + { + var dwOffsets = new int[candCount]; + for (var i = 0; i < candCount; i++) + { + dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int))); + } + + var pageStart = candlist.PageStart; + + var cand = new string[pageSize]; + this.ImmCand.Clear(); + + for (var i = 0; i < pageSize; i++) + { + var offStart = dwOffsets[i + pageStart]; + var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size; + + var pStrStart = candlistPtr + (int)offStart; + var pStrEnd = candlistPtr + (int)offEnd; + + var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64()); + if (len > 0) + { + var candBytes = new byte[len]; + Marshal.Copy(pStrStart, candBytes, 0, len); + + var candStr = Encoding.Unicode.GetString(candBytes); + cand[i] = candStr; + + this.ImmCand.Add(candStr); + } + } + + Marshal.FreeHGlobal(candlistPtr); + } + + break; + case IMECommand.OpenCandidate: + this.ToggleWindow(true); + this.ImmCandNative = default; + this.ImmCand.Clear(); + break; + + case IMECommand.CloseCandidate: + this.ToggleWindow(false); + this.ImmCandNative = default; + this.ImmCand.Clear(); + break; + + default: + break; + } + + break; + case WindowsMessage.WM_IME_COMPOSITION: + if ((lParam & (long)IMEComposition.ResultStr) > 0) + { var hIMC = ImmGetContext(hWnd); if (hIMC == IntPtr.Zero) return 0; - var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0); - if (size == 0) - break; + var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0); + var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize); + ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize); - var candlistPtr = Marshal.AllocHGlobal((int)size); - size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size); + var bytes = new byte[dwSize]; + Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize); + Marshal.FreeHGlobal(unmanagedPointer); - var candlist = this.ImmCandNative = Marshal.PtrToStructure(candlistPtr); - var pageSize = candlist.PageSize; - var candCount = candlist.Count; + var lpstr = Encoding.Unicode.GetString(bytes); + io.AddInputCharactersUTF8(lpstr); - if (pageSize > 0 && candCount > 1) - { - var dwOffsets = new int[candCount]; - for (var i = 0; i < candCount; i++) - { - dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int))); - } - - var pageStart = candlist.PageStart; - - var cand = new string[pageSize]; - this.ImmCand.Clear(); - - for (var i = 0; i < pageSize; i++) - { - var offStart = dwOffsets[i + pageStart]; - var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size; - - var pStrStart = candlistPtr + (int)offStart; - var pStrEnd = candlistPtr + (int)offEnd; - - var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64()); - if (len > 0) - { - var candBytes = new byte[len]; - Marshal.Copy(pStrStart, candBytes, 0, len); - - var candStr = Encoding.Unicode.GetString(candBytes); - cand[i] = candStr; - - this.ImmCand.Add(candStr); - } - } - - Marshal.FreeHGlobal(candlistPtr); - } - - break; - case IMECommand.OpenCandidate: - this.ToggleWindow(true); + this.ImmComp = string.Empty; this.ImmCandNative = default; this.ImmCand.Clear(); - break; - - case IMECommand.CloseCandidate: this.ToggleWindow(false); - this.ImmCandNative = default; - this.ImmCand.Clear(); - break; + } - default: - break; - } + if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause | + IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0) + { + var hIMC = ImmGetContext(hWnd); + if (hIMC == IntPtr.Zero) + return 0; - break; - case WindowsMessage.WM_IME_COMPOSITION: - if ((lParam & (long)IMEComposition.ResultStr) > 0) - { - var hIMC = ImmGetContext(hWnd); - if (hIMC == IntPtr.Zero) - return 0; + var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0); + var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize); + ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize); - var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0); - var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize); - ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize); + var bytes = new byte[dwSize]; + Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize); + Marshal.FreeHGlobal(unmanagedPointer); - var bytes = new byte[dwSize]; - Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize); - Marshal.FreeHGlobal(unmanagedPointer); + var lpstr = Encoding.Unicode.GetString(bytes); + this.ImmComp = lpstr; + if (lpstr == string.Empty) + this.ToggleWindow(false); + } - var lpstr = Encoding.Unicode.GetString(bytes); - io.AddInputCharactersUTF8(lpstr); + break; - this.ImmComp = string.Empty; - this.ImmCandNative = default; - this.ImmCand.Clear(); - this.ToggleWindow(false); - } - - if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause | - IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0) - { - var hIMC = ImmGetContext(hWnd); - if (hIMC == IntPtr.Zero) - return 0; - - var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0); - var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize); - ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize); - - var bytes = new byte[dwSize]; - Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize); - Marshal.FreeHGlobal(unmanagedPointer); - - var lpstr = Encoding.Unicode.GetString(bytes); - this.ImmComp = lpstr; - if (lpstr == string.Empty) - this.ToggleWindow(false); - } - - break; - - default: - break; + default: + break; + } } } - } - catch (Exception ex) - { - Log.Error(ex, "Prevented a crash in an IME hook"); - } + catch (Exception ex) + { + Log.Error(ex, "Prevented a crash in an IME hook"); + } - return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam); + return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam); + } } } diff --git a/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacket.cs b/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacket.cs index a7ab12e4b..9f8834ecd 100644 --- a/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacket.cs +++ b/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacket.cs @@ -1,27 +1,28 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; -namespace Dalamud.Game.Gui.PartyFinder.Internal; - -/// -/// The structure of the PartyFinder packet. -/// -[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Sequential struct marshaling.")] -[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")] -[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")] -[StructLayout(LayoutKind.Sequential)] -internal readonly struct PartyFinderPacket +namespace Dalamud.Game.Gui.PartyFinder.Internal { /// - /// Gets the size of this packet. + /// The structure of the PartyFinder packet. /// - internal static int PacketSize { get; } = Marshal.SizeOf(); + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Sequential struct marshaling.")] + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")] + [StructLayout(LayoutKind.Sequential)] + internal readonly struct PartyFinderPacket + { + /// + /// Gets the size of this packet. + /// + internal static int PacketSize { get; } = Marshal.SizeOf(); - internal readonly int BatchNumber; + internal readonly int BatchNumber; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - private readonly byte[] padding1; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + private readonly byte[] padding1; - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - internal readonly PartyFinderPacketListing[] Listings; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + internal readonly PartyFinderPacketListing[] Listings; + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacketListing.cs b/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacketListing.cs index 53d2831ef..7e8f1e1ef 100644 --- a/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacketListing.cs +++ b/Dalamud/Game/Gui/PartyFinder/Internal/PartyFinderPacketListing.cs @@ -2,97 +2,98 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; -namespace Dalamud.Game.Gui.PartyFinder.Internal; - -/// -/// The structure of an individual listing within a packet. -/// -[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")] -[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")] -[StructLayout(LayoutKind.Sequential)] -internal readonly struct PartyFinderPacketListing +namespace Dalamud.Game.Gui.PartyFinder.Internal { - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - private readonly byte[] header1; - internal readonly uint Id; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] - private readonly byte[] header2; - - internal readonly uint ContentIdLower; - private readonly ushort unknownShort1; - private readonly ushort unknownShort2; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] - private readonly byte[] header3; - - internal readonly byte Category; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - private readonly byte[] header4; - - internal readonly ushort Duty; - internal readonly byte DutyType; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] - private readonly byte[] header5; - - internal readonly ushort World; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - private readonly byte[] header6; - - internal readonly byte Objective; - internal readonly byte BeginnersWelcome; - internal readonly byte Conditions; - internal readonly byte DutyFinderSettings; - internal readonly byte LootRules; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - private readonly byte[] header7; // all zero in every pf I've examined - - internal readonly uint LastPatchHotfixTimestamp; // last time the servers were restarted? - internal readonly ushort SecondsRemaining; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] - private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined - - internal readonly ushort MinimumItemLevel; - internal readonly ushort HomeWorld; - internal readonly ushort CurrentWorld; - - private readonly byte header9; - - internal readonly byte NumSlots; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] - private readonly byte[] header10; - - internal readonly byte SearchArea; - - private readonly byte header11; - - internal readonly byte NumParties; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] - private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32? - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - internal readonly uint[] Slots; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - internal readonly byte[] JobsPresent; - - // Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#. - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - internal readonly byte[] Name; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)] - internal readonly byte[] Description; - - internal bool IsNull() + /// + /// The structure of an individual listing within a packet. + /// + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:Elements should be ordered by access", Justification = "Sequential struct marshaling.")] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the field usage.")] + [StructLayout(LayoutKind.Sequential)] + internal readonly struct PartyFinderPacketListing { - // a valid party finder must have at least one slot set - return this.Slots.All(slot => slot == 0); + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + private readonly byte[] header1; + internal readonly uint Id; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + private readonly byte[] header2; + + internal readonly uint ContentIdLower; + private readonly ushort unknownShort1; + private readonly ushort unknownShort2; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)] + private readonly byte[] header3; + + internal readonly byte Category; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + private readonly byte[] header4; + + internal readonly ushort Duty; + internal readonly byte DutyType; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] + private readonly byte[] header5; + + internal readonly ushort World; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + private readonly byte[] header6; + + internal readonly byte Objective; + internal readonly byte BeginnersWelcome; + internal readonly byte Conditions; + internal readonly byte DutyFinderSettings; + internal readonly byte LootRules; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + private readonly byte[] header7; // all zero in every pf I've examined + + internal readonly uint LastPatchHotfixTimestamp; // last time the servers were restarted? + internal readonly ushort SecondsRemaining; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] + private readonly byte[] header8; // 00 00 01 00 00 00 in every pf I've examined + + internal readonly ushort MinimumItemLevel; + internal readonly ushort HomeWorld; + internal readonly ushort CurrentWorld; + + private readonly byte header9; + + internal readonly byte NumSlots; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + private readonly byte[] header10; + + internal readonly byte SearchArea; + + private readonly byte header11; + + internal readonly byte NumParties; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] + private readonly byte[] header12; // 00 00 00 always. maybe numParties is a u32? + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + internal readonly uint[] Slots; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + internal readonly byte[] JobsPresent; + + // Note that ByValTStr will not work here because the strings are UTF-8 and there's only a CharSet for UTF-16 in C#. + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + internal readonly byte[] Name; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 192)] + internal readonly byte[] Description; + + internal bool IsNull() + { + // a valid party finder must have at least one slot set + return this.Slots.All(slot => slot == 0); + } } } diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs index aa9d28cb1..75da83180 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderAddressResolver.cs @@ -1,20 +1,21 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder; - -/// -/// The address resolver for the class. -/// -public class PartyFinderAddressResolver : BaseAddressResolver +namespace Dalamud.Game.Gui.PartyFinder { /// - /// Gets the address of the native ReceiveListing method. + /// The address resolver for the class. /// - public IntPtr ReceiveListing { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) + public class PartyFinderAddressResolver : BaseAddressResolver { - this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9"); + /// + /// Gets the address of the native ReceiveListing method. + /// + public IntPtr ReceiveListing { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) + { + this.ReceiveListing = sig.ScanText("40 53 41 57 48 83 EC 28 48 8B D9"); + } } } diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs index 7fd7063dc..6d2a7818d 100644 --- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs +++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs @@ -8,132 +8,133 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.Gui.PartyFinder; - -/// -/// This class handles interacting with the native PartyFinder window. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class PartyFinderGui : IDisposable +namespace Dalamud.Game.Gui.PartyFinder { - private readonly PartyFinderAddressResolver address; - private readonly IntPtr memory; - - private readonly Hook receiveListingHook; - /// - /// Initializes a new instance of the class. + /// This class handles interacting with the native PartyFinder window. /// - internal PartyFinderGui() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class PartyFinderGui : IDisposable { - this.address = new PartyFinderAddressResolver(); - this.address.Setup(); + private readonly PartyFinderAddressResolver address; + private readonly IntPtr memory; - this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); + private readonly Hook receiveListingHook; - this.receiveListingHook = new Hook(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour)); - } - - /// - /// Event type fired each time the game receives an individual Party Finder listing. - /// Cannot modify listings but can hide them. - /// - /// The listings received. - /// Additional arguments passed by the game. - public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data); - - /// - /// Event fired each time the game receives an individual Party Finder listing. - /// Cannot modify listings but can hide them. - /// - public event PartyFinderListingEventDelegate ReceiveListing; - - /// - /// Enables this module. - /// - public void Enable() - { - this.receiveListingHook.Enable(); - } - - /// - /// Dispose of m anaged and unmanaged resources. - /// - public void Dispose() - { - this.receiveListingHook.Dispose(); - - try + /// + /// Initializes a new instance of the class. + /// + internal PartyFinderGui() { - Marshal.FreeHGlobal(this.memory); - } - catch (BadImageFormatException) - { - Log.Warning("Could not free PartyFinderGui memory."); - } - } + this.address = new PartyFinderAddressResolver(); + this.address.Setup(); - private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data) - { - try - { - this.HandleListingEvents(data); - } - catch (Exception ex) - { - Log.Error(ex, "Exception on ReceiveListing hook."); + this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize); + + this.receiveListingHook = new Hook(this.address.ReceiveListing, new ReceiveListingDelegate(this.HandleReceiveListingDetour)); } - this.receiveListingHook.Original(managerPtr, data); - } + /// + /// Event type fired each time the game receives an individual Party Finder listing. + /// Cannot modify listings but can hide them. + /// + /// The listings received. + /// Additional arguments passed by the game. + public delegate void PartyFinderListingEventDelegate(PartyFinderListing listing, PartyFinderListingEventArgs args); - private void HandleListingEvents(IntPtr data) - { - var dataPtr = data + 0x10; + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void ReceiveListingDelegate(IntPtr managerPtr, IntPtr data); - var packet = Marshal.PtrToStructure(dataPtr); + /// + /// Event fired each time the game receives an individual Party Finder listing. + /// Cannot modify listings but can hide them. + /// + public event PartyFinderListingEventDelegate ReceiveListing; - // rewriting is an expensive operation, so only do it if necessary - var needToRewrite = false; - - for (var i = 0; i < packet.Listings.Length; i++) + /// + /// Enables this module. + /// + public void Enable() { - // these are empty slots that are not shown to the player - if (packet.Listings[i].IsNull()) + this.receiveListingHook.Enable(); + } + + /// + /// Dispose of m anaged and unmanaged resources. + /// + public void Dispose() + { + this.receiveListingHook.Dispose(); + + try { - continue; + Marshal.FreeHGlobal(this.memory); + } + catch (BadImageFormatException) + { + Log.Warning("Could not free PartyFinderGui memory."); + } + } + + private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data) + { + try + { + this.HandleListingEvents(data); + } + catch (Exception ex) + { + Log.Error(ex, "Exception on ReceiveListing hook."); } - var listing = new PartyFinderListing(packet.Listings[i]); - var args = new PartyFinderListingEventArgs(packet.BatchNumber); - this.ReceiveListing?.Invoke(listing, args); + this.receiveListingHook.Original(managerPtr, data); + } - if (args.Visible) + private void HandleListingEvents(IntPtr data) + { + var dataPtr = data + 0x10; + + var packet = Marshal.PtrToStructure(dataPtr); + + // rewriting is an expensive operation, so only do it if necessary + var needToRewrite = false; + + for (var i = 0; i < packet.Listings.Length; i++) { - continue; + // these are empty slots that are not shown to the player + if (packet.Listings[i].IsNull()) + { + continue; + } + + var listing = new PartyFinderListing(packet.Listings[i]); + var args = new PartyFinderListingEventArgs(packet.BatchNumber); + this.ReceiveListing?.Invoke(listing, args); + + if (args.Visible) + { + continue; + } + + // hide the listing from the player by setting it to a null listing + packet.Listings[i] = default; + needToRewrite = true; } - // hide the listing from the player by setting it to a null listing - packet.Listings[i] = default; - needToRewrite = true; - } + if (!needToRewrite) + { + return; + } - if (!needToRewrite) - { - return; - } + // write our struct into the memory (doing this directly crashes the game) + Marshal.StructureToPtr(packet, this.memory, false); - // write our struct into the memory (doing this directly crashes the game) - Marshal.StructureToPtr(packet, this.memory, false); - - // copy our new memory over the game's - unsafe - { - Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize); + // copy our new memory over the game's + unsafe + { + Buffer.MemoryCopy((void*)this.memory, (void*)dataPtr, PartyFinderPacket.PacketSize, PartyFinderPacket.PacketSize); + } } } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs index 358fd1c54..3eb5ec1b6 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/ConditionFlags.cs @@ -1,25 +1,26 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// Condition flags for the class. -/// -[Flags] -public enum ConditionFlags : uint +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// No duty condition. + /// Condition flags for the class. /// - None = 1, + [Flags] + public enum ConditionFlags : uint + { + /// + /// No duty condition. + /// + None = 1, - /// - /// The duty complete condition. - /// - DutyComplete = 2, + /// + /// The duty complete condition. + /// + DutyComplete = 2, - /// - /// The duty incomplete condition. - /// - DutyIncomplete = 4, + /// + /// The duty incomplete condition. + /// + DutyIncomplete = 4, + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/DutyCategory.cs b/Dalamud/Game/Gui/PartyFinder/Types/DutyCategory.cs index c4a05704d..920325d64 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/DutyCategory.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/DutyCategory.cs @@ -1,47 +1,48 @@ -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// Category flags for the class. -/// -public enum DutyCategory +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// The duty category. + /// Category flags for the class. /// - Duty = 0, + public enum DutyCategory + { + /// + /// The duty category. + /// + Duty = 0, - /// - /// The quest battle category. - /// - QuestBattles = 1 << 0, + /// + /// The quest battle category. + /// + QuestBattles = 1 << 0, - /// - /// The fate category. - /// - Fates = 1 << 1, + /// + /// The fate category. + /// + Fates = 1 << 1, - /// - /// The treasure hunt category. - /// - TreasureHunt = 1 << 2, + /// + /// The treasure hunt category. + /// + TreasureHunt = 1 << 2, - /// - /// The hunt category. - /// - TheHunt = 1 << 3, + /// + /// The hunt category. + /// + TheHunt = 1 << 3, - /// - /// The gathering forays category. - /// - GatheringForays = 1 << 4, + /// + /// The gathering forays category. + /// + GatheringForays = 1 << 4, - /// - /// The deep dungeons category. - /// - DeepDungeons = 1 << 5, + /// + /// The deep dungeons category. + /// + DeepDungeons = 1 << 5, - /// - /// The adventuring forays category. - /// - AdventuringForays = 1 << 6, + /// + /// The adventuring forays category. + /// + AdventuringForays = 1 << 6, + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/DutyFinderSettingsFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/DutyFinderSettingsFlags.cs index e3ab56ed3..03335dbaf 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/DutyFinderSettingsFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/DutyFinderSettingsFlags.cs @@ -1,30 +1,31 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// Duty finder settings flags for the class. -/// -[Flags] -public enum DutyFinderSettingsFlags : uint +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// No duty finder settings. + /// Duty finder settings flags for the class. /// - None = 0, + [Flags] + public enum DutyFinderSettingsFlags : uint + { + /// + /// No duty finder settings. + /// + None = 0, - /// - /// The undersized party setting. - /// - UndersizedParty = 1 << 0, + /// + /// The undersized party setting. + /// + UndersizedParty = 1 << 0, - /// - /// The minimum item level setting. - /// - MinimumItemLevel = 1 << 1, + /// + /// The minimum item level setting. + /// + MinimumItemLevel = 1 << 1, - /// - /// The silence echo setting. - /// - SilenceEcho = 1 << 2, + /// + /// The silence echo setting. + /// + SilenceEcho = 1 << 2, + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/DutyType.cs b/Dalamud/Game/Gui/PartyFinder/Types/DutyType.cs index 41b4c5da3..589d0b91b 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/DutyType.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/DutyType.cs @@ -1,22 +1,23 @@ -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// Duty type flags for the class. -/// -public enum DutyType +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// No duty type. + /// Duty type flags for the class. /// - Other = 0, + public enum DutyType + { + /// + /// No duty type. + /// + Other = 0, - /// - /// The roulette duty type. - /// - Roulette = 1 << 0, + /// + /// The roulette duty type. + /// + Roulette = 1 << 0, - /// - /// The normal duty type. - /// - Normal = 1 << 1, + /// + /// The normal duty type. + /// + Normal = 1 << 1, + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs index 285017e53..98fa1b1fe 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlags.cs @@ -1,145 +1,146 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// Job flags for the class. -/// -[Flags] -public enum JobFlags +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// Gladiator (GLD). + /// Job flags for the class. /// - Gladiator = 1 << 1, + [Flags] + public enum JobFlags + { + /// + /// Gladiator (GLD). + /// + Gladiator = 1 << 1, - /// - /// Pugilist (PGL). - /// - Pugilist = 1 << 2, + /// + /// Pugilist (PGL). + /// + Pugilist = 1 << 2, - /// - /// Marauder (MRD). - /// - Marauder = 1 << 3, + /// + /// Marauder (MRD). + /// + Marauder = 1 << 3, - /// - /// Lancer (LNC). - /// - Lancer = 1 << 4, + /// + /// Lancer (LNC). + /// + Lancer = 1 << 4, - /// - /// Archer (ARC). - /// - Archer = 1 << 5, + /// + /// Archer (ARC). + /// + Archer = 1 << 5, - /// - /// Conjurer (CNJ). - /// - Conjurer = 1 << 6, + /// + /// Conjurer (CNJ). + /// + Conjurer = 1 << 6, - /// - /// Thaumaturge (THM). - /// - Thaumaturge = 1 << 7, + /// + /// Thaumaturge (THM). + /// + Thaumaturge = 1 << 7, - /// - /// Paladin (PLD). - /// - Paladin = 1 << 8, + /// + /// Paladin (PLD). + /// + Paladin = 1 << 8, - /// - /// Monk (MNK). - /// - Monk = 1 << 9, + /// + /// Monk (MNK). + /// + Monk = 1 << 9, - /// - /// Warrior (WAR). - /// - Warrior = 1 << 10, + /// + /// Warrior (WAR). + /// + Warrior = 1 << 10, - /// - /// Dragoon (DRG). - /// - Dragoon = 1 << 11, + /// + /// Dragoon (DRG). + /// + Dragoon = 1 << 11, - /// - /// Bard (BRD). - /// - Bard = 1 << 12, + /// + /// Bard (BRD). + /// + Bard = 1 << 12, - /// - /// White mage (WHM). - /// - WhiteMage = 1 << 13, + /// + /// White mage (WHM). + /// + WhiteMage = 1 << 13, - /// - /// Black mage (BLM). - /// - BlackMage = 1 << 14, + /// + /// Black mage (BLM). + /// + BlackMage = 1 << 14, - /// - /// Arcanist (ACN). - /// - Arcanist = 1 << 15, + /// + /// Arcanist (ACN). + /// + Arcanist = 1 << 15, - /// - /// Summoner (SMN). - /// - Summoner = 1 << 16, + /// + /// Summoner (SMN). + /// + Summoner = 1 << 16, - /// - /// Scholar (SCH). - /// - Scholar = 1 << 17, + /// + /// Scholar (SCH). + /// + Scholar = 1 << 17, - /// - /// Rogue (ROG). - /// - Rogue = 1 << 18, + /// + /// Rogue (ROG). + /// + Rogue = 1 << 18, - /// - /// Ninja (NIN). - /// - Ninja = 1 << 19, + /// + /// Ninja (NIN). + /// + Ninja = 1 << 19, - /// - /// Machinist (MCH). - /// - Machinist = 1 << 20, + /// + /// Machinist (MCH). + /// + Machinist = 1 << 20, - /// - /// Dark Knight (DRK). - /// - DarkKnight = 1 << 21, + /// + /// Dark Knight (DRK). + /// + DarkKnight = 1 << 21, - /// - /// Astrologian (AST). - /// - Astrologian = 1 << 22, + /// + /// Astrologian (AST). + /// + Astrologian = 1 << 22, - /// - /// Samurai (SAM). - /// - Samurai = 1 << 23, + /// + /// Samurai (SAM). + /// + Samurai = 1 << 23, - /// - /// Red mage (RDM). - /// - RedMage = 1 << 24, + /// + /// Red mage (RDM). + /// + RedMage = 1 << 24, - /// - /// Blue mage (BLM). - /// - BlueMage = 1 << 25, + /// + /// Blue mage (BLM). + /// + BlueMage = 1 << 25, - /// - /// Gunbreaker (GNB). - /// - Gunbreaker = 1 << 26, + /// + /// Gunbreaker (GNB). + /// + Gunbreaker = 1 << 26, - /// - /// Dancer (DNC). - /// - Dancer = 1 << 27, + /// + /// Dancer (DNC). + /// + Dancer = 1 << 27, + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs index f66272294..3bb80d0f5 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/JobFlagsExtensions.cs @@ -3,55 +3,56 @@ using System; using Dalamud.Data; using Lumina.Excel.GeneratedSheets; -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// Extensions for the enum. -/// -public static class JobFlagsExtensions +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// Get the actual ClassJob from the in-game sheets for this JobFlags. + /// Extensions for the enum. /// - /// A JobFlags enum member. - /// A DataManager to get the ClassJob from. - /// A ClassJob if found or null if not. - public static ClassJob ClassJob(this JobFlags job, DataManager data) + public static class JobFlagsExtensions { - var jobs = data.GetExcelSheet(); - - uint? row = job switch + /// + /// Get the actual ClassJob from the in-game sheets for this JobFlags. + /// + /// A JobFlags enum member. + /// A DataManager to get the ClassJob from. + /// A ClassJob if found or null if not. + public static ClassJob ClassJob(this JobFlags job, DataManager data) { - JobFlags.Gladiator => 1, - JobFlags.Pugilist => 2, - JobFlags.Marauder => 3, - JobFlags.Lancer => 4, - JobFlags.Archer => 5, - JobFlags.Conjurer => 6, - JobFlags.Thaumaturge => 7, - JobFlags.Paladin => 19, - JobFlags.Monk => 20, - JobFlags.Warrior => 21, - JobFlags.Dragoon => 22, - JobFlags.Bard => 23, - JobFlags.WhiteMage => 24, - JobFlags.BlackMage => 25, - JobFlags.Arcanist => 26, - JobFlags.Summoner => 27, - JobFlags.Scholar => 28, - JobFlags.Rogue => 29, - JobFlags.Ninja => 30, - JobFlags.Machinist => 31, - JobFlags.DarkKnight => 32, - JobFlags.Astrologian => 33, - JobFlags.Samurai => 34, - JobFlags.RedMage => 35, - JobFlags.BlueMage => 36, - JobFlags.Gunbreaker => 37, - JobFlags.Dancer => 38, - _ => null, - }; + var jobs = data.GetExcelSheet(); - return row == null ? null : jobs.GetRow((uint)row); + uint? row = job switch + { + JobFlags.Gladiator => 1, + JobFlags.Pugilist => 2, + JobFlags.Marauder => 3, + JobFlags.Lancer => 4, + JobFlags.Archer => 5, + JobFlags.Conjurer => 6, + JobFlags.Thaumaturge => 7, + JobFlags.Paladin => 19, + JobFlags.Monk => 20, + JobFlags.Warrior => 21, + JobFlags.Dragoon => 22, + JobFlags.Bard => 23, + JobFlags.WhiteMage => 24, + JobFlags.BlackMage => 25, + JobFlags.Arcanist => 26, + JobFlags.Summoner => 27, + JobFlags.Scholar => 28, + JobFlags.Rogue => 29, + JobFlags.Ninja => 30, + JobFlags.Machinist => 31, + JobFlags.DarkKnight => 32, + JobFlags.Astrologian => 33, + JobFlags.Samurai => 34, + JobFlags.RedMage => 35, + JobFlags.BlueMage => 36, + JobFlags.Gunbreaker => 37, + JobFlags.Dancer => 38, + _ => null, + }; + + return row == null ? null : jobs.GetRow((uint)row); + } } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/LootRuleFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/LootRuleFlags.cs index 6e43ecf4c..a1a29ceb8 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/LootRuleFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/LootRuleFlags.cs @@ -1,25 +1,26 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// Loot rule flags for the class. -/// -[Flags] -public enum LootRuleFlags : uint +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// No loot rules. + /// Loot rule flags for the class. /// - None = 0, + [Flags] + public enum LootRuleFlags : uint + { + /// + /// No loot rules. + /// + None = 0, - /// - /// The greed only rule. - /// - GreedOnly = 1, + /// + /// The greed only rule. + /// + GreedOnly = 1, - /// - /// The lootmaster rule. - /// - Lootmaster = 2, + /// + /// The lootmaster rule. + /// + Lootmaster = 2, + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/ObjectiveFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/ObjectiveFlags.cs index 19f56f84c..de3b8fc6b 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/ObjectiveFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/ObjectiveFlags.cs @@ -1,30 +1,31 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// Objective flags for the class. -/// -[Flags] -public enum ObjectiveFlags : uint +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// No objective. + /// Objective flags for the class. /// - None = 0, + [Flags] + public enum ObjectiveFlags : uint + { + /// + /// No objective. + /// + None = 0, - /// - /// The duty completion objective. - /// - DutyCompletion = 1, + /// + /// The duty completion objective. + /// + DutyCompletion = 1, - /// - /// The practice objective. - /// - Practice = 2, + /// + /// The practice objective. + /// + Practice = 2, - /// - /// The loot objective. - /// - Loot = 4, + /// + /// The loot objective. + /// + Loot = 4, + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs index d21b7b803..b0dce07b9 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListing.cs @@ -7,227 +7,228 @@ using Dalamud.Game.Gui.PartyFinder.Internal; using Dalamud.Game.Text.SeStringHandling; using Lumina.Excel.GeneratedSheets; -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// A single listing in party finder. -/// -public class PartyFinderListing +namespace Dalamud.Game.Gui.PartyFinder.Types { - private readonly byte objective; - private readonly byte conditions; - private readonly byte dutyFinderSettings; - private readonly byte lootRules; - private readonly byte searchArea; - private readonly PartyFinderSlot[] slots; - private readonly byte[] jobsPresent; - /// - /// Initializes a new instance of the class. + /// A single listing in party finder. /// - /// The interop listing data. - internal PartyFinderListing(PartyFinderPacketListing listing) + public class PartyFinderListing { - var dataManager = Service.Get(); + private readonly byte objective; + private readonly byte conditions; + private readonly byte dutyFinderSettings; + private readonly byte lootRules; + private readonly byte searchArea; + private readonly PartyFinderSlot[] slots; + private readonly byte[] jobsPresent; - this.objective = listing.Objective; - this.conditions = listing.Conditions; - this.dutyFinderSettings = listing.DutyFinderSettings; - this.lootRules = listing.LootRules; - this.searchArea = listing.SearchArea; - this.slots = listing.Slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray(); - this.jobsPresent = listing.JobsPresent; + /// + /// Initializes a new instance of the class. + /// + /// The interop listing data. + internal PartyFinderListing(PartyFinderPacketListing listing) + { + var dataManager = Service.Get(); + + this.objective = listing.Objective; + this.conditions = listing.Conditions; + this.dutyFinderSettings = listing.DutyFinderSettings; + this.lootRules = listing.LootRules; + this.searchArea = listing.SearchArea; + this.slots = listing.Slots.Select(accepting => new PartyFinderSlot(accepting)).ToArray(); + this.jobsPresent = listing.JobsPresent; + + this.Id = listing.Id; + this.ContentIdLower = listing.ContentIdLower; + this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray()); + this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray()); + this.World = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.World)); + this.HomeWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.HomeWorld)); + this.CurrentWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.CurrentWorld)); + this.Category = (DutyCategory)listing.Category; + this.RawDuty = listing.Duty; + this.Duty = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.Duty)); + this.DutyType = (DutyType)listing.DutyType; + this.BeginnersWelcome = listing.BeginnersWelcome == 1; + this.SecondsRemaining = listing.SecondsRemaining; + this.MinimumItemLevel = listing.MinimumItemLevel; + this.Parties = listing.NumParties; + this.SlotsAvailable = listing.NumSlots; + this.LastPatchHotfixTimestamp = listing.LastPatchHotfixTimestamp; + this.JobsPresent = listing.JobsPresent + .Select(id => new Lazy( + () => id == 0 + ? null + : dataManager.GetExcelSheet().GetRow(id))) + .ToArray(); + } + + /// + /// Gets the ID assigned to this listing by the game's server. + /// + public uint Id { get; } + + /// + /// Gets the lower bits of the player's content ID. + /// + public uint ContentIdLower { get; } + + /// + /// Gets the name of the player hosting this listing. + /// + public SeString Name { get; } + + /// + /// Gets the description of this listing as set by the host. May be multiple lines. + /// + public SeString Description { get; } + + /// + /// Gets the world that this listing was created on. + /// + public Lazy World { get; } + + /// + /// Gets the home world of the listing's host. + /// + public Lazy HomeWorld { get; } + + /// + /// Gets the current world of the listing's host. + /// + public Lazy CurrentWorld { get; } + + /// + /// Gets the Party Finder category this listing is listed under. + /// + public DutyCategory Category { get; } + + /// + /// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings. + /// + public ushort RawDuty { get; } + + /// + /// Gets the duty this listing is for. May be null for non-duty listings. + /// + public Lazy Duty { get; } + + /// + /// Gets the type of duty this listing is for. + /// + public DutyType DutyType { get; } + + /// + /// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game. + /// + public bool BeginnersWelcome { get; } + + /// + /// Gets how many seconds this listing will continue to be available for. It may end before this time if the party + /// fills or the host ends it early. + /// + public ushort SecondsRemaining { get; } + + /// + /// Gets the minimum item level required to join this listing. + /// + public ushort MinimumItemLevel { get; } + + /// + /// Gets the number of parties this listing is recruiting for. + /// + public byte Parties { get; } + + /// + /// Gets the number of player slots this listing is recruiting for. + /// + public byte SlotsAvailable { get; } + + /// + /// Gets the time at which the server this listings is on last restarted for a patch/hotfix. + /// Probably. + /// + public uint LastPatchHotfixTimestamp { get; } + + /// + /// Gets a list of player slots that the Party Finder is accepting. + /// + public IReadOnlyCollection Slots => this.slots; + + /// + /// Gets the objective of this listing. + /// + public ObjectiveFlags Objective => (ObjectiveFlags)this.objective; + + /// + /// Gets the conditions of this listing. + /// + public ConditionFlags Conditions => (ConditionFlags)this.conditions; + + /// + /// Gets the Duty Finder settings that will be used for this listing. + /// + public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings; + + /// + /// Gets the loot rules that will be used for this listing. + /// + public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules; + + /// + /// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one + /// player per job. + /// + public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea; + + /// + /// Gets a list of the class/job IDs that are currently present in the party. + /// + public IReadOnlyCollection RawJobsPresent => this.jobsPresent; + + /// + /// Gets a list of the classes/jobs that are currently present in the party. + /// + public IReadOnlyCollection> JobsPresent { get; } + + #region Indexers + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0; + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0; + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0; + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0; + + /// + /// Check if the given flag is present. + /// + /// The flag to check for. + /// A value indicating whether the flag is present. + public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0; + + #endregion - this.Id = listing.Id; - this.ContentIdLower = listing.ContentIdLower; - this.Name = SeString.Parse(listing.Name.TakeWhile(b => b != 0).ToArray()); - this.Description = SeString.Parse(listing.Description.TakeWhile(b => b != 0).ToArray()); - this.World = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.World)); - this.HomeWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.HomeWorld)); - this.CurrentWorld = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.CurrentWorld)); - this.Category = (DutyCategory)listing.Category; - this.RawDuty = listing.Duty; - this.Duty = new Lazy(() => dataManager.GetExcelSheet().GetRow(listing.Duty)); - this.DutyType = (DutyType)listing.DutyType; - this.BeginnersWelcome = listing.BeginnersWelcome == 1; - this.SecondsRemaining = listing.SecondsRemaining; - this.MinimumItemLevel = listing.MinimumItemLevel; - this.Parties = listing.NumParties; - this.SlotsAvailable = listing.NumSlots; - this.LastPatchHotfixTimestamp = listing.LastPatchHotfixTimestamp; - this.JobsPresent = listing.JobsPresent - .Select(id => new Lazy( - () => id == 0 - ? null - : dataManager.GetExcelSheet().GetRow(id))) - .ToArray(); } - - /// - /// Gets the ID assigned to this listing by the game's server. - /// - public uint Id { get; } - - /// - /// Gets the lower bits of the player's content ID. - /// - public uint ContentIdLower { get; } - - /// - /// Gets the name of the player hosting this listing. - /// - public SeString Name { get; } - - /// - /// Gets the description of this listing as set by the host. May be multiple lines. - /// - public SeString Description { get; } - - /// - /// Gets the world that this listing was created on. - /// - public Lazy World { get; } - - /// - /// Gets the home world of the listing's host. - /// - public Lazy HomeWorld { get; } - - /// - /// Gets the current world of the listing's host. - /// - public Lazy CurrentWorld { get; } - - /// - /// Gets the Party Finder category this listing is listed under. - /// - public DutyCategory Category { get; } - - /// - /// Gets the row ID of the duty this listing is for. May be 0 for non-duty listings. - /// - public ushort RawDuty { get; } - - /// - /// Gets the duty this listing is for. May be null for non-duty listings. - /// - public Lazy Duty { get; } - - /// - /// Gets the type of duty this listing is for. - /// - public DutyType DutyType { get; } - - /// - /// Gets a value indicating whether if this listing is beginner-friendly. Shown with a sprout icon in-game. - /// - public bool BeginnersWelcome { get; } - - /// - /// Gets how many seconds this listing will continue to be available for. It may end before this time if the party - /// fills or the host ends it early. - /// - public ushort SecondsRemaining { get; } - - /// - /// Gets the minimum item level required to join this listing. - /// - public ushort MinimumItemLevel { get; } - - /// - /// Gets the number of parties this listing is recruiting for. - /// - public byte Parties { get; } - - /// - /// Gets the number of player slots this listing is recruiting for. - /// - public byte SlotsAvailable { get; } - - /// - /// Gets the time at which the server this listings is on last restarted for a patch/hotfix. - /// Probably. - /// - public uint LastPatchHotfixTimestamp { get; } - - /// - /// Gets a list of player slots that the Party Finder is accepting. - /// - public IReadOnlyCollection Slots => this.slots; - - /// - /// Gets the objective of this listing. - /// - public ObjectiveFlags Objective => (ObjectiveFlags)this.objective; - - /// - /// Gets the conditions of this listing. - /// - public ConditionFlags Conditions => (ConditionFlags)this.conditions; - - /// - /// Gets the Duty Finder settings that will be used for this listing. - /// - public DutyFinderSettingsFlags DutyFinderSettings => (DutyFinderSettingsFlags)this.dutyFinderSettings; - - /// - /// Gets the loot rules that will be used for this listing. - /// - public LootRuleFlags LootRules => (LootRuleFlags)this.lootRules; - - /// - /// Gets where this listing is searching. Note that this is also used for denoting alliance raid listings and one - /// player per job. - /// - public SearchAreaFlags SearchArea => (SearchAreaFlags)this.searchArea; - - /// - /// Gets a list of the class/job IDs that are currently present in the party. - /// - public IReadOnlyCollection RawJobsPresent => this.jobsPresent; - - /// - /// Gets a list of the classes/jobs that are currently present in the party. - /// - public IReadOnlyCollection> JobsPresent { get; } - - #region Indexers - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[ObjectiveFlags flag] => this.objective == 0 || (this.objective & (uint)flag) > 0; - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[ConditionFlags flag] => this.conditions == 0 || (this.conditions & (uint)flag) > 0; - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[DutyFinderSettingsFlags flag] => this.dutyFinderSettings == 0 || (this.dutyFinderSettings & (uint)flag) > 0; - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[LootRuleFlags flag] => this.lootRules == 0 || (this.lootRules & (uint)flag) > 0; - - /// - /// Check if the given flag is present. - /// - /// The flag to check for. - /// A value indicating whether the flag is present. - public bool this[SearchAreaFlags flag] => this.searchArea == 0 || (this.searchArea & (uint)flag) > 0; - - #endregion - } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListingEventArgs.cs b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListingEventArgs.cs index 4bc603d7a..ff6bd607d 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListingEventArgs.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderListingEventArgs.cs @@ -1,26 +1,27 @@ -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// This class represents additional arguments passed by the game. -/// -public class PartyFinderListingEventArgs +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// Initializes a new instance of the class. + /// This class represents additional arguments passed by the game. /// - /// The batch number. - internal PartyFinderListingEventArgs(int batchNumber) + public class PartyFinderListingEventArgs { - this.BatchNumber = batchNumber; + /// + /// Initializes a new instance of the class. + /// + /// The batch number. + internal PartyFinderListingEventArgs(int batchNumber) + { + this.BatchNumber = batchNumber; + } + + /// + /// Gets the batch number. + /// + public int BatchNumber { get; } + + /// + /// Gets or sets a value indicating whether the listing is visible. + /// + public bool Visible { get; set; } = true; } - - /// - /// Gets the batch number. - /// - public int BatchNumber { get; } - - /// - /// Gets or sets a value indicating whether the listing is visible. - /// - public bool Visible { get; set; } = true; } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderSlot.cs b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderSlot.cs index 03a3fafe1..d740c0c0a 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderSlot.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/PartyFinderSlot.cs @@ -2,49 +2,50 @@ using System; using System.Collections.Generic; using System.Linq; -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// A player slot in a Party Finder listing. -/// -public class PartyFinderSlot +namespace Dalamud.Game.Gui.PartyFinder.Types { - private readonly uint accepting; - private JobFlags[] listAccepting; - /// - /// Initializes a new instance of the class. + /// A player slot in a Party Finder listing. /// - /// The flag value of accepted jobs. - internal PartyFinderSlot(uint accepting) + public class PartyFinderSlot { - this.accepting = accepting; - } + private readonly uint accepting; + private JobFlags[] listAccepting; - /// - /// Gets a list of jobs that this slot is accepting. - /// - public IReadOnlyCollection Accepting - { - get + /// + /// Initializes a new instance of the class. + /// + /// The flag value of accepted jobs. + internal PartyFinderSlot(uint accepting) { - if (this.listAccepting != null) + this.accepting = accepting; + } + + /// + /// Gets a list of jobs that this slot is accepting. + /// + public IReadOnlyCollection Accepting + { + get { + if (this.listAccepting != null) + { + return this.listAccepting; + } + + this.listAccepting = Enum.GetValues(typeof(JobFlags)) + .Cast() + .Where(flag => this[flag]) + .ToArray(); + return this.listAccepting; } - - this.listAccepting = Enum.GetValues(typeof(JobFlags)) - .Cast() - .Where(flag => this[flag]) - .ToArray(); - - return this.listAccepting; } - } - /// - /// Tests if this slot is accepting a job. - /// - /// Job to test. - public bool this[JobFlags flag] => (this.accepting & (uint)flag) > 0; + /// + /// Tests if this slot is accepting a job. + /// + /// Job to test. + public bool this[JobFlags flag] => (this.accepting & (uint)flag) > 0; + } } diff --git a/Dalamud/Game/Gui/PartyFinder/Types/SearchAreaFlags.cs b/Dalamud/Game/Gui/PartyFinder/Types/SearchAreaFlags.cs index 27a3f5ee8..85308c8d1 100644 --- a/Dalamud/Game/Gui/PartyFinder/Types/SearchAreaFlags.cs +++ b/Dalamud/Game/Gui/PartyFinder/Types/SearchAreaFlags.cs @@ -1,35 +1,36 @@ using System; -namespace Dalamud.Game.Gui.PartyFinder.Types; - -/// -/// Search area flags for the class. -/// -[Flags] -public enum SearchAreaFlags : uint +namespace Dalamud.Game.Gui.PartyFinder.Types { /// - /// Datacenter. + /// Search area flags for the class. /// - DataCentre = 1 << 0, + [Flags] + public enum SearchAreaFlags : uint + { + /// + /// Datacenter. + /// + DataCentre = 1 << 0, - /// - /// Private. - /// - Private = 1 << 1, + /// + /// Private. + /// + Private = 1 << 1, - /// - /// Alliance raid. - /// - AllianceRaid = 1 << 2, + /// + /// Alliance raid. + /// + AllianceRaid = 1 << 2, - /// - /// World. - /// - World = 1 << 3, + /// + /// World. + /// + World = 1 << 3, - /// - /// One player per job. - /// - OnePlayerPerJob = 1 << 5, + /// + /// One player per job. + /// + OnePlayerPerJob = 1 << 5, + } } diff --git a/Dalamud/Game/Gui/Toast/QuestToastOptions.cs b/Dalamud/Game/Gui/Toast/QuestToastOptions.cs index 5c2fd14fe..11f09a523 100644 --- a/Dalamud/Game/Gui/Toast/QuestToastOptions.cs +++ b/Dalamud/Game/Gui/Toast/QuestToastOptions.cs @@ -1,31 +1,32 @@ -namespace Dalamud.Game.Gui.Toast; - -/// -/// This class represents options that can be used with the class for the quest toast variant. -/// -public sealed class QuestToastOptions +namespace Dalamud.Game.Gui.Toast { /// - /// Gets or sets the position of the toast on the screen. + /// This class represents options that can be used with the class for the quest toast variant. /// - public QuestToastPosition Position { get; set; } = QuestToastPosition.Centre; + public sealed class QuestToastOptions + { + /// + /// Gets or sets the position of the toast on the screen. + /// + public QuestToastPosition Position { get; set; } = QuestToastPosition.Centre; - /// - /// Gets or sets the ID of the icon that will appear in the toast. - /// - /// This may be 0 for no icon. - /// - public uint IconId { get; set; } = 0; + /// + /// Gets or sets the ID of the icon that will appear in the toast. + /// + /// This may be 0 for no icon. + /// + public uint IconId { get; set; } = 0; - /// - /// Gets or sets a value indicating whether the toast will show a checkmark after appearing. - /// - public bool DisplayCheckmark { get; set; } = false; + /// + /// Gets or sets a value indicating whether the toast will show a checkmark after appearing. + /// + public bool DisplayCheckmark { get; set; } = false; - /// - /// Gets or sets a value indicating whether the toast will play a completion sound. - /// - /// This only works if is non-zero or is true. - /// - public bool PlaySound { get; set; } = false; + /// + /// Gets or sets a value indicating whether the toast will play a completion sound. + /// + /// This only works if is non-zero or is true. + /// + public bool PlaySound { get; set; } = false; + } } diff --git a/Dalamud/Game/Gui/Toast/QuestToastPosition.cs b/Dalamud/Game/Gui/Toast/QuestToastPosition.cs index 4c7d91c36..cc107ab6e 100644 --- a/Dalamud/Game/Gui/Toast/QuestToastPosition.cs +++ b/Dalamud/Game/Gui/Toast/QuestToastPosition.cs @@ -1,22 +1,23 @@ -namespace Dalamud.Game.Gui.Toast; - -/// -/// The alignment of native quest toast windows. -/// -public enum QuestToastPosition +namespace Dalamud.Game.Gui.Toast { /// - /// The toast will be aligned screen centre. + /// The alignment of native quest toast windows. /// - Centre = 0, + public enum QuestToastPosition + { + /// + /// The toast will be aligned screen centre. + /// + Centre = 0, - /// - /// The toast will be aligned screen right. - /// - Right = 1, + /// + /// The toast will be aligned screen right. + /// + Right = 1, - /// - /// The toast will be aligned screen left. - /// - Left = 2, + /// + /// The toast will be aligned screen left. + /// + Left = 2, + } } diff --git a/Dalamud/Game/Gui/Toast/ToastGui.cs b/Dalamud/Game/Gui/Toast/ToastGui.cs index 6094fcb6f..ca24df197 100644 --- a/Dalamud/Game/Gui/Toast/ToastGui.cs +++ b/Dalamud/Game/Gui/Toast/ToastGui.cs @@ -7,421 +7,422 @@ using Dalamud.Hooking; using Dalamud.IoC; using Dalamud.IoC.Internal; -namespace Dalamud.Game.Gui.Toast; - -/// -/// This class facilitates interacting with and creating native toast windows. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed partial class ToastGui : IDisposable +namespace Dalamud.Game.Gui.Toast { - private const uint QuestToastCheckmarkMagic = 60081; - - private readonly ToastGuiAddressResolver address; - - private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new(); - private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new(); - private readonly Queue errorQueue = new(); - - private readonly Hook showNormalToastHook; - private readonly Hook showQuestToastHook; - private readonly Hook showErrorToastHook; - /// - /// Initializes a new instance of the class. + /// This class facilitates interacting with and creating native toast windows. /// - internal ToastGui() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed partial class ToastGui : IDisposable { - this.address = new ToastGuiAddressResolver(); - this.address.Setup(); + private const uint QuestToastCheckmarkMagic = 60081; - this.showNormalToastHook = new Hook(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour)); - this.showQuestToastHook = new Hook(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour)); - this.showErrorToastHook = new Hook(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour)); - } + private readonly ToastGuiAddressResolver address; - #region Event delegates + private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new(); + private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new(); + private readonly Queue errorQueue = new(); - /// - /// A delegate type used when a normal toast window appears. - /// - /// The message displayed. - /// Assorted toast options. - /// Whether the toast has been handled or should be propagated. - public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled); + private readonly Hook showNormalToastHook; + private readonly Hook showQuestToastHook; + private readonly Hook showErrorToastHook; - /// - /// A delegate type used when a quest toast window appears. - /// - /// The message displayed. - /// Assorted toast options. - /// Whether the toast has been handled or should be propagated. - public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled); - - /// - /// A delegate type used when an error toast window appears. - /// - /// The message displayed. - /// Whether the toast has been handled or should be propagated. - public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled); - - #endregion - - #region Marshal delegates - - private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId); - - private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound); - - private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe); - - #endregion - - #region Events - - /// - /// Event that will be fired when a toast is sent by the game or a plugin. - /// - public event OnNormalToastDelegate Toast; - - /// - /// Event that will be fired when a quest toast is sent by the game or a plugin. - /// - public event OnQuestToastDelegate QuestToast; - - /// - /// Event that will be fired when an error toast is sent by the game or a plugin. - /// - public event OnErrorToastDelegate ErrorToast; - - #endregion - - /// - /// Enables this module. - /// - public void Enable() - { - this.showNormalToastHook.Enable(); - this.showQuestToastHook.Enable(); - this.showErrorToastHook.Enable(); - } - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.showNormalToastHook.Dispose(); - this.showQuestToastHook.Dispose(); - this.showErrorToastHook.Dispose(); - } - - /// - /// Process the toast queue. - /// - internal void UpdateQueue() - { - while (this.normalQueue.Count > 0) + /// + /// Initializes a new instance of the class. + /// + internal ToastGui() { - var (message, options) = this.normalQueue.Dequeue(); - this.ShowNormal(message, options); + this.address = new ToastGuiAddressResolver(); + this.address.Setup(); + + this.showNormalToastHook = new Hook(this.address.ShowNormalToast, new ShowNormalToastDelegate(this.HandleNormalToastDetour)); + this.showQuestToastHook = new Hook(this.address.ShowQuestToast, new ShowQuestToastDelegate(this.HandleQuestToastDetour)); + this.showErrorToastHook = new Hook(this.address.ShowErrorToast, new ShowErrorToastDelegate(this.HandleErrorToastDetour)); } - while (this.questQueue.Count > 0) + #region Event delegates + + /// + /// A delegate type used when a normal toast window appears. + /// + /// The message displayed. + /// Assorted toast options. + /// Whether the toast has been handled or should be propagated. + public delegate void OnNormalToastDelegate(ref SeString message, ref ToastOptions options, ref bool isHandled); + + /// + /// A delegate type used when a quest toast window appears. + /// + /// The message displayed. + /// Assorted toast options. + /// Whether the toast has been handled or should be propagated. + public delegate void OnQuestToastDelegate(ref SeString message, ref QuestToastOptions options, ref bool isHandled); + + /// + /// A delegate type used when an error toast window appears. + /// + /// The message displayed. + /// Whether the toast has been handled or should be propagated. + public delegate void OnErrorToastDelegate(ref SeString message, ref bool isHandled); + + #endregion + + #region Marshal delegates + + private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId); + + private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound); + + private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe); + + #endregion + + #region Events + + /// + /// Event that will be fired when a toast is sent by the game or a plugin. + /// + public event OnNormalToastDelegate Toast; + + /// + /// Event that will be fired when a quest toast is sent by the game or a plugin. + /// + public event OnQuestToastDelegate QuestToast; + + /// + /// Event that will be fired when an error toast is sent by the game or a plugin. + /// + public event OnErrorToastDelegate ErrorToast; + + #endregion + + /// + /// Enables this module. + /// + public void Enable() { - var (message, options) = this.questQueue.Dequeue(); - this.ShowQuest(message, options); + this.showNormalToastHook.Enable(); + this.showQuestToastHook.Enable(); + this.showErrorToastHook.Enable(); } - while (this.errorQueue.Count > 0) + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() { - var message = this.errorQueue.Dequeue(); - this.ShowError(message); + this.showNormalToastHook.Dispose(); + this.showQuestToastHook.Dispose(); + this.showErrorToastHook.Dispose(); } - } - private static byte[] Terminate(byte[] source) - { - var terminated = new byte[source.Length + 1]; - Array.Copy(source, 0, terminated, 0, source.Length); - terminated[^1] = 0; - - return terminated; - } - - private SeString ParseString(IntPtr text) - { - var bytes = new List(); - unsafe + /// + /// Process the toast queue. + /// + internal void UpdateQueue() { - var ptr = (byte*)text; - while (*ptr != 0) + while (this.normalQueue.Count > 0) { - bytes.Add(*ptr); - ptr += 1; + var (message, options) = this.normalQueue.Dequeue(); + this.ShowNormal(message, options); + } + + while (this.questQueue.Count > 0) + { + var (message, options) = this.questQueue.Dequeue(); + this.ShowQuest(message, options); + } + + while (this.errorQueue.Count > 0) + { + var message = this.errorQueue.Dequeue(); + this.ShowError(message); } } - // call events - return SeString.Parse(bytes.ToArray()); - } -} - -/// -/// Handles normal toasts. -/// -public sealed partial class ToastGui -{ - /// - /// Show a toast message with the given content. - /// - /// The message to be shown. - /// Options for the toast. - public void ShowNormal(string message, ToastOptions options = null) - { - options ??= new ToastOptions(); - this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options)); - } - - /// - /// Show a toast message with the given content. - /// - /// The message to be shown. - /// Options for the toast. - public void ShowNormal(SeString message, ToastOptions options = null) - { - options ??= new ToastOptions(); - this.normalQueue.Enqueue((message.Encode(), options)); - } - - private void ShowNormal(byte[] bytes, ToastOptions options = null) - { - options ??= new ToastOptions(); - - var manager = Service.Get().GetUIModule(); - - // terminate the string - var terminated = Terminate(bytes); - - unsafe + private static byte[] Terminate(byte[] source) { - fixed (byte* ptr = terminated) + var terminated = new byte[source.Length + 1]; + Array.Copy(source, 0, terminated, 0, source.Length); + terminated[^1] = 0; + + return terminated; + } + + private SeString ParseString(IntPtr text) + { + var bytes = new List(); + unsafe { - this.HandleNormalToastDetour(manager, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0); + var ptr = (byte*)text; + while (*ptr != 0) + { + bytes.Add(*ptr); + ptr += 1; + } + } + + // call events + return SeString.Parse(bytes.ToArray()); + } + } + + /// + /// Handles normal toasts. + /// + public sealed partial class ToastGui + { + /// + /// Show a toast message with the given content. + /// + /// The message to be shown. + /// Options for the toast. + public void ShowNormal(string message, ToastOptions options = null) + { + options ??= new ToastOptions(); + this.normalQueue.Enqueue((Encoding.UTF8.GetBytes(message), options)); + } + + /// + /// Show a toast message with the given content. + /// + /// The message to be shown. + /// Options for the toast. + public void ShowNormal(SeString message, ToastOptions options = null) + { + options ??= new ToastOptions(); + this.normalQueue.Enqueue((message.Encode(), options)); + } + + private void ShowNormal(byte[] bytes, ToastOptions options = null) + { + options ??= new ToastOptions(); + + var manager = Service.Get().GetUIModule(); + + // terminate the string + var terminated = Terminate(bytes); + + unsafe + { + fixed (byte* ptr = terminated) + { + this.HandleNormalToastDetour(manager, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0); + } + } + } + + private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId) + { + if (text == IntPtr.Zero) + { + return IntPtr.Zero; + } + + // call events + var isHandled = false; + var str = this.ParseString(text); + var options = new ToastOptions + { + Position = (ToastPosition)isTop, + Speed = (ToastSpeed)isFast, + }; + + this.Toast?.Invoke(ref str, ref options, ref isHandled); + + // do nothing if handled + if (isHandled) + { + return IntPtr.Zero; + } + + var terminated = Terminate(str.Encode()); + + unsafe + { + fixed (byte* message = terminated) + { + return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId); + } } } } - private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId) + /// + /// Handles quest toasts. + /// + public sealed partial class ToastGui { - if (text == IntPtr.Zero) + /// + /// Show a quest toast message with the given content. + /// + /// The message to be shown. + /// Options for the toast. + public void ShowQuest(string message, QuestToastOptions options = null) { - return IntPtr.Zero; + options ??= new QuestToastOptions(); + this.questQueue.Enqueue((Encoding.UTF8.GetBytes(message), options)); } - // call events - var isHandled = false; - var str = this.ParseString(text); - var options = new ToastOptions + /// + /// Show a quest toast message with the given content. + /// + /// The message to be shown. + /// Options for the toast. + public void ShowQuest(SeString message, QuestToastOptions options = null) { - Position = (ToastPosition)isTop, - Speed = (ToastSpeed)isFast, - }; - - this.Toast?.Invoke(ref str, ref options, ref isHandled); - - // do nothing if handled - if (isHandled) - { - return IntPtr.Zero; + options ??= new QuestToastOptions(); + this.questQueue.Enqueue((message.Encode(), options)); } - var terminated = Terminate(str.Encode()); - - unsafe + private void ShowQuest(byte[] bytes, QuestToastOptions options = null) { - fixed (byte* message = terminated) + options ??= new QuestToastOptions(); + + var manager = Service.Get().GetUIModule(); + + // terminate the string + var terminated = Terminate(bytes); + + var (ioc1, ioc2) = this.DetermineParameterOrder(options); + + unsafe { - return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId); - } - } - } -} - -/// -/// Handles quest toasts. -/// -public sealed partial class ToastGui -{ - /// - /// Show a quest toast message with the given content. - /// - /// The message to be shown. - /// Options for the toast. - public void ShowQuest(string message, QuestToastOptions options = null) - { - options ??= new QuestToastOptions(); - this.questQueue.Enqueue((Encoding.UTF8.GetBytes(message), options)); - } - - /// - /// Show a quest toast message with the given content. - /// - /// The message to be shown. - /// Options for the toast. - public void ShowQuest(SeString message, QuestToastOptions options = null) - { - options ??= new QuestToastOptions(); - this.questQueue.Enqueue((message.Encode(), options)); - } - - private void ShowQuest(byte[] bytes, QuestToastOptions options = null) - { - options ??= new QuestToastOptions(); - - var manager = Service.Get().GetUIModule(); - - // terminate the string - var terminated = Terminate(bytes); - - var (ioc1, ioc2) = this.DetermineParameterOrder(options); - - unsafe - { - fixed (byte* ptr = terminated) - { - this.HandleQuestToastDetour( - manager, - (int)options.Position, - (IntPtr)ptr, - ioc1, - options.PlaySound ? (byte)1 : (byte)0, - ioc2, - 0); - } - } - } - - private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound) - { - if (text == IntPtr.Zero) - { - return 0; - } - - // call events - var isHandled = false; - var str = this.ParseString(text); - var options = new QuestToastOptions - { - Position = (QuestToastPosition)position, - DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic, - IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1, - PlaySound = playSound == 1, - }; - - this.QuestToast?.Invoke(ref str, ref options, ref isHandled); - - // do nothing if handled - if (isHandled) - { - return 0; - } - - var terminated = Terminate(str.Encode()); - - var (ioc1, ioc2) = this.DetermineParameterOrder(options); - - unsafe - { - fixed (byte* message = terminated) - { - return this.showQuestToastHook.Original( - manager, - (int)options.Position, - (IntPtr)message, - ioc1, - options.PlaySound ? (byte)1 : (byte)0, - ioc2, - 0); - } - } - } - - private (uint IconOrCheck1, uint IconOrCheck2) DetermineParameterOrder(QuestToastOptions options) - { - return options.DisplayCheckmark - ? (QuestToastCheckmarkMagic, options.IconId) - : (options.IconId, 0); - } -} - -/// -/// Handles error toasts. -/// -public sealed partial class ToastGui -{ - /// - /// Show an error toast message with the given content. - /// - /// The message to be shown. - public void ShowError(string message) - { - this.errorQueue.Enqueue(Encoding.UTF8.GetBytes(message)); - } - - /// - /// Show an error toast message with the given content. - /// - /// The message to be shown. - public void ShowError(SeString message) - { - this.errorQueue.Enqueue(message.Encode()); - } - - private void ShowError(byte[] bytes) - { - var manager = Service.Get().GetUIModule(); - - // terminate the string - var terminated = Terminate(bytes); - - unsafe - { - fixed (byte* ptr = terminated) - { - this.HandleErrorToastDetour(manager, (IntPtr)ptr, 0); - } - } - } - - private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe) - { - if (text == IntPtr.Zero) - { - return 0; - } - - // call events - var isHandled = false; - var str = this.ParseString(text); - - this.ErrorToast?.Invoke(ref str, ref isHandled); - - // do nothing if handled - if (isHandled) - { - return 0; - } - - var terminated = Terminate(str.Encode()); - - unsafe - { - fixed (byte* message = terminated) - { - return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe); + fixed (byte* ptr = terminated) + { + this.HandleQuestToastDetour( + manager, + (int)options.Position, + (IntPtr)ptr, + ioc1, + options.PlaySound ? (byte)1 : (byte)0, + ioc2, + 0); + } + } + } + + private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound) + { + if (text == IntPtr.Zero) + { + return 0; + } + + // call events + var isHandled = false; + var str = this.ParseString(text); + var options = new QuestToastOptions + { + Position = (QuestToastPosition)position, + DisplayCheckmark = iconOrCheck1 == QuestToastCheckmarkMagic, + IconId = iconOrCheck1 == QuestToastCheckmarkMagic ? iconOrCheck2 : iconOrCheck1, + PlaySound = playSound == 1, + }; + + this.QuestToast?.Invoke(ref str, ref options, ref isHandled); + + // do nothing if handled + if (isHandled) + { + return 0; + } + + var terminated = Terminate(str.Encode()); + + var (ioc1, ioc2) = this.DetermineParameterOrder(options); + + unsafe + { + fixed (byte* message = terminated) + { + return this.showQuestToastHook.Original( + manager, + (int)options.Position, + (IntPtr)message, + ioc1, + options.PlaySound ? (byte)1 : (byte)0, + ioc2, + 0); + } + } + } + + private (uint IconOrCheck1, uint IconOrCheck2) DetermineParameterOrder(QuestToastOptions options) + { + return options.DisplayCheckmark + ? (QuestToastCheckmarkMagic, options.IconId) + : (options.IconId, 0); + } + } + + /// + /// Handles error toasts. + /// + public sealed partial class ToastGui + { + /// + /// Show an error toast message with the given content. + /// + /// The message to be shown. + public void ShowError(string message) + { + this.errorQueue.Enqueue(Encoding.UTF8.GetBytes(message)); + } + + /// + /// Show an error toast message with the given content. + /// + /// The message to be shown. + public void ShowError(SeString message) + { + this.errorQueue.Enqueue(message.Encode()); + } + + private void ShowError(byte[] bytes) + { + var manager = Service.Get().GetUIModule(); + + // terminate the string + var terminated = Terminate(bytes); + + unsafe + { + fixed (byte* ptr = terminated) + { + this.HandleErrorToastDetour(manager, (IntPtr)ptr, 0); + } + } + } + + private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe) + { + if (text == IntPtr.Zero) + { + return 0; + } + + // call events + var isHandled = false; + var str = this.ParseString(text); + + this.ErrorToast?.Invoke(ref str, ref isHandled); + + // do nothing if handled + if (isHandled) + { + return 0; + } + + var terminated = Terminate(str.Encode()); + + unsafe + { + fixed (byte* message = terminated) + { + return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe); + } } } } diff --git a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs b/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs index 4f935b465..75a1e96b6 100644 --- a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs @@ -1,32 +1,33 @@ using System; -namespace Dalamud.Game.Gui.Toast; - -/// -/// An address resolver for the class. -/// -public class ToastGuiAddressResolver : BaseAddressResolver +namespace Dalamud.Game.Gui.Toast { /// - /// Gets the address of the native ShowNormalToast method. + /// An address resolver for the class. /// - public IntPtr ShowNormalToast { get; private set; } - - /// - /// Gets the address of the native ShowQuestToast method. - /// - public IntPtr ShowQuestToast { get; private set; } - - /// - /// Gets the address of the ShowErrorToast method. - /// - public IntPtr ShowErrorToast { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) + public class ToastGuiAddressResolver : BaseAddressResolver { - this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??"); - this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??"); - this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0"); + /// + /// Gets the address of the native ShowNormalToast method. + /// + public IntPtr ShowNormalToast { get; private set; } + + /// + /// Gets the address of the native ShowQuestToast method. + /// + public IntPtr ShowQuestToast { get; private set; } + + /// + /// Gets the address of the ShowErrorToast method. + /// + public IntPtr ShowErrorToast { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) + { + this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??"); + this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??"); + this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0"); + } } } diff --git a/Dalamud/Game/Gui/Toast/ToastOptions.cs b/Dalamud/Game/Gui/Toast/ToastOptions.cs index 32b5ae646..0939bb5bb 100644 --- a/Dalamud/Game/Gui/Toast/ToastOptions.cs +++ b/Dalamud/Game/Gui/Toast/ToastOptions.cs @@ -1,17 +1,18 @@ -namespace Dalamud.Game.Gui.Toast; - -/// -/// This class represents options that can be used with the class. -/// -public sealed class ToastOptions +namespace Dalamud.Game.Gui.Toast { /// - /// Gets or sets the position of the toast on the screen. + /// This class represents options that can be used with the class. /// - public ToastPosition Position { get; set; } = ToastPosition.Bottom; + public sealed class ToastOptions + { + /// + /// Gets or sets the position of the toast on the screen. + /// + public ToastPosition Position { get; set; } = ToastPosition.Bottom; - /// - /// Gets or sets the speed of the toast. - /// - public ToastSpeed Speed { get; set; } = ToastSpeed.Slow; + /// + /// Gets or sets the speed of the toast. + /// + public ToastSpeed Speed { get; set; } = ToastSpeed.Slow; + } } diff --git a/Dalamud/Game/Gui/Toast/ToastPosition.cs b/Dalamud/Game/Gui/Toast/ToastPosition.cs index 3e5696c3e..14f489711 100644 --- a/Dalamud/Game/Gui/Toast/ToastPosition.cs +++ b/Dalamud/Game/Gui/Toast/ToastPosition.cs @@ -1,17 +1,18 @@ -namespace Dalamud.Game.Gui.Toast; - -/// -/// The positioning of native toast windows. -/// -public enum ToastPosition : byte +namespace Dalamud.Game.Gui.Toast { /// - /// The toast will be towards the bottom. + /// The positioning of native toast windows. /// - Bottom = 0, + public enum ToastPosition : byte + { + /// + /// The toast will be towards the bottom. + /// + Bottom = 0, - /// - /// The toast will be towards the top. - /// - Top = 1, + /// + /// The toast will be towards the top. + /// + Top = 1, + } } diff --git a/Dalamud/Game/Gui/Toast/ToastSpeed.cs b/Dalamud/Game/Gui/Toast/ToastSpeed.cs index 1858764cc..0f54df273 100644 --- a/Dalamud/Game/Gui/Toast/ToastSpeed.cs +++ b/Dalamud/Game/Gui/Toast/ToastSpeed.cs @@ -1,17 +1,18 @@ -namespace Dalamud.Game.Gui.Toast; - -/// -/// The speed at which native toast windows will persist. -/// -public enum ToastSpeed : byte +namespace Dalamud.Game.Gui.Toast { /// - /// The toast will take longer to disappear (around four seconds). + /// The speed at which native toast windows will persist. /// - Slow = 0, + public enum ToastSpeed : byte + { + /// + /// The toast will take longer to disappear (around four seconds). + /// + Slow = 0, - /// - /// The toast will disappear more quickly (around two seconds). - /// - Fast = 1, + /// + /// The toast will disappear more quickly (around two seconds). + /// + Fast = 1, + } } diff --git a/Dalamud/Game/Internal/AntiDebug.cs b/Dalamud/Game/Internal/AntiDebug.cs index d435fad9c..7cac7b7dc 100644 --- a/Dalamud/Game/Internal/AntiDebug.cs +++ b/Dalamud/Game/Internal/AntiDebug.cs @@ -3,118 +3,119 @@ using System.Collections.Generic; using Serilog; -namespace Dalamud.Game.Internal; - -/// -/// This class disables anti-debug functionality in the game client. -/// -internal sealed partial class AntiDebug +namespace Dalamud.Game.Internal { - private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; - private byte[] original; - private IntPtr debugCheckAddress; - /// - /// Initializes a new instance of the class. + /// This class disables anti-debug functionality in the game client. /// - public AntiDebug() + internal sealed partial class AntiDebug { - var scanner = Service.Get(); + private readonly byte[] nop = new byte[] { 0x31, 0xC0, 0x90, 0x90, 0x90, 0x90 }; + private byte[] original; + private IntPtr debugCheckAddress; - try + /// + /// Initializes a new instance of the class. + /// + public AntiDebug() { - this.debugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41"); - } - catch (KeyNotFoundException) - { - this.debugCheckAddress = IntPtr.Zero; + var scanner = Service.Get(); + + try + { + this.debugCheckAddress = scanner.ScanText("FF 15 ?? ?? ?? ?? 85 C0 74 11 41"); + } + catch (KeyNotFoundException) + { + this.debugCheckAddress = IntPtr.Zero; + } + + Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); } - Log.Verbose($"Debug check address 0x{this.debugCheckAddress.ToInt64():X}"); + /// + /// Gets a value indicating whether the anti-debugging is enabled. + /// + public bool IsEnabled { get; private set; } = false; + + /// + /// Enables the anti-debugging by overwriting code in memory. + /// + public void Enable() + { + this.original = new byte[this.nop.Length]; + if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled) + { + Log.Information($"Overwriting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); + SafeMemory.ReadBytes(this.debugCheckAddress, this.nop.Length, out this.original); + SafeMemory.WriteBytes(this.debugCheckAddress, this.nop); + } + else + { + Log.Information("Debug check already overwritten?"); + } + + this.IsEnabled = true; + } + + /// + /// Disable the anti-debugging by reverting the overwritten code in memory. + /// + public void Disable() + { + if (this.debugCheckAddress != IntPtr.Zero && this.original != null) + { + Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); + SafeMemory.WriteBytes(this.debugCheckAddress, this.original); + } + else + { + Log.Information("Debug check was not overwritten?"); + } + + this.IsEnabled = false; + } } /// - /// Gets a value indicating whether the anti-debugging is enabled. + /// Implementing IDisposable. /// - public bool IsEnabled { get; private set; } = false; - - /// - /// Enables the anti-debugging by overwriting code in memory. - /// - public void Enable() + internal sealed partial class AntiDebug : IDisposable { - this.original = new byte[this.nop.Length]; - if (this.debugCheckAddress != IntPtr.Zero && !this.IsEnabled) + private bool disposed = false; + + /// + /// Finalizes an instance of the class. + /// + ~AntiDebug() => this.Dispose(false); + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() { - Log.Information($"Overwriting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); - SafeMemory.ReadBytes(this.debugCheckAddress, this.nop.Length, out this.original); - SafeMemory.WriteBytes(this.debugCheckAddress, this.nop); - } - else - { - Log.Information("Debug check already overwritten?"); + this.Dispose(true); + GC.SuppressFinalize(this); } - this.IsEnabled = true; - } - - /// - /// Disable the anti-debugging by reverting the overwritten code in memory. - /// - public void Disable() - { - if (this.debugCheckAddress != IntPtr.Zero && this.original != null) + /// + /// Disposes of managed and unmanaged resources. + /// + /// If this was disposed through calling Dispose() or from being finalized. + private void Dispose(bool disposing) { - Log.Information($"Reverting debug check at 0x{this.debugCheckAddress.ToInt64():X}"); - SafeMemory.WriteBytes(this.debugCheckAddress, this.original); - } - else - { - Log.Information("Debug check was not overwritten?"); - } + if (this.disposed) + return; - this.IsEnabled = false; - } -} - -/// -/// Implementing IDisposable. -/// -internal sealed partial class AntiDebug : IDisposable -{ - private bool disposed = false; - - /// - /// Finalizes an instance of the class. - /// - ~AntiDebug() => this.Dispose(false); - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of managed and unmanaged resources. - /// - /// If this was disposed through calling Dispose() or from being finalized. - private void Dispose(bool disposing) - { - if (this.disposed) - return; - - if (disposing) - { - // If anti-debug is enabled and is being disposed, odds are either the game is exiting, or Dalamud is being reloaded. - // If it is the latter, there's half a chance a debugger is currently attached. There's no real need to disable the - // check in either situation anyways. However if Dalamud is being reloaded, the sig may fail so may as well undo it. - this.Disable(); - } - - this.disposed = true; + if (disposing) + { + // If anti-debug is enabled and is being disposed, odds are either the game is exiting, or Dalamud is being reloaded. + // If it is the latter, there's half a chance a debugger is currently attached. There's no real need to disable the + // check in either situation anyways. However if Dalamud is being reloaded, the sig may fail so may as well undo it. + this.Disable(); + } + + this.disposed = true; + } } } diff --git a/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs b/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs index 4bd369dd3..7703713b3 100644 --- a/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs +++ b/Dalamud/Game/Internal/DXGI/Definitions/ID3D11DeviceVtbl.cs @@ -1,226 +1,227 @@ -namespace Dalamud.Game.Internal.DXGI.Definitions; - -/// -/// Contains a full list of ID3D11Device functions to be used as an indexer into the DirectX Virtual Function Table entries. -/// -internal enum ID3D11DeviceVtbl +namespace Dalamud.Game.Internal.DXGI.Definitions { - // IUnknown - /// - /// IUnknown::QueryInterface method (unknwn.h). + /// Contains a full list of ID3D11Device functions to be used as an indexer into the DirectX Virtual Function Table entries. /// - QueryInterface = 0, + internal enum ID3D11DeviceVtbl + { + // IUnknown - /// - /// IUnknown::AddRef method (unknwn.h). - /// - AddRef = 1, + /// + /// IUnknown::QueryInterface method (unknwn.h). + /// + QueryInterface = 0, - /// - /// IUnknown::Release method (unknwn.h). - /// - Release = 2, + /// + /// IUnknown::AddRef method (unknwn.h). + /// + AddRef = 1, - // ID3D11Device + /// + /// IUnknown::Release method (unknwn.h). + /// + Release = 2, - /// - /// ID3D11Device::CreateBuffer method (d3d11.h). - /// - CreateBuffer = 3, + // ID3D11Device - /// - /// ID3D11Device::CreateTexture1D method (d3d11.h). - /// - CreateTexture1D = 4, + /// + /// ID3D11Device::CreateBuffer method (d3d11.h). + /// + CreateBuffer = 3, - /// - /// ID3D11Device::CreateTexture2D method (d3d11.h). - /// - CreateTexture2D = 5, + /// + /// ID3D11Device::CreateTexture1D method (d3d11.h). + /// + CreateTexture1D = 4, - /// - /// ID3D11Device::CreateTexture3D method (d3d11.h). - /// - CreateTexture3D = 6, + /// + /// ID3D11Device::CreateTexture2D method (d3d11.h). + /// + CreateTexture2D = 5, - /// - /// ID3D11Device::CreateShaderResourceView method (d3d11.h). - /// - CreateShaderResourceView = 7, + /// + /// ID3D11Device::CreateTexture3D method (d3d11.h). + /// + CreateTexture3D = 6, - /// - /// ID3D11Device::CreateUnorderedAccessView method (d3d11.h). - /// - CreateUnorderedAccessView = 8, + /// + /// ID3D11Device::CreateShaderResourceView method (d3d11.h). + /// + CreateShaderResourceView = 7, - /// - /// ID3D11Device::CreateRenderTargetView method (d3d11.h). - /// - CreateRenderTargetView = 9, + /// + /// ID3D11Device::CreateUnorderedAccessView method (d3d11.h). + /// + CreateUnorderedAccessView = 8, - /// - /// ID3D11Device::CreateDepthStencilView method (d3d11.h). - /// - CreateDepthStencilView = 10, + /// + /// ID3D11Device::CreateRenderTargetView method (d3d11.h). + /// + CreateRenderTargetView = 9, - /// - /// ID3D11Device::CreateInputLayout method (d3d11.h). - /// - CreateInputLayout = 11, + /// + /// ID3D11Device::CreateDepthStencilView method (d3d11.h). + /// + CreateDepthStencilView = 10, - /// - /// ID3D11Device::CreateVertexShader method (d3d11.h). - /// - CreateVertexShader = 12, + /// + /// ID3D11Device::CreateInputLayout method (d3d11.h). + /// + CreateInputLayout = 11, - /// - /// ID3D11Device::CreateGeometryShader method (d3d11.h). - /// - CreateGeometryShader = 13, + /// + /// ID3D11Device::CreateVertexShader method (d3d11.h). + /// + CreateVertexShader = 12, - /// - /// ID3D11Device::CreateGeometryShaderWithStreamOutput method (d3d11.h). - /// - CreateGeometryShaderWithStreamOutput = 14, + /// + /// ID3D11Device::CreateGeometryShader method (d3d11.h). + /// + CreateGeometryShader = 13, - /// - /// ID3D11Device::CreatePixelShader method (d3d11.h). - /// - CreatePixelShader = 15, + /// + /// ID3D11Device::CreateGeometryShaderWithStreamOutput method (d3d11.h). + /// + CreateGeometryShaderWithStreamOutput = 14, - /// - /// ID3D11Device::CreateHullShader method (d3d11.h). - /// - CreateHullShader = 16, + /// + /// ID3D11Device::CreatePixelShader method (d3d11.h). + /// + CreatePixelShader = 15, - /// - /// ID3D11Device::CreateDomainShader method (d3d11.h). - /// - CreateDomainShader = 17, + /// + /// ID3D11Device::CreateHullShader method (d3d11.h). + /// + CreateHullShader = 16, - /// - /// ID3D11Device::CreateComputeShader method (d3d11.h). - /// - CreateComputeShader = 18, + /// + /// ID3D11Device::CreateDomainShader method (d3d11.h). + /// + CreateDomainShader = 17, - /// - /// ID3D11Device::CreateClassLinkage method (d3d11.h). - /// - CreateClassLinkage = 19, + /// + /// ID3D11Device::CreateComputeShader method (d3d11.h). + /// + CreateComputeShader = 18, - /// - /// ID3D11Device::CreateBlendState method (d3d11.h). - /// - CreateBlendState = 20, + /// + /// ID3D11Device::CreateClassLinkage method (d3d11.h). + /// + CreateClassLinkage = 19, - /// - /// ID3D11Device::CreateDepthStencilState method (d3d11.h). - /// - CreateDepthStencilState = 21, + /// + /// ID3D11Device::CreateBlendState method (d3d11.h). + /// + CreateBlendState = 20, - /// - /// ID3D11Device::CreateRasterizerState method (d3d11.h). - /// - CreateRasterizerState = 22, + /// + /// ID3D11Device::CreateDepthStencilState method (d3d11.h). + /// + CreateDepthStencilState = 21, - /// - /// ID3D11Device::CreateSamplerState method (d3d11.h). - /// - CreateSamplerState = 23, + /// + /// ID3D11Device::CreateRasterizerState method (d3d11.h). + /// + CreateRasterizerState = 22, - /// - /// ID3D11Device::CreateQuery method (d3d11.h). - /// - CreateQuery = 24, + /// + /// ID3D11Device::CreateSamplerState method (d3d11.h). + /// + CreateSamplerState = 23, - /// - /// ID3D11Device::CreatePredicate method (d3d11.h). - /// - CreatePredicate = 25, + /// + /// ID3D11Device::CreateQuery method (d3d11.h). + /// + CreateQuery = 24, - /// - /// ID3D11Device::CreateCounter method (d3d11.h). - /// - CreateCounter = 26, + /// + /// ID3D11Device::CreatePredicate method (d3d11.h). + /// + CreatePredicate = 25, - /// - /// ID3D11Device::CreateDeferredContext method (d3d11.h). - /// - CreateDeferredContext = 27, + /// + /// ID3D11Device::CreateCounter method (d3d11.h). + /// + CreateCounter = 26, - /// - /// ID3D11Device::OpenSharedResource method (d3d11.h). - /// - OpenSharedResource = 28, + /// + /// ID3D11Device::CreateDeferredContext method (d3d11.h). + /// + CreateDeferredContext = 27, - /// - /// ID3D11Device::CheckFormatSupport method (d3d11.h). - /// - CheckFormatSupport = 29, + /// + /// ID3D11Device::OpenSharedResource method (d3d11.h). + /// + OpenSharedResource = 28, - /// - /// ID3D11Device::CheckMultisampleQualityLevels method (d3d11.h). - /// - CheckMultisampleQualityLevels = 30, + /// + /// ID3D11Device::CheckFormatSupport method (d3d11.h). + /// + CheckFormatSupport = 29, - /// - /// ID3D11Device::CheckCounterInfo method (d3d11.h). - /// - CheckCounterInfo = 31, + /// + /// ID3D11Device::CheckMultisampleQualityLevels method (d3d11.h). + /// + CheckMultisampleQualityLevels = 30, - /// - /// ID3D11Device::CheckCounter method (d3d11.h). - /// - CheckCounter = 32, + /// + /// ID3D11Device::CheckCounterInfo method (d3d11.h). + /// + CheckCounterInfo = 31, - /// - /// ID3D11Device::CheckFeatureSupport method (d3d11.h). - /// - CheckFeatureSupport = 33, + /// + /// ID3D11Device::CheckCounter method (d3d11.h). + /// + CheckCounter = 32, - /// - /// ID3D11Device::GetPrivateData method (d3d11.h). - /// - GetPrivateData = 34, + /// + /// ID3D11Device::CheckFeatureSupport method (d3d11.h). + /// + CheckFeatureSupport = 33, - /// - /// ID3D11Device::SetPrivateData method (d3d11.h). - /// - SetPrivateData = 35, + /// + /// ID3D11Device::GetPrivateData method (d3d11.h). + /// + GetPrivateData = 34, - /// - /// ID3D11Device::SetPrivateDataInterface method (d3d11.h). - /// - SetPrivateDataInterface = 36, + /// + /// ID3D11Device::SetPrivateData method (d3d11.h). + /// + SetPrivateData = 35, - /// - /// ID3D11Device::GetFeatureLevel method (d3d11.h). - /// - GetFeatureLevel = 37, + /// + /// ID3D11Device::SetPrivateDataInterface method (d3d11.h). + /// + SetPrivateDataInterface = 36, - /// - /// ID3D11Device::GetCreationFlags method (d3d11.h). - /// - GetCreationFlags = 38, + /// + /// ID3D11Device::GetFeatureLevel method (d3d11.h). + /// + GetFeatureLevel = 37, - /// - /// ID3D11Device::GetDeviceRemovedReason method (d3d11.h). - /// - GetDeviceRemovedReason = 39, + /// + /// ID3D11Device::GetCreationFlags method (d3d11.h). + /// + GetCreationFlags = 38, - /// - /// ID3D11Device::GetImmediateContext method (d3d11.h). - /// - GetImmediateContext = 40, + /// + /// ID3D11Device::GetDeviceRemovedReason method (d3d11.h). + /// + GetDeviceRemovedReason = 39, - /// - /// ID3D11Device::SetExceptionMode method (d3d11.h). - /// - SetExceptionMode = 41, + /// + /// ID3D11Device::GetImmediateContext method (d3d11.h). + /// + GetImmediateContext = 40, - /// - /// ID3D11Device::GetExceptionMode method (d3d11.h). - /// - GetExceptionMode = 42, + /// + /// ID3D11Device::SetExceptionMode method (d3d11.h). + /// + SetExceptionMode = 41, + + /// + /// ID3D11Device::GetExceptionMode method (d3d11.h). + /// + GetExceptionMode = 42, + } } diff --git a/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs b/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs index 4bcb6fb72..e3d627ce3 100644 --- a/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs +++ b/Dalamud/Game/Internal/DXGI/Definitions/IDXGISwapChainVtbl.cs @@ -1,106 +1,107 @@ -namespace Dalamud.Game.Internal.DXGI.Definitions; - -/// -/// Contains a full list of IDXGISwapChain functions to be used as an indexer into the SwapChain Virtual Function Table -/// entries. -/// -internal enum IDXGISwapChainVtbl +namespace Dalamud.Game.Internal.DXGI.Definitions { - // IUnknown - /// - /// IUnknown::QueryInterface method (unknwn.h). + /// Contains a full list of IDXGISwapChain functions to be used as an indexer into the SwapChain Virtual Function Table + /// entries. /// - QueryInterface = 0, + internal enum IDXGISwapChainVtbl + { + // IUnknown - /// - /// IUnknown::AddRef method (unknwn.h). - /// - AddRef = 1, + /// + /// IUnknown::QueryInterface method (unknwn.h). + /// + QueryInterface = 0, - /// - /// IUnknown::Release method (unknwn.h). - /// - Release = 2, + /// + /// IUnknown::AddRef method (unknwn.h). + /// + AddRef = 1, - // IDXGIObject + /// + /// IUnknown::Release method (unknwn.h). + /// + Release = 2, - /// - /// IDXGIObject::SetPrivateData method (dxgi.h). - /// - SetPrivateData = 3, + // IDXGIObject - /// - /// IDXGIObject::SetPrivateDataInterface method (dxgi.h). - /// - SetPrivateDataInterface = 4, + /// + /// IDXGIObject::SetPrivateData method (dxgi.h). + /// + SetPrivateData = 3, - /// - /// IDXGIObject::GetPrivateData method (dxgi.h). - /// - GetPrivateData = 5, + /// + /// IDXGIObject::SetPrivateDataInterface method (dxgi.h). + /// + SetPrivateDataInterface = 4, - /// - /// IDXGIObject::GetParent method (dxgi.h). - /// - GetParent = 6, + /// + /// IDXGIObject::GetPrivateData method (dxgi.h). + /// + GetPrivateData = 5, - // IDXGIDeviceSubObject + /// + /// IDXGIObject::GetParent method (dxgi.h). + /// + GetParent = 6, - /// - /// IDXGIDeviceSubObject::GetDevice method (dxgi.h). - /// - GetDevice = 7, + // IDXGIDeviceSubObject - // IDXGISwapChain + /// + /// IDXGIDeviceSubObject::GetDevice method (dxgi.h). + /// + GetDevice = 7, - /// - /// IDXGISwapChain::Present method (dxgi.h). - /// - Present = 8, + // IDXGISwapChain - /// - /// IUnknIDXGISwapChainown::GetBuffer method (dxgi.h). - /// - GetBuffer = 9, + /// + /// IDXGISwapChain::Present method (dxgi.h). + /// + Present = 8, - /// - /// IDXGISwapChain::SetFullscreenState method (dxgi.h). - /// - SetFullscreenState = 10, + /// + /// IUnknIDXGISwapChainown::GetBuffer method (dxgi.h). + /// + GetBuffer = 9, - /// - /// IDXGISwapChain::GetFullscreenState method (dxgi.h). - /// - GetFullscreenState = 11, + /// + /// IDXGISwapChain::SetFullscreenState method (dxgi.h). + /// + SetFullscreenState = 10, - /// - /// IDXGISwapChain::GetDesc method (dxgi.h). - /// - GetDesc = 12, + /// + /// IDXGISwapChain::GetFullscreenState method (dxgi.h). + /// + GetFullscreenState = 11, - /// - /// IDXGISwapChain::ResizeBuffers method (dxgi.h). - /// - ResizeBuffers = 13, + /// + /// IDXGISwapChain::GetDesc method (dxgi.h). + /// + GetDesc = 12, - /// - /// IDXGISwapChain::ResizeTarget method (dxgi.h). - /// - ResizeTarget = 14, + /// + /// IDXGISwapChain::ResizeBuffers method (dxgi.h). + /// + ResizeBuffers = 13, - /// - /// IDXGISwapChain::GetContainingOutput method (dxgi.h). - /// - GetContainingOutput = 15, + /// + /// IDXGISwapChain::ResizeTarget method (dxgi.h). + /// + ResizeTarget = 14, - /// - /// IDXGISwapChain::GetFrameStatistics method (dxgi.h). - /// - GetFrameStatistics = 16, + /// + /// IDXGISwapChain::GetContainingOutput method (dxgi.h). + /// + GetContainingOutput = 15, - /// - /// IDXGISwapChain::GetLastPresentCount method (dxgi.h). - /// - GetLastPresentCount = 17, + /// + /// IDXGISwapChain::GetFrameStatistics method (dxgi.h). + /// + GetFrameStatistics = 16, + + /// + /// IDXGISwapChain::GetLastPresentCount method (dxgi.h). + /// + GetLastPresentCount = 17, + } } diff --git a/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs b/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs index 867119be5..eb867dd5c 100644 --- a/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs +++ b/Dalamud/Game/Internal/DXGI/ISwapChainAddressResolver.cs @@ -1,19 +1,20 @@ using System; -namespace Dalamud.Game.Internal.DXGI; - -/// -/// An interface binding for the address resolvers that attempt to find native D3D11 methods. -/// -public interface ISwapChainAddressResolver +namespace Dalamud.Game.Internal.DXGI { /// - /// Gets or sets the address of the native D3D11.Present method. + /// An interface binding for the address resolvers that attempt to find native D3D11 methods. /// - IntPtr Present { get; set; } + public interface ISwapChainAddressResolver + { + /// + /// Gets or sets the address of the native D3D11.Present method. + /// + IntPtr Present { get; set; } - /// - /// Gets or sets the address of the native D3D11.ResizeBuffers method. - /// - IntPtr ResizeBuffers { get; set; } + /// + /// Gets or sets the address of the native D3D11.ResizeBuffers method. + /// + IntPtr ResizeBuffers { get; set; } + } } diff --git a/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs b/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs index ad79dff9f..ac1a419c2 100644 --- a/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs +++ b/Dalamud/Game/Internal/DXGI/SwapChainSigResolver.cs @@ -4,32 +4,33 @@ using System.Linq; using Serilog; -namespace Dalamud.Game.Internal.DXGI; - -/// -/// The address resolver for native D3D11 methods to facilitate displaying the Dalamud UI. -/// -[Obsolete("This has been deprecated in favor of the VTable resolver.")] -public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver +namespace Dalamud.Game.Internal.DXGI { - /// - public IntPtr Present { get; set; } - - /// - public IntPtr ResizeBuffers { get; set; } - - /// - protected override void Setup64Bit(SigScanner sig) + /// + /// The address resolver for native D3D11 methods to facilitate displaying the Dalamud UI. + /// + [Obsolete("This has been deprecated in favor of the VTable resolver.")] + public sealed class SwapChainSigResolver : BaseAddressResolver, ISwapChainAddressResolver { - var module = Process.GetCurrentProcess().Modules.Cast().First(m => m.ModuleName == "dxgi.dll"); + /// + public IntPtr Present { get; set; } - Log.Debug($"Found DXGI: 0x{module.BaseAddress.ToInt64():X}"); + /// + public IntPtr ResizeBuffers { get; set; } - var scanner = new SigScanner(module); + /// + protected override void Setup64Bit(SigScanner sig) + { + var module = Process.GetCurrentProcess().Modules.Cast().First(m => m.ModuleName == "dxgi.dll"); - // This(code after the function head - offset of it) was picked to avoid running into issues with other hooks being installed into this function. - this.Present = scanner.ScanModule("41 8B F0 8B FA 89 54 24 ?? 48 8B D9 48 89 4D ?? C6 44 24 ?? 00") - 0x37; + Log.Debug($"Found DXGI: 0x{module.BaseAddress.ToInt64():X}"); - this.ResizeBuffers = scanner.ScanModule("48 8B C4 55 41 54 41 55 41 56 41 57 48 8D 68 B1 48 81 EC ?? ?? ?? ?? 48 C7 45 ?? ?? ?? ?? ?? 48 89 58 10 48 89 70 18 48 89 78 20 45 8B F9 45 8B E0 44 8B EA 48 8B F9 8B 45 7F 89 44 24 30 8B 75 77 89 74 24 28 44 89 4C 24"); + var scanner = new SigScanner(module); + + // This(code after the function head - offset of it) was picked to avoid running into issues with other hooks being installed into this function. + this.Present = scanner.ScanModule("41 8B F0 8B FA 89 54 24 ?? 48 8B D9 48 89 4D ?? C6 44 24 ?? 00") - 0x37; + + this.ResizeBuffers = scanner.ScanModule("48 8B C4 55 41 54 41 55 41 56 41 57 48 8D 68 B1 48 81 EC ?? ?? ?? ?? 48 C7 45 ?? ?? ?? ?? ?? 48 89 58 10 48 89 70 18 48 89 78 20 45 8B F9 45 8B E0 44 8B EA 48 8B F9 8B 45 7F 89 44 24 30 8B 75 77 89 74 24 28 44 89 4C 24"); + } } } diff --git a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs index a17cf293b..78abae0f0 100644 --- a/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs +++ b/Dalamud/Game/Internal/DXGI/SwapChainVtableResolver.cs @@ -6,77 +6,78 @@ using System.Runtime.InteropServices; using Dalamud.Game.Internal.DXGI.Definitions; using Serilog; -namespace Dalamud.Game.Internal.DXGI; - -/// -/// This class attempts to determine the D3D11 SwapChain vtable addresses via instantiating a new form and inspecting it. -/// -/// -/// If the normal signature based method of resolution fails, this is the backup. -/// -public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver +namespace Dalamud.Game.Internal.DXGI { - /// - public IntPtr Present { get; set; } - - /// - public IntPtr ResizeBuffers { get; set; } - /// - /// Gets a value indicating whether or not ReShade is loaded/used. + /// This class attempts to determine the D3D11 SwapChain vtable addresses via instantiating a new form and inspecting it. /// - public bool IsReshade { get; private set; } - - /// - protected override unsafe void Setup64Bit(SigScanner sig) + /// + /// If the normal signature based method of resolution fails, this is the backup. + /// + public class SwapChainVtableResolver : BaseAddressResolver, ISwapChainAddressResolver { - var kernelDev = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device.Instance(); + /// + public IntPtr Present { get; set; } - var scVtbl = GetVTblAddresses(new IntPtr(kernelDev->SwapChain->DXGISwapChain), Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length); + /// + public IntPtr ResizeBuffers { get; set; } - this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present]; + /// + /// Gets a value indicating whether or not ReShade is loaded/used. + /// + public bool IsReshade { get; private set; } - var modules = Process.GetCurrentProcess().Modules; - foreach (ProcessModule processModule in modules) + /// + protected override unsafe void Setup64Bit(SigScanner sig) { - if (processModule.FileName != null && processModule.FileName.EndsWith("game\\dxgi.dll")) + var kernelDev = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device.Instance(); + + var scVtbl = GetVTblAddresses(new IntPtr(kernelDev->SwapChain->DXGISwapChain), Enum.GetValues(typeof(IDXGISwapChainVtbl)).Length); + + this.Present = scVtbl[(int)IDXGISwapChainVtbl.Present]; + + var modules = Process.GetCurrentProcess().Modules; + foreach (ProcessModule processModule in modules) { - // reshade master@4232872 RVA - // var p = processModule.BaseAddress + 0x82C7E0; // DXGISwapChain::Present - // var p = processModule.BaseAddress + 0x82FAC0; // DXGISwapChain::runtime_present - - var scanner = new SigScanner(processModule); - try + if (processModule.FileName != null && processModule.FileName.EndsWith("game\\dxgi.dll")) { - var p = scanner.ScanText("F6 C2 01 0F 85 ?? ?? ?? ??"); - Log.Information($"ReShade DLL: {processModule.FileName} with DXGISwapChain::runtime_present at {p:X}"); + // reshade master@4232872 RVA + // var p = processModule.BaseAddress + 0x82C7E0; // DXGISwapChain::Present + // var p = processModule.BaseAddress + 0x82FAC0; // DXGISwapChain::runtime_present - this.Present = p; - this.IsReshade = true; - break; - } - catch (Exception ex) - { - Log.Error(ex, "Could not find reshade DXGISwapChain::runtime_present offset!"); + var scanner = new SigScanner(processModule); + try + { + var p = scanner.ScanText("F6 C2 01 0F 85 ?? ?? ?? ??"); + Log.Information($"ReShade DLL: {processModule.FileName} with DXGISwapChain::runtime_present at {p:X}"); + + this.Present = p; + this.IsReshade = true; + break; + } + catch (Exception ex) + { + Log.Error(ex, "Could not find reshade DXGISwapChain::runtime_present offset!"); + } } } + + this.ResizeBuffers = scVtbl[(int)IDXGISwapChainVtbl.ResizeBuffers]; } - this.ResizeBuffers = scVtbl[(int)IDXGISwapChainVtbl.ResizeBuffers]; - } + private static List GetVTblAddresses(IntPtr pointer, int numberOfMethods) + { + return GetVTblAddresses(pointer, 0, numberOfMethods); + } - private static List GetVTblAddresses(IntPtr pointer, int numberOfMethods) - { - return GetVTblAddresses(pointer, 0, numberOfMethods); - } + private static List GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods) + { + var vtblAddresses = new List(); + var vTable = Marshal.ReadIntPtr(pointer); + for (var i = startIndex; i < startIndex + numberOfMethods; i++) + vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes - private static List GetVTblAddresses(IntPtr pointer, int startIndex, int numberOfMethods) - { - var vtblAddresses = new List(); - var vTable = Marshal.ReadIntPtr(pointer); - for (var i = startIndex; i < startIndex + numberOfMethods; i++) - vtblAddresses.Add(Marshal.ReadIntPtr(vTable, i * IntPtr.Size)); // using IntPtr.Size allows us to support both 32 and 64-bit processes - - return vtblAddresses; + return vtblAddresses; + } } } diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index d12f389b0..137cafb27 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -12,215 +12,216 @@ using Serilog; using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType; -namespace Dalamud.Game.Internal; - -/// -/// This class implements in-game Dalamud options in the in-game System menu. -/// -internal sealed unsafe partial class DalamudAtkTweaks +namespace Dalamud.Game.Internal { - private readonly AtkValueChangeType atkValueChangeType; - private readonly AtkValueSetString atkValueSetString; - private readonly Hook hookAgentHudOpenSystemMenu; + /// + /// This class implements in-game Dalamud options in the in-game System menu. + /// + internal sealed unsafe partial class DalamudAtkTweaks + { + private readonly AtkValueChangeType atkValueChangeType; + private readonly AtkValueSetString atkValueSetString; + private readonly Hook hookAgentHudOpenSystemMenu; - // TODO: Make this into events in Framework.Gui - private readonly Hook hookUiModuleRequestMainCommand; + // TODO: Make this into events in Framework.Gui + private readonly Hook hookUiModuleRequestMainCommand; - private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; + private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; + + /// + /// Initializes a new instance of the class. + /// + public DalamudAtkTweaks() + { + var sigScanner = Service.Get(); + + var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); + + this.hookAgentHudOpenSystemMenu = new Hook(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); + + var atkValueChangeTypeAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??"); + this.atkValueChangeType = Marshal.GetDelegateForFunctionPointer(atkValueChangeTypeAddress); + + var atkValueSetStringAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED"); + this.atkValueSetString = Marshal.GetDelegateForFunctionPointer(atkValueSetStringAddress); + + var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??"); + this.hookUiModuleRequestMainCommand = new Hook(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour); + + var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2 "); + this.hookAtkUnitBaseReceiveGlobalEvent = new Hook(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour); + } + + private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); + + private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type); + + private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes); + + private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId); + + private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5); + + /// + /// Enables the . + /// + public void Enable() + { + this.hookAgentHudOpenSystemMenu.Enable(); + this.hookUiModuleRequestMainCommand.Enable(); + this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); + } + + private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg) + { + // Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", Marshal.PtrToStringAnsi(new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus); + + // "SendHotkey" + // 3 == Close + if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && Service.Get().IsFocusManagementEnabled) + { + Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); + return IntPtr.Zero; + } + + return this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, cmd, a3, a4, arg); + } + + private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize) + { + if (WindowSystem.HasAnyWindowSystemFocus && Service.Get().IsFocusManagementEnabled) + { + Log.Verbose($"Cancelling OpenSystemMenu due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); + return; + } + + var configuration = Service.Get(); + + if (!configuration.DoButtonsSystemMenu) + { + this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); + return; + } + + // the max size (hardcoded) is 0xE/15, but the system menu currently uses 0xC/12 + // this is a just in case that doesnt really matter + // see if we can add 2 entries + if (menuSize >= 0xD) + { + this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); + return; + } + + // atkValueArgs is actually an array of AtkValues used as args. all their UI code works like this. + // in this case, menu size is stored in atkValueArgs[4], and the next 15 slots are the MainCommand + // the 15 slots after that, if they exist, are the entry names, but they are otherwise pulled from MainCommand EXD + // reference the original function for more details :) + + // step 1) move all the current menu items down so we can put Dalamud at the top like it deserves + this.atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int + this.atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int); + + for (var i = menuSize + 2; i > 1; i--) + { + var curEntry = &atkValueArgs[i + 5 - 2]; + var nextEntry = &atkValueArgs[i + 5]; + + nextEntry->Int = curEntry->Int; + } + + // step 2) set our new entries to dummy commands + var firstEntry = &atkValueArgs[5]; + firstEntry->Int = 69420; + var secondEntry = &atkValueArgs[6]; + secondEntry->Int = 69421; + + // step 3) create strings for them + // since the game first checks for strings in the AtkValue argument before pulling them from the exd, if we create strings we dont have to worry + // about hooking the exd reader, thank god + var firstStringEntry = &atkValueArgs[5 + 15]; + this.atkValueChangeType(firstStringEntry, ValueType.String); + var secondStringEntry = &atkValueArgs[6 + 15]; + this.atkValueChangeType(secondStringEntry, ValueType.String); + + var strPlugins = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuPlugins", "Dalamud Plugins")); + var strSettings = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuSettings", "Dalamud Settings")); + + // do this the most terrible way possible since im lazy + var bytes = stackalloc byte[strPlugins.Length + 1]; + Marshal.Copy(strPlugins, 0, new IntPtr(bytes), strPlugins.Length); + bytes[strPlugins.Length] = 0x0; + + this.atkValueSetString(firstStringEntry, bytes); // this allocs the string properly using the game's allocators and copies it, so we dont have to worry about memory fuckups + + var bytes2 = stackalloc byte[strSettings.Length + 1]; + Marshal.Copy(strSettings, 0, new IntPtr(bytes2), strSettings.Length); + bytes2[strSettings.Length] = 0x0; + + this.atkValueSetString(secondStringEntry, bytes2); + + // open menu with new size + var sizeEntry = &atkValueArgs[4]; + sizeEntry->UInt = menuSize + 2; + + this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2); + } + + private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId) + { + var dalamudInterface = Service.Get(); + + switch (commandId) + { + case 69420: + dalamudInterface.TogglePluginInstallerWindow(); + break; + case 69421: + dalamudInterface.ToggleSettingsWindow(); + break; + default: + this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId); + break; + } + } + } /// - /// Initializes a new instance of the class. + /// Implements IDisposable. /// - public DalamudAtkTweaks() + internal sealed partial class DalamudAtkTweaks : IDisposable { - var sigScanner = Service.Get(); + private bool disposed = false; - var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 32 C0 4C 8B AC 24 ?? ?? ?? ?? 48 8B 8D ?? ?? ?? ??"); + /// + /// Finalizes an instance of the class. + /// + ~DalamudAtkTweaks() => this.Dispose(false); - this.hookAgentHudOpenSystemMenu = new Hook(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour); - - var atkValueChangeTypeAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 45 84 F6 48 8D 4C 24 ??"); - this.atkValueChangeType = Marshal.GetDelegateForFunctionPointer(atkValueChangeTypeAddress); - - var atkValueSetStringAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 03 ED"); - this.atkValueSetString = Marshal.GetDelegateForFunctionPointer(atkValueSetStringAddress); - - var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??"); - this.hookUiModuleRequestMainCommand = new Hook(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour); - - var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 56 41 57 48 8B EC 48 83 EC 50 44 0F B7 F2 "); - this.hookAtkUnitBaseReceiveGlobalEvent = new Hook(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour); - } - - private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize); - - private delegate void AtkValueChangeType(AtkValue* thisPtr, ValueType type); - - private delegate void AtkValueSetString(AtkValue* thisPtr, byte* bytes); - - private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId); - - private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5); - - /// - /// Enables the . - /// - public void Enable() - { - this.hookAgentHudOpenSystemMenu.Enable(); - this.hookUiModuleRequestMainCommand.Enable(); - this.hookAtkUnitBaseReceiveGlobalEvent.Enable(); - } - - private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg) - { - // Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", Marshal.PtrToStringAnsi(new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus); - - // "SendHotkey" - // 3 == Close - if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && Service.Get().IsFocusManagementEnabled) + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() { - Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); - return IntPtr.Zero; + this.Dispose(true); + GC.SuppressFinalize(this); } - return this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, cmd, a3, a4, arg); - } - - private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize) - { - if (WindowSystem.HasAnyWindowSystemFocus && Service.Get().IsFocusManagementEnabled) + /// + /// Dispose of managed and unmanaged resources. + /// + private void Dispose(bool disposing) { - Log.Verbose($"Cancelling OpenSystemMenu due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}"); - return; - } + if (this.disposed) + return; - var configuration = Service.Get(); + if (disposing) + { + this.hookAgentHudOpenSystemMenu.Dispose(); + this.hookUiModuleRequestMainCommand.Dispose(); + this.hookAtkUnitBaseReceiveGlobalEvent.Dispose(); + } - if (!configuration.DoButtonsSystemMenu) - { - this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); - return; - } - - // the max size (hardcoded) is 0xE/15, but the system menu currently uses 0xC/12 - // this is a just in case that doesnt really matter - // see if we can add 2 entries - if (menuSize >= 0xD) - { - this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize); - return; - } - - // atkValueArgs is actually an array of AtkValues used as args. all their UI code works like this. - // in this case, menu size is stored in atkValueArgs[4], and the next 15 slots are the MainCommand - // the 15 slots after that, if they exist, are the entry names, but they are otherwise pulled from MainCommand EXD - // reference the original function for more details :) - - // step 1) move all the current menu items down so we can put Dalamud at the top like it deserves - this.atkValueChangeType(&atkValueArgs[menuSize + 5], ValueType.Int); // currently this value has no type, set it to int - this.atkValueChangeType(&atkValueArgs[menuSize + 5 + 1], ValueType.Int); - - for (var i = menuSize + 2; i > 1; i--) - { - var curEntry = &atkValueArgs[i + 5 - 2]; - var nextEntry = &atkValueArgs[i + 5]; - - nextEntry->Int = curEntry->Int; - } - - // step 2) set our new entries to dummy commands - var firstEntry = &atkValueArgs[5]; - firstEntry->Int = 69420; - var secondEntry = &atkValueArgs[6]; - secondEntry->Int = 69421; - - // step 3) create strings for them - // since the game first checks for strings in the AtkValue argument before pulling them from the exd, if we create strings we dont have to worry - // about hooking the exd reader, thank god - var firstStringEntry = &atkValueArgs[5 + 15]; - this.atkValueChangeType(firstStringEntry, ValueType.String); - var secondStringEntry = &atkValueArgs[6 + 15]; - this.atkValueChangeType(secondStringEntry, ValueType.String); - - var strPlugins = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuPlugins", "Dalamud Plugins")); - var strSettings = Encoding.UTF8.GetBytes(Loc.Localize("SystemMenuSettings", "Dalamud Settings")); - - // do this the most terrible way possible since im lazy - var bytes = stackalloc byte[strPlugins.Length + 1]; - Marshal.Copy(strPlugins, 0, new IntPtr(bytes), strPlugins.Length); - bytes[strPlugins.Length] = 0x0; - - this.atkValueSetString(firstStringEntry, bytes); // this allocs the string properly using the game's allocators and copies it, so we dont have to worry about memory fuckups - - var bytes2 = stackalloc byte[strSettings.Length + 1]; - Marshal.Copy(strSettings, 0, new IntPtr(bytes2), strSettings.Length); - bytes2[strSettings.Length] = 0x0; - - this.atkValueSetString(secondStringEntry, bytes2); - - // open menu with new size - var sizeEntry = &atkValueArgs[4]; - sizeEntry->UInt = menuSize + 2; - - this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2); - } - - private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId) - { - var dalamudInterface = Service.Get(); - - switch (commandId) - { - case 69420: - dalamudInterface.TogglePluginInstallerWindow(); - break; - case 69421: - dalamudInterface.ToggleSettingsWindow(); - break; - default: - this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId); - break; + this.disposed = true; } } } - -/// -/// Implements IDisposable. -/// -internal sealed partial class DalamudAtkTweaks : IDisposable -{ - private bool disposed = false; - - /// - /// Finalizes an instance of the class. - /// - ~DalamudAtkTweaks() => this.Dispose(false); - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - private void Dispose(bool disposing) - { - if (this.disposed) - return; - - if (disposing) - { - this.hookAgentHudOpenSystemMenu.Dispose(); - this.hookUiModuleRequestMainCommand.Dispose(); - this.hookAtkUnitBaseReceiveGlobalEvent.Dispose(); - } - - this.disposed = true; - } -} diff --git a/Dalamud/Game/Libc/LibcFunction.cs b/Dalamud/Game/Libc/LibcFunction.cs index f4617c7e6..3c2361277 100644 --- a/Dalamud/Game/Libc/LibcFunction.cs +++ b/Dalamud/Game/Libc/LibcFunction.cs @@ -5,74 +5,75 @@ using System.Text; using Dalamud.IoC; using Dalamud.IoC.Internal; -namespace Dalamud.Game.Libc; - -/// -/// This class handles creating cstrings utilizing native game methods. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class LibcFunction +namespace Dalamud.Game.Libc { - private readonly LibcFunctionAddressResolver address; - private readonly StdStringFromCStringDelegate stdStringCtorCString; - private readonly StdStringDeallocateDelegate stdStringDeallocate; - /// - /// Initializes a new instance of the class. + /// This class handles creating cstrings utilizing native game methods. /// - public LibcFunction() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class LibcFunction { - this.address = new LibcFunctionAddressResolver(); - this.address.Setup(); + private readonly LibcFunctionAddressResolver address; + private readonly StdStringFromCStringDelegate stdStringCtorCString; + private readonly StdStringDeallocateDelegate stdStringDeallocate; - this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer(this.address.StdStringFromCstring); - this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer(this.address.StdStringDeallocate); - } + /// + /// Initializes a new instance of the class. + /// + public LibcFunction() + { + this.address = new LibcFunctionAddressResolver(); + this.address.Setup(); - // TODO: prolly callconv is not okay in x86 - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)] byte[] content, IntPtr size); + this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer(this.address.StdStringFromCstring); + this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer(this.address.StdStringDeallocate); + } - // TODO: prolly callconv is not okay in x86 - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr StdStringDeallocateDelegate(IntPtr address); + // TODO: prolly callconv is not okay in x86 + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPArray)] byte[] content, IntPtr size); - /// - /// Create a new string from the given bytes. - /// - /// The bytes to convert. - /// An owned std string object. - public OwnedStdString NewString(byte[] content) - { - // While 0x70 bytes in the memory should be enough in DX11 version, - // I don't trust my analysis so we're just going to allocate almost two times more than that. - var pString = Marshal.AllocHGlobal(256); + // TODO: prolly callconv is not okay in x86 + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr StdStringDeallocateDelegate(IntPtr address); - // Initialize a string - var size = new IntPtr(content.Length); - var pReallocString = this.stdStringCtorCString(pString, content, size); + /// + /// Create a new string from the given bytes. + /// + /// The bytes to convert. + /// An owned std string object. + public OwnedStdString NewString(byte[] content) + { + // While 0x70 bytes in the memory should be enough in DX11 version, + // I don't trust my analysis so we're just going to allocate almost two times more than that. + var pString = Marshal.AllocHGlobal(256); - // Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString); + // Initialize a string + var size = new IntPtr(content.Length); + var pReallocString = this.stdStringCtorCString(pString, content, size); - return new OwnedStdString(pReallocString, this.DeallocateStdString); - } + // Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString); - /// - /// Create a new string form the given bytes. - /// - /// The bytes to convert. - /// A non-default encoding. - /// An owned std string object. - public OwnedStdString NewString(string content, Encoding encoding = null) - { - encoding ??= Encoding.UTF8; + return new OwnedStdString(pReallocString, this.DeallocateStdString); + } - return this.NewString(encoding.GetBytes(content)); - } + /// + /// Create a new string form the given bytes. + /// + /// The bytes to convert. + /// A non-default encoding. + /// An owned std string object. + public OwnedStdString NewString(string content, Encoding encoding = null) + { + encoding ??= Encoding.UTF8; - private void DeallocateStdString(IntPtr address) - { - this.stdStringDeallocate(address); + return this.NewString(encoding.GetBytes(content)); + } + + private void DeallocateStdString(IntPtr address) + { + this.stdStringDeallocate(address); + } } } diff --git a/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs b/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs index dcf687203..4d30a1e74 100644 --- a/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs +++ b/Dalamud/Game/Libc/LibcFunctionAddressResolver.cs @@ -2,29 +2,30 @@ using System; using Dalamud.Game.Internal; -namespace Dalamud.Game.Libc; - -/// -/// The address resolver for the class. -/// -public sealed class LibcFunctionAddressResolver : BaseAddressResolver +namespace Dalamud.Game.Libc { - private delegate IntPtr StringFromCString(); - /// - /// Gets the address of the native StdStringFromCstring method. + /// The address resolver for the class. /// - public IntPtr StdStringFromCstring { get; private set; } - - /// - /// Gets the address of the native StdStringDeallocate method. - /// - public IntPtr StdStringDeallocate { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) + public sealed class LibcFunctionAddressResolver : BaseAddressResolver { - this.StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8"); - this.StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3"); + private delegate IntPtr StringFromCString(); + + /// + /// Gets the address of the native StdStringFromCstring method. + /// + public IntPtr StdStringFromCstring { get; private set; } + + /// + /// Gets the address of the native StdStringDeallocate method. + /// + public IntPtr StdStringDeallocate { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) + { + this.StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8"); + this.StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3"); + } } } diff --git a/Dalamud/Game/Libc/OwnedStdString.cs b/Dalamud/Game/Libc/OwnedStdString.cs index db92257b3..1939e068e 100644 --- a/Dalamud/Game/Libc/OwnedStdString.cs +++ b/Dalamud/Game/Libc/OwnedStdString.cs @@ -1,100 +1,101 @@ using System; using System.Runtime.InteropServices; -namespace Dalamud.Game.Libc; - -/// -/// An address wrapper around the class. -/// -public sealed partial class OwnedStdString +namespace Dalamud.Game.Libc { - private readonly DeallocatorDelegate dealloc; - /// - /// Initializes a new instance of the class. - /// Construct a wrapper around std::string. + /// An address wrapper around the class. /// - /// - /// Violating any of these might cause an undefined hehaviour. - /// 1. This function takes the ownership of the address. - /// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address. - /// 3. std::string object pointed by address must be initialized before calling this function. - /// - /// The address of the owned std string. - /// A deallocator function. - internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc) + public sealed partial class OwnedStdString { - this.Address = address; - this.dealloc = dealloc; - } + private readonly DeallocatorDelegate dealloc; - /// - /// The delegate type that deallocates a std string. - /// - /// Address to deallocate. - internal delegate void DeallocatorDelegate(IntPtr address); - - /// - /// Gets the address of the std string. - /// - public IntPtr Address { get; private set; } - - /// - /// Read the wrapped StdString. - /// - /// The StdString. - public StdString Read() => StdString.ReadFromPointer(this.Address); -} - -/// -/// Implements IDisposable. -/// -public sealed partial class OwnedStdString : IDisposable -{ - private bool isDisposed; - - /// - /// Finalizes an instance of the class. - /// - ~OwnedStdString() => this.Dispose(false); - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - GC.SuppressFinalize(this); - this.Dispose(true); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - /// A value indicating whether this was called via Dispose or finalized. - public void Dispose(bool disposing) - { - if (this.isDisposed) - return; - - this.isDisposed = true; - - if (disposing) + /// + /// Initializes a new instance of the class. + /// Construct a wrapper around std::string. + /// + /// + /// Violating any of these might cause an undefined hehaviour. + /// 1. This function takes the ownership of the address. + /// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address. + /// 3. std::string object pointed by address must be initialized before calling this function. + /// + /// The address of the owned std string. + /// A deallocator function. + internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc) { + this.Address = address; + this.dealloc = dealloc; } - if (this.Address == IntPtr.Zero) + /// + /// The delegate type that deallocates a std string. + /// + /// Address to deallocate. + internal delegate void DeallocatorDelegate(IntPtr address); + + /// + /// Gets the address of the std string. + /// + public IntPtr Address { get; private set; } + + /// + /// Read the wrapped StdString. + /// + /// The StdString. + public StdString Read() => StdString.ReadFromPointer(this.Address); + } + + /// + /// Implements IDisposable. + /// + public sealed partial class OwnedStdString : IDisposable + { + private bool isDisposed; + + /// + /// Finalizes an instance of the class. + /// + ~OwnedStdString() => this.Dispose(false); + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() { - // Something got seriously fucked. - throw new AccessViolationException(); + GC.SuppressFinalize(this); + this.Dispose(true); } - // Deallocate inner string first - this.dealloc(this.Address); + /// + /// Dispose of managed and unmanaged resources. + /// + /// A value indicating whether this was called via Dispose or finalized. + public void Dispose(bool disposing) + { + if (this.isDisposed) + return; - // Free the heap - Marshal.FreeHGlobal(this.Address); + this.isDisposed = true; - // Better safe (running on a nullptr) than sorry. (running on a dangling pointer) - this.Address = IntPtr.Zero; + if (disposing) + { + } + + if (this.Address == IntPtr.Zero) + { + // Something got seriously fucked. + throw new AccessViolationException(); + } + + // Deallocate inner string first + this.dealloc(this.Address); + + // Free the heap + Marshal.FreeHGlobal(this.Address); + + // Better safe (running on a nullptr) than sorry. (running on a dangling pointer) + this.Address = IntPtr.Zero; + } } } diff --git a/Dalamud/Game/Libc/StdString.cs b/Dalamud/Game/Libc/StdString.cs index 816219a82..4d4478687 100644 --- a/Dalamud/Game/Libc/StdString.cs +++ b/Dalamud/Game/Libc/StdString.cs @@ -2,67 +2,68 @@ using System; using System.Runtime.InteropServices; using System.Text; -namespace Dalamud.Game.Libc; - -/// -/// Interation with std::string. -/// -public class StdString +namespace Dalamud.Game.Libc { /// - /// Initializes a new instance of the class. + /// Interation with std::string. /// - private StdString() + public class StdString { - } - - /// - /// Gets the value of the cstring. - /// - public string Value { get; private set; } - - /// - /// Gets or sets the raw byte representation of the cstring. - /// - public byte[] RawData { get; set; } - - /// - /// Marshal a null terminated cstring from memory to a UTF-8 encoded string. - /// - /// Address of the cstring. - /// A UTF-8 encoded string. - public static StdString ReadFromPointer(IntPtr cstring) - { - unsafe + /// + /// Initializes a new instance of the class. + /// + private StdString() { - if (cstring == IntPtr.Zero) + } + + /// + /// Gets the value of the cstring. + /// + public string Value { get; private set; } + + /// + /// Gets or sets the raw byte representation of the cstring. + /// + public byte[] RawData { get; set; } + + /// + /// Marshal a null terminated cstring from memory to a UTF-8 encoded string. + /// + /// Address of the cstring. + /// A UTF-8 encoded string. + public static StdString ReadFromPointer(IntPtr cstring) + { + unsafe { - throw new ArgumentNullException(nameof(cstring)); + if (cstring == IntPtr.Zero) + { + throw new ArgumentNullException(nameof(cstring)); + } + + var innerAddress = Marshal.ReadIntPtr(cstring); + if (innerAddress == IntPtr.Zero) + { + throw new NullReferenceException("Inner reference to the cstring is null."); + } + + // Count the number of chars. String is assumed to be zero-terminated. + + var count = 0; + while (Marshal.ReadByte(innerAddress, count) != 0) + { + count += 1; + } + + // raw copy, as UTF8 string conversion is lossy + var rawData = new byte[count]; + Marshal.Copy(innerAddress, rawData, 0, count); + + return new StdString + { + RawData = rawData, + Value = Encoding.UTF8.GetString(rawData), + }; } - - var innerAddress = Marshal.ReadIntPtr(cstring); - if (innerAddress == IntPtr.Zero) - { - throw new NullReferenceException("Inner reference to the cstring is null."); - } - - // Count the number of chars. String is assumed to be zero-terminated. - - var count = 0; - while (Marshal.ReadByte(innerAddress, count) != 0) - { - count += 1; - } - - // raw copy, as UTF8 string conversion is lossy - var rawData = new byte[count]; - Marshal.Copy(innerAddress, rawData, 0, count); - - return new StdString - { - RawData = rawData, - Value = Encoding.UTF8.GetString(rawData), - }; } } } diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs index 6c717c474..e4feac4d0 100644 --- a/Dalamud/Game/Network/GameNetwork.cs +++ b/Dalamud/Game/Network/GameNetwork.cs @@ -7,178 +7,179 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game.Network; - -/// -/// This class handles interacting with game network events. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class GameNetwork : IDisposable +namespace Dalamud.Game.Network { - private readonly GameNetworkAddressResolver address; - private readonly Hook processZonePacketDownHook; - private readonly Hook processZonePacketUpHook; - private readonly Queue zoneInjectQueue = new(); - - private IntPtr baseAddress; - /// - /// Initializes a new instance of the class. + /// This class handles interacting with game network events. /// - internal GameNetwork() + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class GameNetwork : IDisposable { - this.address = new GameNetworkAddressResolver(); - this.address.Setup(); + private readonly GameNetworkAddressResolver address; + private readonly Hook processZonePacketDownHook; + private readonly Hook processZonePacketUpHook; + private readonly Queue zoneInjectQueue = new(); - Log.Verbose("===== G A M E N E T W O R K ====="); - Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}"); - Log.Verbose($"ProcessZonePacketUp address 0x{this.address.ProcessZonePacketUp.ToInt64():X}"); + private IntPtr baseAddress; - this.processZonePacketDownHook = new Hook(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); - this.processZonePacketUpHook = new Hook(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour); - } - - /// - /// The delegate type of a network message event. - /// - /// The pointer to the raw data. - /// The operation ID code. - /// The source actor ID. - /// The taret actor ID. - /// The direction of the packed. - public delegate void OnNetworkMessageDelegate(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4); - - /// - /// Event that is called when a network message is sent/received. - /// - public event OnNetworkMessageDelegate NetworkMessage; - - /// - /// Enable this module. - /// - public void Enable() - { - this.processZonePacketDownHook.Enable(); - this.processZonePacketUpHook.Enable(); - } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - this.processZonePacketDownHook.Dispose(); - this.processZonePacketUpHook.Dispose(); - } - - /// - /// Process a chat queue. - /// - internal void UpdateQueue() - { - while (this.zoneInjectQueue.Count > 0) + /// + /// Initializes a new instance of the class. + /// + internal GameNetwork() { - var packetData = this.zoneInjectQueue.Dequeue(); + this.address = new GameNetworkAddressResolver(); + this.address.Setup(); - var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length); - Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length); + Log.Verbose("===== G A M E N E T W O R K ====="); + Log.Verbose($"ProcessZonePacketDown address 0x{this.address.ProcessZonePacketDown.ToInt64():X}"); + Log.Verbose($"ProcessZonePacketUp address 0x{this.address.ProcessZonePacketUp.ToInt64():X}"); - if (this.baseAddress != IntPtr.Zero) + this.processZonePacketDownHook = new Hook(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour); + this.processZonePacketUpHook = new Hook(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour); + } + + /// + /// The delegate type of a network message event. + /// + /// The pointer to the raw data. + /// The operation ID code. + /// The source actor ID. + /// The taret actor ID. + /// The direction of the packed. + public delegate void OnNetworkMessageDelegate(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4); + + /// + /// Event that is called when a network message is sent/received. + /// + public event OnNetworkMessageDelegate NetworkMessage; + + /// + /// Enable this module. + /// + public void Enable() + { + this.processZonePacketDownHook.Enable(); + this.processZonePacketUpHook.Enable(); + } + + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + this.processZonePacketDownHook.Dispose(); + this.processZonePacketUpHook.Dispose(); + } + + /// + /// Process a chat queue. + /// + internal void UpdateQueue() + { + while (this.zoneInjectQueue.Count > 0) { - this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData); + var packetData = this.zoneInjectQueue.Dequeue(); + + var unmanagedPacketData = Marshal.AllocHGlobal(packetData.Length); + Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length); + + if (this.baseAddress != IntPtr.Zero) + { + this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData); + } + + Marshal.FreeHGlobal(unmanagedPacketData); } - - Marshal.FreeHGlobal(unmanagedPacketData); } - } - private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) - { - this.baseAddress = a; - - // Go back 0x10 to get back to the start of the packet header - dataPtr -= 0x10; - - try + private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) { - // Call events - this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown); + this.baseAddress = a; + + // Go back 0x10 to get back to the start of the packet header + dataPtr -= 0x10; - this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); - } - catch (Exception ex) - { - string header; try { - var data = new byte[32]; - Marshal.Copy(dataPtr, data, 0, 32); - header = BitConverter.ToString(data); + // Call events + this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr, 0x12), 0, targetId, NetworkMessageDirection.ZoneDown); + + this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); } - catch (Exception) + catch (Exception ex) { - header = "failed"; + string header; + try + { + var data = new byte[32]; + Marshal.Copy(dataPtr, data, 0, 32); + header = BitConverter.ToString(data); + } + catch (Exception) + { + header = "failed"; + } + + Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header); + + this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); } - - Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header); - - this.processZonePacketDownHook.Original(a, targetId, dataPtr + 0x10); } - } - private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4) - { - try + private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4) { - // Call events - // TODO: Implement actor IDs - this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp); - } - catch (Exception ex) - { - string header; try { - var data = new byte[32]; - Marshal.Copy(dataPtr, data, 0, 32); - header = BitConverter.ToString(data); + // Call events + // TODO: Implement actor IDs + this.NetworkMessage?.Invoke(dataPtr + 0x20, (ushort)Marshal.ReadInt16(dataPtr), 0x0, 0x0, NetworkMessageDirection.ZoneUp); } - catch (Exception) + catch (Exception ex) { - header = "failed"; + string header; + try + { + var data = new byte[32]; + Marshal.Copy(dataPtr, data, 0, 32); + header = BitConverter.ToString(data); + } + catch (Exception) + { + header = "failed"; + } + + Log.Error(ex, "Exception on ProcessZonePacketUp hook. Header: " + header); } - Log.Error(ex, "Exception on ProcessZonePacketUp hook. Header: " + header); + return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4); } - return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4); + // private void InjectZoneProtoPacket(byte[] data) + // { + // this.zoneInjectQueue.Enqueue(data); + // } + + // private void InjectActorControl(short cat, int param1) + // { + // var packetData = new byte[] + // { + // 0x14, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00, + // 0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00, + // }; + // + // BitConverter.GetBytes((short)cat).CopyTo(packetData, 0x10); + // + // BitConverter.GetBytes((uint)param1).CopyTo(packetData, 0x14); + // + // this.InjectZoneProtoPacket(packetData); + // } } - - // private void InjectZoneProtoPacket(byte[] data) - // { - // this.zoneInjectQueue.Enqueue(data); - // } - - // private void InjectActorControl(short cat, int param1) - // { - // var packetData = new byte[] - // { - // 0x14, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x7C, 0xC5, 0x5D, 0x00, 0x00, 0x00, 0x00, - // 0x05, 0x00, 0x48, 0xB2, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // 0x00, 0x00, 0x00, 0x00, 0x43, 0x7F, 0x00, 0x00, - // }; - // - // BitConverter.GetBytes((short)cat).CopyTo(packetData, 0x10); - // - // BitConverter.GetBytes((uint)param1).CopyTo(packetData, 0x14); - // - // this.InjectZoneProtoPacket(packetData); - // } } diff --git a/Dalamud/Game/Network/GameNetworkAddressResolver.cs b/Dalamud/Game/Network/GameNetworkAddressResolver.cs index 5be85bd35..130986197 100644 --- a/Dalamud/Game/Network/GameNetworkAddressResolver.cs +++ b/Dalamud/Game/Network/GameNetworkAddressResolver.cs @@ -1,28 +1,31 @@ using System; -namespace Dalamud.Game.Network; +using Dalamud.Game.Internal; -/// -/// The address resolver for the class. -/// -public sealed class GameNetworkAddressResolver : BaseAddressResolver +namespace Dalamud.Game.Network { /// - /// Gets the address of the ProcessZonePacketDown method. + /// The address resolver for the class. /// - public IntPtr ProcessZonePacketDown { get; private set; } - - /// - /// Gets the address of the ProcessZonePacketUp method. - /// - public IntPtr ProcessZonePacketUp { get; private set; } - - /// - protected override void Setup64Bit(SigScanner sig) + public sealed class GameNetworkAddressResolver : BaseAddressResolver { - // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05"); - // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05"); - this.ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2"); - this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??"); + /// + /// Gets the address of the ProcessZonePacketDown method. + /// + public IntPtr ProcessZonePacketDown { get; private set; } + + /// + /// Gets the address of the ProcessZonePacketUp method. + /// + public IntPtr ProcessZonePacketUp { get; private set; } + + /// + protected override void Setup64Bit(SigScanner sig) + { + // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05"); + // ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05"); + this.ProcessZonePacketDown = sig.ScanText("48 89 5C 24 ?? 56 48 83 EC 50 8B F2"); + this.ProcessZonePacketUp = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??"); + } } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs index dadfef604..6ec2a997b 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/IMarketBoardUploader.cs @@ -2,31 +2,32 @@ using System.Threading.Tasks; using Dalamud.Game.Network.Structures; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders; - -/// -/// An interface binding for the Universalis uploader. -/// -internal interface IMarketBoardUploader +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders { /// - /// Upload data about an item. + /// An interface binding for the Universalis uploader. /// - /// The item request data being uploaded. - /// An async task. - Task Upload(MarketBoardItemRequest item); + internal interface IMarketBoardUploader + { + /// + /// Upload data about an item. + /// + /// The item request data being uploaded. + /// An async task. + Task Upload(MarketBoardItemRequest item); - /// - /// Upload tax rate data. - /// - /// The tax rate data being uploaded. - /// An async task. - Task UploadTax(MarketTaxRates taxRates); + /// + /// Upload tax rate data. + /// + /// The tax rate data being uploaded. + /// An async task. + Task UploadTax(MarketTaxRates taxRates); - /// - /// Upload information about a purchase this client has made. - /// - /// The purchase handler data associated with the sale. - /// An async task. - Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler); + /// + /// Upload information about a purchase this client has made. + /// + /// The purchase handler data associated with the sale. + /// An async task. + Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler); + } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs index 18ed5c4b5..bdd07a8af 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/MarketBoardItemRequest.cs @@ -4,63 +4,64 @@ using System.IO; using Dalamud.Game.Network.Structures; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders; - -/// -/// This represents a submission to a marketboard aggregation website. -/// -internal class MarketBoardItemRequest +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders { - private MarketBoardItemRequest() + /// + /// This represents a submission to a marketboard aggregation website. + /// + internal class MarketBoardItemRequest { - } + private MarketBoardItemRequest() + { + } - /// - /// Gets the catalog ID. - /// - public uint CatalogId { get; private set; } + /// + /// Gets the catalog ID. + /// + public uint CatalogId { get; private set; } - /// - /// Gets the amount to arrive. - /// - public byte AmountToArrive { get; private set; } + /// + /// Gets the amount to arrive. + /// + public byte AmountToArrive { get; private set; } - /// - /// Gets the offered item listings. - /// - public List Listings { get; } = new(); + /// + /// Gets the offered item listings. + /// + public List Listings { get; } = new(); - /// - /// Gets the historical item listings. - /// - public List History { get; } = new(); + /// + /// Gets the historical item listings. + /// + public List History { get; } = new(); - /// - /// Gets or sets the listing request ID. - /// - public int ListingsRequestId { get; set; } = -1; + /// + /// Gets or sets the listing request ID. + /// + public int ListingsRequestId { get; set; } = -1; - /// - /// Gets a value indicating whether the upload is complete. - /// - public bool IsDone => this.Listings.Count == this.AmountToArrive && this.History.Count != 0; + /// + /// Gets a value indicating whether the upload is complete. + /// + public bool IsDone => this.Listings.Count == this.AmountToArrive && this.History.Count != 0; - /// - /// Read a packet off the wire. - /// - /// Packet data. - /// An object representing the data read. - public static unsafe MarketBoardItemRequest Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); + /// + /// Read a packet off the wire. + /// + /// Packet data. + /// An object representing the data read. + public static unsafe MarketBoardItemRequest Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); - var output = new MarketBoardItemRequest(); + var output = new MarketBoardItemRequest(); - output.CatalogId = reader.ReadUInt32(); - stream.Position += 0x7; - output.AmountToArrive = reader.ReadByte(); + output.CatalogId = reader.ReadUInt32(); + stream.Position += 0x7; + output.AmountToArrive = reader.ReadByte(); - return output; + return output; + } } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryEntry.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryEntry.cs index 3b95349a9..37467377c 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryEntry.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryEntry.cs @@ -1,57 +1,58 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; - -/// -/// A Universalis API structure. -/// -internal class UniversalisHistoryEntry +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types { /// - /// Gets or sets a value indicating whether the item is HQ or not. + /// A Universalis API structure. /// - [JsonProperty("hq")] - public bool Hq { get; set; } + internal class UniversalisHistoryEntry + { + /// + /// Gets or sets a value indicating whether the item is HQ or not. + /// + [JsonProperty("hq")] + public bool Hq { get; set; } - /// - /// Gets or sets the item price per unit. - /// - [JsonProperty("pricePerUnit")] - public uint PricePerUnit { get; set; } + /// + /// Gets or sets the item price per unit. + /// + [JsonProperty("pricePerUnit")] + public uint PricePerUnit { get; set; } - /// - /// Gets or sets the quantity of items available. - /// - [JsonProperty("quantity")] - public uint Quantity { get; set; } + /// + /// Gets or sets the quantity of items available. + /// + [JsonProperty("quantity")] + public uint Quantity { get; set; } - /// - /// Gets or sets the name of the buyer. - /// - [JsonProperty("buyerName")] - public string BuyerName { get; set; } + /// + /// Gets or sets the name of the buyer. + /// + [JsonProperty("buyerName")] + public string BuyerName { get; set; } - /// - /// Gets or sets a value indicating whether this item was on a mannequin. - /// - [JsonProperty("onMannequin")] - public bool OnMannequin { get; set; } + /// + /// Gets or sets a value indicating whether this item was on a mannequin. + /// + [JsonProperty("onMannequin")] + public bool OnMannequin { get; set; } - /// - /// Gets or sets the seller ID. - /// - [JsonProperty("sellerID")] - public string SellerId { get; set; } + /// + /// Gets or sets the seller ID. + /// + [JsonProperty("sellerID")] + public string SellerId { get; set; } - /// - /// Gets or sets the buyer ID. - /// - [JsonProperty("buyerID")] - public string BuyerId { get; set; } + /// + /// Gets or sets the buyer ID. + /// + [JsonProperty("buyerID")] + public string BuyerId { get; set; } - /// - /// Gets or sets the timestamp of the transaction. - /// - [JsonProperty("timestamp")] - public long Timestamp { get; set; } + /// + /// Gets or sets the timestamp of the transaction. + /// + [JsonProperty("timestamp")] + public long Timestamp { get; set; } + } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryUploadRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryUploadRequest.cs index ba5e4ad47..afa8bf417 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryUploadRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisHistoryUploadRequest.cs @@ -2,34 +2,35 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; - -/// -/// A Universalis API structure. -/// -internal class UniversalisHistoryUploadRequest +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types { /// - /// Gets or sets the world ID. + /// A Universalis API structure. /// - [JsonProperty("worldID")] - public uint WorldId { get; set; } + internal class UniversalisHistoryUploadRequest + { + /// + /// Gets or sets the world ID. + /// + [JsonProperty("worldID")] + public uint WorldId { get; set; } - /// - /// Gets or sets the item ID. - /// - [JsonProperty("itemID")] - public uint ItemId { get; set; } + /// + /// Gets or sets the item ID. + /// + [JsonProperty("itemID")] + public uint ItemId { get; set; } - /// - /// Gets or sets the list of available entries. - /// - [JsonProperty("entries")] - public List Entries { get; set; } + /// + /// Gets or sets the list of available entries. + /// + [JsonProperty("entries")] + public List Entries { get; set; } - /// - /// Gets or sets the uploader ID. - /// - [JsonProperty("uploaderID")] - public string UploaderId { get; set; } + /// + /// Gets or sets the uploader ID. + /// + [JsonProperty("uploaderID")] + public string UploaderId { get; set; } + } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingDeleteRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingDeleteRequest.cs index ed8a82bfa..ea96f934a 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingDeleteRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingDeleteRequest.cs @@ -1,39 +1,40 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; - -/// -/// Request payload for market board purchases. -/// -internal class UniversalisItemListingDeleteRequest +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types { /// - /// Gets or sets the object ID of the retainer associated with the sale. + /// Request payload for market board purchases. /// - [JsonProperty("retainerID")] - public string RetainerId { get; set; } + internal class UniversalisItemListingDeleteRequest + { + /// + /// Gets or sets the object ID of the retainer associated with the sale. + /// + [JsonProperty("retainerID")] + public string RetainerId { get; set; } - /// - /// Gets or sets the object ID of the item listing. - /// - [JsonProperty("listingID")] - public string ListingId { get; set; } + /// + /// Gets or sets the object ID of the item listing. + /// + [JsonProperty("listingID")] + public string ListingId { get; set; } - /// - /// Gets or sets the quantity of the item that was purchased. - /// - [JsonProperty("quantity")] - public uint Quantity { get; set; } + /// + /// Gets or sets the quantity of the item that was purchased. + /// + [JsonProperty("quantity")] + public uint Quantity { get; set; } - /// - /// Gets or sets the unit price of the item. - /// - [JsonProperty("pricePerUnit")] - public uint PricePerUnit { get; set; } + /// + /// Gets or sets the unit price of the item. + /// + [JsonProperty("pricePerUnit")] + public uint PricePerUnit { get; set; } - /// - /// Gets or sets the uploader ID. - /// - [JsonProperty("uploaderID")] - public string UploaderId { get; set; } + /// + /// Gets or sets the uploader ID. + /// + [JsonProperty("uploaderID")] + public string UploaderId { get; set; } + } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsEntry.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsEntry.cs index c4a51b49d..42ff15668 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsEntry.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsEntry.cs @@ -2,94 +2,95 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; - -/// -/// A Universalis API structure. -/// -internal class UniversalisItemListingsEntry +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types { /// - /// Gets or sets the listing ID. + /// A Universalis API structure. /// - [JsonProperty("listingID")] - public string ListingId { get; set; } + internal class UniversalisItemListingsEntry + { + /// + /// Gets or sets the listing ID. + /// + [JsonProperty("listingID")] + public string ListingId { get; set; } - /// - /// Gets or sets a value indicating whether the item is HQ. - /// - [JsonProperty("hq")] - public bool Hq { get; set; } + /// + /// Gets or sets a value indicating whether the item is HQ. + /// + [JsonProperty("hq")] + public bool Hq { get; set; } - /// - /// Gets or sets the item price per unit. - /// - [JsonProperty("pricePerUnit")] - public uint PricePerUnit { get; set; } + /// + /// Gets or sets the item price per unit. + /// + [JsonProperty("pricePerUnit")] + public uint PricePerUnit { get; set; } - /// - /// Gets or sets the item quantity. - /// - [JsonProperty("quantity")] - public uint Quantity { get; set; } + /// + /// Gets or sets the item quantity. + /// + [JsonProperty("quantity")] + public uint Quantity { get; set; } - /// - /// Gets or sets the name of the retainer selling the item. - /// - [JsonProperty("retainerName")] - public string RetainerName { get; set; } + /// + /// Gets or sets the name of the retainer selling the item. + /// + [JsonProperty("retainerName")] + public string RetainerName { get; set; } - /// - /// Gets or sets the ID of the retainer selling the item. - /// - [JsonProperty("retainerID")] - public string RetainerId { get; set; } + /// + /// Gets or sets the ID of the retainer selling the item. + /// + [JsonProperty("retainerID")] + public string RetainerId { get; set; } - /// - /// Gets or sets the name of the user who created the entry. - /// - [JsonProperty("creatorName")] - public string CreatorName { get; set; } + /// + /// Gets or sets the name of the user who created the entry. + /// + [JsonProperty("creatorName")] + public string CreatorName { get; set; } - /// - /// Gets or sets a value indicating whether the item is on a mannequin. - /// - [JsonProperty("onMannequin")] - public bool OnMannequin { get; set; } + /// + /// Gets or sets a value indicating whether the item is on a mannequin. + /// + [JsonProperty("onMannequin")] + public bool OnMannequin { get; set; } - /// - /// Gets or sets the seller ID. - /// - [JsonProperty("sellerID")] - public string SellerId { get; set; } + /// + /// Gets or sets the seller ID. + /// + [JsonProperty("sellerID")] + public string SellerId { get; set; } - /// - /// Gets or sets the ID of the user who created the entry. - /// - [JsonProperty("creatorID")] - public string CreatorId { get; set; } + /// + /// Gets or sets the ID of the user who created the entry. + /// + [JsonProperty("creatorID")] + public string CreatorId { get; set; } - /// - /// Gets or sets the ID of the dye on the item. - /// - [JsonProperty("stainID")] - public int StainId { get; set; } + /// + /// Gets or sets the ID of the dye on the item. + /// + [JsonProperty("stainID")] + public int StainId { get; set; } - /// - /// Gets or sets the city where the selling retainer resides. - /// - [JsonProperty("retainerCity")] - public int RetainerCity { get; set; } + /// + /// Gets or sets the city where the selling retainer resides. + /// + [JsonProperty("retainerCity")] + public int RetainerCity { get; set; } - /// - /// Gets or sets the last time the entry was reviewed. - /// - [JsonProperty("lastReviewTime")] - public long LastReviewTime { get; set; } + /// + /// Gets or sets the last time the entry was reviewed. + /// + [JsonProperty("lastReviewTime")] + public long LastReviewTime { get; set; } - /// - /// Gets or sets the materia attached to the item. - /// - [JsonProperty("materia")] - public List Materia { get; set; } + /// + /// Gets or sets the materia attached to the item. + /// + [JsonProperty("materia")] + public List Materia { get; set; } + } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsUploadRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsUploadRequest.cs index e70b43588..c055b30d9 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsUploadRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemListingsUploadRequest.cs @@ -2,34 +2,35 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; - -/// -/// A Universalis API structure. -/// -internal class UniversalisItemListingsUploadRequest +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types { /// - /// Gets or sets the world ID. + /// A Universalis API structure. /// - [JsonProperty("worldID")] - public uint WorldId { get; set; } + internal class UniversalisItemListingsUploadRequest + { + /// + /// Gets or sets the world ID. + /// + [JsonProperty("worldID")] + public uint WorldId { get; set; } - /// - /// Gets or sets the item ID. - /// - [JsonProperty("itemID")] - public uint ItemId { get; set; } + /// + /// Gets or sets the item ID. + /// + [JsonProperty("itemID")] + public uint ItemId { get; set; } - /// - /// Gets or sets the list of available items. - /// - [JsonProperty("listings")] - public List Listings { get; set; } + /// + /// Gets or sets the list of available items. + /// + [JsonProperty("listings")] + public List Listings { get; set; } - /// - /// Gets or sets the uploader ID. - /// - [JsonProperty("uploaderID")] - public string UploaderId { get; set; } + /// + /// Gets or sets the uploader ID. + /// + [JsonProperty("uploaderID")] + public string UploaderId { get; set; } + } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemMateria.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemMateria.cs index 3480470eb..8c9c99bb5 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemMateria.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisItemMateria.cs @@ -1,21 +1,22 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; - -/// -/// A Universalis API structure. -/// -internal class UniversalisItemMateria +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types { /// - /// Gets or sets the item slot ID. + /// A Universalis API structure. /// - [JsonProperty("slotID")] - public int SlotId { get; set; } + internal class UniversalisItemMateria + { + /// + /// Gets or sets the item slot ID. + /// + [JsonProperty("slotID")] + public int SlotId { get; set; } - /// - /// Gets or sets the materia ID. - /// - [JsonProperty("materiaID")] - public int MateriaId { get; set; } + /// + /// Gets or sets the materia ID. + /// + [JsonProperty("materiaID")] + public int MateriaId { get; set; } + } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs index ccb790f72..0a93f788b 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxData.cs @@ -1,45 +1,46 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; - -/// -/// A Universalis API structure. -/// -internal class UniversalisTaxData +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types { /// - /// Gets or sets Limsa Lominsa's current tax rate. + /// A Universalis API structure. /// - [JsonProperty("limsaLominsa")] - public uint LimsaLominsa { get; set; } + internal class UniversalisTaxData + { + /// + /// Gets or sets Limsa Lominsa's current tax rate. + /// + [JsonProperty("limsaLominsa")] + public uint LimsaLominsa { get; set; } - /// - /// Gets or sets Gridania's current tax rate. - /// - [JsonProperty("gridania")] - public uint Gridania { get; set; } + /// + /// Gets or sets Gridania's current tax rate. + /// + [JsonProperty("gridania")] + public uint Gridania { get; set; } - /// - /// Gets or sets Ul'dah's current tax rate. - /// - [JsonProperty("uldah")] - public uint Uldah { get; set; } + /// + /// Gets or sets Ul'dah's current tax rate. + /// + [JsonProperty("uldah")] + public uint Uldah { get; set; } - /// - /// Gets or sets Ishgard's current tax rate. - /// - [JsonProperty("ishgard")] - public uint Ishgard { get; set; } + /// + /// Gets or sets Ishgard's current tax rate. + /// + [JsonProperty("ishgard")] + public uint Ishgard { get; set; } - /// - /// Gets or sets Kugane's current tax rate. - /// - [JsonProperty("kugane")] - public uint Kugane { get; set; } + /// + /// Gets or sets Kugane's current tax rate. + /// + [JsonProperty("kugane")] + public uint Kugane { get; set; } - /// - /// Gets or sets The Crystarium's current tax rate. - /// - [JsonProperty("crystarium")] - public uint Crystarium { get; set; } + /// + /// Gets or sets The Crystarium's current tax rate. + /// + [JsonProperty("crystarium")] + public uint Crystarium { get; set; } + } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxUploadRequest.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxUploadRequest.cs index d7abb5f89..a11ac0e89 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxUploadRequest.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/Types/UniversalisTaxUploadRequest.cs @@ -1,27 +1,28 @@ using Newtonsoft.Json; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types; - -/// -/// A Universalis API structure. -/// -internal class UniversalisTaxUploadRequest +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types { /// - /// Gets or sets the uploader's ID. + /// A Universalis API structure. /// - [JsonProperty("uploaderID")] - public string UploaderId { get; set; } + internal class UniversalisTaxUploadRequest + { + /// + /// Gets or sets the uploader's ID. + /// + [JsonProperty("uploaderID")] + public string UploaderId { get; set; } - /// - /// Gets or sets the world to retrieve data from. - /// - [JsonProperty("worldID")] - public uint WorldId { get; set; } + /// + /// Gets or sets the world to retrieve data from. + /// + [JsonProperty("worldID")] + public uint WorldId { get; set; } - /// - /// Gets or sets tax data for each city's market. - /// - [JsonProperty("marketTaxRates")] - public UniversalisTaxData TaxData { get; set; } + /// + /// Gets or sets tax data for each city's market. + /// + [JsonProperty("marketTaxRates")] + public UniversalisTaxData TaxData { get; set; } + } } diff --git a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index be1b5cc40..998502739 100644 --- a/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/Internal/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -10,181 +10,182 @@ using Dalamud.Utility; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis; - -/// -/// This class represents an uploader for contributing data to Universalis. -/// -internal class UniversalisMarketBoardUploader : IMarketBoardUploader +namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis { - private const string ApiBase = "https://universalis.app"; - // private const string ApiBase = "https://127.0.0.1:443"; - - private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT"; - /// - /// Initializes a new instance of the class. + /// This class represents an uploader for contributing data to Universalis. /// - public UniversalisMarketBoardUploader() + internal class UniversalisMarketBoardUploader : IMarketBoardUploader { - } + private const string ApiBase = "https://universalis.app"; + // private const string ApiBase = "https://127.0.0.1:443"; - /// - public async Task Upload(MarketBoardItemRequest request) - { - var clientState = Service.Get(); + private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT"; - Log.Verbose("Starting Universalis upload."); - var uploader = clientState.LocalContentId; - - // ==================================================================================== - - var listingsUploadObject = new UniversalisItemListingsUploadRequest + /// + /// Initializes a new instance of the class. + /// + public UniversalisMarketBoardUploader() { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, - UploaderId = uploader.ToString(), - ItemId = request.CatalogId, - Listings = new List(), - }; + } - foreach (var marketBoardItemListing in request.Listings) + /// + public async Task Upload(MarketBoardItemRequest request) { - var universalisListing = new UniversalisItemListingsEntry + var clientState = Service.Get(); + + Log.Verbose("Starting Universalis upload."); + var uploader = clientState.LocalContentId; + + // ==================================================================================== + + var listingsUploadObject = new UniversalisItemListingsUploadRequest { - Hq = marketBoardItemListing.IsHq, - SellerId = marketBoardItemListing.RetainerOwnerId.ToString(), - RetainerName = marketBoardItemListing.RetainerName, - RetainerId = marketBoardItemListing.RetainerId.ToString(), - CreatorId = marketBoardItemListing.ArtisanId.ToString(), - CreatorName = marketBoardItemListing.PlayerName, - OnMannequin = marketBoardItemListing.OnMannequin, - LastReviewTime = ((DateTimeOffset)marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(), - PricePerUnit = marketBoardItemListing.PricePerUnit, - Quantity = marketBoardItemListing.ItemQuantity, - RetainerCity = marketBoardItemListing.RetainerCityId, - Materia = new List(), + WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, + UploaderId = uploader.ToString(), + ItemId = request.CatalogId, + Listings = new List(), }; - foreach (var itemMateria in marketBoardItemListing.Materia) + foreach (var marketBoardItemListing in request.Listings) { - universalisListing.Materia.Add(new UniversalisItemMateria + var universalisListing = new UniversalisItemListingsEntry { - MateriaId = itemMateria.MateriaId, - SlotId = itemMateria.Index, + Hq = marketBoardItemListing.IsHq, + SellerId = marketBoardItemListing.RetainerOwnerId.ToString(), + RetainerName = marketBoardItemListing.RetainerName, + RetainerId = marketBoardItemListing.RetainerId.ToString(), + CreatorId = marketBoardItemListing.ArtisanId.ToString(), + CreatorName = marketBoardItemListing.PlayerName, + OnMannequin = marketBoardItemListing.OnMannequin, + LastReviewTime = ((DateTimeOffset)marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(), + PricePerUnit = marketBoardItemListing.PricePerUnit, + Quantity = marketBoardItemListing.ItemQuantity, + RetainerCity = marketBoardItemListing.RetainerCityId, + Materia = new List(), + }; + + foreach (var itemMateria in marketBoardItemListing.Materia) + { + universalisListing.Materia.Add(new UniversalisItemMateria + { + MateriaId = itemMateria.MateriaId, + SlotId = itemMateria.Index, + }); + } + + listingsUploadObject.Listings.Add(universalisListing); + } + + var listingPath = "/upload"; + var listingUpload = JsonConvert.SerializeObject(listingsUploadObject); + Log.Verbose($"{listingPath}: {listingUpload}"); + await Util.HttpClient.PostAsync($"{ApiBase}{listingPath}/{ApiKey}", new StringContent(listingUpload, Encoding.UTF8, "application/json")); + + // ==================================================================================== + + var historyUploadObject = new UniversalisHistoryUploadRequest + { + WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, + UploaderId = uploader.ToString(), + ItemId = request.CatalogId, + Entries = new List(), + }; + + foreach (var marketBoardHistoryListing in request.History) + { + historyUploadObject.Entries.Add(new UniversalisHistoryEntry + { + BuyerName = marketBoardHistoryListing.BuyerName, + Hq = marketBoardHistoryListing.IsHq, + OnMannequin = marketBoardHistoryListing.OnMannequin, + PricePerUnit = marketBoardHistoryListing.SalePrice, + Quantity = marketBoardHistoryListing.Quantity, + Timestamp = ((DateTimeOffset)marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds(), }); } - listingsUploadObject.Listings.Add(universalisListing); + var historyPath = "/upload"; + var historyUpload = JsonConvert.SerializeObject(historyUploadObject); + Log.Verbose($"{historyPath}: {historyUpload}"); + await Util.HttpClient.PostAsync($"{ApiBase}{historyPath}/{ApiKey}", new StringContent(historyUpload, Encoding.UTF8, "application/json")); + + // ==================================================================================== + + Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId); } - var listingPath = "/upload"; - var listingUpload = JsonConvert.SerializeObject(listingsUploadObject); - Log.Verbose($"{listingPath}: {listingUpload}"); - await Util.HttpClient.PostAsync($"{ApiBase}{listingPath}/{ApiKey}", new StringContent(listingUpload, Encoding.UTF8, "application/json")); - - // ==================================================================================== - - var historyUploadObject = new UniversalisHistoryUploadRequest + /// + public async Task UploadTax(MarketTaxRates taxRates) { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, - UploaderId = uploader.ToString(), - ItemId = request.CatalogId, - Entries = new List(), - }; + var clientState = Service.Get(); - foreach (var marketBoardHistoryListing in request.History) - { - historyUploadObject.Entries.Add(new UniversalisHistoryEntry + // ==================================================================================== + + var taxUploadObject = new UniversalisTaxUploadRequest { - BuyerName = marketBoardHistoryListing.BuyerName, - Hq = marketBoardHistoryListing.IsHq, - OnMannequin = marketBoardHistoryListing.OnMannequin, - PricePerUnit = marketBoardHistoryListing.SalePrice, - Quantity = marketBoardHistoryListing.Quantity, - Timestamp = ((DateTimeOffset)marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds(), - }); + WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, + UploaderId = clientState.LocalContentId.ToString(), + TaxData = new UniversalisTaxData + { + LimsaLominsa = taxRates.LimsaLominsaTax, + Gridania = taxRates.GridaniaTax, + Uldah = taxRates.UldahTax, + Ishgard = taxRates.IshgardTax, + Kugane = taxRates.KuganeTax, + Crystarium = taxRates.CrystariumTax, + }, + }; + + var taxPath = "/upload"; + var taxUpload = JsonConvert.SerializeObject(taxUploadObject); + Log.Verbose($"{taxPath}: {taxUpload}"); + + await Util.HttpClient.PostAsync($"{ApiBase}{taxPath}/{ApiKey}", new StringContent(taxUpload, Encoding.UTF8, "application/json")); + + // ==================================================================================== + + Log.Verbose("Universalis tax upload completed."); } - var historyPath = "/upload"; - var historyUpload = JsonConvert.SerializeObject(historyUploadObject); - Log.Verbose($"{historyPath}: {historyUpload}"); - await Util.HttpClient.PostAsync($"{ApiBase}{historyPath}/{ApiKey}", new StringContent(historyUpload, Encoding.UTF8, "application/json")); - - // ==================================================================================== - - Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId); - } - - /// - public async Task UploadTax(MarketTaxRates taxRates) - { - var clientState = Service.Get(); - - // ==================================================================================== - - var taxUploadObject = new UniversalisTaxUploadRequest + /// + /// + /// It may seem backwards that an upload only performs a delete request, however this is not trying + /// to track the available listings, that is done via the listings packet. All this does is remove + /// a listing, or delete it, when a purchase has been made. + /// + public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler) { - WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0, - UploaderId = clientState.LocalContentId.ToString(), - TaxData = new UniversalisTaxData + var clientState = Service.Get(); + + var itemId = purchaseHandler.CatalogId; + var worldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0; + + // ==================================================================================== + + var deleteListingObject = new UniversalisItemListingDeleteRequest { - LimsaLominsa = taxRates.LimsaLominsaTax, - Gridania = taxRates.GridaniaTax, - Uldah = taxRates.UldahTax, - Ishgard = taxRates.IshgardTax, - Kugane = taxRates.KuganeTax, - Crystarium = taxRates.CrystariumTax, - }, - }; + PricePerUnit = purchaseHandler.PricePerUnit, + Quantity = purchaseHandler.ItemQuantity, + ListingId = purchaseHandler.ListingId.ToString(), + RetainerId = purchaseHandler.RetainerId.ToString(), + UploaderId = clientState.LocalContentId.ToString(), + }; - var taxPath = "/upload"; - var taxUpload = JsonConvert.SerializeObject(taxUploadObject); - Log.Verbose($"{taxPath}: {taxUpload}"); + var deletePath = $"/api/{worldId}/{itemId}/delete"; + var deleteListing = JsonConvert.SerializeObject(deleteListingObject); + Log.Verbose($"{deletePath}: {deleteListing}"); - await Util.HttpClient.PostAsync($"{ApiBase}{taxPath}/{ApiKey}", new StringContent(taxUpload, Encoding.UTF8, "application/json")); + var content = new StringContent(deleteListing, Encoding.UTF8, "application/json"); + var message = new HttpRequestMessage(HttpMethod.Post, $"{ApiBase}{deletePath}"); + message.Headers.Add("Authorization", ApiKey); + message.Content = content; - // ==================================================================================== + await Util.HttpClient.SendAsync(message); - Log.Verbose("Universalis tax upload completed."); - } + // ==================================================================================== - /// - /// - /// It may seem backwards that an upload only performs a delete request, however this is not trying - /// to track the available listings, that is done via the listings packet. All this does is remove - /// a listing, or delete it, when a purchase has been made. - /// - public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler) - { - var clientState = Service.Get(); - - var itemId = purchaseHandler.CatalogId; - var worldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0; - - // ==================================================================================== - - var deleteListingObject = new UniversalisItemListingDeleteRequest - { - PricePerUnit = purchaseHandler.PricePerUnit, - Quantity = purchaseHandler.ItemQuantity, - ListingId = purchaseHandler.ListingId.ToString(), - RetainerId = purchaseHandler.RetainerId.ToString(), - UploaderId = clientState.LocalContentId.ToString(), - }; - - var deletePath = $"/api/{worldId}/{itemId}/delete"; - var deleteListing = JsonConvert.SerializeObject(deleteListingObject); - Log.Verbose($"{deletePath}: {deleteListing}"); - - var content = new StringContent(deleteListing, Encoding.UTF8, "application/json"); - var message = new HttpRequestMessage(HttpMethod.Post, $"{ApiBase}{deletePath}"); - message.Headers.Add("Authorization", ApiKey); - message.Content = content; - - await Util.HttpClient.SendAsync(message); - - // ==================================================================================== - - Log.Verbose("Universalis purchase upload completed."); + Log.Verbose("Universalis purchase upload completed."); + } } } diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index 352c0462a..4d7b7145e 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -16,277 +16,278 @@ using Dalamud.Utility; using Lumina.Excel.GeneratedSheets; using Serilog; -namespace Dalamud.Game.Network.Internal; - -/// -/// This class handles network notifications and uploading market board data. -/// -internal class NetworkHandlers +namespace Dalamud.Game.Network.Internal { - private readonly List marketBoardRequests = new(); - - private readonly bool optOutMbUploads; - private readonly IMarketBoardUploader uploader; - - private MarketBoardPurchaseHandler marketBoardPurchaseHandler; - /// - /// Initializes a new instance of the class. + /// This class handles network notifications and uploading market board data. /// - public NetworkHandlers() + internal class NetworkHandlers { - this.optOutMbUploads = Service.Get().OptOutMbCollection; + private readonly List marketBoardRequests = new(); - this.uploader = new UniversalisMarketBoardUploader(); + private readonly bool optOutMbUploads; + private readonly IMarketBoardUploader uploader; - Service.Get().NetworkMessage += this.OnNetworkMessage; - } + private MarketBoardPurchaseHandler marketBoardPurchaseHandler; - /// - /// Event which gets fired when a duty is ready. - /// - public event EventHandler CfPop; - - private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) - { - var dataManager = Service.GetNullable(); - - if (dataManager?.IsDataReady == false) - return; - - var configuration = Service.Get(); - - if (direction == NetworkMessageDirection.ZoneUp) + /// + /// Initializes a new instance of the class. + /// + public NetworkHandlers() { + this.optOutMbUploads = Service.Get().OptOutMbCollection; + + this.uploader = new UniversalisMarketBoardUploader(); + + Service.Get().NetworkMessage += this.OnNetworkMessage; + } + + /// + /// Event which gets fired when a duty is ready. + /// + public event EventHandler CfPop; + + private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) + { + var dataManager = Service.GetNullable(); + + if (dataManager?.IsDataReady == false) + return; + + var configuration = Service.Get(); + + if (direction == NetworkMessageDirection.ZoneUp) + { + if (!this.optOutMbUploads) + { + if (opCode == dataManager.ClientOpCodes["MarketBoardPurchaseHandler"]) + { + this.marketBoardPurchaseHandler = MarketBoardPurchaseHandler.Read(dataPtr); + return; + } + } + + return; + } + + if (opCode == dataManager.ServerOpCodes["CfNotifyPop"]) + { + this.HandleCfPop(dataPtr); + return; + } + if (!this.optOutMbUploads) { - if (opCode == dataManager.ClientOpCodes["MarketBoardPurchaseHandler"]) + if (opCode == dataManager.ServerOpCodes["MarketBoardItemRequestStart"]) { - this.marketBoardPurchaseHandler = MarketBoardPurchaseHandler.Read(dataPtr); - return; - } - } + var data = MarketBoardItemRequest.Read(dataPtr); + this.marketBoardRequests.Add(data); - return; - } - - if (opCode == dataManager.ServerOpCodes["CfNotifyPop"]) - { - this.HandleCfPop(dataPtr); - return; - } - - if (!this.optOutMbUploads) - { - if (opCode == dataManager.ServerOpCodes["MarketBoardItemRequestStart"]) - { - var data = MarketBoardItemRequest.Read(dataPtr); - this.marketBoardRequests.Add(data); - - Log.Verbose($"NEW MB REQUEST START: item#{data.CatalogId} amount#{data.AmountToArrive}"); - return; - } - - if (opCode == dataManager.ServerOpCodes["MarketBoardOfferings"]) - { - var listing = MarketBoardCurrentOfferings.Read(dataPtr); - - var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone); - - if (request == default) - { - Log.Error($"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}"); + Log.Verbose($"NEW MB REQUEST START: item#{data.CatalogId} amount#{data.AmountToArrive}"); return; } - if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive) + if (opCode == dataManager.ServerOpCodes["MarketBoardOfferings"]) { - Log.Error($"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}"); - return; - } + var listing = MarketBoardCurrentOfferings.Read(dataPtr); - if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId) - { - Log.Error($"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}"); - return; - } + var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone); - if (request.ListingsRequestId == -1 && request.Listings.Count > 0) - { - Log.Error($"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}"); - return; - } + if (request == default) + { + Log.Error($"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}"); + return; + } - if (request.ListingsRequestId == -1) - { - request.ListingsRequestId = listing.RequestId; - Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}"); - } + if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive) + { + Log.Error($"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}"); + return; + } - request.Listings.AddRange(listing.ItemListings); + if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId) + { + Log.Error($"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}"); + return; + } - Log.Verbose( - "Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}", - listing.ItemListings.Count, - request.ListingsRequestId, - request.Listings.Count, - request.AmountToArrive, - request.CatalogId); + if (request.ListingsRequestId == -1 && request.Listings.Count > 0) + { + Log.Error($"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}"); + return; + } + + if (request.ListingsRequestId == -1) + { + request.ListingsRequestId = listing.RequestId; + Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}"); + } + + request.Listings.AddRange(listing.ItemListings); - if (request.IsDone) - { Log.Verbose( - "Market Board request finished, starting upload: request#{0} item#{1} amount#{2}", + "Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}", + listing.ItemListings.Count, request.ListingsRequestId, - request.CatalogId, - request.AmountToArrive); + request.Listings.Count, + request.AmountToArrive, + request.CatalogId); - Task.Run(() => this.uploader.Upload(request)) - .ContinueWith((task) => Log.Error(task.Exception, "Market Board offerings data upload failed."), TaskContinuationOptions.OnlyOnFaulted); + if (request.IsDone) + { + Log.Verbose( + "Market Board request finished, starting upload: request#{0} item#{1} amount#{2}", + request.ListingsRequestId, + request.CatalogId, + request.AmountToArrive); + + Task.Run(() => this.uploader.Upload(request)) + .ContinueWith((task) => Log.Error(task.Exception, "Market Board offerings data upload failed."), TaskContinuationOptions.OnlyOnFaulted); + } + + return; } + if (opCode == dataManager.ServerOpCodes["MarketBoardHistory"]) + { + var listing = MarketBoardHistory.Read(dataPtr); + + var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId); + + if (request == default) + { + Log.Error($"Market Board data arrived without a corresponding request: item#{listing.CatalogId}"); + return; + } + + if (request.ListingsRequestId != -1) + { + Log.Error($"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}"); + return; + } + + request.History.AddRange(listing.HistoryListings); + + Log.Verbose("Added history for item#{0}", listing.CatalogId); + + if (request.AmountToArrive == 0) + { + Log.Verbose("Request had 0 amount, uploading now"); + + Task.Run(() => this.uploader.Upload(request)) + .ContinueWith((task) => Log.Error(task.Exception, "Market Board history data upload failed."), TaskContinuationOptions.OnlyOnFaulted); + } + } + + if (opCode == dataManager.ServerOpCodes["MarketTaxRates"]) + { + var category = (uint)Marshal.ReadInt32(dataPtr); + + // Result dialog packet does not contain market tax rates + if (category != 720905) + { + return; + } + + var taxes = MarketTaxRates.Read(dataPtr); + + Log.Verbose( + "MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}", + taxes.LimsaLominsaTax, + taxes.GridaniaTax, + taxes.UldahTax, + taxes.IshgardTax, + taxes.KuganeTax, + taxes.CrystariumTax); + + Task.Run(() => this.uploader.UploadTax(taxes)) + .ContinueWith((task) => Log.Error(task.Exception, "Market Board tax data upload failed."), TaskContinuationOptions.OnlyOnFaulted); + + return; + } + + if (opCode == dataManager.ServerOpCodes["MarketBoardPurchase"]) + { + if (this.marketBoardPurchaseHandler == null) + return; + + var purchase = MarketBoardPurchase.Read(dataPtr); + + var sameQty = purchase.ItemQuantity == this.marketBoardPurchaseHandler.ItemQuantity; + var itemMatch = purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId; + var itemMatchHq = purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId + 1_000_000; + + // Transaction succeeded + if (sameQty && (itemMatch || itemMatchHq)) + { + Log.Verbose($"Bought {purchase.ItemQuantity}x {this.marketBoardPurchaseHandler.CatalogId} for {this.marketBoardPurchaseHandler.PricePerUnit * purchase.ItemQuantity} gils, listing id is {this.marketBoardPurchaseHandler.ListingId}"); + + var handler = this.marketBoardPurchaseHandler; // Capture the object so that we don't pass in a null one when the task starts. + + Task.Run(() => this.uploader.UploadPurchase(handler)) + .ContinueWith((task) => Log.Error(task.Exception, "Market Board purchase data upload failed."), TaskContinuationOptions.OnlyOnFaulted); + } + + this.marketBoardPurchaseHandler = null; + return; + } + } + } + + private unsafe void HandleCfPop(IntPtr dataPtr) + { + var dataManager = Service.Get(); + var configuration = Service.Get(); + + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 64); + using var reader = new BinaryReader(stream); + + var notifyType = reader.ReadByte(); + stream.Position += 0x13; + var conditionId = reader.ReadUInt16(); + + if (notifyType != 3) + return; + + var cfConditionSheet = dataManager.GetExcelSheet()!; + var cfCondition = cfConditionSheet.GetRow(conditionId); + + if (cfCondition == null) + { + Log.Error($"CFC key {conditionId} not in Lumina data."); return; } - if (opCode == dataManager.ServerOpCodes["MarketBoardHistory"]) + var cfcName = cfCondition.Name.ToString(); + if (cfcName.IsNullOrEmpty()) { - var listing = MarketBoardHistory.Read(dataPtr); - - var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId); - - if (request == default) - { - Log.Error($"Market Board data arrived without a corresponding request: item#{listing.CatalogId}"); - return; - } - - if (request.ListingsRequestId != -1) - { - Log.Error($"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}"); - return; - } - - request.History.AddRange(listing.HistoryListings); - - Log.Verbose("Added history for item#{0}", listing.CatalogId); - - if (request.AmountToArrive == 0) - { - Log.Verbose("Request had 0 amount, uploading now"); - - Task.Run(() => this.uploader.Upload(request)) - .ContinueWith((task) => Log.Error(task.Exception, "Market Board history data upload failed."), TaskContinuationOptions.OnlyOnFaulted); - } + cfcName = "Duty Roulette"; + cfCondition.Image = 112324; } - if (opCode == dataManager.ServerOpCodes["MarketTaxRates"]) + // Flash window + if (configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated()) { - var category = (uint)Marshal.ReadInt32(dataPtr); - - // Result dialog packet does not contain market tax rates - if (category != 720905) + var flashInfo = new NativeFunctions.FlashWindowInfo { - return; + Size = (uint)Marshal.SizeOf(), + Count = uint.MaxValue, + Timeout = 0, + Flags = NativeFunctions.FlashWindow.All | NativeFunctions.FlashWindow.TimerNoFG, + Hwnd = Process.GetCurrentProcess().MainWindowHandle, + }; + NativeFunctions.FlashWindowEx(ref flashInfo); + } + + Task.Run(() => + { + if (configuration.DutyFinderChatMessage) + { + Service.Get().Print($"Duty pop: {cfcName}"); } - var taxes = MarketTaxRates.Read(dataPtr); - - Log.Verbose( - "MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}", - taxes.LimsaLominsaTax, - taxes.GridaniaTax, - taxes.UldahTax, - taxes.IshgardTax, - taxes.KuganeTax, - taxes.CrystariumTax); - - Task.Run(() => this.uploader.UploadTax(taxes)) - .ContinueWith((task) => Log.Error(task.Exception, "Market Board tax data upload failed."), TaskContinuationOptions.OnlyOnFaulted); - - return; - } - - if (opCode == dataManager.ServerOpCodes["MarketBoardPurchase"]) - { - if (this.marketBoardPurchaseHandler == null) - return; - - var purchase = MarketBoardPurchase.Read(dataPtr); - - var sameQty = purchase.ItemQuantity == this.marketBoardPurchaseHandler.ItemQuantity; - var itemMatch = purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId; - var itemMatchHq = purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId + 1_000_000; - - // Transaction succeeded - if (sameQty && (itemMatch || itemMatchHq)) - { - Log.Verbose($"Bought {purchase.ItemQuantity}x {this.marketBoardPurchaseHandler.CatalogId} for {this.marketBoardPurchaseHandler.PricePerUnit * purchase.ItemQuantity} gils, listing id is {this.marketBoardPurchaseHandler.ListingId}"); - - var handler = this.marketBoardPurchaseHandler; // Capture the object so that we don't pass in a null one when the task starts. - - Task.Run(() => this.uploader.UploadPurchase(handler)) - .ContinueWith((task) => Log.Error(task.Exception, "Market Board purchase data upload failed."), TaskContinuationOptions.OnlyOnFaulted); - } - - this.marketBoardPurchaseHandler = null; - return; - } + this.CfPop?.Invoke(this, cfCondition); + }).ContinueWith((task) => Log.Error(task.Exception, "CfPop.Invoke failed."), TaskContinuationOptions.OnlyOnFaulted); } } - - private unsafe void HandleCfPop(IntPtr dataPtr) - { - var dataManager = Service.Get(); - var configuration = Service.Get(); - - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 64); - using var reader = new BinaryReader(stream); - - var notifyType = reader.ReadByte(); - stream.Position += 0x13; - var conditionId = reader.ReadUInt16(); - - if (notifyType != 3) - return; - - var cfConditionSheet = dataManager.GetExcelSheet()!; - var cfCondition = cfConditionSheet.GetRow(conditionId); - - if (cfCondition == null) - { - Log.Error($"CFC key {conditionId} not in Lumina data."); - return; - } - - var cfcName = cfCondition.Name.ToString(); - if (cfcName.IsNullOrEmpty()) - { - cfcName = "Duty Roulette"; - cfCondition.Image = 112324; - } - - // Flash window - if (configuration.DutyFinderTaskbarFlash && !NativeFunctions.ApplicationIsActivated()) - { - var flashInfo = new NativeFunctions.FlashWindowInfo - { - Size = (uint)Marshal.SizeOf(), - Count = uint.MaxValue, - Timeout = 0, - Flags = NativeFunctions.FlashWindow.All | NativeFunctions.FlashWindow.TimerNoFG, - Hwnd = Process.GetCurrentProcess().MainWindowHandle, - }; - NativeFunctions.FlashWindowEx(ref flashInfo); - } - - Task.Run(() => - { - if (configuration.DutyFinderChatMessage) - { - Service.Get().Print($"Duty pop: {cfcName}"); - } - - this.CfPop?.Invoke(this, cfCondition); - }).ContinueWith((task) => Log.Error(task.Exception, "CfPop.Invoke failed."), TaskContinuationOptions.OnlyOnFaulted); - } } diff --git a/Dalamud/Game/Network/Internal/WinSockHandlers.cs b/Dalamud/Game/Network/Internal/WinSockHandlers.cs index 792e80d8e..82f2f0d91 100644 --- a/Dalamud/Game/Network/Internal/WinSockHandlers.cs +++ b/Dalamud/Game/Network/Internal/WinSockHandlers.cs @@ -5,57 +5,58 @@ using System.Runtime.InteropServices; using Dalamud.Hooking; using Dalamud.Hooking.Internal; -namespace Dalamud.Game.Network.Internal; - -/// -/// This class enables TCP optimizations in the game socket for better performance. -/// -internal sealed class WinSockHandlers : IDisposable +namespace Dalamud.Game.Network.Internal { - private Hook ws2SocketHook; - /// - /// Initializes a new instance of the class. + /// This class enables TCP optimizations in the game socket for better performance. /// - public WinSockHandlers() + internal sealed class WinSockHandlers : IDisposable { - this.ws2SocketHook = Hook.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true); - this.ws2SocketHook?.Enable(); - } + private Hook ws2SocketHook; - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate IntPtr SocketDelegate(int af, int type, int protocol); - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.ws2SocketHook?.Dispose(); - } - - private IntPtr OnSocket(int af, int type, int protocol) - { - var socket = this.ws2SocketHook.Original(af, type, protocol); - - // IPPROTO_TCP - if (type == 1) + /// + /// Initializes a new instance of the class. + /// + public WinSockHandlers() { - // INVALID_SOCKET - if (socket != new IntPtr(-1)) - { - // In case you're not aware of it: (albeit you should) - // https://linux.die.net/man/7/tcp - // https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf - var value = new IntPtr(1); - _ = NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4); - - // Enable tcp_quickack option. This option is undocumented in MSDN but it is supported in Windows 7 and onwards. - value = new IntPtr(1); - _ = NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.AddMembership, ref value, 4); - } + this.ws2SocketHook = Hook.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true); + this.ws2SocketHook?.Enable(); } - return socket; + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + private delegate IntPtr SocketDelegate(int af, int type, int protocol); + + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.ws2SocketHook?.Dispose(); + } + + private IntPtr OnSocket(int af, int type, int protocol) + { + var socket = this.ws2SocketHook.Original(af, type, protocol); + + // IPPROTO_TCP + if (type == 1) + { + // INVALID_SOCKET + if (socket != new IntPtr(-1)) + { + // In case you're not aware of it: (albeit you should) + // https://linux.die.net/man/7/tcp + // https://assets.extrahop.com/whitepapers/TCP-Optimization-Guide-by-ExtraHop.pdf + var value = new IntPtr(1); + _ = NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.NoDelay, ref value, 4); + + // Enable tcp_quickack option. This option is undocumented in MSDN but it is supported in Windows 7 and onwards. + value = new IntPtr(1); + _ = NativeFunctions.SetSockOpt(socket, SocketOptionLevel.Tcp, SocketOptionName.AddMembership, ref value, 4); + } + } + + return socket; + } } } diff --git a/Dalamud/Game/Network/NetworkMessageDirection.cs b/Dalamud/Game/Network/NetworkMessageDirection.cs index 87cce5173..79d9d2df2 100644 --- a/Dalamud/Game/Network/NetworkMessageDirection.cs +++ b/Dalamud/Game/Network/NetworkMessageDirection.cs @@ -1,17 +1,18 @@ -namespace Dalamud.Game.Network; - -/// -/// This represents the direction of a network message. -/// -public enum NetworkMessageDirection +namespace Dalamud.Game.Network { /// - /// A zone down message. + /// This represents the direction of a network message. /// - ZoneDown, + public enum NetworkMessageDirection + { + /// + /// A zone down message. + /// + ZoneDown, - /// - /// A zone up message. - /// - ZoneUp, + /// + /// A zone up message. + /// + ZoneUp, + } } diff --git a/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs b/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs index 4d57a0fbd..364a21454 100644 --- a/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs +++ b/Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs @@ -3,224 +3,225 @@ using System.Collections.Generic; using System.IO; using System.Text; -namespace Dalamud.Game.Network.Structures; - -/// -/// This class represents the current market board offerings from a game network packet. -/// -public class MarketBoardCurrentOfferings +namespace Dalamud.Game.Network.Structures { - private MarketBoardCurrentOfferings() + /// + /// This class represents the current market board offerings from a game network packet. + /// + public class MarketBoardCurrentOfferings { - } - - /// - /// Gets the list of individual item listings. - /// - public List ItemListings { get; } = new(); - - /// - /// Gets the listing end index. - /// - public int ListingIndexEnd { get; internal set; } - - /// - /// Gets the listing start index. - /// - public int ListingIndexStart { get; internal set; } - - /// - /// Gets the request ID. - /// - public int RequestId { get; internal set; } - - /// - /// Read a object from memory. - /// - /// Address to read. - /// A new object. - public static unsafe MarketBoardCurrentOfferings Read(IntPtr dataPtr) - { - var output = new MarketBoardCurrentOfferings(); - - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); - - for (var i = 0; i < 10; i++) + private MarketBoardCurrentOfferings() { - var listingEntry = new MarketBoardItemListing(); + } - listingEntry.ListingId = reader.ReadUInt64(); - listingEntry.RetainerId = reader.ReadUInt64(); - listingEntry.RetainerOwnerId = reader.ReadUInt64(); - listingEntry.ArtisanId = reader.ReadUInt64(); - listingEntry.PricePerUnit = reader.ReadUInt32(); - listingEntry.TotalTax = reader.ReadUInt32(); - listingEntry.ItemQuantity = reader.ReadUInt32(); - listingEntry.CatalogId = reader.ReadUInt32(); - listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime; + /// + /// Gets the list of individual item listings. + /// + public List ItemListings { get; } = new(); - reader.ReadUInt16(); // container - reader.ReadUInt32(); // slot - reader.ReadUInt16(); // durability - reader.ReadUInt16(); // spiritbond + /// + /// Gets the listing end index. + /// + public int ListingIndexEnd { get; internal set; } - for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++) + /// + /// Gets the listing start index. + /// + public int ListingIndexStart { get; internal set; } + + /// + /// Gets the request ID. + /// + public int RequestId { get; internal set; } + + /// + /// Read a object from memory. + /// + /// Address to read. + /// A new object. + public static unsafe MarketBoardCurrentOfferings Read(IntPtr dataPtr) + { + var output = new MarketBoardCurrentOfferings(); + + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); + + for (var i = 0; i < 10; i++) { - var materiaVal = reader.ReadUInt16(); - var materiaEntry = new MarketBoardItemListing.ItemMateria() + var listingEntry = new MarketBoardItemListing(); + + listingEntry.ListingId = reader.ReadUInt64(); + listingEntry.RetainerId = reader.ReadUInt64(); + listingEntry.RetainerOwnerId = reader.ReadUInt64(); + listingEntry.ArtisanId = reader.ReadUInt64(); + listingEntry.PricePerUnit = reader.ReadUInt32(); + listingEntry.TotalTax = reader.ReadUInt32(); + listingEntry.ItemQuantity = reader.ReadUInt32(); + listingEntry.CatalogId = reader.ReadUInt32(); + listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime; + + reader.ReadUInt16(); // container + reader.ReadUInt32(); // slot + reader.ReadUInt16(); // durability + reader.ReadUInt16(); // spiritbond + + for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++) { - MateriaId = (materiaVal & 0xFF0) >> 4, - Index = materiaVal & 0xF, - }; + var materiaVal = reader.ReadUInt16(); + var materiaEntry = new MarketBoardItemListing.ItemMateria() + { + MateriaId = (materiaVal & 0xFF0) >> 4, + Index = materiaVal & 0xF, + }; - if (materiaEntry.MateriaId != 0) - listingEntry.Materia.Add(materiaEntry); + if (materiaEntry.MateriaId != 0) + listingEntry.Materia.Add(materiaEntry); + } + + reader.ReadUInt16(); + reader.ReadUInt32(); + + listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000'); + listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000'); + listingEntry.IsHq = reader.ReadBoolean(); + listingEntry.MateriaCount = reader.ReadByte(); + listingEntry.OnMannequin = reader.ReadBoolean(); + listingEntry.RetainerCityId = reader.ReadByte(); + listingEntry.StainId = reader.ReadUInt16(); + + reader.ReadUInt16(); + reader.ReadUInt32(); + + if (listingEntry.CatalogId != 0) + output.ItemListings.Add(listingEntry); } - reader.ReadUInt16(); - reader.ReadUInt32(); + output.ListingIndexEnd = reader.ReadByte(); + output.ListingIndexStart = reader.ReadByte(); + output.RequestId = reader.ReadUInt16(); - listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000'); - listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000'); - listingEntry.IsHq = reader.ReadBoolean(); - listingEntry.MateriaCount = reader.ReadByte(); - listingEntry.OnMannequin = reader.ReadBoolean(); - listingEntry.RetainerCityId = reader.ReadByte(); - listingEntry.StainId = reader.ReadUInt16(); - - reader.ReadUInt16(); - reader.ReadUInt32(); - - if (listingEntry.CatalogId != 0) - output.ItemListings.Add(listingEntry); - } - - output.ListingIndexEnd = reader.ReadByte(); - output.ListingIndexStart = reader.ReadByte(); - output.RequestId = reader.ReadUInt16(); - - return output; - } - - /// - /// This class represents the current market board offering of a single item from the network packet. - /// - public class MarketBoardItemListing - { - /// - /// Initializes a new instance of the class. - /// - internal MarketBoardItemListing() - { + return output; } /// - /// Gets the artisan ID. + /// This class represents the current market board offering of a single item from the network packet. /// - public ulong ArtisanId { get; internal set; } - - /// - /// Gets the catalog ID. - /// - public uint CatalogId { get; internal set; } - - /// - /// Gets a value indicating whether the item is HQ. - /// - public bool IsHq { get; internal set; } - - /// - /// Gets the item quantity. - /// - public uint ItemQuantity { get; internal set; } - - /// - /// Gets the time this offering was last reviewed. - /// - public DateTime LastReviewTime { get; internal set; } - - /// - /// Gets the listing ID. - /// - public ulong ListingId { get; internal set; } - - /// - /// Gets the list of materia attached to this item. - /// - public List Materia { get; } = new(); - - /// - /// Gets the amount of attached materia. - /// - public int MateriaCount { get; internal set; } - - /// - /// Gets a value indicating whether this item is on a mannequin. - /// - public bool OnMannequin { get; internal set; } - - /// - /// Gets the player name. - /// - public string PlayerName { get; internal set; } - - /// - /// Gets the price per unit. - /// - public uint PricePerUnit { get; internal set; } - - /// - /// Gets the city ID of the retainer selling the item. - /// - public int RetainerCityId { get; internal set; } - - /// - /// Gets the ID of the retainer selling the item. - /// - public ulong RetainerId { get; internal set; } - - /// - /// Gets the name of the retainer. - /// - public string RetainerName { get; internal set; } - - /// - /// Gets the ID of the retainer's owner. - /// - public ulong RetainerOwnerId { get; internal set; } - - /// - /// Gets the stain or applied dye of the item. - /// - public int StainId { get; internal set; } - - /// - /// Gets the total tax. - /// - public uint TotalTax { get; internal set; } - - /// - /// This represents the materia slotted to an . - /// - public class ItemMateria + public class MarketBoardItemListing { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - internal ItemMateria() + internal MarketBoardItemListing() { } /// - /// Gets the materia index. + /// Gets the artisan ID. /// - public int Index { get; internal set; } + public ulong ArtisanId { get; internal set; } /// - /// Gets the materia ID. + /// Gets the catalog ID. /// - public int MateriaId { get; internal set; } + public uint CatalogId { get; internal set; } + + /// + /// Gets a value indicating whether the item is HQ. + /// + public bool IsHq { get; internal set; } + + /// + /// Gets the item quantity. + /// + public uint ItemQuantity { get; internal set; } + + /// + /// Gets the time this offering was last reviewed. + /// + public DateTime LastReviewTime { get; internal set; } + + /// + /// Gets the listing ID. + /// + public ulong ListingId { get; internal set; } + + /// + /// Gets the list of materia attached to this item. + /// + public List Materia { get; } = new(); + + /// + /// Gets the amount of attached materia. + /// + public int MateriaCount { get; internal set; } + + /// + /// Gets a value indicating whether this item is on a mannequin. + /// + public bool OnMannequin { get; internal set; } + + /// + /// Gets the player name. + /// + public string PlayerName { get; internal set; } + + /// + /// Gets the price per unit. + /// + public uint PricePerUnit { get; internal set; } + + /// + /// Gets the city ID of the retainer selling the item. + /// + public int RetainerCityId { get; internal set; } + + /// + /// Gets the ID of the retainer selling the item. + /// + public ulong RetainerId { get; internal set; } + + /// + /// Gets the name of the retainer. + /// + public string RetainerName { get; internal set; } + + /// + /// Gets the ID of the retainer's owner. + /// + public ulong RetainerOwnerId { get; internal set; } + + /// + /// Gets the stain or applied dye of the item. + /// + public int StainId { get; internal set; } + + /// + /// Gets the total tax. + /// + public uint TotalTax { get; internal set; } + + /// + /// This represents the materia slotted to an . + /// + public class ItemMateria + { + /// + /// Initializes a new instance of the class. + /// + internal ItemMateria() + { + } + + /// + /// Gets the materia index. + /// + public int Index { get; internal set; } + + /// + /// Gets the materia ID. + /// + public int MateriaId { get; internal set; } + } } } } diff --git a/Dalamud/Game/Network/Structures/MarketBoardHistory.cs b/Dalamud/Game/Network/Structures/MarketBoardHistory.cs index 69532afd6..d753b0498 100644 --- a/Dalamud/Game/Network/Structures/MarketBoardHistory.cs +++ b/Dalamud/Game/Network/Structures/MarketBoardHistory.cs @@ -3,120 +3,121 @@ using System.Collections.Generic; using System.IO; using System.Text; -namespace Dalamud.Game.Network.Structures; - -/// -/// This class represents the market board history from a game network packet. -/// -public class MarketBoardHistory +namespace Dalamud.Game.Network.Structures { /// - /// Initializes a new instance of the class. + /// This class represents the market board history from a game network packet. /// - internal MarketBoardHistory() + public class MarketBoardHistory { - } - - /// - /// Gets the catalog ID. - /// - public uint CatalogId { get; private set; } - - /// - /// Gets the second catalog ID. - /// - public uint CatalogId2 { get; private set; } - - /// - /// Gets the list of individual item history listings. - /// - public List HistoryListings { get; } = new(); - - /// - /// Read a object from memory. - /// - /// Address to read. - /// A new object. - public static unsafe MarketBoardHistory Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); - - var output = new MarketBoardHistory(); - - output.CatalogId = reader.ReadUInt32(); - output.CatalogId2 = reader.ReadUInt32(); - - for (var i = 0; i < 20; i++) + /// + /// Initializes a new instance of the class. + /// + internal MarketBoardHistory() { - var listingEntry = new MarketBoardHistoryListing + } + + /// + /// Gets the catalog ID. + /// + public uint CatalogId { get; private set; } + + /// + /// Gets the second catalog ID. + /// + public uint CatalogId2 { get; private set; } + + /// + /// Gets the list of individual item history listings. + /// + public List HistoryListings { get; } = new(); + + /// + /// Read a object from memory. + /// + /// Address to read. + /// A new object. + public static unsafe MarketBoardHistory Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); + + var output = new MarketBoardHistory(); + + output.CatalogId = reader.ReadUInt32(); + output.CatalogId2 = reader.ReadUInt32(); + + for (var i = 0; i < 20; i++) { - SalePrice = reader.ReadUInt32(), - PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime, - Quantity = reader.ReadUInt32(), - IsHq = reader.ReadBoolean(), - }; + var listingEntry = new MarketBoardHistoryListing + { + SalePrice = reader.ReadUInt32(), + PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime, + Quantity = reader.ReadUInt32(), + IsHq = reader.ReadBoolean(), + }; - reader.ReadBoolean(); + reader.ReadBoolean(); - listingEntry.OnMannequin = reader.ReadBoolean(); - listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000'); - listingEntry.NextCatalogId = reader.ReadUInt32(); + listingEntry.OnMannequin = reader.ReadBoolean(); + listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000'); + listingEntry.NextCatalogId = reader.ReadUInt32(); - output.HistoryListings.Add(listingEntry); + output.HistoryListings.Add(listingEntry); - if (listingEntry.NextCatalogId == 0) - break; + if (listingEntry.NextCatalogId == 0) + break; + } + + return output; } - return output; - } - - /// - /// This class represents the market board history of a single item from the network packet. - /// - public class MarketBoardHistoryListing - { /// - /// Initializes a new instance of the class. + /// This class represents the market board history of a single item from the network packet. /// - internal MarketBoardHistoryListing() + public class MarketBoardHistoryListing { + /// + /// Initializes a new instance of the class. + /// + internal MarketBoardHistoryListing() + { + } + + /// + /// Gets the buyer's name. + /// + public string BuyerName { get; internal set; } + + /// + /// Gets the next entry's catalog ID. + /// + public uint NextCatalogId { get; internal set; } + + /// + /// Gets a value indicating whether the item is HQ. + /// + public bool IsHq { get; internal set; } + + /// + /// Gets a value indicating whether the item is on a mannequin. + /// + public bool OnMannequin { get; internal set; } + + /// + /// Gets the time of purchase. + /// + public DateTime PurchaseTime { get; internal set; } + + /// + /// Gets the quantity. + /// + public uint Quantity { get; internal set; } + + /// + /// Gets the sale price. + /// + public uint SalePrice { get; internal set; } } - - /// - /// Gets the buyer's name. - /// - public string BuyerName { get; internal set; } - - /// - /// Gets the next entry's catalog ID. - /// - public uint NextCatalogId { get; internal set; } - - /// - /// Gets a value indicating whether the item is HQ. - /// - public bool IsHq { get; internal set; } - - /// - /// Gets a value indicating whether the item is on a mannequin. - /// - public bool OnMannequin { get; internal set; } - - /// - /// Gets the time of purchase. - /// - public DateTime PurchaseTime { get; internal set; } - - /// - /// Gets the quantity. - /// - public uint Quantity { get; internal set; } - - /// - /// Gets the sale price. - /// - public uint SalePrice { get; internal set; } } } diff --git a/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs b/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs index e3530aea7..5fc0de786 100644 --- a/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs +++ b/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs @@ -1,44 +1,45 @@ using System; using System.IO; -namespace Dalamud.Game.Network.Structures; - -/// -/// Represents market board purchase information. This message is received from the -/// server when a purchase is made at a market board. -/// -internal class MarketBoardPurchase +namespace Dalamud.Game.Network.Structures { - private MarketBoardPurchase() + /// + /// Represents market board purchase information. This message is received from the + /// server when a purchase is made at a market board. + /// + internal class MarketBoardPurchase { - } + private MarketBoardPurchase() + { + } - /// - /// Gets the item ID of the item that was purchased. - /// - public uint CatalogId { get; private set; } + /// + /// Gets the item ID of the item that was purchased. + /// + public uint CatalogId { get; private set; } - /// - /// Gets the quantity of the item that was purchased. - /// - public uint ItemQuantity { get; private set; } + /// + /// Gets the quantity of the item that was purchased. + /// + public uint ItemQuantity { get; private set; } - /// - /// Reads market board purchase information from the struct at the provided pointer. - /// - /// A pointer to a struct containing market board purchase information from the server. - /// An object representing the data read. - public static unsafe MarketBoardPurchase Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); + /// + /// Reads market board purchase information from the struct at the provided pointer. + /// + /// A pointer to a struct containing market board purchase information from the server. + /// An object representing the data read. + public static unsafe MarketBoardPurchase Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); - var output = new MarketBoardPurchase(); + var output = new MarketBoardPurchase(); - output.CatalogId = reader.ReadUInt32(); - stream.Position += 4; - output.ItemQuantity = reader.ReadUInt32(); + output.CatalogId = reader.ReadUInt32(); + stream.Position += 4; + output.ItemQuantity = reader.ReadUInt32(); - return output; + return output; + } } } diff --git a/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs b/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs index 8d1717acb..0557b0e0d 100644 --- a/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs +++ b/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs @@ -1,61 +1,62 @@ using System; using System.IO; -namespace Dalamud.Game.Network.Structures; - -/// -/// Represents market board purchase information. This message is sent from the -/// client when a purchase is made at a market board. -/// -internal class MarketBoardPurchaseHandler +namespace Dalamud.Game.Network.Structures { - private MarketBoardPurchaseHandler() + /// + /// Represents market board purchase information. This message is sent from the + /// client when a purchase is made at a market board. + /// + internal class MarketBoardPurchaseHandler { - } + private MarketBoardPurchaseHandler() + { + } - /// - /// Gets the object ID of the retainer associated with the sale. - /// - public ulong RetainerId { get; private set; } + /// + /// Gets the object ID of the retainer associated with the sale. + /// + public ulong RetainerId { get; private set; } - /// - /// Gets the object ID of the item listing. - /// - public ulong ListingId { get; private set; } + /// + /// Gets the object ID of the item listing. + /// + public ulong ListingId { get; private set; } - /// - /// Gets the item ID of the item that was purchased. - /// - public uint CatalogId { get; private set; } + /// + /// Gets the item ID of the item that was purchased. + /// + public uint CatalogId { get; private set; } - /// - /// Gets the quantity of the item that was purchased. - /// - public uint ItemQuantity { get; private set; } + /// + /// Gets the quantity of the item that was purchased. + /// + public uint ItemQuantity { get; private set; } - /// - /// Gets the unit price of the item. - /// - public uint PricePerUnit { get; private set; } + /// + /// Gets the unit price of the item. + /// + public uint PricePerUnit { get; private set; } - /// - /// Reads market board purchase information from the struct at the provided pointer. - /// - /// A pointer to a struct containing market board purchase information from the client. - /// An object representing the data read. - public static unsafe MarketBoardPurchaseHandler Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); + /// + /// Reads market board purchase information from the struct at the provided pointer. + /// + /// A pointer to a struct containing market board purchase information from the client. + /// An object representing the data read. + public static unsafe MarketBoardPurchaseHandler Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); - var output = new MarketBoardPurchaseHandler(); + var output = new MarketBoardPurchaseHandler(); - output.RetainerId = reader.ReadUInt64(); - output.ListingId = reader.ReadUInt64(); - output.CatalogId = reader.ReadUInt32(); - output.ItemQuantity = reader.ReadUInt32(); - output.PricePerUnit = reader.ReadUInt32(); + output.RetainerId = reader.ReadUInt64(); + output.ListingId = reader.ReadUInt64(); + output.CatalogId = reader.ReadUInt32(); + output.ItemQuantity = reader.ReadUInt32(); + output.PricePerUnit = reader.ReadUInt32(); - return output; + return output; + } } } diff --git a/Dalamud/Game/Network/Structures/MarketTaxRates.cs b/Dalamud/Game/Network/Structures/MarketTaxRates.cs index 654bdd1a3..4fb6a2b13 100644 --- a/Dalamud/Game/Network/Structures/MarketTaxRates.cs +++ b/Dalamud/Game/Network/Structures/MarketTaxRates.cs @@ -1,67 +1,68 @@ using System; using System.IO; -namespace Dalamud.Game.Network.Structures; - -/// -/// This class represents the market tax rates from a game network packet. -/// -public class MarketTaxRates +namespace Dalamud.Game.Network.Structures { - private MarketTaxRates() + /// + /// This class represents the market tax rates from a game network packet. + /// + public class MarketTaxRates { - } + private MarketTaxRates() + { + } - /// - /// Gets the tax rate in Limsa Lominsa. - /// - public uint LimsaLominsaTax { get; private set; } + /// + /// Gets the tax rate in Limsa Lominsa. + /// + public uint LimsaLominsaTax { get; private set; } - /// - /// Gets the tax rate in Gridania. - /// - public uint GridaniaTax { get; private set; } + /// + /// Gets the tax rate in Gridania. + /// + public uint GridaniaTax { get; private set; } - /// - /// Gets the tax rate in Ul'dah. - /// - public uint UldahTax { get; private set; } + /// + /// Gets the tax rate in Ul'dah. + /// + public uint UldahTax { get; private set; } - /// - /// Gets the tax rate in Ishgard. - /// - public uint IshgardTax { get; private set; } + /// + /// Gets the tax rate in Ishgard. + /// + public uint IshgardTax { get; private set; } - /// - /// Gets the tax rate in Kugane. - /// - public uint KuganeTax { get; private set; } + /// + /// Gets the tax rate in Kugane. + /// + public uint KuganeTax { get; private set; } - /// - /// Gets the tax rate in the Crystarium. - /// - public uint CrystariumTax { get; private set; } + /// + /// Gets the tax rate in the Crystarium. + /// + public uint CrystariumTax { get; private set; } - /// - /// Read a object from memory. - /// - /// Address to read. - /// A new object. - public static unsafe MarketTaxRates Read(IntPtr dataPtr) - { - using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); - using var reader = new BinaryReader(stream); + /// + /// Read a object from memory. + /// + /// Address to read. + /// A new object. + public static unsafe MarketTaxRates Read(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); - var output = new MarketTaxRates(); + var output = new MarketTaxRates(); - stream.Position += 8; - output.LimsaLominsaTax = reader.ReadUInt32(); - output.GridaniaTax = reader.ReadUInt32(); - output.UldahTax = reader.ReadUInt32(); - output.IshgardTax = reader.ReadUInt32(); - output.KuganeTax = reader.ReadUInt32(); - output.CrystariumTax = reader.ReadUInt32(); + stream.Position += 8; + output.LimsaLominsaTax = reader.ReadUInt32(); + output.GridaniaTax = reader.ReadUInt32(); + output.UldahTax = reader.ReadUInt32(); + output.IshgardTax = reader.ReadUInt32(); + output.KuganeTax = reader.ReadUInt32(); + output.CrystariumTax = reader.ReadUInt32(); - return output; + return output; + } } } diff --git a/Dalamud/Game/SigScanner.cs b/Dalamud/Game/SigScanner.cs index 70a09c5b1..e35b17cc4 100644 --- a/Dalamud/Game/SigScanner.cs +++ b/Dalamud/Game/SigScanner.cs @@ -9,474 +9,475 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Serilog; -namespace Dalamud.Game; - -/// -/// A SigScanner facilitates searching for memory signatures in a given ProcessModule. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -public sealed class SigScanner : IDisposable +namespace Dalamud.Game { - private IntPtr moduleCopyPtr; - private long moduleCopyOffset; - /// - /// Initializes a new instance of the class using the main module of the current process. + /// A SigScanner facilitates searching for memory signatures in a given ProcessModule. /// - /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. - public SigScanner(bool doCopy = false) - : this(Process.GetCurrentProcess().MainModule!, doCopy) + [PluginInterface] + [InterfaceVersion("1.0")] + public sealed class SigScanner : IDisposable { - } + private IntPtr moduleCopyPtr; + private long moduleCopyOffset; - /// - /// Initializes a new instance of the class. - /// - /// The ProcessModule to be used for scanning. - /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. - public SigScanner(ProcessModule module, bool doCopy = false) - { - this.Module = module; - this.Is32BitProcess = !Environment.Is64BitProcess; - this.IsCopy = doCopy; - - // Limit the search space to .text section. - this.SetupSearchSpace(module); - - if (this.IsCopy) - this.SetupCopiedSegments(); - - Log.Verbose($"Module base: 0x{this.TextSectionBase.ToInt64():X}"); - Log.Verbose($"Module size: 0x{this.TextSectionSize:X}"); - } - - /// - /// Gets a value indicating whether or not the search on this module is performed on a copy. - /// - public bool IsCopy { get; } - - /// - /// Gets a value indicating whether or not the ProcessModule is 32-bit. - /// - public bool Is32BitProcess { get; } - - /// - /// Gets the base address of the search area. When copied, this will be the address of the copy. - /// - public IntPtr SearchBase => this.IsCopy ? this.moduleCopyPtr : this.Module.BaseAddress; - - /// - /// Gets the base address of the .text section search area. - /// - public IntPtr TextSectionBase => new(this.SearchBase.ToInt64() + this.TextSectionOffset); - - /// - /// Gets the offset of the .text section from the base of the module. - /// - public long TextSectionOffset { get; private set; } - - /// - /// Gets the size of the text section. - /// - public int TextSectionSize { get; private set; } - - /// - /// Gets the base address of the .data section search area. - /// - public IntPtr DataSectionBase => new(this.SearchBase.ToInt64() + this.DataSectionOffset); - - /// - /// Gets the offset of the .data section from the base of the module. - /// - public long DataSectionOffset { get; private set; } - - /// - /// Gets the size of the .data section. - /// - public int DataSectionSize { get; private set; } - - /// - /// Gets the base address of the .rdata section search area. - /// - public IntPtr RDataSectionBase => new(this.SearchBase.ToInt64() + this.RDataSectionOffset); - - /// - /// Gets the offset of the .rdata section from the base of the module. - /// - public long RDataSectionOffset { get; private set; } - - /// - /// Gets the size of the .rdata section. - /// - public int RDataSectionSize { get; private set; } - - /// - /// Gets the ProcessModule on which the search is performed. - /// - public ProcessModule Module { get; } - - private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize; - - /// - /// Scan memory for a signature. - /// - /// The base address to scan from. - /// The amount of bytes to scan. - /// The signature to search for. - /// The found offset. - public static IntPtr Scan(IntPtr baseAddress, int size, string signature) - { - var (needle, mask) = ParseSignature(signature); - var index = IndexOf(baseAddress, size, needle, mask); - if (index < 0) - throw new KeyNotFoundException($"Can't find a signature of {signature}"); - return baseAddress + index; - } - - /// - /// Try scanning memory for a signature. - /// - /// The base address to scan from. - /// The amount of bytes to scan. - /// The signature to search for. - /// The offset, if found. - /// true if the signature was found. - public static bool TryScan(IntPtr baseAddress, int size, string signature, out IntPtr result) - { - try + /// + /// Initializes a new instance of the class using the main module of the current process. + /// + /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. + public SigScanner(bool doCopy = false) + : this(Process.GetCurrentProcess().MainModule!, doCopy) { - result = Scan(baseAddress, size, signature); - return true; } - catch (KeyNotFoundException) + + /// + /// Initializes a new instance of the class. + /// + /// The ProcessModule to be used for scanning. + /// Whether or not to copy the module upon initialization for search operations to use, as to not get disturbed by possible hooks. + public SigScanner(ProcessModule module, bool doCopy = false) { - result = IntPtr.Zero; - return false; + this.Module = module; + this.Is32BitProcess = !Environment.Is64BitProcess; + this.IsCopy = doCopy; + + // Limit the search space to .text section. + this.SetupSearchSpace(module); + + if (this.IsCopy) + this.SetupCopiedSegments(); + + Log.Verbose($"Module base: 0x{this.TextSectionBase.ToInt64():X}"); + Log.Verbose($"Module size: 0x{this.TextSectionSize:X}"); } - } - /// - /// Scan for a .data address using a .text function. - /// This is intended to be used with IDA sigs. - /// Place your cursor on the line calling a static address, and create and IDA sig. - /// - /// The signature of the function using the data. - /// The offset from function start of the instruction using the data. - /// An IntPtr to the static memory location. - public IntPtr GetStaticAddressFromSig(string signature, int offset = 0) - { - var instrAddr = this.ScanText(signature); - instrAddr = IntPtr.Add(instrAddr, offset); - var bAddr = (long)this.Module.BaseAddress; - long num; + /// + /// Gets a value indicating whether or not the search on this module is performed on a copy. + /// + public bool IsCopy { get; } - do + /// + /// Gets a value indicating whether or not the ProcessModule is 32-bit. + /// + public bool Is32BitProcess { get; } + + /// + /// Gets the base address of the search area. When copied, this will be the address of the copy. + /// + public IntPtr SearchBase => this.IsCopy ? this.moduleCopyPtr : this.Module.BaseAddress; + + /// + /// Gets the base address of the .text section search area. + /// + public IntPtr TextSectionBase => new(this.SearchBase.ToInt64() + this.TextSectionOffset); + + /// + /// Gets the offset of the .text section from the base of the module. + /// + public long TextSectionOffset { get; private set; } + + /// + /// Gets the size of the text section. + /// + public int TextSectionSize { get; private set; } + + /// + /// Gets the base address of the .data section search area. + /// + public IntPtr DataSectionBase => new(this.SearchBase.ToInt64() + this.DataSectionOffset); + + /// + /// Gets the offset of the .data section from the base of the module. + /// + public long DataSectionOffset { get; private set; } + + /// + /// Gets the size of the .data section. + /// + public int DataSectionSize { get; private set; } + + /// + /// Gets the base address of the .rdata section search area. + /// + public IntPtr RDataSectionBase => new(this.SearchBase.ToInt64() + this.RDataSectionOffset); + + /// + /// Gets the offset of the .rdata section from the base of the module. + /// + public long RDataSectionOffset { get; private set; } + + /// + /// Gets the size of the .rdata section. + /// + public int RDataSectionSize { get; private set; } + + /// + /// Gets the ProcessModule on which the search is performed. + /// + public ProcessModule Module { get; } + + private IntPtr TextSectionTop => this.TextSectionBase + this.TextSectionSize; + + /// + /// Scan memory for a signature. + /// + /// The base address to scan from. + /// The amount of bytes to scan. + /// The signature to search for. + /// The found offset. + public static IntPtr Scan(IntPtr baseAddress, int size, string signature) { - instrAddr = IntPtr.Add(instrAddr, 1); - num = Marshal.ReadInt32(instrAddr) + (long)instrAddr + 4 - bAddr; + var (needle, mask) = ParseSignature(signature); + var index = IndexOf(baseAddress, size, needle, mask); + if (index < 0) + throw new KeyNotFoundException($"Can't find a signature of {signature}"); + return baseAddress + index; } - while (!(num >= this.DataSectionOffset && num <= this.DataSectionOffset + this.DataSectionSize) - && !(num >= this.RDataSectionOffset && num <= this.RDataSectionOffset + this.RDataSectionSize)); - return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4); - } - - /// - /// Try scanning for a .data address using a .text function. - /// This is intended to be used with IDA sigs. - /// Place your cursor on the line calling a static address, and create and IDA sig. - /// - /// The signature of the function using the data. - /// An IntPtr to the static memory location, if found. - /// The offset from function start of the instruction using the data. - /// true if the signature was found. - public bool TryGetStaticAddressFromSig(string signature, out IntPtr result, int offset = 0) - { - try + /// + /// Try scanning memory for a signature. + /// + /// The base address to scan from. + /// The amount of bytes to scan. + /// The signature to search for. + /// The offset, if found. + /// true if the signature was found. + public static bool TryScan(IntPtr baseAddress, int size, string signature, out IntPtr result) { - result = this.GetStaticAddressFromSig(signature, offset); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; - } - } - - /// - /// Scan for a byte signature in the .data section. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanData(string signature) - { - var scanRet = Scan(this.DataSectionBase, this.DataSectionSize, signature); - - if (this.IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - - return scanRet; - } - - /// - /// Try scanning for a byte signature in the .data section. - /// - /// The signature. - /// The real offset of the signature, if found. - /// true if the signature was found. - public bool TryScanData(string signature, out IntPtr result) - { - try - { - result = this.ScanData(signature); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; - } - } - - /// - /// Scan for a byte signature in the whole module search area. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanModule(string signature) - { - var scanRet = Scan(this.SearchBase, this.Module.ModuleMemorySize, signature); - - if (this.IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - - return scanRet; - } - - /// - /// Try scanning for a byte signature in the whole module search area. - /// - /// The signature. - /// The real offset of the signature, if found. - /// true if the signature was found. - public bool TryScanModule(string signature, out IntPtr result) - { - try - { - result = this.ScanModule(signature); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; - } - } - - /// - /// Resolve a RVA address. - /// - /// The address of the next instruction. - /// The relative offset. - /// The calculated offset. - public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) - { - if (this.Is32BitProcess) throw new NotSupportedException("32 bit is not supported."); - return nextInstAddr + relOffset; - } - - /// - /// Scan for a byte signature in the .text section. - /// - /// The signature. - /// The real offset of the found signature. - public IntPtr ScanText(string signature) - { - var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; - - var scanRet = Scan(mBase, this.TextSectionSize, signature); - - if (this.IsCopy) - scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); - - var insnByte = Marshal.ReadByte(scanRet); - - if (insnByte == 0xE8 || insnByte == 0xE9) - return ReadJmpCallSig(scanRet); - - return scanRet; - } - - /// - /// Try scanning for a byte signature in the .text section. - /// - /// The signature. - /// The real offset of the signature, if found. - /// true if the signature was found. - public bool TryScanText(string signature, out IntPtr result) - { - try - { - result = this.ScanText(signature); - return true; - } - catch (KeyNotFoundException) - { - result = IntPtr.Zero; - return false; - } - } - - /// - /// Free the memory of the copied module search area on object disposal, if applicable. - /// - public void Dispose() - { - Marshal.FreeHGlobal(this.moduleCopyPtr); - } - - /// - /// Helper for ScanText to get the correct address for IDA sigs that mark the first JMP or CALL location. - /// - /// The address the JMP or CALL sig resolved to. - /// The real offset of the signature. - private static IntPtr ReadJmpCallSig(IntPtr sigLocation) - { - var jumpOffset = Marshal.ReadInt32(sigLocation, 1); - return IntPtr.Add(sigLocation, 5 + jumpOffset); - } - - private static (byte[] Needle, bool[] Mask) ParseSignature(string signature) - { - signature = signature.Replace(" ", string.Empty); - if (signature.Length % 2 != 0) - throw new ArgumentException("Signature without whitespaces must be divisible by two.", nameof(signature)); - - var needleLength = signature.Length / 2; - var needle = new byte[needleLength]; - var mask = new bool[needleLength]; - for (var i = 0; i < needleLength; i++) - { - var hexString = signature.Substring(i * 2, 2); - if (hexString == "??" || hexString == "**") + try { - needle[i] = 0; - mask[i] = true; - continue; + result = Scan(baseAddress, size, signature); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + /// + /// Scan for a .data address using a .text function. + /// This is intended to be used with IDA sigs. + /// Place your cursor on the line calling a static address, and create and IDA sig. + /// + /// The signature of the function using the data. + /// The offset from function start of the instruction using the data. + /// An IntPtr to the static memory location. + public IntPtr GetStaticAddressFromSig(string signature, int offset = 0) + { + var instrAddr = this.ScanText(signature); + instrAddr = IntPtr.Add(instrAddr, offset); + var bAddr = (long)this.Module.BaseAddress; + long num; + + do + { + instrAddr = IntPtr.Add(instrAddr, 1); + num = Marshal.ReadInt32(instrAddr) + (long)instrAddr + 4 - bAddr; + } + while (!(num >= this.DataSectionOffset && num <= this.DataSectionOffset + this.DataSectionSize) + && !(num >= this.RDataSectionOffset && num <= this.RDataSectionOffset + this.RDataSectionSize)); + + return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4); + } + + /// + /// Try scanning for a .data address using a .text function. + /// This is intended to be used with IDA sigs. + /// Place your cursor on the line calling a static address, and create and IDA sig. + /// + /// The signature of the function using the data. + /// An IntPtr to the static memory location, if found. + /// The offset from function start of the instruction using the data. + /// true if the signature was found. + public bool TryGetStaticAddressFromSig(string signature, out IntPtr result, int offset = 0) + { + try + { + result = this.GetStaticAddressFromSig(signature, offset); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + /// + /// Scan for a byte signature in the .data section. + /// + /// The signature. + /// The real offset of the found signature. + public IntPtr ScanData(string signature) + { + var scanRet = Scan(this.DataSectionBase, this.DataSectionSize, signature); + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + return scanRet; + } + + /// + /// Try scanning for a byte signature in the .data section. + /// + /// The signature. + /// The real offset of the signature, if found. + /// true if the signature was found. + public bool TryScanData(string signature, out IntPtr result) + { + try + { + result = this.ScanData(signature); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + /// + /// Scan for a byte signature in the whole module search area. + /// + /// The signature. + /// The real offset of the found signature. + public IntPtr ScanModule(string signature) + { + var scanRet = Scan(this.SearchBase, this.Module.ModuleMemorySize, signature); + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + return scanRet; + } + + /// + /// Try scanning for a byte signature in the whole module search area. + /// + /// The signature. + /// The real offset of the signature, if found. + /// true if the signature was found. + public bool TryScanModule(string signature, out IntPtr result) + { + try + { + result = this.ScanModule(signature); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + /// + /// Resolve a RVA address. + /// + /// The address of the next instruction. + /// The relative offset. + /// The calculated offset. + public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) + { + if (this.Is32BitProcess) throw new NotSupportedException("32 bit is not supported."); + return nextInstAddr + relOffset; + } + + /// + /// Scan for a byte signature in the .text section. + /// + /// The signature. + /// The real offset of the found signature. + public IntPtr ScanText(string signature) + { + var mBase = this.IsCopy ? this.moduleCopyPtr : this.TextSectionBase; + + var scanRet = Scan(mBase, this.TextSectionSize, signature); + + if (this.IsCopy) + scanRet = new IntPtr(scanRet.ToInt64() - this.moduleCopyOffset); + + var insnByte = Marshal.ReadByte(scanRet); + + if (insnByte == 0xE8 || insnByte == 0xE9) + return ReadJmpCallSig(scanRet); + + return scanRet; + } + + /// + /// Try scanning for a byte signature in the .text section. + /// + /// The signature. + /// The real offset of the signature, if found. + /// true if the signature was found. + public bool TryScanText(string signature, out IntPtr result) + { + try + { + result = this.ScanText(signature); + return true; + } + catch (KeyNotFoundException) + { + result = IntPtr.Zero; + return false; + } + } + + /// + /// Free the memory of the copied module search area on object disposal, if applicable. + /// + public void Dispose() + { + Marshal.FreeHGlobal(this.moduleCopyPtr); + } + + /// + /// Helper for ScanText to get the correct address for IDA sigs that mark the first JMP or CALL location. + /// + /// The address the JMP or CALL sig resolved to. + /// The real offset of the signature. + private static IntPtr ReadJmpCallSig(IntPtr sigLocation) + { + var jumpOffset = Marshal.ReadInt32(sigLocation, 1); + return IntPtr.Add(sigLocation, 5 + jumpOffset); + } + + private static (byte[] Needle, bool[] Mask) ParseSignature(string signature) + { + signature = signature.Replace(" ", string.Empty); + if (signature.Length % 2 != 0) + throw new ArgumentException("Signature without whitespaces must be divisible by two.", nameof(signature)); + + var needleLength = signature.Length / 2; + var needle = new byte[needleLength]; + var mask = new bool[needleLength]; + for (var i = 0; i < needleLength; i++) + { + var hexString = signature.Substring(i * 2, 2); + if (hexString == "??" || hexString == "**") + { + needle[i] = 0; + mask[i] = true; + continue; + } + + needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier); + mask[i] = false; } - needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier); - mask[i] = false; + return (needle, mask); } - return (needle, mask); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe int IndexOf(IntPtr bufferPtr, int bufferLength, byte[] needle, bool[] mask) - { - if (needle.Length > bufferLength) return -1; - var badShift = BuildBadCharTable(needle, mask); - var last = needle.Length - 1; - var offset = 0; - var maxoffset = bufferLength - needle.Length; - var buffer = (byte*)bufferPtr; - - while (offset <= maxoffset) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe int IndexOf(IntPtr bufferPtr, int bufferLength, byte[] needle, bool[] mask) { - int position; - for (position = last; needle[position] == *(buffer + position + offset) || mask[position]; position--) + if (needle.Length > bufferLength) return -1; + var badShift = BuildBadCharTable(needle, mask); + var last = needle.Length - 1; + var offset = 0; + var maxoffset = bufferLength - needle.Length; + var buffer = (byte*)bufferPtr; + + while (offset <= maxoffset) { - if (position == 0) - return offset; + int position; + for (position = last; needle[position] == *(buffer + position + offset) || mask[position]; position--) + { + if (position == 0) + return offset; + } + + offset += badShift[*(buffer + offset + last)]; } - offset += badShift[*(buffer + offset + last)]; + return -1; } - return -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int[] BuildBadCharTable(byte[] needle, bool[] mask) - { - int idx; - var last = needle.Length - 1; - var badShift = new int[256]; - for (idx = last; idx > 0 && !mask[idx]; --idx) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int[] BuildBadCharTable(byte[] needle, bool[] mask) { + int idx; + var last = needle.Length - 1; + var badShift = new int[256]; + for (idx = last; idx > 0 && !mask[idx]; --idx) + { + } + + var diff = last - idx; + if (diff == 0) diff = 1; + + for (idx = 0; idx <= 255; ++idx) + badShift[idx] = diff; + for (idx = last - diff; idx < last; ++idx) + badShift[needle[idx]] = last - idx; + return badShift; } - var diff = last - idx; - if (diff == 0) diff = 1; - - for (idx = 0; idx <= 255; ++idx) - badShift[idx] = diff; - for (idx = last - diff; idx < last; ++idx) - badShift[needle[idx]] = last - idx; - return badShift; - } - - private void SetupSearchSpace(ProcessModule module) - { - var baseAddress = module.BaseAddress; - - // We don't want to read all of IMAGE_DOS_HEADER or IMAGE_NT_HEADER stuff so we cheat here. - var ntNewOffset = Marshal.ReadInt32(baseAddress, 0x3C); - var ntHeader = baseAddress + ntNewOffset; - - // IMAGE_NT_HEADER - var fileHeader = ntHeader + 4; - var numSections = Marshal.ReadInt16(ntHeader, 6); - - // IMAGE_OPTIONAL_HEADER - var optionalHeader = fileHeader + 20; - - IntPtr sectionHeader; - if (this.Is32BitProcess) // IMAGE_OPTIONAL_HEADER32 - sectionHeader = optionalHeader + 224; - else // IMAGE_OPTIONAL_HEADER64 - sectionHeader = optionalHeader + 240; - - // IMAGE_SECTION_HEADER - var sectionCursor = sectionHeader; - for (var i = 0; i < numSections; i++) + private void SetupSearchSpace(ProcessModule module) { - var sectionName = Marshal.ReadInt64(sectionCursor); + var baseAddress = module.BaseAddress; + // We don't want to read all of IMAGE_DOS_HEADER or IMAGE_NT_HEADER stuff so we cheat here. + var ntNewOffset = Marshal.ReadInt32(baseAddress, 0x3C); + var ntHeader = baseAddress + ntNewOffset; + + // IMAGE_NT_HEADER + var fileHeader = ntHeader + 4; + var numSections = Marshal.ReadInt16(ntHeader, 6); + + // IMAGE_OPTIONAL_HEADER + var optionalHeader = fileHeader + 20; + + IntPtr sectionHeader; + if (this.Is32BitProcess) // IMAGE_OPTIONAL_HEADER32 + sectionHeader = optionalHeader + 224; + else // IMAGE_OPTIONAL_HEADER64 + sectionHeader = optionalHeader + 240; + + // IMAGE_SECTION_HEADER + var sectionCursor = sectionHeader; + for (var i = 0; i < numSections; i++) + { + var sectionName = Marshal.ReadInt64(sectionCursor); + + // .text + switch (sectionName) + { + case 0x747865742E: // .text + this.TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12); + this.TextSectionSize = Marshal.ReadInt32(sectionCursor, 8); + break; + case 0x617461642E: // .data + this.DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); + this.DataSectionSize = Marshal.ReadInt32(sectionCursor, 8); + break; + case 0x61746164722E: // .rdata + this.RDataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); + this.RDataSectionSize = Marshal.ReadInt32(sectionCursor, 8); + break; + } + + sectionCursor += 40; + } + } + + private unsafe void SetupCopiedSegments() + { // .text - switch (sectionName) - { - case 0x747865742E: // .text - this.TextSectionOffset = Marshal.ReadInt32(sectionCursor, 12); - this.TextSectionSize = Marshal.ReadInt32(sectionCursor, 8); - break; - case 0x617461642E: // .data - this.DataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); - this.DataSectionSize = Marshal.ReadInt32(sectionCursor, 8); - break; - case 0x61746164722E: // .rdata - this.RDataSectionOffset = Marshal.ReadInt32(sectionCursor, 12); - this.RDataSectionSize = Marshal.ReadInt32(sectionCursor, 8); - break; - } + this.moduleCopyPtr = Marshal.AllocHGlobal(this.Module.ModuleMemorySize); + Buffer.MemoryCopy( + this.Module.BaseAddress.ToPointer(), + this.moduleCopyPtr.ToPointer(), + this.Module.ModuleMemorySize, + this.Module.ModuleMemorySize); - sectionCursor += 40; + this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64(); } } - - private unsafe void SetupCopiedSegments() - { - // .text - this.moduleCopyPtr = Marshal.AllocHGlobal(this.Module.ModuleMemorySize); - Buffer.MemoryCopy( - this.Module.BaseAddress.ToPointer(), - this.moduleCopyPtr.ToPointer(), - this.Module.ModuleMemorySize, - this.Module.ModuleMemorySize); - - this.moduleCopyOffset = this.moduleCopyPtr.ToInt64() - this.Module.BaseAddress.ToInt64(); - } } diff --git a/Dalamud/Game/Text/Sanitizer/ISanitizer.cs b/Dalamud/Game/Text/Sanitizer/ISanitizer.cs index 65603951a..ffaa9cc0a 100644 --- a/Dalamud/Game/Text/Sanitizer/ISanitizer.cs +++ b/Dalamud/Game/Text/Sanitizer/ISanitizer.cs @@ -1,39 +1,40 @@ using System.Collections.Generic; -namespace Dalamud.Game.Text.Sanitizer; - -/// -/// Sanitize strings to remove soft hyphens and other special characters. -/// -public interface ISanitizer +namespace Dalamud.Game.Text.Sanitizer { /// - /// Creates a sanitized string using current clientLanguage. + /// Sanitize strings to remove soft hyphens and other special characters. /// - /// An unsanitized string to sanitize. - /// A sanitized string. - string Sanitize(string unsanitizedString); + public interface ISanitizer + { + /// + /// Creates a sanitized string using current clientLanguage. + /// + /// An unsanitized string to sanitize. + /// A sanitized string. + string Sanitize(string unsanitizedString); - /// - /// Creates a sanitized string using request clientLanguage. - /// - /// An unsanitized string to sanitize. - /// Target language for sanitized strings. - /// A sanitized string. - string Sanitize(string unsanitizedString, ClientLanguage clientLanguage); + /// + /// Creates a sanitized string using request clientLanguage. + /// + /// An unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A sanitized string. + string Sanitize(string unsanitizedString, ClientLanguage clientLanguage); - /// - /// Creates a list of sanitized strings using current clientLanguage. - /// - /// List of unsanitized string to sanitize. - /// A list of sanitized strings. - IEnumerable Sanitize(IEnumerable unsanitizedStrings); + /// + /// Creates a list of sanitized strings using current clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// A list of sanitized strings. + IEnumerable Sanitize(IEnumerable unsanitizedStrings); - /// - /// Creates a list of sanitized strings using requested clientLanguage. - /// - /// List of unsanitized string to sanitize. - /// Target language for sanitized strings. - /// A list of sanitized strings. - IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage); + /// + /// Creates a list of sanitized strings using requested clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A list of sanitized strings. + IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage); + } } diff --git a/Dalamud/Game/Text/Sanitizer/Sanitizer.cs b/Dalamud/Game/Text/Sanitizer/Sanitizer.cs index 0647f5d28..0cf1f1ea6 100644 --- a/Dalamud/Game/Text/Sanitizer/Sanitizer.cs +++ b/Dalamud/Game/Text/Sanitizer/Sanitizer.cs @@ -2,109 +2,110 @@ using System; using System.Collections.Generic; using System.Linq; -namespace Dalamud.Game.Text.Sanitizer; - -/// -/// Sanitize strings to remove soft hyphens and other special characters. -/// -public class Sanitizer : ISanitizer +namespace Dalamud.Game.Text.Sanitizer { - private static readonly Dictionary DESanitizationDict = new() - { - { "\u0020\u2020", string.Empty }, // dagger - }; - - private static readonly Dictionary FRSanitizationDict = new() - { - { "\u0153", "\u006F\u0065" }, // ligature oe - }; - - private readonly ClientLanguage defaultClientLanguage; - /// - /// Initializes a new instance of the class. + /// Sanitize strings to remove soft hyphens and other special characters. /// - /// Default clientLanguage for sanitizing strings. - public Sanitizer(ClientLanguage defaultClientLanguage) + public class Sanitizer : ISanitizer { - this.defaultClientLanguage = defaultClientLanguage; - } - - /// - /// Creates a sanitized string using current clientLanguage. - /// - /// An unsanitized string to sanitize. - /// A sanitized string. - public string Sanitize(string unsanitizedString) - { - return SanitizeByLanguage(unsanitizedString, this.defaultClientLanguage); - } - - /// - /// Creates a sanitized string using request clientLanguage. - /// - /// An unsanitized string to sanitize. - /// Target language for sanitized strings. - /// A sanitized string. - public string Sanitize(string unsanitizedString, ClientLanguage clientLanguage) - { - return SanitizeByLanguage(unsanitizedString, clientLanguage); - } - - /// - /// Creates a list of sanitized strings using current clientLanguage. - /// - /// List of unsanitized string to sanitize. - /// A list of sanitized strings. - public IEnumerable Sanitize(IEnumerable unsanitizedStrings) - { - return SanitizeByLanguage(unsanitizedStrings, this.defaultClientLanguage); - } - - /// - /// Creates a list of sanitized strings using requested clientLanguage. - /// - /// List of unsanitized string to sanitize. - /// Target language for sanitized strings. - /// A list of sanitized strings. - public IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage) - { - return SanitizeByLanguage(unsanitizedStrings, clientLanguage); - } - - private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage) - { - var sanitizedString = FilterUnprintableCharacters(unsanitizedString); - return clientLanguage switch + private static readonly Dictionary DESanitizationDict = new() { - ClientLanguage.Japanese or ClientLanguage.English => sanitizedString, - ClientLanguage.German => FilterByDict(sanitizedString, DESanitizationDict), - ClientLanguage.French => FilterByDict(sanitizedString, FRSanitizationDict), - _ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null), + { "\u0020\u2020", string.Empty }, // dagger }; - } - private static IEnumerable SanitizeByLanguage(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage) - { - return clientLanguage switch + private static readonly Dictionary FRSanitizationDict = new() { - ClientLanguage.Japanese => unsanitizedStrings.Select(FilterUnprintableCharacters), - ClientLanguage.English => unsanitizedStrings.Select(FilterUnprintableCharacters), - ClientLanguage.German => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), DESanitizationDict)), - ClientLanguage.French => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), FRSanitizationDict)), - _ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null), + { "\u0153", "\u006F\u0065" }, // ligature oe }; - } - private static string FilterUnprintableCharacters(string str) - { - return new string(str?.Where(ch => ch >= 0x20).ToArray()); - } + private readonly ClientLanguage defaultClientLanguage; - private static string FilterByDict(string str, Dictionary dict) - { - return dict.Aggregate( - str, (current, kvp) => - current.Replace(kvp.Key, kvp.Value)); + /// + /// Initializes a new instance of the class. + /// + /// Default clientLanguage for sanitizing strings. + public Sanitizer(ClientLanguage defaultClientLanguage) + { + this.defaultClientLanguage = defaultClientLanguage; + } + + /// + /// Creates a sanitized string using current clientLanguage. + /// + /// An unsanitized string to sanitize. + /// A sanitized string. + public string Sanitize(string unsanitizedString) + { + return SanitizeByLanguage(unsanitizedString, this.defaultClientLanguage); + } + + /// + /// Creates a sanitized string using request clientLanguage. + /// + /// An unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A sanitized string. + public string Sanitize(string unsanitizedString, ClientLanguage clientLanguage) + { + return SanitizeByLanguage(unsanitizedString, clientLanguage); + } + + /// + /// Creates a list of sanitized strings using current clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// A list of sanitized strings. + public IEnumerable Sanitize(IEnumerable unsanitizedStrings) + { + return SanitizeByLanguage(unsanitizedStrings, this.defaultClientLanguage); + } + + /// + /// Creates a list of sanitized strings using requested clientLanguage. + /// + /// List of unsanitized string to sanitize. + /// Target language for sanitized strings. + /// A list of sanitized strings. + public IEnumerable Sanitize(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage) + { + return SanitizeByLanguage(unsanitizedStrings, clientLanguage); + } + + private static string SanitizeByLanguage(string unsanitizedString, ClientLanguage clientLanguage) + { + var sanitizedString = FilterUnprintableCharacters(unsanitizedString); + return clientLanguage switch + { + ClientLanguage.Japanese or ClientLanguage.English => sanitizedString, + ClientLanguage.German => FilterByDict(sanitizedString, DESanitizationDict), + ClientLanguage.French => FilterByDict(sanitizedString, FRSanitizationDict), + _ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null), + }; + } + + private static IEnumerable SanitizeByLanguage(IEnumerable unsanitizedStrings, ClientLanguage clientLanguage) + { + return clientLanguage switch + { + ClientLanguage.Japanese => unsanitizedStrings.Select(FilterUnprintableCharacters), + ClientLanguage.English => unsanitizedStrings.Select(FilterUnprintableCharacters), + ClientLanguage.German => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), DESanitizationDict)), + ClientLanguage.French => unsanitizedStrings.Select(original => FilterByDict(FilterUnprintableCharacters(original), FRSanitizationDict)), + _ => throw new ArgumentOutOfRangeException(nameof(clientLanguage), clientLanguage, null), + }; + } + + private static string FilterUnprintableCharacters(string str) + { + return new string(str?.Where(ch => ch >= 0x20).ToArray()); + } + + private static string FilterByDict(string str, Dictionary dict) + { + return dict.Aggregate( + str, (current, kvp) => + current.Replace(kvp.Key, kvp.Value)); + } } } diff --git a/Dalamud/Game/Text/SeIconChar.cs b/Dalamud/Game/Text/SeIconChar.cs index cf162ff63..539868135 100644 --- a/Dalamud/Game/Text/SeIconChar.cs +++ b/Dalamud/Game/Text/SeIconChar.cs @@ -1,742 +1,743 @@ -namespace Dalamud.Game.Text; - -/// -/// Special unicode characters with game-related symbols that work both in-game and in any dalamud window. -/// -public enum SeIconChar +namespace Dalamud.Game.Text { /// - /// The sprout icon unicode character. - /// - BotanistSprout = 0xE034, - - /// - /// The item level icon unicode character. - /// - ItemLevel = 0xE033, - - /// - /// The auto translate open icon unicode character. - /// - AutoTranslateOpen = 0xE040, - - /// - /// The auto translate close icon unicode character. - /// - AutoTranslateClose = 0xE041, - - /// - /// The high quality icon unicode character. - /// - HighQuality = 0xE03C, - - /// - /// The clock icon unicode character. - /// - Clock = 0xE031, - - /// - /// The gil icon unicode character. - /// - Gil = 0xE049, - - /// - /// The Hydaelyn icon unicode character. - /// - Hyadelyn = 0xE048, - - /// - /// The no mouse click icon unicode character. - /// - MouseNoClick = 0xE050, - - /// - /// The left mouse click icon unicode character. - /// - MouseLeftClick = 0xE051, - - /// - /// The right mouse click icon unicode character. - /// - MouseRightClick = 0xE052, - - /// - /// The left/right mouse click icon unicode character. - /// - MouseBothClick = 0xE053, - - /// - /// The mouse wheel icon unicode character. - /// - MouseWheel = 0xE054, - - /// - /// The mouse with a 1 icon unicode character. - /// - Mouse1 = 0xE055, - - /// - /// The mouse with a 2 icon unicode character. - /// - Mouse2 = 0xE056, - - /// - /// The mouse with a 3 icon unicode character. - /// - Mouse3 = 0xE057, - - /// - /// The mouse with a 4 icon unicode character. - /// - Mouse4 = 0xE058, - - /// - /// The mouse with a 5 icon unicode character. - /// - Mouse5 = 0xE059, - - /// - /// The level English icon unicode character. - /// - LevelEn = 0xE06A, - - /// - /// The level German icon unicode character. - /// - LevelDe = 0xE06B, - - /// - /// The level French icon unicode character. - /// - LevelFr = 0xE06C, - - /// - /// The experience icon unicode character. - /// - Experience = 0xE0BC, - - /// - /// The experience filled icon unicode character. - /// - ExperienceFilled = 0xE0BD, - - /// - /// The A.M. time icon unicode character. - /// - TimeAm = 0xE06D, - - /// - /// The P.M. time icon unicode character. - /// - TimePm = 0xE06E, - - /// - /// The right arrow icon unicode character. - /// - ArrowRight = 0xE06F, - - /// - /// The down arrow icon unicode character. - /// - ArrowDown = 0xE035, - - /// - /// The number 0 icon unicode character. - /// - Number0 = 0xE060, - - /// - /// The number 1 icon unicode character. - /// - Number1 = 0xE061, - - /// - /// The number 2 icon unicode character. - /// - Number2 = 0xE062, - - /// - /// The number 3 icon unicode character. - /// - Number3 = 0xE063, - - /// - /// The number 4 icon unicode character. - /// - Number4 = 0xE064, - - /// - /// The number 5 icon unicode character. - /// - Number5 = 0xE065, - - /// - /// The number 6 icon unicode character. - /// - Number6 = 0xE066, - - /// - /// The number 7 icon unicode character. - /// - Number7 = 0xE067, - - /// - /// The number 8 icon unicode character. - /// - Number8 = 0xE068, - - /// - /// The number 9 icon unicode character. - /// - Number9 = 0xE069, - - /// - /// The boxed number 0 icon unicode character. - /// - BoxedNumber0 = 0xE08F, - - /// - /// The boxed number 1 icon unicode character. - /// - BoxedNumber1 = 0xE090, - - /// - /// The boxed number 2 icon unicode character. - /// - BoxedNumber2 = 0xE091, - - /// - /// The boxed number 3 icon unicode character. - /// - BoxedNumber3 = 0xE092, - - /// - /// The boxed number 4 icon unicode character. - /// - BoxedNumber4 = 0xE093, - - /// - /// The boxed number 5 icon unicode character. - /// - BoxedNumber5 = 0xE094, - - /// - /// The boxed number 6 icon unicode character. - /// - BoxedNumber6 = 0xE095, - - /// - /// The boxed number 7 icon unicode character. - /// - BoxedNumber7 = 0xE096, - - /// - /// The boxed number 8 icon unicode character. - /// - BoxedNumber8 = 0xE097, - - /// - /// The boxed number 9 icon unicode character. - /// - BoxedNumber9 = 0xE098, - - /// - /// The boxed number 10 icon unicode character. - /// - BoxedNumber10 = 0xE099, - - /// - /// The boxed number 11 icon unicode character. - /// - BoxedNumber11 = 0xE09A, - - /// - /// The boxed number 12 icon unicode character. - /// - BoxedNumber12 = 0xE09B, - - /// - /// The boxed number 13 icon unicode character. - /// - BoxedNumber13 = 0xE09C, - - /// - /// The boxed number 14 icon unicode character. - /// - BoxedNumber14 = 0xE09D, - - /// - /// The boxed number 15 icon unicode character. - /// - BoxedNumber15 = 0xE09E, - - /// - /// The boxed number 16 icon unicode character. - /// - BoxedNumber16 = 0xE09F, - - /// - /// The boxed number 17 icon unicode character. - /// - BoxedNumber17 = 0xE0A0, - - /// - /// The boxed number 18 icon unicode character. - /// - BoxedNumber18 = 0xE0A1, - - /// - /// The boxed number 19 icon unicode character. - /// - BoxedNumber19 = 0xE0A2, - - /// - /// The boxed number 20 icon unicode character. - /// - BoxedNumber20 = 0xE0A3, - - /// - /// The boxed number 21 icon unicode character. - /// - BoxedNumber21 = 0xE0A4, - - /// - /// The boxed number 22 icon unicode character. - /// - BoxedNumber22 = 0xE0A5, - - /// - /// The boxed number 23 icon unicode character. - /// - BoxedNumber23 = 0xE0A6, - - /// - /// The boxed number 24 icon unicode character. - /// - BoxedNumber24 = 0xE0A7, - - /// - /// The boxed number 25 icon unicode character. - /// - BoxedNumber25 = 0xE0A8, - - /// - /// The boxed number 26 icon unicode character. - /// - BoxedNumber26 = 0xE0A9, - - /// - /// The boxed number 27 icon unicode character. - /// - BoxedNumber27 = 0xE0AA, - - /// - /// The boxed number 28 icon unicode character. - /// - BoxedNumber28 = 0xE0AB, - - /// - /// The boxed number 29 icon unicode character. - /// - BoxedNumber29 = 0xE0AC, - - /// - /// The boxed number 30 icon unicode character. - /// - BoxedNumber30 = 0xE0AD, - - /// - /// The boxed number 31 icon unicode character. - /// - BoxedNumber31 = 0xE0AE, - - /// - /// The boxed plus icon unicode character. - /// - BoxedPlus = 0xE0AF, - - /// - /// The bosed question mark icon unicode character. - /// - BoxedQuestionMark = 0xE070, - - /// - /// The boxed star icon unicode character. - /// - BoxedStar = 0xE0C0, - - /// - /// The boxed Roman numeral 1 (I) icon unicode character. - /// - BoxedRoman1 = 0xE0C1, - - /// - /// The boxed Roman numeral 2 (II) icon unicode character. - /// - BoxedRoman2 = 0xE0C2, - - /// - /// The boxed Roman numeral 3 (III) icon unicode character. - /// - BoxedRoman3 = 0xE0C3, - - /// - /// The boxed Roman numeral 4 (IV) icon unicode character. - /// - BoxedRoman4 = 0xE0C4, - - /// - /// The boxed Roman numeral 5 (V) icon unicode character. - /// - BoxedRoman5 = 0xE0C5, - - /// - /// The boxed Roman numeral 6 (VI) icon unicode character. - /// - BoxedRoman6 = 0xE0C6, - - /// - /// The boxed letter A icon unicode character. - /// - BoxedLetterA = 0xE071, - - /// - /// The boxed letter B icon unicode character. - /// - BoxedLetterB = 0xE072, - - /// - /// The boxed letter C icon unicode character. - /// - BoxedLetterC = 0xE073, - - /// - /// The boxed letter D icon unicode character. - /// - BoxedLetterD = 0xE074, - - /// - /// The boxed letter E icon unicode character. - /// - BoxedLetterE = 0xE075, - - /// - /// The boxed letter F icon unicode character. - /// - BoxedLetterF = 0xE076, - - /// - /// The boxed letter G icon unicode character. - /// - BoxedLetterG = 0xE077, - - /// - /// The boxed letter H icon unicode character. - /// - BoxedLetterH = 0xE078, - - /// - /// The boxed letter I icon unicode character. - /// - BoxedLetterI = 0xE079, - - /// - /// The boxed letter J icon unicode character. - /// - BoxedLetterJ = 0xE07A, - - /// - /// The boxed letter K icon unicode character. - /// - BoxedLetterK = 0xE07B, - - /// - /// The boxed letter L icon unicode character. - /// - BoxedLetterL = 0xE07C, - - /// - /// The boxed letter M icon unicode character. - /// - BoxedLetterM = 0xE07D, - - /// - /// The boxed letter N icon unicode character. - /// - BoxedLetterN = 0xE07E, - - /// - /// The boxed letter O icon unicode character. - /// - BoxedLetterO = 0xE07F, - - /// - /// The boxed letter P icon unicode character. - /// - BoxedLetterP = 0xE080, - - /// - /// The boxed letter Q icon unicode character. - /// - BoxedLetterQ = 0xE081, - - /// - /// The boxed letter R icon unicode character. - /// - BoxedLetterR = 0xE082, - - /// - /// The boxed letter S icon unicode character. - /// - BoxedLetterS = 0xE083, - - /// - /// The boxed letter T icon unicode character. - /// - BoxedLetterT = 0xE084, - - /// - /// The boxed letter U icon unicode character. - /// - BoxedLetterU = 0xE085, - - /// - /// The boxed letter V icon unicode character. - /// - BoxedLetterV = 0xE086, - - /// - /// The boxed letter W icon unicode character. - /// - BoxedLetterW = 0xE087, - - /// - /// The boxed letter X icon unicode character. - /// - BoxedLetterX = 0xE088, - - /// - /// The boxed letter Y icon unicode character. - /// - BoxedLetterY = 0xE089, - - /// - /// The boxed letter Z icon unicode character. - /// - BoxedLetterZ = 0xE08A, - - /// - /// The circle icon unicode character. - /// - Circle = 0xE04A, - - /// - /// The square icon unicode character. - /// - Square = 0xE04B, - - /// - /// The cross icon unicode character. - /// - Cross = 0xE04C, - - /// - /// The triangle icon unicode character. - /// - Triangle = 0xE04D, - - /// - /// The hexagon icon unicode character. - /// - Hexagon = 0xE042, - - /// - /// The no-circle/prohobited icon unicode character. - /// - Prohibited = 0xE043, - - /// - /// The dice icon unicode character. - /// - Dice = 0xE03E, - - /// - /// The debuff icon unicode character. - /// - Debuff = 0xE05B, - - /// - /// The buff icon unicode character. - /// - Buff = 0xE05C, - - /// - /// The cross-world icon unicode character. - /// - CrossWorld = 0xE05D, - - /// - /// The Eureka level icon unicode character. - /// - EurekaLevel = 0xE03A, - - /// - /// The link marker icon unicode character. - /// - LinkMarker = 0xE0BB, - - /// - /// The glamoured icon unicode character. - /// - Glamoured = 0xE03B, - - /// - /// The glamoured and dyed icon unicode character. - /// - GlamouredDyed = 0xE04E, - - /// - /// The synced quest icon unicode character. - /// - QuestSync = 0xE0BE, - - /// - /// The repeatable quest icon unicode character. - /// - QuestRepeatable = 0xE0BF, - - /// - /// The IME hiragana icon unicode character. - /// - ImeHiragana = 0xE020, - - /// - /// The IME katakana icon unicode character. - /// - ImeKatakana = 0xE021, - - /// - /// The IME alphanumeric icon unicode character. - /// - ImeAlphanumeric = 0xE022, - - /// - /// The IME katakana half-width icon unicode character. - /// - ImeKatakanaHalfWidth = 0xE023, - - /// - /// The IME alphanumeric half-width icon unicode character. - /// - ImeAlphanumericHalfWidth = 0xE024, - - /// - /// The instance (1) icon unicode character. - /// - Instance1 = 0xE0B1, - - /// - /// The instance (2) icon unicode character. - /// - Instance2 = 0xE0B2, - - /// - /// The instance (3) icon unicode character. - /// - Instance3 = 0xE0B3, - - /// - /// The instance (4) icon unicode character. - /// - Instance4 = 0xE0B4, - - /// - /// The instance (5) icon unicode character. - /// - Instance5 = 0xE0B5, - - /// - /// The instance (6) icon unicode character. - /// - Instance6 = 0xE0B6, - - /// - /// The instance (7) icon unicode character. - /// - Instance7 = 0xE0B7, - - /// - /// The instance (8) icon unicode character. - /// - Instance8 = 0xE0B8, - - /// - /// The instance (9) icon unicode character. - /// - Instance9 = 0xE0B9, - - /// - /// The instance merged icon unicode character. - /// - InstanceMerged = 0xE0BA, - - /// - /// The English local time icon unicode character. - /// - LocalTimeEn = 0xE0D0, - - /// - /// The English server time icon unicode character. - /// - ServerTimeEn = 0xE0D1, - - /// - /// The English Eorzea time icon unicode character. - /// - EorzeaTimeEn = 0xE0D2, - - /// - /// The German local time icon unicode character. - /// - LocalTimeDe = 0xE0D3, - - /// - /// The German server time icon unicode character. - /// - ServerTimeDe = 0xE0D4, - - /// - /// The German Eorzea time icon unicode character. - /// - EorzeaTimeDe = 0xE0D5, - - /// - /// The French local time icon unicode character. - /// - LocalTimeFr = 0xE0D6, - - /// - /// The French server time icon unicode character. - /// - ServerTimeFr = 0xE0D7, - - /// - /// The French Eorzea time icon unicode character. - /// - EorzeaTimeFr = 0xE0D8, - - /// - /// The Japanese local time icon unicode character. - /// - LocalTimeJa = 0xE0D9, - - /// - /// The Japanese server time icon unicode character. - /// - ServerTimeJa = 0xE0DA, - - /// - /// The Japanese Eorzea time icon unicode character. - /// - EorzeaTimeJa = 0xE0DB, + /// Special unicode characters with game-related symbols that work both in-game and in any dalamud window. + /// + public enum SeIconChar + { + /// + /// The sprout icon unicode character. + /// + BotanistSprout = 0xE034, + + /// + /// The item level icon unicode character. + /// + ItemLevel = 0xE033, + + /// + /// The auto translate open icon unicode character. + /// + AutoTranslateOpen = 0xE040, + + /// + /// The auto translate close icon unicode character. + /// + AutoTranslateClose = 0xE041, + + /// + /// The high quality icon unicode character. + /// + HighQuality = 0xE03C, + + /// + /// The clock icon unicode character. + /// + Clock = 0xE031, + + /// + /// The gil icon unicode character. + /// + Gil = 0xE049, + + /// + /// The Hydaelyn icon unicode character. + /// + Hyadelyn = 0xE048, + + /// + /// The no mouse click icon unicode character. + /// + MouseNoClick = 0xE050, + + /// + /// The left mouse click icon unicode character. + /// + MouseLeftClick = 0xE051, + + /// + /// The right mouse click icon unicode character. + /// + MouseRightClick = 0xE052, + + /// + /// The left/right mouse click icon unicode character. + /// + MouseBothClick = 0xE053, + + /// + /// The mouse wheel icon unicode character. + /// + MouseWheel = 0xE054, + + /// + /// The mouse with a 1 icon unicode character. + /// + Mouse1 = 0xE055, + + /// + /// The mouse with a 2 icon unicode character. + /// + Mouse2 = 0xE056, + + /// + /// The mouse with a 3 icon unicode character. + /// + Mouse3 = 0xE057, + + /// + /// The mouse with a 4 icon unicode character. + /// + Mouse4 = 0xE058, + + /// + /// The mouse with a 5 icon unicode character. + /// + Mouse5 = 0xE059, + + /// + /// The level English icon unicode character. + /// + LevelEn = 0xE06A, + + /// + /// The level German icon unicode character. + /// + LevelDe = 0xE06B, + + /// + /// The level French icon unicode character. + /// + LevelFr = 0xE06C, + + /// + /// The experience icon unicode character. + /// + Experience = 0xE0BC, + + /// + /// The experience filled icon unicode character. + /// + ExperienceFilled = 0xE0BD, + + /// + /// The A.M. time icon unicode character. + /// + TimeAm = 0xE06D, + + /// + /// The P.M. time icon unicode character. + /// + TimePm = 0xE06E, + + /// + /// The right arrow icon unicode character. + /// + ArrowRight = 0xE06F, + + /// + /// The down arrow icon unicode character. + /// + ArrowDown = 0xE035, + + /// + /// The number 0 icon unicode character. + /// + Number0 = 0xE060, + + /// + /// The number 1 icon unicode character. + /// + Number1 = 0xE061, + + /// + /// The number 2 icon unicode character. + /// + Number2 = 0xE062, + + /// + /// The number 3 icon unicode character. + /// + Number3 = 0xE063, + + /// + /// The number 4 icon unicode character. + /// + Number4 = 0xE064, + + /// + /// The number 5 icon unicode character. + /// + Number5 = 0xE065, + + /// + /// The number 6 icon unicode character. + /// + Number6 = 0xE066, + + /// + /// The number 7 icon unicode character. + /// + Number7 = 0xE067, + + /// + /// The number 8 icon unicode character. + /// + Number8 = 0xE068, + + /// + /// The number 9 icon unicode character. + /// + Number9 = 0xE069, + + /// + /// The boxed number 0 icon unicode character. + /// + BoxedNumber0 = 0xE08F, + + /// + /// The boxed number 1 icon unicode character. + /// + BoxedNumber1 = 0xE090, + + /// + /// The boxed number 2 icon unicode character. + /// + BoxedNumber2 = 0xE091, + + /// + /// The boxed number 3 icon unicode character. + /// + BoxedNumber3 = 0xE092, + + /// + /// The boxed number 4 icon unicode character. + /// + BoxedNumber4 = 0xE093, + + /// + /// The boxed number 5 icon unicode character. + /// + BoxedNumber5 = 0xE094, + + /// + /// The boxed number 6 icon unicode character. + /// + BoxedNumber6 = 0xE095, + + /// + /// The boxed number 7 icon unicode character. + /// + BoxedNumber7 = 0xE096, + + /// + /// The boxed number 8 icon unicode character. + /// + BoxedNumber8 = 0xE097, + + /// + /// The boxed number 9 icon unicode character. + /// + BoxedNumber9 = 0xE098, + + /// + /// The boxed number 10 icon unicode character. + /// + BoxedNumber10 = 0xE099, + + /// + /// The boxed number 11 icon unicode character. + /// + BoxedNumber11 = 0xE09A, + + /// + /// The boxed number 12 icon unicode character. + /// + BoxedNumber12 = 0xE09B, + + /// + /// The boxed number 13 icon unicode character. + /// + BoxedNumber13 = 0xE09C, + + /// + /// The boxed number 14 icon unicode character. + /// + BoxedNumber14 = 0xE09D, + + /// + /// The boxed number 15 icon unicode character. + /// + BoxedNumber15 = 0xE09E, + + /// + /// The boxed number 16 icon unicode character. + /// + BoxedNumber16 = 0xE09F, + + /// + /// The boxed number 17 icon unicode character. + /// + BoxedNumber17 = 0xE0A0, + + /// + /// The boxed number 18 icon unicode character. + /// + BoxedNumber18 = 0xE0A1, + + /// + /// The boxed number 19 icon unicode character. + /// + BoxedNumber19 = 0xE0A2, + + /// + /// The boxed number 20 icon unicode character. + /// + BoxedNumber20 = 0xE0A3, + + /// + /// The boxed number 21 icon unicode character. + /// + BoxedNumber21 = 0xE0A4, + + /// + /// The boxed number 22 icon unicode character. + /// + BoxedNumber22 = 0xE0A5, + + /// + /// The boxed number 23 icon unicode character. + /// + BoxedNumber23 = 0xE0A6, + + /// + /// The boxed number 24 icon unicode character. + /// + BoxedNumber24 = 0xE0A7, + + /// + /// The boxed number 25 icon unicode character. + /// + BoxedNumber25 = 0xE0A8, + + /// + /// The boxed number 26 icon unicode character. + /// + BoxedNumber26 = 0xE0A9, + + /// + /// The boxed number 27 icon unicode character. + /// + BoxedNumber27 = 0xE0AA, + + /// + /// The boxed number 28 icon unicode character. + /// + BoxedNumber28 = 0xE0AB, + + /// + /// The boxed number 29 icon unicode character. + /// + BoxedNumber29 = 0xE0AC, + + /// + /// The boxed number 30 icon unicode character. + /// + BoxedNumber30 = 0xE0AD, + + /// + /// The boxed number 31 icon unicode character. + /// + BoxedNumber31 = 0xE0AE, + + /// + /// The boxed plus icon unicode character. + /// + BoxedPlus = 0xE0AF, + + /// + /// The bosed question mark icon unicode character. + /// + BoxedQuestionMark = 0xE070, + + /// + /// The boxed star icon unicode character. + /// + BoxedStar = 0xE0C0, + + /// + /// The boxed Roman numeral 1 (I) icon unicode character. + /// + BoxedRoman1 = 0xE0C1, + + /// + /// The boxed Roman numeral 2 (II) icon unicode character. + /// + BoxedRoman2 = 0xE0C2, + + /// + /// The boxed Roman numeral 3 (III) icon unicode character. + /// + BoxedRoman3 = 0xE0C3, + + /// + /// The boxed Roman numeral 4 (IV) icon unicode character. + /// + BoxedRoman4 = 0xE0C4, + + /// + /// The boxed Roman numeral 5 (V) icon unicode character. + /// + BoxedRoman5 = 0xE0C5, + + /// + /// The boxed Roman numeral 6 (VI) icon unicode character. + /// + BoxedRoman6 = 0xE0C6, + + /// + /// The boxed letter A icon unicode character. + /// + BoxedLetterA = 0xE071, + + /// + /// The boxed letter B icon unicode character. + /// + BoxedLetterB = 0xE072, + + /// + /// The boxed letter C icon unicode character. + /// + BoxedLetterC = 0xE073, + + /// + /// The boxed letter D icon unicode character. + /// + BoxedLetterD = 0xE074, + + /// + /// The boxed letter E icon unicode character. + /// + BoxedLetterE = 0xE075, + + /// + /// The boxed letter F icon unicode character. + /// + BoxedLetterF = 0xE076, + + /// + /// The boxed letter G icon unicode character. + /// + BoxedLetterG = 0xE077, + + /// + /// The boxed letter H icon unicode character. + /// + BoxedLetterH = 0xE078, + + /// + /// The boxed letter I icon unicode character. + /// + BoxedLetterI = 0xE079, + + /// + /// The boxed letter J icon unicode character. + /// + BoxedLetterJ = 0xE07A, + + /// + /// The boxed letter K icon unicode character. + /// + BoxedLetterK = 0xE07B, + + /// + /// The boxed letter L icon unicode character. + /// + BoxedLetterL = 0xE07C, + + /// + /// The boxed letter M icon unicode character. + /// + BoxedLetterM = 0xE07D, + + /// + /// The boxed letter N icon unicode character. + /// + BoxedLetterN = 0xE07E, + + /// + /// The boxed letter O icon unicode character. + /// + BoxedLetterO = 0xE07F, + + /// + /// The boxed letter P icon unicode character. + /// + BoxedLetterP = 0xE080, + + /// + /// The boxed letter Q icon unicode character. + /// + BoxedLetterQ = 0xE081, + + /// + /// The boxed letter R icon unicode character. + /// + BoxedLetterR = 0xE082, + + /// + /// The boxed letter S icon unicode character. + /// + BoxedLetterS = 0xE083, + + /// + /// The boxed letter T icon unicode character. + /// + BoxedLetterT = 0xE084, + + /// + /// The boxed letter U icon unicode character. + /// + BoxedLetterU = 0xE085, + + /// + /// The boxed letter V icon unicode character. + /// + BoxedLetterV = 0xE086, + + /// + /// The boxed letter W icon unicode character. + /// + BoxedLetterW = 0xE087, + + /// + /// The boxed letter X icon unicode character. + /// + BoxedLetterX = 0xE088, + + /// + /// The boxed letter Y icon unicode character. + /// + BoxedLetterY = 0xE089, + + /// + /// The boxed letter Z icon unicode character. + /// + BoxedLetterZ = 0xE08A, + + /// + /// The circle icon unicode character. + /// + Circle = 0xE04A, + + /// + /// The square icon unicode character. + /// + Square = 0xE04B, + + /// + /// The cross icon unicode character. + /// + Cross = 0xE04C, + + /// + /// The triangle icon unicode character. + /// + Triangle = 0xE04D, + + /// + /// The hexagon icon unicode character. + /// + Hexagon = 0xE042, + + /// + /// The no-circle/prohobited icon unicode character. + /// + Prohibited = 0xE043, + + /// + /// The dice icon unicode character. + /// + Dice = 0xE03E, + + /// + /// The debuff icon unicode character. + /// + Debuff = 0xE05B, + + /// + /// The buff icon unicode character. + /// + Buff = 0xE05C, + + /// + /// The cross-world icon unicode character. + /// + CrossWorld = 0xE05D, + + /// + /// The Eureka level icon unicode character. + /// + EurekaLevel = 0xE03A, + + /// + /// The link marker icon unicode character. + /// + LinkMarker = 0xE0BB, + + /// + /// The glamoured icon unicode character. + /// + Glamoured = 0xE03B, + + /// + /// The glamoured and dyed icon unicode character. + /// + GlamouredDyed = 0xE04E, + + /// + /// The synced quest icon unicode character. + /// + QuestSync = 0xE0BE, + + /// + /// The repeatable quest icon unicode character. + /// + QuestRepeatable = 0xE0BF, + + /// + /// The IME hiragana icon unicode character. + /// + ImeHiragana = 0xE020, + + /// + /// The IME katakana icon unicode character. + /// + ImeKatakana = 0xE021, + + /// + /// The IME alphanumeric icon unicode character. + /// + ImeAlphanumeric = 0xE022, + + /// + /// The IME katakana half-width icon unicode character. + /// + ImeKatakanaHalfWidth = 0xE023, + + /// + /// The IME alphanumeric half-width icon unicode character. + /// + ImeAlphanumericHalfWidth = 0xE024, + + /// + /// The instance (1) icon unicode character. + /// + Instance1 = 0xE0B1, + + /// + /// The instance (2) icon unicode character. + /// + Instance2 = 0xE0B2, + + /// + /// The instance (3) icon unicode character. + /// + Instance3 = 0xE0B3, + + /// + /// The instance (4) icon unicode character. + /// + Instance4 = 0xE0B4, + + /// + /// The instance (5) icon unicode character. + /// + Instance5 = 0xE0B5, + + /// + /// The instance (6) icon unicode character. + /// + Instance6 = 0xE0B6, + + /// + /// The instance (7) icon unicode character. + /// + Instance7 = 0xE0B7, + + /// + /// The instance (8) icon unicode character. + /// + Instance8 = 0xE0B8, + + /// + /// The instance (9) icon unicode character. + /// + Instance9 = 0xE0B9, + + /// + /// The instance merged icon unicode character. + /// + InstanceMerged = 0xE0BA, + + /// + /// The English local time icon unicode character. + /// + LocalTimeEn = 0xE0D0, + + /// + /// The English server time icon unicode character. + /// + ServerTimeEn = 0xE0D1, + + /// + /// The English Eorzea time icon unicode character. + /// + EorzeaTimeEn = 0xE0D2, + + /// + /// The German local time icon unicode character. + /// + LocalTimeDe = 0xE0D3, + + /// + /// The German server time icon unicode character. + /// + ServerTimeDe = 0xE0D4, + + /// + /// The German Eorzea time icon unicode character. + /// + EorzeaTimeDe = 0xE0D5, + + /// + /// The French local time icon unicode character. + /// + LocalTimeFr = 0xE0D6, + + /// + /// The French server time icon unicode character. + /// + ServerTimeFr = 0xE0D7, + + /// + /// The French Eorzea time icon unicode character. + /// + EorzeaTimeFr = 0xE0D8, + + /// + /// The Japanese local time icon unicode character. + /// + LocalTimeJa = 0xE0D9, + + /// + /// The Japanese server time icon unicode character. + /// + ServerTimeJa = 0xE0DA, + + /// + /// The Japanese Eorzea time icon unicode character. + /// + EorzeaTimeJa = 0xE0DB, + } } diff --git a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs index 32acc0ad5..6b82100e2 100644 --- a/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs +++ b/Dalamud/Game/Text/SeStringHandling/BitmapFontIcon.cs @@ -1,437 +1,438 @@ -namespace Dalamud.Game.Text.SeStringHandling; - -/// -/// This class represents special icons that can appear in chat naturally or as IconPayloads. -/// -public enum BitmapFontIcon : uint +namespace Dalamud.Game.Text.SeStringHandling { /// - /// No icon. - /// - None = 0, - - /// - /// The controller D-pad up icon. - /// - ControllerDPadUp = 1, - - /// - /// The controller D-pad down icon. - /// - ControllerDPadDown = 2, - - /// - /// The controller D-pad left icon. - /// - ControllerDPadLeft = 3, - - /// - /// The controller D-pad right icon. - /// - ControllerDPadRight = 4, - - /// - /// The controller D-pad up/down icon. - /// - ControllerDPadUpDown = 5, - - /// - /// The controller D-pad left/right icon. - /// - ControllerDPadLeftRight = 6, - - /// - /// The controller D-pad all directions icon. - /// - ControllerDPadAll = 7, - - /// - /// The controller button 0 icon (Xbox: B, PlayStation: Circle). - /// - ControllerButton0 = 8, - - /// - /// The controller button 1 icon (XBox: A, PlayStation: Cross). - /// - ControllerButton1 = 9, - - /// - /// The controller button 2 icon (XBox: X, PlayStation: Square). - /// - ControllerButton2 = 10, - - /// - /// The controller button 3 icon (BBox: Y, PlayStation: Triangle). - /// - ControllerButton3 = 11, - - /// - /// The controller left shoulder button icon. - /// - ControllerShoulderLeft = 12, - - /// - /// The controller right shoulder button icon. - /// - ControllerShoulderRight = 13, - - /// - /// The controller left trigger button icon. - /// - ControllerTriggerLeft = 14, - - /// - /// The controller right trigger button icon. - /// - ControllerTriggerRight = 15, - - /// - /// The controller left analog stick in icon. - /// - ControllerAnalogLeftStickIn = 16, - - /// - /// The controller right analog stick in icon. - /// - ControllerAnalogRightStickIn = 17, - - /// - /// The controller start button icon. - /// - ControllerStart = 18, - - /// - /// The controller back button icon. - /// - ControllerBack = 19, - - /// - /// The controller left analog stick icon. - /// - ControllerAnalogLeftStick = 20, - - /// - /// The controller left analog stick up/down icon. - /// - ControllerAnalogLeftStickUpDown = 21, - - /// - /// The controller left analog stick left/right icon. - /// - ControllerAnalogLeftStickLeftRight = 22, - - /// - /// The controller right analog stick icon. - /// - ControllerAnalogRightStick = 23, - - /// - /// The controller right analog stick up/down icon. - /// - ControllerAnalogRightStickUpDown = 24, - - /// - /// The controller right analog stick left/right icon. - /// - ControllerAnalogRightStickLeftRight = 25, - - /// - /// The La Noscea region icon. - /// - LaNoscea = 51, - - /// - /// The Black Shroud region icon. - /// - BlackShroud = 52, - - /// - /// The Thanalan region icon. - /// - Thanalan = 53, - - /// - /// The auto translate begin icon. - /// - AutoTranslateBegin = 54, - - /// - /// The auto translate end icon. - /// - AutoTranslateEnd = 55, - - /// - /// The fire element icon. - /// - ElementFire = 56, - - /// - /// The ice element icon. - /// - ElementIce = 57, - - /// - /// The wind element icon. - /// - ElementWind = 58, - - /// - /// The earth element icon. - /// - ElementEarth = 59, - - /// - /// The lightning element icon. - /// - ElementLightning = 60, - - /// - /// The water element icon. - /// - ElementWater = 61, - - /// - /// The level sync icon. - /// - LevelSync = 62, - - /// - /// The warning icon. - /// - Warning = 63, - - /// - /// The Ishgard region icon. - /// - Ishgard = 64, - - /// - /// The Aetheryte icon. - /// - Aetheryte = 65, - - /// - /// The Aethernet icon. - /// - Aethernet = 66, - - /// - /// The gold star icon. - /// - GoldStar = 67, - - /// - /// The silver star icon. - /// - SilverStar = 68, - - /// - /// The green dot icon. - /// - GreenDot = 70, - - /// - /// The unsheathed sword icon. - /// - SwordUnsheathed = 71, - - /// - /// The sheathed sword icon. - /// - SwordSheathed = 72, - - /// - /// The dice icon. - /// - Dice = 73, - - /// - /// The flyable zone icon. - /// - FlyZone = 74, - - /// - /// The no-flying zone icon. - /// - FlyZoneLocked = 75, - - /// - /// The no-circle/prohibited icon. - /// - NoCircle = 76, - - /// - /// The sprout icon. - /// - NewAdventurer = 77, - - /// - /// The mentor icon. - /// - Mentor = 78, - - /// - /// The PvE mentor icon. - /// - MentorPvE = 79, - - /// - /// The crafting mentor icon. - /// - MentorCrafting = 80, - - /// - /// The PvP mentor icon. - /// - MentorPvP = 81, - - /// - /// The tank role icon. - /// - Tank = 82, - - /// - /// The healer role icon. - /// - Healer = 83, - - /// - /// The DPS role icon. - /// - DPS = 84, - - /// - /// The crafter role icon. - /// - Crafter = 85, - - /// - /// The gatherer role icon. - /// - Gatherer = 86, - - /// - /// The "any" role icon. - /// - AnyClass = 87, - - /// - /// The cross-world icon. - /// - CrossWorld = 88, - - /// - /// The slay type Fate icon. - /// - FateSlay = 89, - - /// - /// The boss type Fate icon. - /// - FateBoss = 90, - - /// - /// The gather type Fate icon. - /// - FateGather = 91, - - /// - /// The defend type Fate icon. - /// - FateDefend = 92, - - /// - /// The escort type Fate icon. - /// - FateEscort = 93, - - /// - /// The special type 1 Fate icon. - /// - FateSpecial1 = 94, - - /// - /// The returner icon. - /// - Returner = 95, - - /// - /// The Far-East region icon. - /// - FarEast = 96, - - /// - /// The Gyr Albania region icon. - /// - GyrAbania = 97, - - /// - /// The special type 2 Fate icon. - /// - FateSpecial2 = 98, - - /// - /// The priority world icon. - /// - PriorityWorld = 99, - - /// - /// The elemental level icon. - /// - ElementalLevel = 100, - - /// - /// The exclamation rectangle icon. - /// - ExclamationRectangle = 101, - - /// - /// The notorious monster icon. - /// - NotoriousMonster = 102, - - /// - /// The recording icon. - /// - Recording = 103, - - /// - /// The alarm icon. - /// - Alarm = 104, - - /// - /// The arrow up icon. - /// - ArrowUp = 105, - - /// - /// The arrow down icon. - /// - ArrowDown = 106, - - /// - /// The Crystarium region icon. - /// - Crystarium = 107, - - /// - /// The mentor problem icon. - /// - MentorProblem = 108, - - /// - /// The unknown gold type Fate icon. - /// - FateUnknownGold = 109, - - /// - /// The orange diamond icon. - /// - OrangeDiamond = 110, - - /// - /// The crafting type Fate icon. - /// - FateCrafting = 111, + /// This class represents special icons that can appear in chat naturally or as IconPayloads. + /// + public enum BitmapFontIcon : uint + { + /// + /// No icon. + /// + None = 0, + + /// + /// The controller D-pad up icon. + /// + ControllerDPadUp = 1, + + /// + /// The controller D-pad down icon. + /// + ControllerDPadDown = 2, + + /// + /// The controller D-pad left icon. + /// + ControllerDPadLeft = 3, + + /// + /// The controller D-pad right icon. + /// + ControllerDPadRight = 4, + + /// + /// The controller D-pad up/down icon. + /// + ControllerDPadUpDown = 5, + + /// + /// The controller D-pad left/right icon. + /// + ControllerDPadLeftRight = 6, + + /// + /// The controller D-pad all directions icon. + /// + ControllerDPadAll = 7, + + /// + /// The controller button 0 icon (Xbox: B, PlayStation: Circle). + /// + ControllerButton0 = 8, + + /// + /// The controller button 1 icon (XBox: A, PlayStation: Cross). + /// + ControllerButton1 = 9, + + /// + /// The controller button 2 icon (XBox: X, PlayStation: Square). + /// + ControllerButton2 = 10, + + /// + /// The controller button 3 icon (BBox: Y, PlayStation: Triangle). + /// + ControllerButton3 = 11, + + /// + /// The controller left shoulder button icon. + /// + ControllerShoulderLeft = 12, + + /// + /// The controller right shoulder button icon. + /// + ControllerShoulderRight = 13, + + /// + /// The controller left trigger button icon. + /// + ControllerTriggerLeft = 14, + + /// + /// The controller right trigger button icon. + /// + ControllerTriggerRight = 15, + + /// + /// The controller left analog stick in icon. + /// + ControllerAnalogLeftStickIn = 16, + + /// + /// The controller right analog stick in icon. + /// + ControllerAnalogRightStickIn = 17, + + /// + /// The controller start button icon. + /// + ControllerStart = 18, + + /// + /// The controller back button icon. + /// + ControllerBack = 19, + + /// + /// The controller left analog stick icon. + /// + ControllerAnalogLeftStick = 20, + + /// + /// The controller left analog stick up/down icon. + /// + ControllerAnalogLeftStickUpDown = 21, + + /// + /// The controller left analog stick left/right icon. + /// + ControllerAnalogLeftStickLeftRight = 22, + + /// + /// The controller right analog stick icon. + /// + ControllerAnalogRightStick = 23, + + /// + /// The controller right analog stick up/down icon. + /// + ControllerAnalogRightStickUpDown = 24, + + /// + /// The controller right analog stick left/right icon. + /// + ControllerAnalogRightStickLeftRight = 25, + + /// + /// The La Noscea region icon. + /// + LaNoscea = 51, + + /// + /// The Black Shroud region icon. + /// + BlackShroud = 52, + + /// + /// The Thanalan region icon. + /// + Thanalan = 53, + + /// + /// The auto translate begin icon. + /// + AutoTranslateBegin = 54, + + /// + /// The auto translate end icon. + /// + AutoTranslateEnd = 55, + + /// + /// The fire element icon. + /// + ElementFire = 56, + + /// + /// The ice element icon. + /// + ElementIce = 57, + + /// + /// The wind element icon. + /// + ElementWind = 58, + + /// + /// The earth element icon. + /// + ElementEarth = 59, + + /// + /// The lightning element icon. + /// + ElementLightning = 60, + + /// + /// The water element icon. + /// + ElementWater = 61, + + /// + /// The level sync icon. + /// + LevelSync = 62, + + /// + /// The warning icon. + /// + Warning = 63, + + /// + /// The Ishgard region icon. + /// + Ishgard = 64, + + /// + /// The Aetheryte icon. + /// + Aetheryte = 65, + + /// + /// The Aethernet icon. + /// + Aethernet = 66, + + /// + /// The gold star icon. + /// + GoldStar = 67, + + /// + /// The silver star icon. + /// + SilverStar = 68, + + /// + /// The green dot icon. + /// + GreenDot = 70, + + /// + /// The unsheathed sword icon. + /// + SwordUnsheathed = 71, + + /// + /// The sheathed sword icon. + /// + SwordSheathed = 72, + + /// + /// The dice icon. + /// + Dice = 73, + + /// + /// The flyable zone icon. + /// + FlyZone = 74, + + /// + /// The no-flying zone icon. + /// + FlyZoneLocked = 75, + + /// + /// The no-circle/prohibited icon. + /// + NoCircle = 76, + + /// + /// The sprout icon. + /// + NewAdventurer = 77, + + /// + /// The mentor icon. + /// + Mentor = 78, + + /// + /// The PvE mentor icon. + /// + MentorPvE = 79, + + /// + /// The crafting mentor icon. + /// + MentorCrafting = 80, + + /// + /// The PvP mentor icon. + /// + MentorPvP = 81, + + /// + /// The tank role icon. + /// + Tank = 82, + + /// + /// The healer role icon. + /// + Healer = 83, + + /// + /// The DPS role icon. + /// + DPS = 84, + + /// + /// The crafter role icon. + /// + Crafter = 85, + + /// + /// The gatherer role icon. + /// + Gatherer = 86, + + /// + /// The "any" role icon. + /// + AnyClass = 87, + + /// + /// The cross-world icon. + /// + CrossWorld = 88, + + /// + /// The slay type Fate icon. + /// + FateSlay = 89, + + /// + /// The boss type Fate icon. + /// + FateBoss = 90, + + /// + /// The gather type Fate icon. + /// + FateGather = 91, + + /// + /// The defend type Fate icon. + /// + FateDefend = 92, + + /// + /// The escort type Fate icon. + /// + FateEscort = 93, + + /// + /// The special type 1 Fate icon. + /// + FateSpecial1 = 94, + + /// + /// The returner icon. + /// + Returner = 95, + + /// + /// The Far-East region icon. + /// + FarEast = 96, + + /// + /// The Gyr Albania region icon. + /// + GyrAbania = 97, + + /// + /// The special type 2 Fate icon. + /// + FateSpecial2 = 98, + + /// + /// The priority world icon. + /// + PriorityWorld = 99, + + /// + /// The elemental level icon. + /// + ElementalLevel = 100, + + /// + /// The exclamation rectangle icon. + /// + ExclamationRectangle = 101, + + /// + /// The notorious monster icon. + /// + NotoriousMonster = 102, + + /// + /// The recording icon. + /// + Recording = 103, + + /// + /// The alarm icon. + /// + Alarm = 104, + + /// + /// The arrow up icon. + /// + ArrowUp = 105, + + /// + /// The arrow down icon. + /// + ArrowDown = 106, + + /// + /// The Crystarium region icon. + /// + Crystarium = 107, + + /// + /// The mentor problem icon. + /// + MentorProblem = 108, + + /// + /// The unknown gold type Fate icon. + /// + FateUnknownGold = 109, + + /// + /// The orange diamond icon. + /// + OrangeDiamond = 110, + + /// + /// The crafting type Fate icon. + /// + FateCrafting = 111, + } } diff --git a/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs b/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs index 7be809ba2..5bcb2deb0 100644 --- a/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs +++ b/Dalamud/Game/Text/SeStringHandling/ITextProvider.cs @@ -1,12 +1,13 @@ -namespace Dalamud.Game.Text.SeStringHandling; - -/// -/// An interface binding for a payload that can provide readable Text. -/// -public interface ITextProvider +namespace Dalamud.Game.Text.SeStringHandling { /// - /// Gets the readable text. + /// An interface binding for a payload that can provide readable Text. /// - string Text { get; } + public interface ITextProvider + { + /// + /// Gets the readable text. + /// + string Text { get; } + } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payload.cs b/Dalamud/Game/Text/SeStringHandling/Payload.cs index b1fe708b3..8a1e03cb1 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payload.cs @@ -15,404 +15,405 @@ using Serilog; // - [SeString] some way to add surrounding formatting information as flags/data to text (or other?) payloads? // eg, if a text payload is surrounded by italics payloads, strip them out and mark the text payload as italicized -namespace Dalamud.Game.Text.SeStringHandling; - -/// -/// This class represents a parsed SeString payload. -/// -public abstract partial class Payload -{ - // private for now, since subclasses shouldn't interact with this. - // To force-invalidate it, Dirty can be set to true - private byte[] encodedData; - - /// - /// Gets the Lumina instance to use for any necessary data lookups. - /// - public DataManager DataResolver => Service.Get(); - - /// - /// Gets the type of this payload. - /// - public abstract PayloadType Type { get; } - - /// - /// Gets or sets a value indicating whether whether this payload has been modified since the last Encode(). - /// - public bool Dirty { get; protected set; } = true; - - /// - /// Decodes a binary representation of a payload into its corresponding nice object payload. - /// - /// A reader positioned at the start of the payload, and containing at least one entire payload. - /// The constructed Payload-derived object that was decoded from the binary data. - public static Payload Decode(BinaryReader reader) - { - var payloadStartPos = reader.BaseStream.Position; - - Payload payload; - - var initialByte = reader.ReadByte(); - reader.BaseStream.Position--; - if (initialByte != START_BYTE) - { - payload = DecodeText(reader); - } - else - { - payload = DecodeChunk(reader); - } - - // for now, cache off the actual binary data for this payload, so we don't have to - // regenerate it if the payload isn't modified - // TODO: probably better ways to handle this - var payloadEndPos = reader.BaseStream.Position; - - reader.BaseStream.Position = payloadStartPos; - payload.encodedData = reader.ReadBytes((int)(payloadEndPos - payloadStartPos)); - payload.Dirty = false; - - // Log.Verbose($"got payload bytes {BitConverter.ToString(payload.encodedData).Replace("-", " ")}"); - - reader.BaseStream.Position = payloadEndPos; - - return payload; - } - - /// - /// Encode this payload object into a byte[] useable in-game for things like the chat log. - /// - /// If true, ignores any cached value and forcibly reencodes the payload from its internal representation. - /// A byte[] suitable for use with in-game handlers such as the chat log. - public byte[] Encode(bool force = false) - { - if (this.Dirty || force) - { - this.encodedData = this.EncodeImpl(); - this.Dirty = false; - } - - return this.encodedData; - } - - /// - /// Encodes the internal state of this payload into a byte[] suitable for sending to in-game - /// handlers such as the chat log. - /// - /// Encoded binary payload data suitable for use with in-game handlers. - protected abstract byte[] EncodeImpl(); - - /// - /// Decodes a byte stream from the game into a payload object. - /// - /// A BinaryReader containing at least all the data for this payload. - /// The location holding the end of the data for this payload. - // TODO: endOfStream is somewhat legacy now that payload length is always handled correctly. - // This could be changed to just take a straight byte[], but that would complicate reading - // but we could probably at least remove the end param - protected abstract void DecodeImpl(BinaryReader reader, long endOfStream); - - private static Payload DecodeChunk(BinaryReader reader) - { - Payload payload = null; - - reader.ReadByte(); // START_BYTE - var chunkType = (SeStringChunkType)reader.ReadByte(); - var chunkLen = GetInteger(reader); - - var packetStart = reader.BaseStream.Position; - - // any unhandled payload types will be turned into a RawPayload with the exact same binary data - switch (chunkType) - { - case SeStringChunkType.EmphasisItalic: - payload = new EmphasisItalicPayload(); - break; - - case SeStringChunkType.NewLine: - payload = NewLinePayload.Payload; - break; - - case SeStringChunkType.SeHyphen: - payload = SeHyphenPayload.Payload; - break; - - case SeStringChunkType.Interactable: - { - var subType = (EmbeddedInfoType)reader.ReadByte(); - switch (subType) - { - case EmbeddedInfoType.PlayerName: - payload = new PlayerPayload(); - break; - - case EmbeddedInfoType.ItemLink: - payload = new ItemPayload(); - break; - - case EmbeddedInfoType.MapPositionLink: - payload = new MapLinkPayload(); - break; - - case EmbeddedInfoType.Status: - payload = new StatusPayload(); - break; - - case EmbeddedInfoType.QuestLink: - payload = new QuestPayload(); - break; - - case EmbeddedInfoType.DalamudLink: - payload = new DalamudLinkPayload(); - break; - - case EmbeddedInfoType.LinkTerminator: - // this has no custom handling and so needs to fallthrough to ensure it is captured - default: - // but I'm also tired of this log - if (subType != EmbeddedInfoType.LinkTerminator) - { - Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType); - } - - // rewind so we capture the Interactable byte in the raw data - reader.BaseStream.Seek(-1, SeekOrigin.Current); - break; - } - } - - break; - - case SeStringChunkType.AutoTranslateKey: - payload = new AutoTranslatePayload(); - break; - - case SeStringChunkType.UIForeground: - payload = new UIForegroundPayload(); - break; - - case SeStringChunkType.UIGlow: - payload = new UIGlowPayload(); - break; - - case SeStringChunkType.Icon: - payload = new IconPayload(); - break; - - default: - Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType); - break; - } - - payload ??= new RawPayload((byte)chunkType); - payload.DecodeImpl(reader, reader.BaseStream.Position + chunkLen - 1); - - // read through the rest of the packet - var readBytes = (uint)(reader.BaseStream.Position - packetStart); - reader.ReadBytes((int)(chunkLen - readBytes + 1)); // +1 for the END_BYTE marker - - return payload; - } - - private static Payload DecodeText(BinaryReader reader) - { - var payload = new TextPayload(); - payload.DecodeImpl(reader, reader.BaseStream.Length); - - return payload; - } -} - -/// -/// Parsing helpers. -/// -public abstract partial class Payload +namespace Dalamud.Game.Text.SeStringHandling { /// - /// The start byte of a payload. + /// This class represents a parsed SeString payload. /// - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")] - protected const byte START_BYTE = 0x02; - - /// - /// The end byte of a payload. - /// - [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")] - protected const byte END_BYTE = 0x03; - - /// - /// This represents the type of embedded info in a payload. - /// - public enum EmbeddedInfoType + public abstract partial class Payload { - /// - /// A player's name. - /// - PlayerName = 0x01, + // private for now, since subclasses shouldn't interact with this. + // To force-invalidate it, Dirty can be set to true + private byte[] encodedData; /// - /// The link to an iteme. + /// Gets the Lumina instance to use for any necessary data lookups. /// - ItemLink = 0x03, + public DataManager DataResolver => Service.Get(); /// - /// The link to a map position. + /// Gets the type of this payload. /// - MapPositionLink = 0x04, + public abstract PayloadType Type { get; } /// - /// The link to a quest. + /// Gets or sets a value indicating whether whether this payload has been modified since the last Encode(). /// - QuestLink = 0x05, + public bool Dirty { get; protected set; } = true; /// - /// A status effect. + /// Decodes a binary representation of a payload into its corresponding nice object payload. /// - Status = 0x09, - - /// - /// A custom Dalamud link. - /// - DalamudLink = 0x0F, - - /// - /// A link terminator. - /// - /// - /// It is not exactly clear what this is, but seems to always follow a link. - /// - LinkTerminator = 0xCF, - } - - /// - /// This represents the type of payload and how it should be encoded. - /// - protected enum SeStringChunkType - { - /// - /// See the class. - /// - Icon = 0x12, - - /// - /// See the class. - /// - EmphasisItalic = 0x1A, - - /// - /// See the . - /// - NewLine = 0x10, - - /// - /// See the class. - /// - SeHyphen = 0x1F, - - /// - /// See any of the link-type classes: - /// , - /// , - /// , - /// , - /// , - /// . - /// - Interactable = 0x27, - - /// - /// See the class. - /// - AutoTranslateKey = 0x2E, - - /// - /// See the class. - /// - UIForeground = 0x48, - - /// - /// See the class. - /// - UIGlow = 0x49, - } - - /// - /// Retrieve the packed integer from SE's native data format. - /// - /// The BinaryReader instance. - /// An integer. - // made protected, unless we actually want to use it externally - // in which case it should probably go live somewhere else - protected static uint GetInteger(BinaryReader input) - { - uint marker = input.ReadByte(); - if (marker < 0xD0) - return marker - 1; - - // the game adds 0xF0 marker for values >= 0xCF - // uasge of 0xD0-0xEF is unknown, should we throw here? - // if (marker < 0xF0) throw new NotSupportedException(); - - marker = (marker + 1) & 0b1111; - - var ret = new byte[4]; - for (var i = 3; i >= 0; i--) + /// A reader positioned at the start of the payload, and containing at least one entire payload. + /// The constructed Payload-derived object that was decoded from the binary data. + public static Payload Decode(BinaryReader reader) { - ret[i] = (marker & (1 << i)) == 0 ? (byte)0 : input.ReadByte(); - } + var payloadStartPos = reader.BaseStream.Position; - return BitConverter.ToUInt32(ret, 0); - } + Payload payload; - /// - /// Create a packed integer in Se's native data format. - /// - /// The value to pack. - /// A packed integer. - protected static byte[] MakeInteger(uint value) - { - if (value < 0xCF) - { - return new byte[] { (byte)(value + 1) }; - } - - var bytes = BitConverter.GetBytes(value); - - var ret = new List() { 0xF0 }; - for (var i = 3; i >= 0; i--) - { - if (bytes[i] != 0) + var initialByte = reader.ReadByte(); + reader.BaseStream.Position--; + if (initialByte != START_BYTE) { - ret.Add(bytes[i]); - ret[0] |= (byte)(1 << i); + payload = DecodeText(reader); } + else + { + payload = DecodeChunk(reader); + } + + // for now, cache off the actual binary data for this payload, so we don't have to + // regenerate it if the payload isn't modified + // TODO: probably better ways to handle this + var payloadEndPos = reader.BaseStream.Position; + + reader.BaseStream.Position = payloadStartPos; + payload.encodedData = reader.ReadBytes((int)(payloadEndPos - payloadStartPos)); + payload.Dirty = false; + + // Log.Verbose($"got payload bytes {BitConverter.ToString(payload.encodedData).Replace("-", " ")}"); + + reader.BaseStream.Position = payloadEndPos; + + return payload; } - ret[0] -= 1; + /// + /// Encode this payload object into a byte[] useable in-game for things like the chat log. + /// + /// If true, ignores any cached value and forcibly reencodes the payload from its internal representation. + /// A byte[] suitable for use with in-game handlers such as the chat log. + public byte[] Encode(bool force = false) + { + if (this.Dirty || force) + { + this.encodedData = this.EncodeImpl(); + this.Dirty = false; + } - return ret.ToArray(); + return this.encodedData; + } + + /// + /// Encodes the internal state of this payload into a byte[] suitable for sending to in-game + /// handlers such as the chat log. + /// + /// Encoded binary payload data suitable for use with in-game handlers. + protected abstract byte[] EncodeImpl(); + + /// + /// Decodes a byte stream from the game into a payload object. + /// + /// A BinaryReader containing at least all the data for this payload. + /// The location holding the end of the data for this payload. + // TODO: endOfStream is somewhat legacy now that payload length is always handled correctly. + // This could be changed to just take a straight byte[], but that would complicate reading + // but we could probably at least remove the end param + protected abstract void DecodeImpl(BinaryReader reader, long endOfStream); + + private static Payload DecodeChunk(BinaryReader reader) + { + Payload payload = null; + + reader.ReadByte(); // START_BYTE + var chunkType = (SeStringChunkType)reader.ReadByte(); + var chunkLen = GetInteger(reader); + + var packetStart = reader.BaseStream.Position; + + // any unhandled payload types will be turned into a RawPayload with the exact same binary data + switch (chunkType) + { + case SeStringChunkType.EmphasisItalic: + payload = new EmphasisItalicPayload(); + break; + + case SeStringChunkType.NewLine: + payload = NewLinePayload.Payload; + break; + + case SeStringChunkType.SeHyphen: + payload = SeHyphenPayload.Payload; + break; + + case SeStringChunkType.Interactable: + { + var subType = (EmbeddedInfoType)reader.ReadByte(); + switch (subType) + { + case EmbeddedInfoType.PlayerName: + payload = new PlayerPayload(); + break; + + case EmbeddedInfoType.ItemLink: + payload = new ItemPayload(); + break; + + case EmbeddedInfoType.MapPositionLink: + payload = new MapLinkPayload(); + break; + + case EmbeddedInfoType.Status: + payload = new StatusPayload(); + break; + + case EmbeddedInfoType.QuestLink: + payload = new QuestPayload(); + break; + + case EmbeddedInfoType.DalamudLink: + payload = new DalamudLinkPayload(); + break; + + case EmbeddedInfoType.LinkTerminator: + // this has no custom handling and so needs to fallthrough to ensure it is captured + default: + // but I'm also tired of this log + if (subType != EmbeddedInfoType.LinkTerminator) + { + Log.Verbose("Unhandled EmbeddedInfoType: {0}", subType); + } + + // rewind so we capture the Interactable byte in the raw data + reader.BaseStream.Seek(-1, SeekOrigin.Current); + break; + } + } + + break; + + case SeStringChunkType.AutoTranslateKey: + payload = new AutoTranslatePayload(); + break; + + case SeStringChunkType.UIForeground: + payload = new UIForegroundPayload(); + break; + + case SeStringChunkType.UIGlow: + payload = new UIGlowPayload(); + break; + + case SeStringChunkType.Icon: + payload = new IconPayload(); + break; + + default: + Log.Verbose("Unhandled SeStringChunkType: {0}", chunkType); + break; + } + + payload ??= new RawPayload((byte)chunkType); + payload.DecodeImpl(reader, reader.BaseStream.Position + chunkLen - 1); + + // read through the rest of the packet + var readBytes = (uint)(reader.BaseStream.Position - packetStart); + reader.ReadBytes((int)(chunkLen - readBytes + 1)); // +1 for the END_BYTE marker + + return payload; + } + + private static Payload DecodeText(BinaryReader reader) + { + var payload = new TextPayload(); + payload.DecodeImpl(reader, reader.BaseStream.Length); + + return payload; + } } /// - /// From a binary packed integer, get the high and low bytes. + /// Parsing helpers. /// - /// The BinaryReader instance. - /// The high and low bytes. - protected static (uint High, uint Low) GetPackedIntegers(BinaryReader input) + public abstract partial class Payload { - var value = GetInteger(input); - return (value >> 16, value & 0xFFFF); - } + /// + /// The start byte of a payload. + /// + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")] + protected const byte START_BYTE = 0x02; - /// - /// Create a packed integer from the given high and low bytes. - /// - /// The high order bytes. - /// The low order bytes. - /// A packed integer. - protected static byte[] MakePackedInteger(uint high, uint low) - { - var value = (high << 16) | (low & 0xFFFF); - return MakeInteger(value); + /// + /// The end byte of a payload. + /// + [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "This is prefered.")] + protected const byte END_BYTE = 0x03; + + /// + /// This represents the type of embedded info in a payload. + /// + public enum EmbeddedInfoType + { + /// + /// A player's name. + /// + PlayerName = 0x01, + + /// + /// The link to an iteme. + /// + ItemLink = 0x03, + + /// + /// The link to a map position. + /// + MapPositionLink = 0x04, + + /// + /// The link to a quest. + /// + QuestLink = 0x05, + + /// + /// A status effect. + /// + Status = 0x09, + + /// + /// A custom Dalamud link. + /// + DalamudLink = 0x0F, + + /// + /// A link terminator. + /// + /// + /// It is not exactly clear what this is, but seems to always follow a link. + /// + LinkTerminator = 0xCF, + } + + /// + /// This represents the type of payload and how it should be encoded. + /// + protected enum SeStringChunkType + { + /// + /// See the class. + /// + Icon = 0x12, + + /// + /// See the class. + /// + EmphasisItalic = 0x1A, + + /// + /// See the . + /// + NewLine = 0x10, + + /// + /// See the class. + /// + SeHyphen = 0x1F, + + /// + /// See any of the link-type classes: + /// , + /// , + /// , + /// , + /// , + /// . + /// + Interactable = 0x27, + + /// + /// See the class. + /// + AutoTranslateKey = 0x2E, + + /// + /// See the class. + /// + UIForeground = 0x48, + + /// + /// See the class. + /// + UIGlow = 0x49, + } + + /// + /// Retrieve the packed integer from SE's native data format. + /// + /// The BinaryReader instance. + /// An integer. + // made protected, unless we actually want to use it externally + // in which case it should probably go live somewhere else + protected static uint GetInteger(BinaryReader input) + { + uint marker = input.ReadByte(); + if (marker < 0xD0) + return marker - 1; + + // the game adds 0xF0 marker for values >= 0xCF + // uasge of 0xD0-0xEF is unknown, should we throw here? + // if (marker < 0xF0) throw new NotSupportedException(); + + marker = (marker + 1) & 0b1111; + + var ret = new byte[4]; + for (var i = 3; i >= 0; i--) + { + ret[i] = (marker & (1 << i)) == 0 ? (byte)0 : input.ReadByte(); + } + + return BitConverter.ToUInt32(ret, 0); + } + + /// + /// Create a packed integer in Se's native data format. + /// + /// The value to pack. + /// A packed integer. + protected static byte[] MakeInteger(uint value) + { + if (value < 0xCF) + { + return new byte[] { (byte)(value + 1) }; + } + + var bytes = BitConverter.GetBytes(value); + + var ret = new List() { 0xF0 }; + for (var i = 3; i >= 0; i--) + { + if (bytes[i] != 0) + { + ret.Add(bytes[i]); + ret[0] |= (byte)(1 << i); + } + } + + ret[0] -= 1; + + return ret.ToArray(); + } + + /// + /// From a binary packed integer, get the high and low bytes. + /// + /// The BinaryReader instance. + /// The high and low bytes. + protected static (uint High, uint Low) GetPackedIntegers(BinaryReader input) + { + var value = GetInteger(input); + return (value >> 16, value & 0xFFFF); + } + + /// + /// Create a packed integer from the given high and low bytes. + /// + /// The high order bytes. + /// The low order bytes. + /// A packed integer. + protected static byte[] MakePackedInteger(uint high, uint low) + { + var value = (high << 16) | (low & 0xFFFF); + return MakeInteger(value); + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/PayloadType.cs b/Dalamud/Game/Text/SeStringHandling/PayloadType.cs index c58f2954d..93bcc7e3e 100644 --- a/Dalamud/Game/Text/SeStringHandling/PayloadType.cs +++ b/Dalamud/Game/Text/SeStringHandling/PayloadType.cs @@ -1,82 +1,83 @@ -namespace Dalamud.Game.Text.SeStringHandling; - -/// -/// All parsed types of SeString payloads. -/// -public enum PayloadType +namespace Dalamud.Game.Text.SeStringHandling { /// - /// An unknown SeString. + /// All parsed types of SeString payloads. /// - Unknown, + public enum PayloadType + { + /// + /// An unknown SeString. + /// + Unknown, - /// - /// An SeString payload representing a player link. - /// - Player, + /// + /// An SeString payload representing a player link. + /// + Player, - /// - /// An SeString payload representing an Item link. - /// - Item, + /// + /// An SeString payload representing an Item link. + /// + Item, - /// - /// An SeString payload representing an Status Effect link. - /// - Status, + /// + /// An SeString payload representing an Status Effect link. + /// + Status, - /// - /// An SeString payload representing raw, typed text. - /// - RawText, + /// + /// An SeString payload representing raw, typed text. + /// + RawText, - /// - /// An SeString payload representing a text foreground color. - /// - UIForeground, + /// + /// An SeString payload representing a text foreground color. + /// + UIForeground, - /// - /// An SeString payload representing a text glow color. - /// - UIGlow, + /// + /// An SeString payload representing a text glow color. + /// + UIGlow, - /// - /// An SeString payload representing a map position link, such as from <flag> or <pos>. - /// - MapLink, + /// + /// An SeString payload representing a map position link, such as from <flag> or <pos>. + /// + MapLink, - /// - /// An SeString payload representing an auto-translate dictionary entry. - /// - AutoTranslateText, + /// + /// An SeString payload representing an auto-translate dictionary entry. + /// + AutoTranslateText, - /// - /// An SeString payload representing italic emphasis formatting on text. - /// - EmphasisItalic, + /// + /// An SeString payload representing italic emphasis formatting on text. + /// + EmphasisItalic, - /// - /// An SeString payload representing a bitmap icon. - /// - Icon, + /// + /// An SeString payload representing a bitmap icon. + /// + Icon, - /// - /// A SeString payload representing a quest link. - /// - Quest, + /// + /// A SeString payload representing a quest link. + /// + Quest, - /// - /// A SeString payload representing a custom clickable link for dalamud plugins. - /// - DalamudLink, + /// + /// A SeString payload representing a custom clickable link for dalamud plugins. + /// + DalamudLink, - /// - /// An SeString payload representing a newline character. - /// - NewLine, + /// + /// An SeString payload representing a newline character. + /// + NewLine, - /// - /// An SeString payload representing a doublewide SE hypen. - /// - SeHyphen, + /// + /// An SeString payload representing a doublewide SE hypen. + /// + SeHyphen, + } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs index e239037f6..8e9eb5b65 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/AutoTranslatePayload.cs @@ -7,164 +7,165 @@ using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload containing an auto-translation/completion chat message. -/// -public class AutoTranslatePayload : Payload, ITextProvider +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private string text; - - [JsonProperty] - private uint group; - - [JsonProperty] - private uint key; - /// - /// Initializes a new instance of the class. - /// Creates a new auto-translate payload. + /// An SeString Payload containing an auto-translation/completion chat message. /// - /// The group id for this message. - /// The key/row id for this message. Which table this is in depends on the group id and details the Completion table. - /// - /// This table is somewhat complicated in structure, and so using this constructor may not be very nice. - /// There is probably little use to create one of these, however. - /// - public AutoTranslatePayload(uint group, uint key) + public class AutoTranslatePayload : Payload, ITextProvider { - // TODO: friendlier ctor? not sure how to handle that given how weird the tables are - this.group = group; - this.key = key; - } + private string text; - /// - /// Initializes a new instance of the class. - /// - internal AutoTranslatePayload() - { - } + [JsonProperty] + private uint group; - /// - public override PayloadType Type => PayloadType.AutoTranslateText; + [JsonProperty] + private uint key; - /// - /// Gets the actual text displayed in-game for this payload. - /// - /// - /// Value is evaluated lazily and cached. - /// - public string Text - { - get + /// + /// Initializes a new instance of the class. + /// Creates a new auto-translate payload. + /// + /// The group id for this message. + /// The key/row id for this message. Which table this is in depends on the group id and details the Completion table. + /// + /// This table is somewhat complicated in structure, and so using this constructor may not be very nice. + /// There is probably little use to create one of these, however. + /// + public AutoTranslatePayload(uint group, uint key) { - // wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads - return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {this.Resolve()} {(char)SeIconChar.AutoTranslateClose}"; + // TODO: friendlier ctor? not sure how to handle that given how weird the tables are + this.group = group; + this.key = key; } - } - /// - /// Returns a string that represents the current object. - /// - /// A string that represents the current object. - public override string ToString() - { - return $"{this.Type} - Group: {this.group}, Key: {this.key}, Text: {this.Text}"; - } + /// + /// Initializes a new instance of the class. + /// + internal AutoTranslatePayload() + { + } - /// - protected override byte[] EncodeImpl() - { - var keyBytes = MakeInteger(this.key); + /// + public override PayloadType Type => PayloadType.AutoTranslateText; - var chunkLen = keyBytes.Length + 2; - var bytes = new List() + /// + /// Gets the actual text displayed in-game for this payload. + /// + /// + /// Value is evaluated lazily and cached. + /// + public string Text + { + get + { + // wrap the text in the colored brackets that is uses in-game, since those are not actually part of any of the payloads + return this.text ??= $"{(char)SeIconChar.AutoTranslateOpen} {this.Resolve()} {(char)SeIconChar.AutoTranslateClose}"; + } + } + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return $"{this.Type} - Group: {this.group}, Key: {this.key}, Text: {this.Text}"; + } + + /// + protected override byte[] EncodeImpl() + { + var keyBytes = MakeInteger(this.key); + + var chunkLen = keyBytes.Length + 2; + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.AutoTranslateKey, (byte)chunkLen, (byte)this.group, }; - bytes.AddRange(keyBytes); - bytes.Add(END_BYTE); + bytes.AddRange(keyBytes); + bytes.Add(END_BYTE); - return bytes.ToArray(); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - // 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.key = GetInteger(reader); - } - - private string Resolve() - { - string value = null; - - var sheet = this.DataResolver.GetExcelSheet(); - - Completion row = null; - try - { - // 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); + return bytes.ToArray(); } - catch - { - } // don't care, row will be null - if (row?.Group == this.group) + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - // if the row exists in this table and the group matches, this is actually the correct data - value = row.Text; + // 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.key = GetInteger(reader); } - else + + private string Resolve() { + string value = null; + + var sheet = this.DataResolver.GetExcelSheet(); + + Completion row = null; try { - // 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); - // 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, - _ => throw new Exception(actualTableName), - }; - - value = name; + // 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); } - catch (Exception e) + catch { - Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this.Type} - Group: {this.group}, Key: {this.key}"); - } - } + } // don't care, row will be null - return value; + 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; + } + else + { + try + { + // 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); + // 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, + _ => throw new Exception(actualTableName), + }; + + value = name; + } + catch (Exception e) + { + Log.Error(e, $"AutoTranslatePayload - failed to resolve: {this.Type} - Group: {this.group}, Key: {this.key}"); + } + } + + return value; + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs index 7a1ed417c..6aedf04ed 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/DalamudLinkPayload.cs @@ -3,56 +3,57 @@ using System.Collections.Generic; using System.IO; using System.Text; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// This class represents a custom Dalamud clickable chat link. -/// -public class DalamudLinkPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - /// - public override PayloadType Type => PayloadType.DalamudLink; - /// - /// Gets the plugin command ID to be linked. + /// This class represents a custom Dalamud clickable chat link. /// - public uint CommandId { get; internal set; } = 0; - - /// - /// Gets the plugin name to be linked. - /// - public string Plugin { get; internal set; } = string.Empty; - - /// - public override string ToString() + public class DalamudLinkPayload : Payload { - return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}"; - } + /// + public override PayloadType Type => PayloadType.DalamudLink; - /// - protected override byte[] EncodeImpl() - { - var pluginBytes = Encoding.UTF8.GetBytes(this.Plugin); - var commandBytes = MakeInteger(this.CommandId); - var chunkLen = 3 + pluginBytes.Length + commandBytes.Length; + /// + /// Gets the plugin command ID to be linked. + /// + public uint CommandId { get; internal set; } = 0; - if (chunkLen > 255) + /// + /// Gets the plugin name to be linked. + /// + public string Plugin { get; internal set; } = string.Empty; + + /// + public override string ToString() { - throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload"); + return $"{this.Type} - Plugin: {this.Plugin}, Command: {this.CommandId}"; } - var bytes = new List { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.DalamudLink }; - bytes.Add((byte)pluginBytes.Length); - bytes.AddRange(pluginBytes); - bytes.AddRange(commandBytes); - bytes.Add(END_BYTE); - return bytes.ToArray(); - } + /// + protected override byte[] EncodeImpl() + { + var pluginBytes = Encoding.UTF8.GetBytes(this.Plugin); + var commandBytes = MakeInteger(this.CommandId); + var chunkLen = 3 + pluginBytes.Length + commandBytes.Length; - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte())); - this.CommandId = GetInteger(reader); + if (chunkLen > 255) + { + throw new Exception("Chunk is too long. Plugin name exceeds limits for DalamudLinkPayload"); + } + + var bytes = new List { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.DalamudLink }; + bytes.Add((byte)pluginBytes.Length); + bytes.AddRange(pluginBytes); + bytes.AddRange(commandBytes); + bytes.Add(END_BYTE); + return bytes.ToArray(); + } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.Plugin = Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadByte())); + this.CommandId = GetInteger(reader); + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs index ff3d67d06..b6c3bbd76 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/EmphasisItalicPayload.cs @@ -2,80 +2,81 @@ using System; using System.Collections.Generic; using System.IO; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload containing information about enabling or disabling italics formatting on following text. -/// -/// -/// As with other formatting payloads, this is only useful in a payload block, where it affects any subsequent -/// text payloads. -/// -public class EmphasisItalicPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { /// - /// Initializes a new instance of the class. - /// Creates an EmphasisItalicPayload. + /// An SeString Payload containing information about enabling or disabling italics formatting on following text. /// - /// Whether italics formatting should be enabled or disabled for following text. - public EmphasisItalicPayload(bool enabled) + /// + /// As with other formatting payloads, this is only useful in a payload block, where it affects any subsequent + /// text payloads. + /// + public class EmphasisItalicPayload : Payload { - this.IsEnabled = enabled; - } + /// + /// Initializes a new instance of the class. + /// Creates an EmphasisItalicPayload. + /// + /// Whether italics formatting should be enabled or disabled for following text. + public EmphasisItalicPayload(bool enabled) + { + this.IsEnabled = enabled; + } - /// - /// Initializes a new instance of the class. - /// Creates an EmphasisItalicPayload. - /// - internal EmphasisItalicPayload() - { - } + /// + /// Initializes a new instance of the class. + /// Creates an EmphasisItalicPayload. + /// + internal EmphasisItalicPayload() + { + } - /// - /// Gets a payload representing enabling italics on following text. - /// - public static EmphasisItalicPayload ItalicsOn => new(true); + /// + /// Gets a payload representing enabling italics on following text. + /// + public static EmphasisItalicPayload ItalicsOn => new(true); - /// - /// Gets a payload representing disabling italics on following text. - /// - public static EmphasisItalicPayload ItalicsOff => new(false); + /// + /// Gets a payload representing disabling italics on following text. + /// + public static EmphasisItalicPayload ItalicsOff => new(false); - /// - /// Gets a value indicating whether this payload enables italics formatting for following text. - /// - public bool IsEnabled { get; private set; } + /// + /// Gets a value indicating whether this payload enables italics formatting for following text. + /// + public bool IsEnabled { get; private set; } - /// - public override PayloadType Type => PayloadType.EmphasisItalic; + /// + public override PayloadType Type => PayloadType.EmphasisItalic; - /// - public override string ToString() - { - return $"{this.Type} - Enabled: {this.IsEnabled}"; - } + /// + public override string ToString() + { + return $"{this.Type} - Enabled: {this.IsEnabled}"; + } - /// - protected override byte[] EncodeImpl() - { - // realistically this will always be a single byte of value 1 or 2 - // but we'll treat it normally anyway - var enabledBytes = MakeInteger(this.IsEnabled ? 1u : 0); + /// + protected override byte[] EncodeImpl() + { + // realistically this will always be a single byte of value 1 or 2 + // but we'll treat it normally anyway + var enabledBytes = MakeInteger(this.IsEnabled ? 1u : 0); - var chunkLen = enabledBytes.Length + 1; - var bytes = new List() + var chunkLen = enabledBytes.Length + 1; + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.EmphasisItalic, (byte)chunkLen, }; - bytes.AddRange(enabledBytes); - bytes.Add(END_BYTE); + bytes.AddRange(enabledBytes); + bytes.Add(END_BYTE); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.IsEnabled = GetInteger(reader) == 1; + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.IsEnabled = GetInteger(reader) == 1; + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs index 5336d2242..69588f085 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/IconPayload.cs @@ -1,62 +1,63 @@ using System.Collections.Generic; using System.IO; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// SeString payload representing a bitmap icon from fontIcon. -/// -public class IconPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { /// - /// Initializes a new instance of the class. - /// Create a Icon payload for the specified icon. + /// SeString payload representing a bitmap icon from fontIcon. /// - /// The Icon. - public IconPayload(BitmapFontIcon icon) + public class IconPayload : Payload { - this.Icon = icon; - } - - /// - /// Initializes a new instance of the class. - /// Create a Icon payload for the specified icon. - /// - internal IconPayload() - { - } - - /// - public override PayloadType Type => PayloadType.Icon; - - /// - /// Gets or sets the icon the payload represents. - /// - public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None; - - /// - public override string ToString() - { - return $"{this.Type} - {this.Icon}"; - } - - /// - protected override byte[] EncodeImpl() - { - var indexBytes = MakeInteger((uint)this.Icon); - var chunkLen = indexBytes.Length + 1; - var bytes = new List(new byte[] + /// + /// Initializes a new instance of the class. + /// Create a Icon payload for the specified icon. + /// + /// The Icon. + public IconPayload(BitmapFontIcon icon) { - START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen, - }); - bytes.AddRange(indexBytes); - bytes.Add(END_BYTE); - return bytes.ToArray(); - } + this.Icon = icon; + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.Icon = (BitmapFontIcon)GetInteger(reader); + /// + /// Initializes a new instance of the class. + /// Create a Icon payload for the specified icon. + /// + internal IconPayload() + { + } + + /// + public override PayloadType Type => PayloadType.Icon; + + /// + /// Gets or sets the icon the payload represents. + /// + public BitmapFontIcon Icon { get; set; } = BitmapFontIcon.None; + + /// + public override string ToString() + { + return $"{this.Type} - {this.Icon}"; + } + + /// + protected override byte[] EncodeImpl() + { + var indexBytes = MakeInteger((uint)this.Icon); + var chunkLen = indexBytes.Length + 1; + var bytes = new List(new byte[] + { + START_BYTE, (byte)SeStringChunkType.Icon, (byte)chunkLen, + }); + bytes.AddRange(indexBytes); + bytes.Add(END_BYTE); + return bytes.ToArray(); + } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.Icon = (BitmapFontIcon)GetInteger(reader); + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs index 7cce11709..9714852e9 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/ItemPayload.cs @@ -6,183 +6,184 @@ using System.Text; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload representing an interactable item link. -/// -public class ItemPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private Item item; - - // mainly to allow overriding the name (for things like owo) - // TODO: even though this is present in some item links, it may not really have a use at all - // For things like owo, changing the text payload is probably correct, whereas changing the - // actual embedded name might not work properly. - private string displayName = null; - - [JsonProperty] - private uint itemId; - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable item link for the specified item. + /// An SeString Payload representing an interactable item link. /// - /// The id of the item. - /// Whether or not the link should be for the high-quality variant of the item. - /// An optional name to include in the item link. Typically this should - /// be left as null, or set to the normal item name. Actual overrides are better done with the subsequent - /// TextPayload that is a part of a full item link in chat. - public ItemPayload(uint itemId, bool isHQ, string displayNameOverride = null) + public class ItemPayload : Payload { - this.itemId = itemId; - this.IsHQ = isHQ; - this.displayName = displayNameOverride; - } + private Item item; - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable item link for the specified item. - /// - internal ItemPayload() - { - } + // mainly to allow overriding the name (for things like owo) + // TODO: even though this is present in some item links, it may not really have a use at all + // For things like owo, changing the text payload is probably correct, whereas changing the + // actual embedded name might not work properly. + private string displayName = null; - /// - public override PayloadType Type => PayloadType.Item; + [JsonProperty] + private uint itemId; - /// - /// Gets or sets the displayed name for this item link. Note that incoming links only sometimes have names embedded, - /// often the name is only present in a following text payload. - /// - public string DisplayName - { - get + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable item link for the specified item. + /// + /// The id of the item. + /// Whether or not the link should be for the high-quality variant of the item. + /// An optional name to include in the item link. Typically this should + /// be left as null, or set to the normal item name. Actual overrides are better done with the subsequent + /// TextPayload that is a part of a full item link in chat. + public ItemPayload(uint itemId, bool isHQ, string displayNameOverride = null) { - return this.displayName; + this.itemId = itemId; + this.IsHQ = isHQ; + this.displayName = displayNameOverride; } - set + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable item link for the specified item. + /// + internal ItemPayload() { - this.displayName = value; - this.Dirty = true; } - } - /// - /// Gets the raw item ID of this payload. - /// - [JsonIgnore] - public uint ItemId => this.itemId; + /// + public override PayloadType Type => PayloadType.Item; - /// - /// Gets the underlying Lumina Item represented by this payload. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public Item Item => this.item ??= this.DataResolver.GetExcelSheet().GetRow(this.itemId); - - /// - /// Gets a value indicating whether or not this item link is for a high-quality version of the item. - /// - [JsonProperty] - public bool IsHQ { get; private set; } = false; - - /// - public override string ToString() - { - return $"{this.Type} - ItemId: {this.itemId}, IsHQ: {this.IsHQ}, Name: {this.displayName ?? this.Item.Name}"; - } - - /// - protected override byte[] EncodeImpl() - { - var actualItemId = this.IsHQ ? this.itemId + 1000000 : this.itemId; - var idBytes = MakeInteger(actualItemId); - var hasName = !string.IsNullOrEmpty(this.displayName); - - var chunkLen = idBytes.Length + 4; - if (hasName) + /// + /// Gets or sets the displayed name for this item link. Note that incoming links only sometimes have names embedded, + /// often the name is only present in a following text payload. + /// + public string DisplayName { - // 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself - chunkLen += 1 + 1 + this.displayName.Length; - if (this.IsHQ) + get { - chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space + return this.displayName; + } + + set + { + this.displayName = value; + this.Dirty = true; } } - var bytes = new List() + /// + /// Gets the raw item ID of this payload. + /// + [JsonIgnore] + public uint ItemId => this.itemId; + + /// + /// Gets the underlying Lumina Item represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Item Item => this.item ??= this.DataResolver.GetExcelSheet().GetRow(this.itemId); + + /// + /// Gets a value indicating whether or not this item link is for a high-quality version of the item. + /// + [JsonProperty] + public bool IsHQ { get; private set; } = false; + + /// + public override string ToString() + { + return $"{this.Type} - ItemId: {this.itemId}, IsHQ: {this.IsHQ}, Name: {this.displayName ?? this.Item.Name}"; + } + + /// + protected override byte[] EncodeImpl() + { + var actualItemId = this.IsHQ ? this.itemId + 1000000 : this.itemId; + var idBytes = MakeInteger(actualItemId); + var hasName = !string.IsNullOrEmpty(this.displayName); + + var chunkLen = idBytes.Length + 4; + if (hasName) + { + // 1 additional unknown byte compared to the nameless version, 1 byte for the name length, and then the name itself + chunkLen += 1 + 1 + this.displayName.Length; + if (this.IsHQ) + { + chunkLen += 4; // unicode representation of the HQ symbol is 3 bytes, preceded by a space + } + } + + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.ItemLink, }; - bytes.AddRange(idBytes); - // unk - bytes.AddRange(new byte[] { 0x02, 0x01 }); + bytes.AddRange(idBytes); + // unk + bytes.AddRange(new byte[] { 0x02, 0x01 }); - // Links don't have to include the name, but if they do, it requires additional work - if (hasName) - { - var nameLen = this.displayName.Length + 1; - if (this.IsHQ) + // Links don't have to include the name, but if they do, it requires additional work + if (hasName) { - nameLen += 4; // space plus 3 bytes for HQ symbol - } + var nameLen = this.displayName.Length + 1; + if (this.IsHQ) + { + nameLen += 4; // space plus 3 bytes for HQ symbol + } - bytes.AddRange(new byte[] - { + bytes.AddRange(new byte[] + { 0xFF, // unk (byte)nameLen, - }); - bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName)); + }); + bytes.AddRange(Encoding.UTF8.GetBytes(this.displayName)); - if (this.IsHQ) - { - // space and HQ symbol - bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC }); - } - } - - bytes.Add(END_BYTE); - - return bytes.ToArray(); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.itemId = GetInteger(reader); - - if (this.itemId > 1000000) - { - this.itemId -= 1000000; - this.IsHQ = true; - } - - if (reader.BaseStream.Position + 3 < endOfStream) - { - // unk - reader.ReadBytes(3); - - var itemNameLen = (int)GetInteger(reader); - var itemNameBytes = reader.ReadBytes(itemNameLen); - - // it probably isn't necessary to store this, as we now get the lumina Item - // on demand from the id, which will have the name - // For incoming links, the name "should?" always match - // but we'll store it for use in encode just in case it doesn't - - // HQ items have the HQ symbol as part of the name, but since we already recorded - // the HQ flag, we want just the bare name - if (this.IsHQ) - { - itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray(); + if (this.IsHQ) + { + // space and HQ symbol + bytes.AddRange(new byte[] { 0x20, 0xEE, 0x80, 0xBC }); + } } - this.displayName = Encoding.UTF8.GetString(itemNameBytes); + bytes.Add(END_BYTE); + + return bytes.ToArray(); + } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.itemId = GetInteger(reader); + + if (this.itemId > 1000000) + { + this.itemId -= 1000000; + this.IsHQ = true; + } + + if (reader.BaseStream.Position + 3 < endOfStream) + { + // unk + reader.ReadBytes(3); + + var itemNameLen = (int)GetInteger(reader); + var itemNameBytes = reader.ReadBytes(itemNameLen); + + // it probably isn't necessary to store this, as we now get the lumina Item + // on demand from the id, which will have the name + // For incoming links, the name "should?" always match + // but we'll store it for use in encode just in case it doesn't + + // HQ items have the HQ symbol as part of the name, but since we already recorded + // the HQ flag, we want just the bare name + if (this.IsHQ) + { + itemNameBytes = itemNameBytes.Take(itemNameLen - 4).ToArray(); + } + + this.displayName = Encoding.UTF8.GetString(itemNameBytes); + } } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs index 51c2f0e61..8f7418b48 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/MapLinkPayload.cs @@ -5,231 +5,232 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload representing an interactable map position link. -/// -public class MapLinkPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private Map map; - private TerritoryType territoryType; - private string placeNameRegion; - private string placeName; - - [JsonProperty] - private uint territoryTypeId; - - [JsonProperty] - private uint mapId; - /// - /// Initializes a new instance of the class. - /// Creates an interactable MapLinkPayload from a human-readable position. + /// An SeString Payload representing an interactable map position link. /// - /// The id of the TerritoryType entry for this link. - /// The id of the Map entry for this link. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - public MapLinkPayload(uint territoryTypeId, uint mapId, float niceXCoord, float niceYCoord, float fudgeFactor = 0.05f) + public class MapLinkPayload : Payload { - this.territoryTypeId = territoryTypeId; - this.mapId = mapId; - // this fudge is necessary basically to ensure we don't shift down a full tenth - // because essentially values are truncated instead of rounded, so 3.09999f will become - // 3.0f and not 3.1f - this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.SizeFactor); - this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor); - } + private Map map; + private TerritoryType territoryType; + private string placeNameRegion; + private string placeName; - /// - /// Initializes a new instance of the class. - /// Creates an interactable MapLinkPayload from a raw position. - /// - /// The id of the TerritoryType entry for this link. - /// The id of the Map entry for this link. - /// The internal raw x-coordinate for this link. - /// The internal raw y-coordinate for this link. - public MapLinkPayload(uint territoryTypeId, uint mapId, int rawX, int rawY) - { - this.territoryTypeId = territoryTypeId; - this.mapId = mapId; - this.RawX = rawX; - this.RawY = rawY; - } + [JsonProperty] + private uint territoryTypeId; - /// - /// Initializes a new instance of the class. - /// Creates an interactable MapLinkPayload from a human-readable position. - /// - internal MapLinkPayload() - { - } + [JsonProperty] + private uint mapId; - /// - public override PayloadType Type => PayloadType.MapLink; - - /// - /// Gets the Map specified for this map link. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public Map Map => this.map ??= this.DataResolver.GetExcelSheet().GetRow(this.mapId); - - /// - /// Gets the TerritoryType specified for this map link. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet().GetRow(this.territoryTypeId); - - /// - /// Gets the internal x-coordinate for this map position. - /// - public int RawX { get; private set; } - - /// - /// Gets the internal y-coordinate for this map position. - /// - public int RawY { get; private set; } - - // these could be cached, but this isn't really too egregious - - /// - /// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded. - /// - public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor); - - /// - /// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded. - /// - [JsonIgnore] - public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor); - - // there is no Z; it's purely in the text payload where applicable - - /// - /// Gets the printable map coordinates for this link. This value tries to match the in-game printable text as closely - /// as possible but is an approximation and may be slightly off for some positions. - /// - [JsonIgnore] - public string CoordinateString - { - get + /// + /// Initializes a new instance of the class. + /// Creates an interactable MapLinkPayload from a human-readable position. + /// + /// The id of the TerritoryType entry for this link. + /// The id of the Map entry for this link. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + public MapLinkPayload(uint territoryTypeId, uint mapId, float niceXCoord, float niceYCoord, float fudgeFactor = 0.05f) { - // this truncates the values to one decimal without rounding, which is what the game does - // the fudge also just attempts to correct the truncated/displayed value for rounding/fp issues - // TODO: should this fudge factor be the same as in the ctor? currently not since that is customizable - const float fudge = 0.02f; - var x = Math.Truncate((this.XCoord + fudge) * 10.0f) / 10.0f; - var y = Math.Truncate((this.YCoord + fudge) * 10.0f) / 10.0f; - - // the formatting and spacing the game uses - return $"( {x:0.0} , {y:0.0} )"; + this.territoryTypeId = territoryTypeId; + this.mapId = mapId; + // this fudge is necessary basically to ensure we don't shift down a full tenth + // because essentially values are truncated instead of rounded, so 3.09999f will become + // 3.0f and not 3.1f + this.RawX = this.ConvertMapCoordinateToRawPosition(niceXCoord + fudgeFactor, this.Map.SizeFactor); + this.RawY = this.ConvertMapCoordinateToRawPosition(niceYCoord + fudgeFactor, this.Map.SizeFactor); } - } - /// - /// Gets the region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea". - /// - [JsonIgnore] - public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name; + /// + /// Initializes a new instance of the class. + /// Creates an interactable MapLinkPayload from a raw position. + /// + /// The id of the TerritoryType entry for this link. + /// The id of the Map entry for this link. + /// The internal raw x-coordinate for this link. + /// The internal raw y-coordinate for this link. + public MapLinkPayload(uint territoryTypeId, uint mapId, int rawX, int rawY) + { + this.territoryTypeId = territoryTypeId; + this.mapId = mapId; + this.RawX = rawX; + this.RawY = rawY; + } - /// - /// Gets the place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks". - /// - [JsonIgnore] - public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name; + /// + /// Initializes a new instance of the class. + /// Creates an interactable MapLinkPayload from a human-readable position. + /// + internal MapLinkPayload() + { + } - /// - /// Gets the data string for this map link, for use by internal game functions that take a string variant and not a binary payload. - /// - public string DataString => $"m:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}"; + /// + public override PayloadType Type => PayloadType.MapLink; - /// - public override string ToString() - { - return $"{this.Type} - TerritoryTypeId: {this.territoryTypeId}, MapId: {this.mapId}, RawX: {this.RawX}, RawY: {this.RawY}, display: {this.PlaceName} {this.CoordinateString}"; - } + /// + /// Gets the Map specified for this map link. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Map Map => this.map ??= this.DataResolver.GetExcelSheet().GetRow(this.mapId); - /// - protected override byte[] EncodeImpl() - { - var packedTerritoryAndMapBytes = MakePackedInteger(this.territoryTypeId, this.mapId); - var xBytes = MakeInteger(unchecked((uint)this.RawX)); - var yBytes = MakeInteger(unchecked((uint)this.RawY)); + /// + /// Gets the TerritoryType specified for this map link. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public TerritoryType TerritoryType => this.territoryType ??= this.DataResolver.GetExcelSheet().GetRow(this.territoryTypeId); - var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length; + /// + /// Gets the internal x-coordinate for this map position. + /// + public int RawX { get; private set; } - var bytes = new List() + /// + /// Gets the internal y-coordinate for this map position. + /// + public int RawY { get; private set; } + + // these could be cached, but this isn't really too egregious + + /// + /// Gets the readable x-coordinate position for this map link. This value is approximate and unrounded. + /// + public float XCoord => this.ConvertRawPositionToMapCoordinate(this.RawX, this.Map.SizeFactor); + + /// + /// Gets the readable y-coordinate position for this map link. This value is approximate and unrounded. + /// + [JsonIgnore] + public float YCoord => this.ConvertRawPositionToMapCoordinate(this.RawY, this.Map.SizeFactor); + + // there is no Z; it's purely in the text payload where applicable + + /// + /// Gets the printable map coordinates for this link. This value tries to match the in-game printable text as closely + /// as possible but is an approximation and may be slightly off for some positions. + /// + [JsonIgnore] + public string CoordinateString + { + get + { + // this truncates the values to one decimal without rounding, which is what the game does + // the fudge also just attempts to correct the truncated/displayed value for rounding/fp issues + // TODO: should this fudge factor be the same as in the ctor? currently not since that is customizable + const float fudge = 0.02f; + var x = Math.Truncate((this.XCoord + fudge) * 10.0f) / 10.0f; + var y = Math.Truncate((this.YCoord + fudge) * 10.0f) / 10.0f; + + // the formatting and spacing the game uses + return $"( {x:0.0} , {y:0.0} )"; + } + } + + /// + /// Gets the region name for this map link. This corresponds to the upper zone name found in the actual in-game map UI. eg, "La Noscea". + /// + [JsonIgnore] + public string PlaceNameRegion => this.placeNameRegion ??= this.TerritoryType.PlaceNameRegion.Value?.Name; + + /// + /// Gets the place name for this map link. This corresponds to the lower zone name found in the actual in-game map UI. eg, "Limsa Lominsa Upper Decks". + /// + [JsonIgnore] + public string PlaceName => this.placeName ??= this.TerritoryType.PlaceName.Value?.Name; + + /// + /// Gets the data string for this map link, for use by internal game functions that take a string variant and not a binary payload. + /// + public string DataString => $"m:{this.TerritoryType.RowId},{this.Map.RowId},{this.RawX},{this.RawY}"; + + /// + public override string ToString() + { + return $"{this.Type} - TerritoryTypeId: {this.territoryTypeId}, MapId: {this.mapId}, RawX: {this.RawX}, RawY: {this.RawY}, display: {this.PlaceName} {this.CoordinateString}"; + } + + /// + protected override byte[] EncodeImpl() + { + var packedTerritoryAndMapBytes = MakePackedInteger(this.territoryTypeId, this.mapId); + var xBytes = MakeInteger(unchecked((uint)this.RawX)); + var yBytes = MakeInteger(unchecked((uint)this.RawY)); + + var chunkLen = 4 + packedTerritoryAndMapBytes.Length + xBytes.Length + yBytes.Length; + + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.MapPositionLink, }; - bytes.AddRange(packedTerritoryAndMapBytes); - bytes.AddRange(xBytes); - bytes.AddRange(yBytes); + bytes.AddRange(packedTerritoryAndMapBytes); + bytes.AddRange(xBytes); + bytes.AddRange(yBytes); - // unk - bytes.AddRange(new byte[] { 0xFF, 0x01, END_BYTE }); + // unk + bytes.AddRange(new byte[] { 0xFF, 0x01, END_BYTE }); - return bytes.ToArray(); - } - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - // for debugging for now - var oldPos = reader.BaseStream.Position; - var bytes = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); - reader.BaseStream.Position = oldPos; - - try - { - (this.territoryTypeId, this.mapId) = GetPackedIntegers(reader); - this.RawX = unchecked((int)GetInteger(reader)); - this.RawY = unchecked((int)GetInteger(reader)); - // the Z coordinate is never in this chunk, just the text (if applicable) - - // seems to always be FF 01 - reader.ReadBytes(2); + return bytes.ToArray(); } - catch (NotSupportedException) + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) { - Serilog.Log.Information($"Unsupported map bytes {BitConverter.ToString(bytes).Replace("-", " ")}"); - // we still want to break here for now, or we'd just throw again later - throw; + // for debugging for now + var oldPos = reader.BaseStream.Position; + var bytes = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position)); + reader.BaseStream.Position = oldPos; + + try + { + (this.territoryTypeId, this.mapId) = GetPackedIntegers(reader); + this.RawX = unchecked((int)GetInteger(reader)); + this.RawY = unchecked((int)GetInteger(reader)); + // the Z coordinate is never in this chunk, just the text (if applicable) + + // seems to always be FF 01 + reader.ReadBytes(2); + } + catch (NotSupportedException) + { + Serilog.Log.Information($"Unsupported map bytes {BitConverter.ToString(bytes).Replace("-", " ")}"); + // we still want to break here for now, or we'd just throw again later + throw; + } } + + #region ugliness + + // from https://github.com/xivapi/ffxiv-datamining/blob/master/docs/MapCoordinates.md + // extra 1/1000 because that is how the network ints are done + private float ConvertRawPositionToMapCoordinate(int pos, float scale) + { + var c = scale / 100.0f; + var scaledPos = pos * c / 1000.0f; + + return (41.0f / c * ((scaledPos + 1024.0f) / 2048.0f)) + 1.0f; + } + + // Created as the inverse of ConvertRawPositionToMapCoordinate(), since no one seemed to have a version of that + private int ConvertMapCoordinateToRawPosition(float pos, float scale) + { + var c = scale / 100.0f; + + var scaledPos = (((pos - 1.0f) * c / 41.0f * 2048.0f) - 1024.0f) / c; + scaledPos *= 1000.0f; + + return (int)scaledPos; + } + + #endregion } - - #region ugliness - - // from https://github.com/xivapi/ffxiv-datamining/blob/master/docs/MapCoordinates.md - // extra 1/1000 because that is how the network ints are done - private float ConvertRawPositionToMapCoordinate(int pos, float scale) - { - var c = scale / 100.0f; - var scaledPos = pos * c / 1000.0f; - - return (41.0f / c * ((scaledPos + 1024.0f) / 2048.0f)) + 1.0f; - } - - // Created as the inverse of ConvertRawPositionToMapCoordinate(), since no one seemed to have a version of that - private int ConvertMapCoordinateToRawPosition(float pos, float scale) - { - var c = scale / 100.0f; - - var scaledPos = (((pos - 1.0f) * c / 41.0f * 2048.0f) - 1024.0f) / c; - scaledPos *= 1000.0f; - - return (int)scaledPos; - } - - #endregion } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/NewLinePayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/NewLinePayload.cs index 3df724e75..13aba8077 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/NewLinePayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/NewLinePayload.cs @@ -1,33 +1,34 @@ using System; using System.IO; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// A wrapped newline character. -/// -public class NewLinePayload : Payload, ITextProvider +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.NewLine, 0x01, END_BYTE }; - /// - /// Gets an instance of NewLinePayload. + /// A wrapped newline character. /// - public static NewLinePayload Payload => new(); - - /// - /// Gets the text of this payload, evaluates to Environment.NewLine. - /// - public string Text => Environment.NewLine; - - /// - public override PayloadType Type => PayloadType.NewLine; - - /// - protected override byte[] EncodeImpl() => this.bytes; - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) + public class NewLinePayload : Payload, ITextProvider { + private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.NewLine, 0x01, END_BYTE }; + + /// + /// Gets an instance of NewLinePayload. + /// + public static NewLinePayload Payload => new(); + + /// + /// Gets the text of this payload, evaluates to Environment.NewLine. + /// + public string Text => Environment.NewLine; + + /// + public override PayloadType Type => PayloadType.NewLine; + + /// + protected override byte[] EncodeImpl() => this.bytes; + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs index ea4bab53d..01d8331e1 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/PlayerPayload.cs @@ -5,89 +5,89 @@ using System.Text; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload representing a player link. -/// -public class PlayerPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private World world; - - [JsonProperty] - private uint serverId; - - [JsonProperty] - private string playerName; - /// - /// Initializes a new instance of the class. - /// Create a PlayerPayload link for the specified player. + /// An SeString Payload representing a player link. /// - /// The player's displayed name. - /// The player's home server id. - public PlayerPayload(string playerName, uint serverId) + public class PlayerPayload : Payload { - this.playerName = playerName; - this.serverId = serverId; - } + private World world; - /// - /// Initializes a new instance of the class. - /// Create a PlayerPayload link for the specified player. - /// - internal PlayerPayload() - { - } + [JsonProperty] + private uint serverId; - /// - /// Gets the Lumina object representing the player's home server. - /// - /// - /// Value is evaluated lazily and cached. - /// - [JsonIgnore] - public World World => this.world ??= this.DataResolver.GetExcelSheet().GetRow(this.serverId); + [JsonProperty] + private string playerName; - /// - /// Gets or sets the player's displayed name. This does not contain the server name. - /// - [JsonIgnore] - public string PlayerName - { - get + /// + /// Initializes a new instance of the class. + /// Create a PlayerPayload link for the specified player. + /// + /// The player's displayed name. + /// The player's home server id. + public PlayerPayload(string playerName, uint serverId) { - return this.playerName; + this.playerName = playerName; + this.serverId = serverId; } - set + /// + /// Initializes a new instance of the class. + /// Create a PlayerPayload link for the specified player. + /// + internal PlayerPayload() { - this.playerName = value; - this.Dirty = true; } - } - /// - /// Gets the text representation of this player link matching how it might appear in-game. - /// The world name will always be present. - /// - [JsonIgnore] - public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}"; + /// + /// Gets the Lumina object representing the player's home server. + /// + /// + /// Value is evaluated lazily and cached. + /// + [JsonIgnore] + public World World => this.world ??= this.DataResolver.GetExcelSheet().GetRow(this.serverId); - /// - public override PayloadType Type => PayloadType.Player; + /// + /// Gets or sets the player's displayed name. This does not contain the server name. + /// + [JsonIgnore] + public string PlayerName + { + get + { + return this.playerName; + } - /// - public override string ToString() - { - return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}"; - } + set + { + this.playerName = value; + this.Dirty = true; + } + } - /// - protected override byte[] EncodeImpl() - { - var chunkLen = this.playerName.Length + 7; - var bytes = new List() + /// + /// Gets the text representation of this player link matching how it might appear in-game. + /// The world name will always be present. + /// + [JsonIgnore] + public string DisplayedName => $"{this.PlayerName}{(char)SeIconChar.CrossWorld}{this.World.Name}"; + + /// + public override PayloadType Type => PayloadType.Player; + + /// + public override string ToString() + { + return $"{this.Type} - PlayerName: {this.PlayerName}, ServerId: {this.serverId}, ServerName: {this.World.Name}"; + } + + /// + protected override byte[] EncodeImpl() + { + var chunkLen = this.playerName.Length + 7; + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.PlayerName, @@ -98,38 +98,39 @@ public class PlayerPayload : Payload (byte)(this.playerName.Length + 1), }; - bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); - bytes.Add(END_BYTE); + bytes.AddRange(Encoding.UTF8.GetBytes(this.playerName)); + bytes.Add(END_BYTE); - // TODO: should these really be here? additional payloads should come in separately already... + // TODO: should these really be here? additional payloads should come in separately already... - // encoded names are followed by the name in plain text again - // use the payload parsing for consistency, as this is technically a new chunk - bytes.AddRange(new TextPayload(this.playerName).Encode()); + // encoded names are followed by the name in plain text again + // use the payload parsing for consistency, as this is technically a new chunk + bytes.AddRange(new TextPayload(this.playerName).Encode()); - // unsure about this entire packet, but it seems to always follow a name - bytes.AddRange(new byte[] - { + // unsure about this entire packet, but it seems to always follow a name + bytes.AddRange(new byte[] + { START_BYTE, (byte)SeStringChunkType.Interactable, 0x07, (byte)EmbeddedInfoType.LinkTerminator, 0x01, 0x01, 0x01, 0xFF, 0x01, END_BYTE, - }); + }); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - // unk - reader.ReadByte(); + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + // unk + reader.ReadByte(); - this.serverId = GetInteger(reader); + this.serverId = GetInteger(reader); - // unk - reader.ReadBytes(2); + // unk + reader.ReadBytes(2); - var nameLen = (int)GetInteger(reader); - this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen)); + var nameLen = (int)GetInteger(reader); + this.playerName = Encoding.UTF8.GetString(reader.ReadBytes(nameLen)); + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs index f53819b87..d9dca651b 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/QuestPayload.cs @@ -4,74 +4,75 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload representing an interactable quest link. -/// -public class QuestPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private Quest quest; - - [JsonProperty] - private uint questId; - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable quest link for the specified quest. + /// An SeString Payload representing an interactable quest link. /// - /// The id of the quest. - public QuestPayload(uint questId) + public class QuestPayload : Payload { - this.questId = questId; - } + private Quest quest; - /// - /// Initializes a new instance of the class. - /// Creates a payload representing an interactable quest link for the specified quest. - /// - internal QuestPayload() - { - } + [JsonProperty] + private uint questId; - /// - public override PayloadType Type => PayloadType.Quest; + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable quest link for the specified quest. + /// + /// The id of the quest. + public QuestPayload(uint questId) + { + this.questId = questId; + } - /// - /// Gets the underlying Lumina Quest represented by this payload. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet().GetRow(this.questId); + /// + /// Initializes a new instance of the class. + /// Creates a payload representing an interactable quest link for the specified quest. + /// + internal QuestPayload() + { + } - /// - public override string ToString() - { - return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}"; - } + /// + public override PayloadType Type => PayloadType.Quest; - /// - protected override byte[] EncodeImpl() - { - var idBytes = MakeInteger((ushort)this.questId); - var chunkLen = idBytes.Length + 4; + /// + /// Gets the underlying Lumina Quest represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Quest Quest => this.quest ??= this.DataResolver.GetExcelSheet().GetRow(this.questId); - var bytes = new List() + /// + public override string ToString() + { + return $"{this.Type} - QuestId: {this.questId}, Name: {this.Quest?.Name ?? "QUEST NOT FOUND"}"; + } + + /// + protected override byte[] EncodeImpl() + { + var idBytes = MakeInteger((ushort)this.questId); + var chunkLen = idBytes.Length + 4; + + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.QuestLink, }; - bytes.AddRange(idBytes); - bytes.AddRange(new byte[] { 0x01, 0x01, END_BYTE }); - return bytes.ToArray(); - } + bytes.AddRange(idBytes); + bytes.AddRange(new byte[] { 0x01, 0x01, END_BYTE }); + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - // Game uses int16, Luimina uses int32 - this.questId = GetInteger(reader) + 65536; + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + // Game uses int16, Luimina uses int32 + this.questId = GetInteger(reader) + 65536; + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs index d83026e87..0d80f015d 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/RawPayload.cs @@ -5,115 +5,116 @@ using System.Linq; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload representing unhandled raw payload data. -/// Mainly useful for constructing unhandled hardcoded payloads, or forwarding any unknown -/// payloads without modification. -/// -public class RawPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - [JsonProperty] - private byte chunkType; - - [JsonProperty] - private byte[] data; - /// - /// Initializes a new instance of the class. + /// An SeString Payload representing unhandled raw payload data. + /// Mainly useful for constructing unhandled hardcoded payloads, or forwarding any unknown + /// payloads without modification. /// - /// The payload data. - public RawPayload(byte[] data) + public class RawPayload : Payload { - // this payload is 'special' in that we require the entire chunk to be passed in - // and not just the data after the header - // This sets data to hold the chunk data fter the header, excluding the END_BYTE - this.chunkType = data[1]; - this.data = data.Skip(3).Take(data.Length - 4).ToArray(); - } + [JsonProperty] + private byte chunkType; - /// - /// Initializes a new instance of the class. - /// - /// The chunk type. - [JsonConstructor] - internal RawPayload(byte chunkType) - { - this.chunkType = chunkType; - } + [JsonProperty] + private byte[] data; - /// - /// Gets a fixed Payload representing a common link-termination sequence, found in many payload chains. - /// - public static RawPayload LinkTerminator => new(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 }); - - /// - public override PayloadType Type => PayloadType.Unknown; - - /// - /// Gets the entire payload byte sequence for this payload. - /// The returned data is a clone and modifications will not be persisted. - /// - [JsonIgnore] - public byte[] Data - { - // this is a bit different from the underlying data - // We need to store just the chunk data for decode to behave nicely, but when reading data out - // it makes more sense to get the entire payload - get + /// + /// Initializes a new instance of the class. + /// + /// The payload data. + public RawPayload(byte[] data) { - // for now don't allow modifying the contents - // because we don't really have a way to track Dirty - return (byte[])this.Encode().Clone(); - } - } - - /// - public override bool Equals(object obj) - { - if (obj is RawPayload rp) - { - if (rp.Data.Length != this.Data.Length) return false; - return !this.Data.Where((t, i) => rp.Data[i] != t).Any(); + // this payload is 'special' in that we require the entire chunk to be passed in + // and not just the data after the header + // This sets data to hold the chunk data fter the header, excluding the END_BYTE + this.chunkType = data[1]; + this.data = data.Skip(3).Take(data.Length - 4).ToArray(); } - return false; - } + /// + /// Initializes a new instance of the class. + /// + /// The chunk type. + [JsonConstructor] + internal RawPayload(byte chunkType) + { + this.chunkType = chunkType; + } - /// - public override int GetHashCode() - { - return HashCode.Combine(this.Type, this.chunkType, this.data); - } + /// + /// Gets a fixed Payload representing a common link-termination sequence, found in many payload chains. + /// + public static RawPayload LinkTerminator => new(new byte[] { 0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03 }); - /// - public override string ToString() - { - return $"{this.Type} - Data: {BitConverter.ToString(this.Data).Replace("-", " ")}"; - } + /// + public override PayloadType Type => PayloadType.Unknown; - /// - protected override byte[] EncodeImpl() - { - var chunkLen = this.data.Length + 1; + /// + /// Gets the entire payload byte sequence for this payload. + /// The returned data is a clone and modifications will not be persisted. + /// + [JsonIgnore] + public byte[] Data + { + // this is a bit different from the underlying data + // We need to store just the chunk data for decode to behave nicely, but when reading data out + // it makes more sense to get the entire payload + get + { + // for now don't allow modifying the contents + // because we don't really have a way to track Dirty + return (byte[])this.Encode().Clone(); + } + } - var bytes = new List() + /// + public override bool Equals(object obj) + { + if (obj is RawPayload rp) + { + if (rp.Data.Length != this.Data.Length) return false; + return !this.Data.Where((t, i) => rp.Data[i] != t).Any(); + } + + return false; + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(this.Type, this.chunkType, this.data); + } + + /// + public override string ToString() + { + return $"{this.Type} - Data: {BitConverter.ToString(this.Data).Replace("-", " ")}"; + } + + /// + protected override byte[] EncodeImpl() + { + var chunkLen = this.data.Length + 1; + + var bytes = new List() { START_BYTE, this.chunkType, (byte)chunkLen, }; - bytes.AddRange(this.data); + bytes.AddRange(this.data); - bytes.Add(END_BYTE); + bytes.Add(END_BYTE); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1)); + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.data = reader.ReadBytes((int)(endOfStream - reader.BaseStream.Position + 1)); + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs index 1739b9cda..bb50f6a02 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/SeHyphenPayload.cs @@ -1,32 +1,33 @@ using System.IO; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// A wrapped '–'. -/// -public class SeHyphenPayload : Payload, ITextProvider +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.SeHyphen, 0x01, END_BYTE }; - /// - /// Gets an instance of SeHyphenPayload. + /// A wrapped '–'. /// - public static SeHyphenPayload Payload => new(); - - /// - /// Gets the text, just a '–'. - /// - public string Text => "–"; - - /// - public override PayloadType Type => PayloadType.SeHyphen; - - /// - protected override byte[] EncodeImpl() => this.bytes; - - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) + public class SeHyphenPayload : Payload, ITextProvider { + private readonly byte[] bytes = { START_BYTE, (byte)SeStringChunkType.SeHyphen, 0x01, END_BYTE }; + + /// + /// Gets an instance of SeHyphenPayload. + /// + public static SeHyphenPayload Payload => new(); + + /// + /// Gets the text, just a '–'. + /// + public string Text => "–"; + + /// + public override PayloadType Type => PayloadType.SeHyphen; + + /// + protected override byte[] EncodeImpl() => this.bytes; + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs index f6408d279..e7df98ae8 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/StatusPayload.cs @@ -4,75 +4,76 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload representing an interactable status link. -/// -public class StatusPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private Status status; - - [JsonProperty] - private uint statusId; - /// - /// Initializes a new instance of the class. - /// Creates a new StatusPayload for the given status id. + /// An SeString Payload representing an interactable status link. /// - /// The id of the Status for this link. - public StatusPayload(uint statusId) + public class StatusPayload : Payload { - this.statusId = statusId; - } + private Status status; - /// - /// Initializes a new instance of the class. - /// Creates a new StatusPayload for the given status id. - /// - internal StatusPayload() - { - } + [JsonProperty] + private uint statusId; - /// - public override PayloadType Type => PayloadType.Status; + /// + /// Initializes a new instance of the class. + /// Creates a new StatusPayload for the given status id. + /// + /// The id of the Status for this link. + public StatusPayload(uint statusId) + { + this.statusId = statusId; + } - /// - /// Gets the Lumina Status object represented by this payload. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public Status Status => this.status ??= this.DataResolver.GetExcelSheet().GetRow(this.statusId); + /// + /// Initializes a new instance of the class. + /// Creates a new StatusPayload for the given status id. + /// + internal StatusPayload() + { + } - /// - public override string ToString() - { - return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}"; - } + /// + public override PayloadType Type => PayloadType.Status; - /// - protected override byte[] EncodeImpl() - { - var idBytes = MakeInteger(this.statusId); + /// + /// Gets the Lumina Status object represented by this payload. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public Status Status => this.status ??= this.DataResolver.GetExcelSheet().GetRow(this.statusId); - var chunkLen = idBytes.Length + 7; - var bytes = new List() + /// + public override string ToString() + { + return $"{this.Type} - StatusId: {this.statusId}, Name: {this.Status.Name}"; + } + + /// + protected override byte[] EncodeImpl() + { + var idBytes = MakeInteger(this.statusId); + + var chunkLen = idBytes.Length + 7; + var bytes = new List() { START_BYTE, (byte)SeStringChunkType.Interactable, (byte)chunkLen, (byte)EmbeddedInfoType.Status, }; - bytes.AddRange(idBytes); - // unk - bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE }); + bytes.AddRange(idBytes); + // unk + bytes.AddRange(new byte[] { 0x01, 0x01, 0xFF, 0x02, 0x20, END_BYTE }); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.statusId = GetInteger(reader); + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.statusId = GetInteger(reader); + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs index 9d8e64fa6..d12bdd3a7 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/TextPayload.cs @@ -5,97 +5,98 @@ using System.Text; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload representing a plain text string. -/// -public class TextPayload : Payload, ITextProvider +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - [JsonProperty] - private string text; - /// - /// Initializes a new instance of the class. - /// Creates a new TextPayload for the given text. + /// An SeString Payload representing a plain text string. /// - /// The text to include for this payload. - public TextPayload(string text) + public class TextPayload : Payload, ITextProvider { - this.text = text; - } + [JsonProperty] + private string text; - /// - /// Initializes a new instance of the class. - /// Creates a new TextPayload for the given text. - /// - internal TextPayload() - { - } - - /// - public override PayloadType Type => PayloadType.RawText; - - /// - /// Gets or sets the text contained in this payload. - /// This may contain SE's special unicode characters. - /// - [JsonIgnore] - public string Text - { - get + /// + /// Initializes a new instance of the class. + /// Creates a new TextPayload for the given text. + /// + /// The text to include for this payload. + public TextPayload(string text) { - return this.text; + this.text = text; } - set + /// + /// Initializes a new instance of the class. + /// Creates a new TextPayload for the given text. + /// + internal TextPayload() { - this.text = value; - this.Dirty = true; - } - } - - /// - public override string ToString() - { - return $"{this.Type} - Text: {this.Text}"; - } - - /// - protected override byte[] EncodeImpl() - { - // special case to allow for empty text payloads, so users don't have to check - // this may change or go away - if (string.IsNullOrEmpty(this.text)) - { - return Array.Empty(); } - return Encoding.UTF8.GetBytes(this.text); - } + /// + public override PayloadType Type => PayloadType.RawText; - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - var textBytes = new List(); - - while (reader.BaseStream.Position < endOfStream) + /// + /// Gets or sets the text contained in this payload. + /// This may contain SE's special unicode characters. + /// + [JsonIgnore] + public string Text { - var nextByte = reader.ReadByte(); - if (nextByte == START_BYTE) + get { - // rewind since this byte isn't part of this payload - reader.BaseStream.Position--; - break; + return this.text; } - textBytes.Add(nextByte); + set + { + this.text = value; + this.Dirty = true; + } } - if (textBytes.Count > 0) + /// + public override string ToString() { - // TODO: handling of the game's assorted special unicode characters - this.text = Encoding.UTF8.GetString(textBytes.ToArray()); + return $"{this.Type} - Text: {this.Text}"; + } + + /// + protected override byte[] EncodeImpl() + { + // special case to allow for empty text payloads, so users don't have to check + // this may change or go away + if (string.IsNullOrEmpty(this.text)) + { + return Array.Empty(); + } + + return Encoding.UTF8.GetBytes(this.text); + } + + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + var textBytes = new List(); + + while (reader.BaseStream.Position < endOfStream) + { + var nextByte = reader.ReadByte(); + if (nextByte == START_BYTE) + { + // rewind since this byte isn't part of this payload + reader.BaseStream.Position--; + break; + } + + textBytes.Add(nextByte); + } + + if (textBytes.Count > 0) + { + // TODO: handling of the game's assorted special unicode characters + this.text = Encoding.UTF8.GetString(textBytes.ToArray()); + } } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs index ff6d783fb..4934004bf 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIForegroundPayload.cs @@ -4,110 +4,111 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload representing a UI foreground color applied to following text payloads. -/// -public class UIForegroundPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private UIColor color; - - [JsonProperty] - private ushort colorKey; - /// - /// Initializes a new instance of the class. - /// Creates a new UIForegroundPayload for the given UIColor key. + /// An SeString Payload representing a UI foreground color applied to following text payloads. /// - /// A UIColor key. - public UIForegroundPayload(ushort colorKey) + public class UIForegroundPayload : Payload { - this.colorKey = colorKey; - } + private UIColor color; - /// - /// Initializes a new instance of the class. - /// Creates a new UIForegroundPayload for the given UIColor key. - /// - internal UIForegroundPayload() - { - } + [JsonProperty] + private ushort colorKey; - /// - /// Gets a payload representing disabling foreground color on following text. - /// - // TODO Make this work with DI - public static UIForegroundPayload UIForegroundOff => new(0); - - /// - public override PayloadType Type => PayloadType.UIForeground; - - /// - /// Gets a value indicating whether or not this payload represents applying a foreground color, or disabling one. - /// - public bool IsEnabled => this.ColorKey != 0; - - /// - /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); - - /// - /// Gets or sets the color key used as a lookup in the UIColor table for this foreground color. - /// - [JsonIgnore] - public ushort ColorKey - { - get + /// + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. + /// + /// A UIColor key. + public UIForegroundPayload(ushort colorKey) { - return this.colorKey; + this.colorKey = colorKey; } - set + /// + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. + /// + internal UIForegroundPayload() { - this.colorKey = value; - this.color = null; - this.Dirty = true; } - } - /// - /// Gets the Red/Green/Blue values for this foreground color, encoded as a typical hex color. - /// - [JsonIgnore] - public uint RGB => this.UIColor.UIForeground & 0xFFFFFF; + /// + /// Gets a payload representing disabling foreground color on following text. + /// + // TODO Make this work with DI + public static UIForegroundPayload UIForegroundOff => new(0); - /// - public override string ToString() - { - return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; - } + /// + public override PayloadType Type => PayloadType.UIForeground; - /// - protected override byte[] EncodeImpl() - { - var colorBytes = MakeInteger(this.colorKey); - var chunkLen = colorBytes.Length + 1; + /// + /// Gets a value indicating whether or not this payload represents applying a foreground color, or disabling one. + /// + public bool IsEnabled => this.ColorKey != 0; - var bytes = new List(new byte[] + /// + /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIForeground. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + + /// + /// Gets or sets the color key used as a lookup in the UIColor table for this foreground color. + /// + [JsonIgnore] + public ushort ColorKey { + get + { + return this.colorKey; + } + + set + { + this.colorKey = value; + this.color = null; + this.Dirty = true; + } + } + + /// + /// Gets the Red/Green/Blue values for this foreground color, encoded as a typical hex color. + /// + [JsonIgnore] + public uint RGB => this.UIColor.UIForeground & 0xFFFFFF; + + /// + public override string ToString() + { + return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; + } + + /// + protected override byte[] EncodeImpl() + { + var colorBytes = MakeInteger(this.colorKey); + var chunkLen = colorBytes.Length + 1; + + var bytes = new List(new byte[] + { START_BYTE, (byte)SeStringChunkType.UIForeground, (byte)chunkLen, - }); + }); - bytes.AddRange(colorBytes); - bytes.Add(END_BYTE); + bytes.AddRange(colorBytes); + bytes.Add(END_BYTE); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.colorKey = (ushort)GetInteger(reader); + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.colorKey = (ushort)GetInteger(reader); + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs index ad5dfd7d3..480aae24a 100644 --- a/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs +++ b/Dalamud/Game/Text/SeStringHandling/Payloads/UIGlowPayload.cs @@ -4,110 +4,111 @@ using System.IO; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling.Payloads; - -/// -/// An SeString Payload representing a UI glow color applied to following text payloads. -/// -public class UIGlowPayload : Payload +namespace Dalamud.Game.Text.SeStringHandling.Payloads { - private UIColor color; - - [JsonProperty] - private ushort colorKey; - /// - /// Initializes a new instance of the class. - /// Creates a new UIForegroundPayload for the given UIColor key. + /// An SeString Payload representing a UI glow color applied to following text payloads. /// - /// A UIColor key. - public UIGlowPayload(ushort colorKey) + public class UIGlowPayload : Payload { - this.colorKey = colorKey; - } + private UIColor color; - /// - /// Initializes a new instance of the class. - /// Creates a new UIForegroundPayload for the given UIColor key. - /// - internal UIGlowPayload() - { - } + [JsonProperty] + private ushort colorKey; - /// - /// Gets a payload representing disabling glow color on following text. - /// - // TODO Make this work with DI - public static UIGlowPayload UIGlowOff => new(0); - - /// - public override PayloadType Type => PayloadType.UIGlow; - - /// - /// Gets or sets the color key used as a lookup in the UIColor table for this glow color. - /// - [JsonIgnore] - public ushort ColorKey - { - get + /// + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. + /// + /// A UIColor key. + public UIGlowPayload(ushort colorKey) { - return this.colorKey; + this.colorKey = colorKey; } - set + /// + /// Initializes a new instance of the class. + /// Creates a new UIForegroundPayload for the given UIColor key. + /// + internal UIGlowPayload() { - this.colorKey = value; - this.color = null; - this.Dirty = true; } - } - /// - /// Gets a value indicating whether or not this payload represents applying a glow color, or disabling one. - /// - public bool IsEnabled => this.ColorKey != 0; + /// + /// Gets a payload representing disabling glow color on following text. + /// + // TODO Make this work with DI + public static UIGlowPayload UIGlowOff => new(0); - /// - /// Gets the Red/Green/Blue values for this glow color, encoded as a typical hex color. - /// - [JsonIgnore] - public uint RGB => this.UIColor.UIGlow & 0xFFFFFF; + /// + public override PayloadType Type => PayloadType.UIGlow; - /// - /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow. - /// - /// - /// The value is evaluated lazily and cached. - /// - [JsonIgnore] - public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); - - /// - public override string ToString() - { - return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; - } - - /// - protected override byte[] EncodeImpl() - { - var colorBytes = MakeInteger(this.colorKey); - var chunkLen = colorBytes.Length + 1; - - var bytes = new List(new byte[] + /// + /// Gets or sets the color key used as a lookup in the UIColor table for this glow color. + /// + [JsonIgnore] + public ushort ColorKey { + get + { + return this.colorKey; + } + + set + { + this.colorKey = value; + this.color = null; + this.Dirty = true; + } + } + + /// + /// Gets a value indicating whether or not this payload represents applying a glow color, or disabling one. + /// + public bool IsEnabled => this.ColorKey != 0; + + /// + /// Gets the Red/Green/Blue values for this glow color, encoded as a typical hex color. + /// + [JsonIgnore] + public uint RGB => this.UIColor.UIGlow & 0xFFFFFF; + + /// + /// Gets a Lumina UIColor object representing this payload. The actual color data is at UIColor.UIGlow. + /// + /// + /// The value is evaluated lazily and cached. + /// + [JsonIgnore] + public UIColor UIColor => this.color ??= this.DataResolver.GetExcelSheet().GetRow(this.colorKey); + + /// + public override string ToString() + { + return $"{this.Type} - UIColor: {this.colorKey} color: {(this.IsEnabled ? this.RGB : 0)}"; + } + + /// + protected override byte[] EncodeImpl() + { + var colorBytes = MakeInteger(this.colorKey); + var chunkLen = colorBytes.Length + 1; + + var bytes = new List(new byte[] + { START_BYTE, (byte)SeStringChunkType.UIGlow, (byte)chunkLen, - }); + }); - bytes.AddRange(colorBytes); - bytes.Add(END_BYTE); + bytes.AddRange(colorBytes); + bytes.Add(END_BYTE); - return bytes.ToArray(); - } + return bytes.ToArray(); + } - /// - protected override void DecodeImpl(BinaryReader reader, long endOfStream) - { - this.colorKey = (ushort)GetInteger(reader); + /// + protected override void DecodeImpl(BinaryReader reader, long endOfStream) + { + this.colorKey = (ushort)GetInteger(reader); + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index 8f08cacf0..c7c3cff34 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -10,360 +10,361 @@ using Dalamud.Utility; using Lumina.Excel.GeneratedSheets; using Newtonsoft.Json; -namespace Dalamud.Game.Text.SeStringHandling; - -/// -/// This class represents a parsed SeString. -/// -public class SeString +namespace Dalamud.Game.Text.SeStringHandling { /// - /// Initializes a new instance of the class. - /// Creates a new SeString from an ordered list of payloads. + /// This class represents a parsed SeString. /// - public SeString() + public class SeString { - this.Payloads = new List(); - } + /// + /// Initializes a new instance of the class. + /// Creates a new SeString from an ordered list of payloads. + /// + public SeString() + { + this.Payloads = new List(); + } - /// - /// Initializes a new instance of the class. - /// Creates a new SeString from an ordered list of payloads. - /// - /// The Payload objects to make up this string. - [JsonConstructor] - public SeString(List payloads) - { - this.Payloads = payloads; - } + /// + /// Initializes a new instance of the class. + /// Creates a new SeString from an ordered list of payloads. + /// + /// The Payload objects to make up this string. + [JsonConstructor] + public SeString(List payloads) + { + this.Payloads = payloads; + } - /// - /// Initializes a new instance of the class. - /// Creates a new SeString from an ordered list of payloads. - /// - /// The Payload objects to make up this string. - public SeString(params Payload[] payloads) - { - this.Payloads = new List(payloads); - } + /// + /// Initializes a new instance of the class. + /// Creates a new SeString from an ordered list of payloads. + /// + /// The Payload objects to make up this string. + public SeString(params Payload[] payloads) + { + this.Payloads = new List(payloads); + } - /// - /// Gets a list of Payloads necessary to display the arrow link marker icon in chat - /// with the appropriate glow and coloring. - /// - /// A list of all the payloads required to insert the link marker. - public static IEnumerable TextArrowPayloads => new List(new Payload[] - { + /// + /// Gets a list of Payloads necessary to display the arrow link marker icon in chat + /// with the appropriate glow and coloring. + /// + /// A list of all the payloads required to insert the link marker. + public static IEnumerable TextArrowPayloads => new List(new Payload[] + { new UIForegroundPayload(0x01F4), new UIGlowPayload(0x01F5), new TextPayload($"{(char)SeIconChar.LinkMarker}"), UIGlowPayload.UIGlowOff, UIForegroundPayload.UIForegroundOff, - }); + }); - /// - /// Gets an empty SeString. - /// - public static SeString Empty => new(); + /// + /// Gets an empty SeString. + /// + public static SeString Empty => new(); - /// - /// Gets the ordered list of payloads included in this SeString. - /// - public List Payloads { get; } + /// + /// Gets the ordered list of payloads included in this SeString. + /// + public List Payloads { get; } - /// - /// Gets all of the raw text from a message as a single joined string. - /// - /// - /// All the raw text from the contained payloads, joined into a single string. - /// - public string TextValue - { - get + /// + /// Gets all of the raw text from a message as a single joined string. + /// + /// + /// All the raw text from the contained payloads, joined into a single string. + /// + public string TextValue { - return this.Payloads - .Where(p => p is ITextProvider) - .Cast() - .Aggregate(new StringBuilder(), (sb, tp) => sb.Append(tp.Text), sb => sb.ToString()); - } - } - - /// - /// Implicitly convert a string into a SeString containing a . - /// - /// string to convert. - /// Equivalent SeString. - public static implicit operator SeString(string str) => new(new TextPayload(str)); - - /// - /// Implicitly convert a string into a SeString containing a . - /// - /// string to convert. - /// Equivalent SeString. - public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString(); - - /// - /// Parse a binary game message into an SeString. - /// - /// Pointer to the string's data in memory. - /// Length of the string's data in memory. - /// An SeString containing parsed Payload objects for each payload in the data. - public static unsafe SeString Parse(byte* ptr, int len) - { - if (ptr == null) - return Empty; - - var payloads = new List(); - - using (var stream = new UnmanagedMemoryStream(ptr, len)) - using (var reader = new BinaryReader(stream)) - { - while (stream.Position < len) + get { - var payload = Payload.Decode(reader); - if (payload != null) - payloads.Add(payload); + return this.Payloads + .Where(p => p is ITextProvider) + .Cast() + .Aggregate(new StringBuilder(), (sb, tp) => sb.Append(tp.Text), sb => sb.ToString()); } } - return new SeString(payloads); - } + /// + /// Implicitly convert a string into a SeString containing a . + /// + /// string to convert. + /// Equivalent SeString. + public static implicit operator SeString(string str) => new(new TextPayload(str)); - /// - /// Parse a binary game message into an SeString. - /// - /// Binary message payload data in SE's internal format. - /// An SeString containing parsed Payload objects for each payload in the data. - public static unsafe SeString Parse(ReadOnlySpan data) - { - fixed (byte* ptr = data) + /// + /// Implicitly convert a string into a SeString containing a . + /// + /// string to convert. + /// Equivalent SeString. + public static explicit operator SeString(Lumina.Text.SeString str) => str.ToDalamudString(); + + /// + /// Parse a binary game message into an SeString. + /// + /// Pointer to the string's data in memory. + /// Length of the string's data in memory. + /// An SeString containing parsed Payload objects for each payload in the data. + public static unsafe SeString Parse(byte* ptr, int len) { - return Parse(ptr, data.Length); - } - } + if (ptr == null) + return Empty; - /// - /// Parse a binary game message into an SeString. - /// - /// Binary message payload data in SE's internal format. - /// An SeString containing parsed Payload objects for each payload in the data. - public static SeString Parse(byte[] bytes) => Parse(new ReadOnlySpan(bytes)); + var payloads = new List(); - /// - /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. - /// - /// The id of the item to link. - /// Whether to link the high-quality variant of the item. - /// An optional name override to display, instead of the actual item name. - /// An SeString containing all the payloads necessary to display an item link in the chat log. - public static SeString CreateItemLink(uint itemId, bool isHq, string? displayNameOverride = null) - { - var data = Service.Get(); + using (var stream = new UnmanagedMemoryStream(ptr, len)) + using (var reader = new BinaryReader(stream)) + { + while (stream.Position < len) + { + var payload = Payload.Decode(reader); + if (payload != null) + payloads.Add(payload); + } + } - var displayName = displayNameOverride ?? data.GetExcelSheet()?.GetRow(itemId)?.Name; - if (isHq) - { - displayName += $" {(char)SeIconChar.HighQuality}"; + return new SeString(payloads); } - // TODO: probably a cleaner way to build these than doing the bulk+insert - var payloads = new List(new Payload[] + /// + /// Parse a binary game message into an SeString. + /// + /// Binary message payload data in SE's internal format. + /// An SeString containing parsed Payload objects for each payload in the data. + public static unsafe SeString Parse(ReadOnlySpan data) { + fixed (byte* ptr = data) + { + return Parse(ptr, data.Length); + } + } + + /// + /// Parse a binary game message into an SeString. + /// + /// Binary message payload data in SE's internal format. + /// An SeString containing parsed Payload objects for each payload in the data. + public static SeString Parse(byte[] bytes) => Parse(new ReadOnlySpan(bytes)); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. + /// + /// The id of the item to link. + /// Whether to link the high-quality variant of the item. + /// An optional name override to display, instead of the actual item name. + /// An SeString containing all the payloads necessary to display an item link in the chat log. + public static SeString CreateItemLink(uint itemId, bool isHq, string? displayNameOverride = null) + { + var data = Service.Get(); + + var displayName = displayNameOverride ?? data.GetExcelSheet()?.GetRow(itemId)?.Name; + if (isHq) + { + displayName += $" {(char)SeIconChar.HighQuality}"; + } + + // TODO: probably a cleaner way to build these than doing the bulk+insert + var payloads = new List(new Payload[] + { new UIForegroundPayload(0x0225), new UIGlowPayload(0x0226), new ItemPayload(itemId, isHq), // arrow goes here new TextPayload(displayName), RawPayload.LinkTerminator, - // sometimes there is another set of uiglow/foreground off payloads here - // might be necessary when including additional text after the item name - }); - payloads.InsertRange(3, TextArrowPayloads); + // sometimes there is another set of uiglow/foreground off payloads here + // might be necessary when including additional text after the item name + }); + payloads.InsertRange(3, TextArrowPayloads); - return new SeString(payloads); - } + return new SeString(payloads); + } - /// - /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. - /// - /// The Lumina Item to link. - /// Whether to link the high-quality variant of the item. - /// An optional name override to display, instead of the actual item name. - /// An SeString containing all the payloads necessary to display an item link in the chat log. - public static SeString CreateItemLink(Item item, bool isHq, string? displayNameOverride = null) - { - return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name); - } - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. - /// - /// The id of the TerritoryType for this map link. - /// The id of the Map for this map link. - /// The raw x-coordinate for this link. - /// The raw y-coordinate for this link.. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - public static SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) - { - var mapPayload = new MapLinkPayload(territoryId, mapId, rawX, rawY); - var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}"; - - var payloads = new List(new Payload[] + /// + /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. + /// + /// The Lumina Item to link. + /// Whether to link the high-quality variant of the item. + /// An optional name override to display, instead of the actual item name. + /// An SeString containing all the payloads necessary to display an item link in the chat log. + public static SeString CreateItemLink(Item item, bool isHq, string? displayNameOverride = null) { - mapPayload, - // arrow goes here - new TextPayload(nameString), - RawPayload.LinkTerminator, - }); - payloads.InsertRange(1, TextArrowPayloads); + return CreateItemLink(item.RowId, isHq, displayNameOverride ?? item.Name); + } - return new SeString(payloads); - } - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. - /// - /// The id of the TerritoryType for this map link. - /// The id of the Map for this map link. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - public static SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) - { - var mapPayload = new MapLinkPayload(territoryId, mapId, xCoord, yCoord, fudgeFactor); - var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}"; - - var payloads = new List(new Payload[] + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. + /// + /// The id of the TerritoryType for this map link. + /// The id of the Map for this map link. + /// The raw x-coordinate for this link. + /// The raw y-coordinate for this link.. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + public static SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) { - mapPayload, - // arrow goes here - new TextPayload(nameString), - RawPayload.LinkTerminator, - }); - payloads.InsertRange(1, TextArrowPayloads); + var mapPayload = new MapLinkPayload(territoryId, mapId, rawX, rawY); + var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}"; - return new SeString(payloads); - } - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. - /// Returns null if no corresponding PlaceName was found. - /// - /// The name of the location for this link. This should be exactly the name as seen in a displayed map link in-game for the same zone. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - public static SeString? CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) - { - var data = Service.Get(); - - var mapSheet = data.GetExcelSheet(); - - var matches = data.GetExcelSheet() - .Where(row => row.Name.ToString().ToLowerInvariant() == placeName.ToLowerInvariant()) - .ToArray(); - - foreach (var place in matches) - { - var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId); - if (map != null) + var payloads = new List(new Payload[] { - return CreateMapLink(map.TerritoryType.Row, map.RowId, xCoord, yCoord, fudgeFactor); + mapPayload, + // arrow goes here + new TextPayload(nameString), + RawPayload.LinkTerminator, + }); + payloads.InsertRange(1, TextArrowPayloads); + + return new SeString(payloads); + } + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. + /// + /// The id of the TerritoryType for this map link. + /// The id of the Map for this map link. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + public static SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) + { + var mapPayload = new MapLinkPayload(territoryId, mapId, xCoord, yCoord, fudgeFactor); + var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}"; + + var payloads = new List(new Payload[] + { + mapPayload, + // arrow goes here + new TextPayload(nameString), + RawPayload.LinkTerminator, + }); + payloads.InsertRange(1, TextArrowPayloads); + + return new SeString(payloads); + } + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. + /// Returns null if no corresponding PlaceName was found. + /// + /// The name of the location for this link. This should be exactly the name as seen in a displayed map link in-game for the same zone. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + public static SeString? CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) + { + var data = Service.Get(); + + var mapSheet = data.GetExcelSheet(); + + var matches = data.GetExcelSheet() + .Where(row => row.Name.ToString().ToLowerInvariant() == placeName.ToLowerInvariant()) + .ToArray(); + + foreach (var place in matches) + { + var map = mapSheet.FirstOrDefault(row => row.PlaceName.Row == place.RowId); + if (map != null) + { + return CreateMapLink(map.TerritoryType.Row, map.RowId, xCoord, yCoord, fudgeFactor); + } } + + // TODO: empty? throw? + return null; } - // TODO: empty? throw? - return null; - } - - /// - /// Creates a SeString from a json. (For testing - not recommended for production use.) - /// - /// A serialized SeString produced by ToJson() . - /// A SeString initialized with values from the json. - public static SeString? FromJson(string json) - { - var s = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + /// + /// Creates a SeString from a json. (For testing - not recommended for production use.) + /// + /// A serialized SeString produced by ToJson() . + /// A SeString initialized with values from the json. + public static SeString? FromJson(string json) { - PreserveReferencesHandling = PreserveReferencesHandling.Objects, - TypeNameHandling = TypeNameHandling.Auto, - ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, - }); + var s = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + TypeNameHandling = TypeNameHandling.Auto, + ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor, + }); - return s; - } - - /// - /// Serializes the SeString to json. - /// - /// An json representation of this object. - public string ToJson() - { - return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings() - { - PreserveReferencesHandling = PreserveReferencesHandling.Objects, - ReferenceLoopHandling = ReferenceLoopHandling.Ignore, - TypeNameHandling = TypeNameHandling.Auto, - }); - } - - /// - /// Appends the contents of one SeString to this one. - /// - /// The SeString to append to this one. - /// This object. - public SeString Append(SeString other) - { - this.Payloads.AddRange(other.Payloads); - return this; - } - - /// - /// Appends a list of payloads to this SeString. - /// - /// The Payloads to append. - /// This object. - public SeString Append(List payloads) - { - this.Payloads.AddRange(payloads); - return this; - } - - /// - /// Appends a single payload to this SeString. - /// - /// The payload to append. - /// This object. - public SeString Append(Payload payload) - { - this.Payloads.Add(payload); - return this; - } - - /// - /// Encodes the Payloads in this SeString into a binary representation - /// suitable for use by in-game handlers, such as the chat log. - /// - /// The binary encoded payload data. - public byte[] Encode() - { - var messageBytes = new List(); - foreach (var p in this.Payloads) - { - messageBytes.AddRange(p.Encode()); + return s; } - return messageBytes.ToArray(); - } + /// + /// Serializes the SeString to json. + /// + /// An json representation of this object. + public string ToJson() + { + return JsonConvert.SerializeObject(this, Formatting.Indented, new JsonSerializerSettings() + { + PreserveReferencesHandling = PreserveReferencesHandling.Objects, + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + TypeNameHandling = TypeNameHandling.Auto, + }); + } - /// - /// Get the text value of this SeString. - /// - /// The TextValue property. - public override string ToString() - { - return this.TextValue; + /// + /// Appends the contents of one SeString to this one. + /// + /// The SeString to append to this one. + /// This object. + public SeString Append(SeString other) + { + this.Payloads.AddRange(other.Payloads); + return this; + } + + /// + /// Appends a list of payloads to this SeString. + /// + /// The Payloads to append. + /// This object. + public SeString Append(List payloads) + { + this.Payloads.AddRange(payloads); + return this; + } + + /// + /// Appends a single payload to this SeString. + /// + /// The payload to append. + /// This object. + public SeString Append(Payload payload) + { + this.Payloads.Add(payload); + return this; + } + + /// + /// Encodes the Payloads in this SeString into a binary representation + /// suitable for use by in-game handlers, such as the chat log. + /// + /// The binary encoded payload data. + public byte[] Encode() + { + var messageBytes = new List(); + foreach (var p in this.Payloads) + { + messageBytes.AddRange(p.Encode()); + } + + return messageBytes.ToArray(); + } + + /// + /// Get the text value of this SeString. + /// + /// The TextValue property. + public override string ToString() + { + return this.TextValue; + } } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs index 10c3b6347..16f2b4209 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeStringManager.cs @@ -9,108 +9,109 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Lumina.Excel.GeneratedSheets; -namespace Dalamud.Game.Text.SeStringHandling; - -/// -/// This class facilitates creating new SeStrings and breaking down existing ones into their individual payload components. -/// -[PluginInterface] -[InterfaceVersion("1.0")] -[Obsolete("This class is obsolete. Please use the static methods on SeString instead.")] -public sealed class SeStringManager +namespace Dalamud.Game.Text.SeStringHandling { /// - /// Initializes a new instance of the class. + /// This class facilitates creating new SeStrings and breaking down existing ones into their individual payload components. /// - internal SeStringManager() + [PluginInterface] + [InterfaceVersion("1.0")] + [Obsolete("This class is obsolete. Please use the static methods on SeString instead.")] + public sealed class SeStringManager { + /// + /// Initializes a new instance of the class. + /// + internal SeStringManager() + { + } + + /// + /// Parse a binary game message into an SeString. + /// + /// Pointer to the string's data in memory. + /// Length of the string's data in memory. + /// An SeString containing parsed Payload objects for each payload in the data. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public unsafe SeString Parse(byte* ptr, int len) => SeString.Parse(ptr, len); + + /// + /// Parse a binary game message into an SeString. + /// + /// Binary message payload data in SE's internal format. + /// An SeString containing parsed Payload objects for each payload in the data. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public unsafe SeString Parse(ReadOnlySpan data) => SeString.Parse(data); + + /// + /// Parse a binary game message into an SeString. + /// + /// Binary message payload data in SE's internal format. + /// An SeString containing parsed Payload objects for each payload in the data. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString Parse(byte[] bytes) => SeString.Parse(new ReadOnlySpan(bytes)); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. + /// + /// The id of the item to link. + /// Whether to link the high-quality variant of the item. + /// An optional name override to display, instead of the actual item name. + /// An SeString containing all the payloads necessary to display an item link in the chat log. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(itemId, isHQ, displayNameOverride); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. + /// + /// The Lumina Item to link. + /// Whether to link the high-quality variant of the item. + /// An optional name override to display, instead of the actual item name. + /// An SeString containing all the payloads necessary to display an item link in the chat log. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateItemLink(Item item, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(item, isHQ, displayNameOverride); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. + /// + /// The id of the TerritoryType for this map link. + /// The id of the Map for this map link. + /// The raw x-coordinate for this link. + /// The raw y-coordinate for this link.. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) => + SeString.CreateMapLink(territoryId, mapId, rawX, rawY); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. + /// + /// The id of the TerritoryType for this map link. + /// The id of the Map for this map link. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(territoryId, mapId, xCoord, yCoord, fudgeFactor); + + /// + /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. + /// + /// The name of the location for this link. This should be exactly the name as seen in a displayed map link in-game for the same zone. + /// The human-readable x-coordinate for this link. + /// The human-readable y-coordinate for this link. + /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. + /// An SeString containing all of the payloads necessary to display a map link in the chat log. + [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] + public SeString CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(placeName, xCoord, yCoord, fudgeFactor); + + /// + /// Creates a list of Payloads necessary to display the arrow link marker icon in chat + /// with the appropriate glow and coloring. + /// + /// A list of all the payloads required to insert the link marker. + [Obsolete("This data is obsolete. Please use the static version on SeString instead.", true)] + public List TextArrowPayloads() => new(SeString.TextArrowPayloads); } - - /// - /// Parse a binary game message into an SeString. - /// - /// Pointer to the string's data in memory. - /// Length of the string's data in memory. - /// An SeString containing parsed Payload objects for each payload in the data. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public unsafe SeString Parse(byte* ptr, int len) => SeString.Parse(ptr, len); - - /// - /// Parse a binary game message into an SeString. - /// - /// Binary message payload data in SE's internal format. - /// An SeString containing parsed Payload objects for each payload in the data. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public unsafe SeString Parse(ReadOnlySpan data) => SeString.Parse(data); - - /// - /// Parse a binary game message into an SeString. - /// - /// Binary message payload data in SE's internal format. - /// An SeString containing parsed Payload objects for each payload in the data. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString Parse(byte[] bytes) => SeString.Parse(new ReadOnlySpan(bytes)); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. - /// - /// The id of the item to link. - /// Whether to link the high-quality variant of the item. - /// An optional name override to display, instead of the actual item name. - /// An SeString containing all the payloads necessary to display an item link in the chat log. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(itemId, isHQ, displayNameOverride); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log. - /// - /// The Lumina Item to link. - /// Whether to link the high-quality variant of the item. - /// An optional name override to display, instead of the actual item name. - /// An SeString containing all the payloads necessary to display an item link in the chat log. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateItemLink(Item item, bool isHQ, string displayNameOverride = null) => SeString.CreateItemLink(item, isHQ, displayNameOverride); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. - /// - /// The id of the TerritoryType for this map link. - /// The id of the Map for this map link. - /// The raw x-coordinate for this link. - /// The raw y-coordinate for this link.. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY) => - SeString.CreateMapLink(territoryId, mapId, rawX, rawY); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log. - /// - /// The id of the TerritoryType for this map link. - /// The id of the Map for this map link. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(territoryId, mapId, xCoord, yCoord, fudgeFactor); - - /// - /// Creates an SeString representing an entire Payload chain that can be used to link a map position in the chat log, matching a specified zone name. - /// - /// The name of the location for this link. This should be exactly the name as seen in a displayed map link in-game for the same zone. - /// The human-readable x-coordinate for this link. - /// The human-readable y-coordinate for this link. - /// An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases. - /// An SeString containing all of the payloads necessary to display a map link in the chat log. - [Obsolete("This method is obsolete. Please use the static methods on SeString instead.", true)] - public SeString CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f) => SeString.CreateMapLink(placeName, xCoord, yCoord, fudgeFactor); - - /// - /// Creates a list of Payloads necessary to display the arrow link marker icon in chat - /// with the appropriate glow and coloring. - /// - /// A list of all the payloads required to insert the link marker. - [Obsolete("This data is obsolete. Please use the static version on SeString instead.", true)] - public List TextArrowPayloads() => new(SeString.TextArrowPayloads); } diff --git a/Dalamud/Game/Text/XivChatEntry.cs b/Dalamud/Game/Text/XivChatEntry.cs index afc89b906..a5a11766e 100644 --- a/Dalamud/Game/Text/XivChatEntry.cs +++ b/Dalamud/Game/Text/XivChatEntry.cs @@ -2,35 +2,36 @@ using System; using Dalamud.Game.Text.SeStringHandling; -namespace Dalamud.Game.Text; - -/// -/// This class represents a single chat log entry. -/// -public sealed class XivChatEntry +namespace Dalamud.Game.Text { /// - /// Gets or sets the type of entry. + /// This class represents a single chat log entry. /// - public XivChatType Type { get; set; } = XivChatType.Debug; + public sealed class XivChatEntry + { + /// + /// Gets or sets the type of entry. + /// + public XivChatType Type { get; set; } = XivChatType.Debug; - /// - /// Gets or sets the sender ID. - /// - public uint SenderId { get; set; } + /// + /// Gets or sets the sender ID. + /// + public uint SenderId { get; set; } - /// - /// Gets or sets the sender name. - /// - public SeString Name { get; set; } = string.Empty; + /// + /// Gets or sets the sender name. + /// + public SeString Name { get; set; } = string.Empty; - /// - /// Gets or sets the message. - /// - public SeString Message { get; set; } = string.Empty; + /// + /// Gets or sets the message. + /// + public SeString Message { get; set; } = string.Empty; - /// - /// Gets or sets the message parameters. - /// - public IntPtr Parameters { get; set; } + /// + /// Gets or sets the message parameters. + /// + public IntPtr Parameters { get; set; } + } } diff --git a/Dalamud/Game/Text/XivChatType.cs b/Dalamud/Game/Text/XivChatType.cs index b5d7bf2b3..d89fc5f0c 100644 --- a/Dalamud/Game/Text/XivChatType.cs +++ b/Dalamud/Game/Text/XivChatType.cs @@ -1,236 +1,237 @@ -namespace Dalamud.Game.Text; - -/// -/// The FFXIV chat types as seen in the LogKind ex table. -/// -public enum XivChatType : ushort // FIXME: this is a single byte +namespace Dalamud.Game.Text { /// - /// No chat type. + /// The FFXIV chat types as seen in the LogKind ex table. /// - None = 0, + public enum XivChatType : ushort // FIXME: this is a single byte + { + /// + /// No chat type. + /// + None = 0, - /// - /// The debug chat type. - /// - Debug = 1, + /// + /// The debug chat type. + /// + Debug = 1, - /// - /// The urgent chat type. - /// - [XivChatTypeInfo("Urgent", "urgent", 0xFF9400D3)] - Urgent = 2, + /// + /// The urgent chat type. + /// + [XivChatTypeInfo("Urgent", "urgent", 0xFF9400D3)] + Urgent = 2, - /// - /// The notice chat type. - /// - [XivChatTypeInfo("Notice", "notice", 0xFF9400D3)] - Notice = 3, + /// + /// The notice chat type. + /// + [XivChatTypeInfo("Notice", "notice", 0xFF9400D3)] + Notice = 3, - /// - /// The say chat type. - /// - [XivChatTypeInfo("Say", "say", 0xFFFFFFFF)] - Say = 10, + /// + /// The say chat type. + /// + [XivChatTypeInfo("Say", "say", 0xFFFFFFFF)] + Say = 10, - /// - /// The shout chat type. - /// - [XivChatTypeInfo("Shout", "shout", 0xFFFF4500)] - Shout = 11, + /// + /// The shout chat type. + /// + [XivChatTypeInfo("Shout", "shout", 0xFFFF4500)] + Shout = 11, - /// - /// The outgoing tell chat type. - /// - TellOutgoing = 12, + /// + /// The outgoing tell chat type. + /// + TellOutgoing = 12, - /// - /// The incoming tell chat type. - /// - [XivChatTypeInfo("Tell", "tell", 0xFFFF69B4)] - TellIncoming = 13, + /// + /// The incoming tell chat type. + /// + [XivChatTypeInfo("Tell", "tell", 0xFFFF69B4)] + TellIncoming = 13, - /// - /// The party chat type. - /// - [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] - Party = 14, + /// + /// The party chat type. + /// + [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] + Party = 14, - /// - /// The alliance chat type. - /// - [XivChatTypeInfo("Alliance", "alliance", 0xFFFF4500)] - Alliance = 15, + /// + /// The alliance chat type. + /// + [XivChatTypeInfo("Alliance", "alliance", 0xFFFF4500)] + Alliance = 15, - /// - /// The linkshell 1 chat type. - /// - [XivChatTypeInfo("Linkshell 1", "ls1", 0xFF228B22)] - Ls1 = 16, + /// + /// The linkshell 1 chat type. + /// + [XivChatTypeInfo("Linkshell 1", "ls1", 0xFF228B22)] + Ls1 = 16, - /// - /// The linkshell 2 chat type. - /// - [XivChatTypeInfo("Linkshell 2", "ls2", 0xFF228B22)] - Ls2 = 17, + /// + /// The linkshell 2 chat type. + /// + [XivChatTypeInfo("Linkshell 2", "ls2", 0xFF228B22)] + Ls2 = 17, - /// - /// The linkshell 3 chat type. - /// - [XivChatTypeInfo("Linkshell 3", "ls3", 0xFF228B22)] - Ls3 = 18, + /// + /// The linkshell 3 chat type. + /// + [XivChatTypeInfo("Linkshell 3", "ls3", 0xFF228B22)] + Ls3 = 18, - /// - /// The linkshell 4 chat type. - /// - [XivChatTypeInfo("Linkshell 4", "ls4", 0xFF228B22)] - Ls4 = 19, + /// + /// The linkshell 4 chat type. + /// + [XivChatTypeInfo("Linkshell 4", "ls4", 0xFF228B22)] + Ls4 = 19, - /// - /// The linkshell 5 chat type. - /// - [XivChatTypeInfo("Linkshell 5", "ls5", 0xFF228B22)] - Ls5 = 20, + /// + /// The linkshell 5 chat type. + /// + [XivChatTypeInfo("Linkshell 5", "ls5", 0xFF228B22)] + Ls5 = 20, - /// - /// The linkshell 6 chat type. - /// - [XivChatTypeInfo("Linkshell 6", "ls6", 0xFF228B22)] - Ls6 = 21, + /// + /// The linkshell 6 chat type. + /// + [XivChatTypeInfo("Linkshell 6", "ls6", 0xFF228B22)] + Ls6 = 21, - /// - /// The linkshell 7 chat type. - /// - [XivChatTypeInfo("Linkshell 7", "ls7", 0xFF228B22)] - Ls7 = 22, + /// + /// The linkshell 7 chat type. + /// + [XivChatTypeInfo("Linkshell 7", "ls7", 0xFF228B22)] + Ls7 = 22, - /// - /// The linkshell 8 chat type. - /// - [XivChatTypeInfo("Linkshell 8", "ls8", 0xFF228B22)] - Ls8 = 23, + /// + /// The linkshell 8 chat type. + /// + [XivChatTypeInfo("Linkshell 8", "ls8", 0xFF228B22)] + Ls8 = 23, - /// - /// The free company chat type. - /// - [XivChatTypeInfo("Free Company", "fc", 0xFF00BFFF)] - FreeCompany = 24, + /// + /// The free company chat type. + /// + [XivChatTypeInfo("Free Company", "fc", 0xFF00BFFF)] + FreeCompany = 24, - /// - /// The novice network chat type. - /// - [XivChatTypeInfo("Novice Network", "nn", 0xFF8B4513)] - NoviceNetwork = 27, + /// + /// The novice network chat type. + /// + [XivChatTypeInfo("Novice Network", "nn", 0xFF8B4513)] + NoviceNetwork = 27, - /// - /// The custom emotes chat type. - /// - [XivChatTypeInfo("Custom Emotes", "emote", 0xFF8B4513)] - CustomEmote = 28, + /// + /// The custom emotes chat type. + /// + [XivChatTypeInfo("Custom Emotes", "emote", 0xFF8B4513)] + CustomEmote = 28, - /// - /// The standard emotes chat type. - /// - [XivChatTypeInfo("Standard Emotes", "emote", 0xFF8B4513)] - StandardEmote = 29, + /// + /// The standard emotes chat type. + /// + [XivChatTypeInfo("Standard Emotes", "emote", 0xFF8B4513)] + StandardEmote = 29, - /// - /// The yell chat type. - /// - [XivChatTypeInfo("Yell", "yell", 0xFFFFFF00)] - Yell = 30, + /// + /// The yell chat type. + /// + [XivChatTypeInfo("Yell", "yell", 0xFFFFFF00)] + Yell = 30, - /// - /// The cross-world party chat type. - /// - [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] - CrossParty = 32, + /// + /// The cross-world party chat type. + /// + [XivChatTypeInfo("Party", "party", 0xFF1E90FF)] + CrossParty = 32, - /// - /// The PvP team chat type. - /// - [XivChatTypeInfo("PvP Team", "pvpt", 0xFFF4A460)] - PvPTeam = 36, + /// + /// The PvP team chat type. + /// + [XivChatTypeInfo("PvP Team", "pvpt", 0xFFF4A460)] + PvPTeam = 36, - /// - /// The cross-world linkshell chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 1", "cw1", 0xFF1E90FF)] - CrossLinkShell1 = 37, + /// + /// The cross-world linkshell chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 1", "cw1", 0xFF1E90FF)] + CrossLinkShell1 = 37, - /// - /// The echo chat type. - /// - [XivChatTypeInfo("Echo", "echo", 0xFF808080)] - Echo = 56, + /// + /// The echo chat type. + /// + [XivChatTypeInfo("Echo", "echo", 0xFF808080)] + Echo = 56, - /// - /// The system error chat type. - /// - SystemError = 58, + /// + /// The system error chat type. + /// + SystemError = 58, - /// - /// The system message chat type. - /// - SystemMessage = 57, + /// + /// The system message chat type. + /// + SystemMessage = 57, - /// - /// The system message (gathering) chat type. - /// - GatheringSystemMessage = 59, + /// + /// The system message (gathering) chat type. + /// + GatheringSystemMessage = 59, - /// - /// The error message chat type. - /// - ErrorMessage = 60, + /// + /// The error message chat type. + /// + ErrorMessage = 60, - /// - /// The retainer sale chat type. - /// - /// - /// This might be used for other purposes. - /// - RetainerSale = 71, + /// + /// The retainer sale chat type. + /// + /// + /// This might be used for other purposes. + /// + RetainerSale = 71, - /// - /// The cross-world linkshell 2 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)] - CrossLinkShell2 = 101, + /// + /// The cross-world linkshell 2 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)] + CrossLinkShell2 = 101, - /// - /// The cross-world linkshell 3 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 3", "cw3", 0xFF1E90FF)] - CrossLinkShell3 = 102, + /// + /// The cross-world linkshell 3 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 3", "cw3", 0xFF1E90FF)] + CrossLinkShell3 = 102, - /// - /// The cross-world linkshell 4 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 4", "cw4", 0xFF1E90FF)] - CrossLinkShell4 = 103, + /// + /// The cross-world linkshell 4 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 4", "cw4", 0xFF1E90FF)] + CrossLinkShell4 = 103, - /// - /// The cross-world linkshell 5 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 5", "cw5", 0xFF1E90FF)] - CrossLinkShell5 = 104, + /// + /// The cross-world linkshell 5 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 5", "cw5", 0xFF1E90FF)] + CrossLinkShell5 = 104, - /// - /// The cross-world linkshell 6 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 6", "cw6", 0xFF1E90FF)] - CrossLinkShell6 = 105, + /// + /// The cross-world linkshell 6 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 6", "cw6", 0xFF1E90FF)] + CrossLinkShell6 = 105, - /// - /// The cross-world linkshell 7 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 7", "cw7", 0xFF1E90FF)] - CrossLinkShell7 = 106, + /// + /// The cross-world linkshell 7 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 7", "cw7", 0xFF1E90FF)] + CrossLinkShell7 = 106, - /// - /// The cross-world linkshell 8 chat type. - /// - [XivChatTypeInfo("Crossworld Linkshell 8", "cw8", 0xFF1E90FF)] - CrossLinkShell8 = 107, + /// + /// The cross-world linkshell 8 chat type. + /// + [XivChatTypeInfo("Crossworld Linkshell 8", "cw8", 0xFF1E90FF)] + CrossLinkShell8 = 107, + } } diff --git a/Dalamud/Game/Text/XivChatTypeExtensions.cs b/Dalamud/Game/Text/XivChatTypeExtensions.cs index 3bdeb1525..a26687c47 100644 --- a/Dalamud/Game/Text/XivChatTypeExtensions.cs +++ b/Dalamud/Game/Text/XivChatTypeExtensions.cs @@ -1,19 +1,20 @@ using Dalamud.Utility; -namespace Dalamud.Game.Text; - -/// -/// Extension methods for the type. -/// -public static class XivChatTypeExtensions +namespace Dalamud.Game.Text { /// - /// Get the InfoAttribute associated with this chat type. + /// Extension methods for the type. /// - /// The chat type. - /// The info attribute. - public static XivChatTypeInfoAttribute GetDetails(this XivChatType chatType) + public static class XivChatTypeExtensions { - return chatType.GetAttribute(); + /// + /// Get the InfoAttribute associated with this chat type. + /// + /// The chat type. + /// The info attribute. + public static XivChatTypeInfoAttribute GetDetails(this XivChatType chatType) + { + return chatType.GetAttribute(); + } } } diff --git a/Dalamud/Game/Text/XivChatTypeInfoAttribute.cs b/Dalamud/Game/Text/XivChatTypeInfoAttribute.cs index 91bdeef40..e549ac761 100644 --- a/Dalamud/Game/Text/XivChatTypeInfoAttribute.cs +++ b/Dalamud/Game/Text/XivChatTypeInfoAttribute.cs @@ -1,38 +1,39 @@ using System; -namespace Dalamud.Game.Text; - -/// -/// Storage for relevant information associated with the chat type. -/// -[AttributeUsage(AttributeTargets.Field)] -public class XivChatTypeInfoAttribute : Attribute +namespace Dalamud.Game.Text { /// - /// Initializes a new instance of the class. + /// Storage for relevant information associated with the chat type. /// - /// The fancy name. - /// The name slug. - /// The default color. - internal XivChatTypeInfoAttribute(string fancyName, string slug, uint defaultColor) + [AttributeUsage(AttributeTargets.Field)] + public class XivChatTypeInfoAttribute : Attribute { - this.FancyName = fancyName; - this.Slug = slug; - this.DefaultColor = defaultColor; + /// + /// Initializes a new instance of the class. + /// + /// The fancy name. + /// The name slug. + /// The default color. + internal XivChatTypeInfoAttribute(string fancyName, string slug, uint defaultColor) + { + this.FancyName = fancyName; + this.Slug = slug; + this.DefaultColor = defaultColor; + } + + /// + /// Gets the "fancy" name of the type. + /// + public string FancyName { get; } + + /// + /// Gets the type name slug or short-form. + /// + public string Slug { get; } + + /// + /// Gets the type default color. + /// + public uint DefaultColor { get; } } - - /// - /// Gets the "fancy" name of the type. - /// - public string FancyName { get; } - - /// - /// Gets the type name slug or short-form. - /// - public string Slug { get; } - - /// - /// Gets the type default color. - /// - public uint DefaultColor { get; } } diff --git a/Dalamud/Hooking/AsmHook.cs b/Dalamud/Hooking/AsmHook.cs index 043b02b9e..35eac4efd 100644 --- a/Dalamud/Hooking/AsmHook.cs +++ b/Dalamud/Hooking/AsmHook.cs @@ -6,177 +6,178 @@ using Dalamud.Hooking.Internal; using Dalamud.Memory; using Reloaded.Hooks; -namespace Dalamud.Hooking; - -/// -/// Manages a hook which can be used to intercept a call to native function. -/// This class is basically a thin wrapper around the LocalHook type to provide helper functions. -/// -public sealed class AsmHook : IDisposable, IDalamudHook +namespace Dalamud.Hooking { - private readonly IntPtr address; - private readonly Reloaded.Hooks.Definitions.IAsmHook hookImpl; - - private bool isActivated = false; - private bool isEnabled = false; - - private DynamicMethod statsMethod; - /// - /// Initializes a new instance of the class. - /// This is an assembly hook and should not be used for except under unique circumstances. - /// Hook is not activated until Enable() method is called. + /// Manages a hook which can be used to intercept a call to native function. + /// This class is basically a thin wrapper around the LocalHook type to provide helper functions. /// - /// A memory address to install a hook. - /// Assembly code representing your hook. - /// The name of what you are hooking, since a delegate is not required. - /// How the hook is inserted into the execution flow. - public AsmHook(IntPtr address, byte[] assembly, string name, AsmHookBehaviour asmHookBehaviour = AsmHookBehaviour.ExecuteFirst) + public sealed class AsmHook : IDisposable, IDalamudHook { - address = HookManager.FollowJmp(address); + private readonly IntPtr address; + private readonly Reloaded.Hooks.Definitions.IAsmHook hookImpl; - var hasOtherHooks = HookManager.Originals.ContainsKey(address); - if (!hasOtherHooks) + private bool isActivated = false; + private bool isEnabled = false; + + private DynamicMethod statsMethod; + + /// + /// Initializes a new instance of the class. + /// This is an assembly hook and should not be used for except under unique circumstances. + /// Hook is not activated until Enable() method is called. + /// + /// A memory address to install a hook. + /// Assembly code representing your hook. + /// The name of what you are hooking, since a delegate is not required. + /// How the hook is inserted into the execution flow. + public AsmHook(IntPtr address, byte[] assembly, string name, AsmHookBehaviour asmHookBehaviour = AsmHookBehaviour.ExecuteFirst) { - MemoryHelper.ReadRaw(address, 0x32, out var original); - HookManager.Originals[address] = original; + address = HookManager.FollowJmp(address); + + var hasOtherHooks = HookManager.Originals.ContainsKey(address); + if (!hasOtherHooks) + { + MemoryHelper.ReadRaw(address, 0x32, out var original); + HookManager.Originals[address] = original; + } + + this.address = address; + this.hookImpl = ReloadedHooks.Instance.CreateAsmHook(assembly, address.ToInt64(), (Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour)asmHookBehaviour); + + this.statsMethod = new DynamicMethod(name, null, null); + this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); + var dele = this.statsMethod.CreateDelegate(typeof(Action)); + + HookManager.TrackedHooks.Add(new HookInfo(this, dele, Assembly.GetCallingAssembly())); } - this.address = address; - this.hookImpl = ReloadedHooks.Instance.CreateAsmHook(assembly, address.ToInt64(), (Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour)asmHookBehaviour); - - this.statsMethod = new DynamicMethod(name, null, null); - this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); - var dele = this.statsMethod.CreateDelegate(typeof(Action)); - - HookManager.TrackedHooks.Add(new HookInfo(this, dele, Assembly.GetCallingAssembly())); - } - - /// - /// Initializes a new instance of the class. - /// This is an assembly hook and should not be used for except under unique circumstances. - /// Hook is not activated until Enable() method is called. - /// - /// A memory address to install a hook. - /// FASM syntax assembly code representing your hook. The first line should be use64. - /// The name of what you are hooking, since a delegate is not required. - /// How the hook is inserted into the execution flow. - public AsmHook(IntPtr address, string[] assembly, string name, AsmHookBehaviour asmHookBehaviour = AsmHookBehaviour.ExecuteFirst) - { - address = HookManager.FollowJmp(address); - - var hasOtherHooks = HookManager.Originals.ContainsKey(address); - if (!hasOtherHooks) + /// + /// Initializes a new instance of the class. + /// This is an assembly hook and should not be used for except under unique circumstances. + /// Hook is not activated until Enable() method is called. + /// + /// A memory address to install a hook. + /// FASM syntax assembly code representing your hook. The first line should be use64. + /// The name of what you are hooking, since a delegate is not required. + /// How the hook is inserted into the execution flow. + public AsmHook(IntPtr address, string[] assembly, string name, AsmHookBehaviour asmHookBehaviour = AsmHookBehaviour.ExecuteFirst) { - MemoryHelper.ReadRaw(address, 0x32, out var original); - HookManager.Originals[address] = original; + address = HookManager.FollowJmp(address); + + var hasOtherHooks = HookManager.Originals.ContainsKey(address); + if (!hasOtherHooks) + { + MemoryHelper.ReadRaw(address, 0x32, out var original); + HookManager.Originals[address] = original; + } + + this.address = address; + this.hookImpl = ReloadedHooks.Instance.CreateAsmHook(assembly, address.ToInt64(), (Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour)asmHookBehaviour); + + this.statsMethod = new DynamicMethod(name, null, null); + this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); + var dele = this.statsMethod.CreateDelegate(typeof(Action)); + + HookManager.TrackedHooks.Add(new HookInfo(this, dele, Assembly.GetCallingAssembly())); } - this.address = address; - this.hookImpl = ReloadedHooks.Instance.CreateAsmHook(assembly, address.ToInt64(), (Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour)asmHookBehaviour); + /// + /// Gets a memory address of the target function. + /// + /// Hook is already disposed. + public IntPtr Address + { + get + { + this.CheckDisposed(); + return this.address; + } + } - this.statsMethod = new DynamicMethod(name, null, null); - this.statsMethod.GetILGenerator().Emit(OpCodes.Ret); - var dele = this.statsMethod.CreateDelegate(typeof(Action)); + /// + /// Gets a value indicating whether or not the hook is enabled. + /// + public bool IsEnabled + { + get + { + this.CheckDisposed(); + return this.isEnabled; + } + } - HookManager.TrackedHooks.Add(new HookInfo(this, dele, Assembly.GetCallingAssembly())); - } + /// + /// Gets a value indicating whether or not the hook has been disposed. + /// + public bool IsDisposed { get; private set; } - /// - /// Gets a memory address of the target function. - /// - /// Hook is already disposed. - public IntPtr Address - { - get + /// + public string BackendName => "Reloaded/Asm"; + + /// + /// Remove a hook from the current process. + /// + public void Dispose() + { + if (this.IsDisposed) + return; + + this.IsDisposed = true; + + if (this.isEnabled) + { + this.isEnabled = false; + this.hookImpl.Disable(); + } + } + + /// + /// Starts intercepting a call to the function. + /// + public void Enable() { this.CheckDisposed(); - return this.address; - } - } - /// - /// Gets a value indicating whether or not the hook is enabled. - /// - public bool IsEnabled - { - get + if (!this.isActivated) + { + this.isActivated = true; + this.hookImpl.Activate(); + } + + if (!this.isEnabled) + { + this.isEnabled = true; + this.hookImpl.Enable(); + } + } + + /// + /// Stops intercepting a call to the function. + /// + public void Disable() { this.CheckDisposed(); - return this.isEnabled; - } - } - /// - /// Gets a value indicating whether or not the hook has been disposed. - /// - public bool IsDisposed { get; private set; } + if (!this.isEnabled) + return; - /// - public string BackendName => "Reloaded/Asm"; - - /// - /// Remove a hook from the current process. - /// - public void Dispose() - { - if (this.IsDisposed) - return; - - this.IsDisposed = true; - - if (this.isEnabled) - { - this.isEnabled = false; - this.hookImpl.Disable(); - } - } - - /// - /// Starts intercepting a call to the function. - /// - public void Enable() - { - this.CheckDisposed(); - - if (!this.isActivated) - { - this.isActivated = true; - this.hookImpl.Activate(); + if (this.isEnabled) + { + this.isEnabled = false; + this.hookImpl.Disable(); + } } - if (!this.isEnabled) + /// + /// Check if this object has been disposed already. + /// + private void CheckDisposed() { - this.isEnabled = true; - this.hookImpl.Enable(); - } - } - - /// - /// Stops intercepting a call to the function. - /// - public void Disable() - { - this.CheckDisposed(); - - if (!this.isEnabled) - return; - - if (this.isEnabled) - { - this.isEnabled = false; - this.hookImpl.Disable(); - } - } - - /// - /// Check if this object has been disposed already. - /// - private void CheckDisposed() - { - if (this.IsDisposed) - { - throw new ObjectDisposedException(message: "Hook is already disposed", null); + if (this.IsDisposed) + { + throw new ObjectDisposedException(message: "Hook is already disposed", null); + } } } } diff --git a/Dalamud/Hooking/AsmHookBehaviour.cs b/Dalamud/Hooking/AsmHookBehaviour.cs index 6676ee670..9f856ae67 100644 --- a/Dalamud/Hooking/AsmHookBehaviour.cs +++ b/Dalamud/Hooking/AsmHookBehaviour.cs @@ -1,23 +1,24 @@ -namespace Dalamud.Hooking; - -/// -/// Defines the behaviour used by the Dalamud.Hooking.AsmHook. -/// This is equivalent to the same enumeration in Reloaded and is included so you do not have to reference the assembly. -/// -public enum AsmHookBehaviour +namespace Dalamud.Hooking { /// - /// Executes your assembly code before the original. + /// Defines the behaviour used by the Dalamud.Hooking.AsmHook. + /// This is equivalent to the same enumeration in Reloaded and is included so you do not have to reference the assembly. /// - ExecuteFirst = 0, + public enum AsmHookBehaviour + { + /// + /// Executes your assembly code before the original. + /// + ExecuteFirst = 0, - /// - /// Executes your assembly code after the original. - /// - ExecuteAfter = 1, + /// + /// Executes your assembly code after the original. + /// + ExecuteAfter = 1, - /// - /// Do not execute original replaced code (Dangerous!). - /// - DoNotExecuteOriginal = 2, + /// + /// Do not execute original replaced code (Dangerous!). + /// + DoNotExecuteOriginal = 2, + } } diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index 3a8c34656..430527ed0 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -7,259 +7,260 @@ using Dalamud.Hooking.Internal; using Dalamud.Memory; using Reloaded.Hooks; -namespace Dalamud.Hooking; - -/// -/// Manages a hook which can be used to intercept a call to native function. -/// This class is basically a thin wrapper around the LocalHook type to provide helper functions. -/// -/// Delegate type to represents a function prototype. This must be the same prototype as original function do. -public sealed class Hook : IDisposable, IDalamudHook where T : Delegate +namespace Dalamud.Hooking { - private readonly IntPtr address; - private readonly Reloaded.Hooks.Definitions.IHook hookImpl; - private readonly MinSharp.Hook minHookImpl; - private readonly bool isMinHook; - /// - /// Initializes a new instance of the class. - /// Hook is not activated until Enable() method is called. + /// Manages a hook which can be used to intercept a call to native function. + /// This class is basically a thin wrapper around the LocalHook type to provide helper functions. /// - /// A memory address to install a hook. - /// Callback function. Delegate must have a same original function prototype. - public Hook(IntPtr address, T detour) - : this(address, detour, false, Assembly.GetCallingAssembly()) + /// Delegate type to represents a function prototype. This must be the same prototype as original function do. + public sealed class Hook : IDisposable, IDalamudHook where T : Delegate { - } + private readonly IntPtr address; + private readonly Reloaded.Hooks.Definitions.IHook hookImpl; + private readonly MinSharp.Hook minHookImpl; + private readonly bool isMinHook; - /// - /// Initializes a new instance of the class. - /// 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. - /// Use the MinHook hooking library instead of Reloaded. - public Hook(IntPtr address, T detour, bool useMinHook) - : this(address, detour, useMinHook, Assembly.GetCallingAssembly()) - { - } - - private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly) - { - address = HookManager.FollowJmp(address); - this.isMinHook = !EnvironmentConfiguration.DalamudForceReloaded && (EnvironmentConfiguration.DalamudForceMinHook || useMinHook); - - var hasOtherHooks = HookManager.Originals.ContainsKey(address); - if (!hasOtherHooks) + /// + /// Initializes a new instance of the class. + /// Hook is not activated until Enable() method is called. + /// + /// A memory address to install a hook. + /// Callback function. Delegate must have a same original function prototype. + public Hook(IntPtr address, T detour) + : this(address, detour, false, Assembly.GetCallingAssembly()) { - MemoryHelper.ReadRaw(address, 0x32, out var original); - HookManager.Originals[address] = original; } - this.address = address; - if (this.isMinHook) + /// + /// Initializes a new instance of the class. + /// 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. + /// Use the MinHook hooking library instead of Reloaded. + public Hook(IntPtr address, T detour, bool useMinHook) + : this(address, detour, useMinHook, Assembly.GetCallingAssembly()) { - if (!HookManager.MultiHookTracker.TryGetValue(address, out var indexList)) - indexList = HookManager.MultiHookTracker[address] = new(); - - var index = (ulong)indexList.Count; - - this.minHookImpl = new MinSharp.Hook(address, detour, index); - - // Add afterwards, so the hookIdent starts at 0. - indexList.Add(this); - } - else - { - this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); } - HookManager.TrackedHooks.Add(new HookInfo(this, detour, callingAssembly)); - } - - /// - /// Gets a memory address of the target function. - /// - /// Hook is already disposed. - public IntPtr Address - { - get + private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly) { - this.CheckDisposed(); - return this.address; - } - } + address = HookManager.FollowJmp(address); + this.isMinHook = !EnvironmentConfiguration.DalamudForceReloaded && (EnvironmentConfiguration.DalamudForceMinHook || useMinHook); - /// - /// Gets a delegate function that can be used to call the actual function as if function is not hooked yet. - /// - /// Hook is already disposed. - public T Original - { - get - { - this.CheckDisposed(); + var hasOtherHooks = HookManager.Originals.ContainsKey(address); + if (!hasOtherHooks) + { + MemoryHelper.ReadRaw(address, 0x32, out var original); + HookManager.Originals[address] = original; + } + + this.address = address; if (this.isMinHook) { - return this.minHookImpl.Original; + if (!HookManager.MultiHookTracker.TryGetValue(address, out var indexList)) + indexList = HookManager.MultiHookTracker[address] = new(); + + var index = (ulong)indexList.Count; + + this.minHookImpl = new MinSharp.Hook(address, detour, index); + + // Add afterwards, so the hookIdent starts at 0. + indexList.Add(this); } else { - return this.hookImpl.OriginalFunction; + this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); } - } - } - /// - /// Gets a value indicating whether or not the hook is enabled. - /// - public bool IsEnabled - { - get + HookManager.TrackedHooks.Add(new HookInfo(this, detour, callingAssembly)); + } + + /// + /// Gets a memory address of the target function. + /// + /// Hook is already disposed. + public IntPtr Address { - this.CheckDisposed(); - if (this.isMinHook) + get { - return this.minHookImpl.Enabled; + this.CheckDisposed(); + return this.address; } - else + } + + /// + /// Gets a delegate function that can be used to call the actual function as if function is not hooked yet. + /// + /// Hook is already disposed. + public T Original + { + get { - return this.hookImpl.IsHookEnabled; + this.CheckDisposed(); + if (this.isMinHook) + { + return this.minHookImpl.Original; + } + else + { + return this.hookImpl.OriginalFunction; + } } } - } - /// - /// Gets a value indicating whether or not the hook has been disposed. - /// - public bool IsDisposed { get; private set; } - - /// - public string BackendName - { - get + /// + /// Gets a value indicating whether or not the hook is enabled. + /// + public bool IsEnabled { - if (this.isMinHook) - return "MinHook"; - - return "Reloaded"; - } - } - - /// - /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. - /// The hook is not activated until Enable() method is called. - /// - /// A name of the module currently loaded in the memory. (e.g. ws2_32.dll). - /// A name of the exported function name (e.g. send). - /// Callback function. Delegate must have a same original function prototype. - /// The hook with the supplied parameters. - public static Hook FromSymbol(string moduleName, string exportName, T detour) - => FromSymbol(moduleName, exportName, detour, false); - - /// - /// 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 name of the module currently loaded in the memory. (e.g. ws2_32.dll). - /// A name of the exported function name (e.g. send). - /// Callback function. Delegate must have a same original function prototype. - /// Use the MinHook hooking library instead of Reloaded. - /// The hook with the supplied parameters. - public static Hook FromSymbol(string moduleName, string exportName, T detour, bool useMinHook) - { - var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName); - if (moduleHandle == IntPtr.Zero) - throw new Exception($"Could not get a handle to module {moduleName}"); - - var procAddress = NativeFunctions.GetProcAddress(moduleHandle, exportName); - if (procAddress == IntPtr.Zero) - throw new Exception($"Could not get the address of {moduleName}::{exportName}"); - - return new Hook(procAddress, detour, useMinHook); - } - - /// - /// Remove a hook from the current process. - /// - public void Dispose() - { - if (this.IsDisposed) - return; - - if (this.isMinHook) - { - this.minHookImpl.Dispose(); - - var index = HookManager.MultiHookTracker[this.address].IndexOf(this); - HookManager.MultiHookTracker[this.address][index] = null; - } - else - { - this.Disable(); - } - - this.IsDisposed = true; - } - - /// - /// Starts intercepting a call to the function. - /// - public void Enable() - { - this.CheckDisposed(); - - if (this.isMinHook) - { - if (!this.minHookImpl.Enabled) + get { - this.minHookImpl.Enable(); + this.CheckDisposed(); + if (this.isMinHook) + { + return this.minHookImpl.Enabled; + } + else + { + return this.hookImpl.IsHookEnabled; + } } } - else + + /// + /// Gets a value indicating whether or not the hook has been disposed. + /// + public bool IsDisposed { get; private set; } + + /// + public string BackendName { - if (!this.hookImpl.IsHookActivated) - this.hookImpl.Activate(); - - if (!this.hookImpl.IsHookEnabled) - this.hookImpl.Enable(); - } - } - - /// - /// Stops intercepting a call to the function. - /// - public void Disable() - { - this.CheckDisposed(); - - if (this.isMinHook) - { - if (this.minHookImpl.Enabled) + get { - this.minHookImpl.Disable(); + if (this.isMinHook) + return "MinHook"; + + return "Reloaded"; } } - else + + /// + /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. + /// The hook is not activated until Enable() method is called. + /// + /// A name of the module currently loaded in the memory. (e.g. ws2_32.dll). + /// A name of the exported function name (e.g. send). + /// Callback function. Delegate must have a same original function prototype. + /// The hook with the supplied parameters. + public static Hook FromSymbol(string moduleName, string exportName, T detour) + => FromSymbol(moduleName, exportName, detour, false); + + /// + /// 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 name of the module currently loaded in the memory. (e.g. ws2_32.dll). + /// A name of the exported function name (e.g. send). + /// Callback function. Delegate must have a same original function prototype. + /// Use the MinHook hooking library instead of Reloaded. + /// The hook with the supplied parameters. + public static Hook FromSymbol(string moduleName, string exportName, T detour, bool useMinHook) { - if (!this.hookImpl.IsHookActivated) + var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName); + if (moduleHandle == IntPtr.Zero) + throw new Exception($"Could not get a handle to module {moduleName}"); + + var procAddress = NativeFunctions.GetProcAddress(moduleHandle, exportName); + if (procAddress == IntPtr.Zero) + throw new Exception($"Could not get the address of {moduleName}::{exportName}"); + + return new Hook(procAddress, detour, useMinHook); + } + + /// + /// Remove a hook from the current process. + /// + public void Dispose() + { + if (this.IsDisposed) return; - if (this.hookImpl.IsHookEnabled) - this.hookImpl.Disable(); - } - } + if (this.isMinHook) + { + this.minHookImpl.Dispose(); - /// - /// Check if this object has been disposed already. - /// - private void CheckDisposed() - { - if (this.IsDisposed) + var index = HookManager.MultiHookTracker[this.address].IndexOf(this); + HookManager.MultiHookTracker[this.address][index] = null; + } + else + { + this.Disable(); + } + + this.IsDisposed = true; + } + + /// + /// Starts intercepting a call to the function. + /// + public void Enable() { - throw new ObjectDisposedException(message: "Hook is already disposed", null); + this.CheckDisposed(); + + if (this.isMinHook) + { + if (!this.minHookImpl.Enabled) + { + this.minHookImpl.Enable(); + } + } + else + { + if (!this.hookImpl.IsHookActivated) + this.hookImpl.Activate(); + + if (!this.hookImpl.IsHookEnabled) + this.hookImpl.Enable(); + } + } + + /// + /// Stops intercepting a call to the function. + /// + public void Disable() + { + this.CheckDisposed(); + + if (this.isMinHook) + { + if (this.minHookImpl.Enabled) + { + this.minHookImpl.Disable(); + } + } + else + { + if (!this.hookImpl.IsHookActivated) + return; + + if (this.hookImpl.IsHookEnabled) + this.hookImpl.Disable(); + } + } + + /// + /// Check if this object has been disposed already. + /// + private void CheckDisposed() + { + if (this.IsDisposed) + { + throw new ObjectDisposedException(message: "Hook is already disposed", null); + } } } } diff --git a/Dalamud/Hooking/IDalamudHook.cs b/Dalamud/Hooking/IDalamudHook.cs index 1104597a1..5a9ae2716 100644 --- a/Dalamud/Hooking/IDalamudHook.cs +++ b/Dalamud/Hooking/IDalamudHook.cs @@ -1,29 +1,30 @@ using System; -namespace Dalamud.Hooking; - -/// -/// Interface describing a generic hook. -/// -public interface IDalamudHook +namespace Dalamud.Hooking { /// - /// Gets the address to hook. + /// Interface describing a generic hook. /// - public IntPtr Address { get; } + public interface IDalamudHook + { + /// + /// Gets the address to hook. + /// + public IntPtr Address { get; } - /// - /// Gets a value indicating whether or not the hook is enabled. - /// - public bool IsEnabled { get; } + /// + /// Gets a value indicating whether or not the hook is enabled. + /// + public bool IsEnabled { get; } - /// - /// Gets a value indicating whether or not the hook is disposed. - /// - public bool IsDisposed { get; } + /// + /// Gets a value indicating whether or not the hook is disposed. + /// + public bool IsDisposed { get; } - /// - /// Gets the name of the hooking backend used for the hook. - /// - public string BackendName { get; } + /// + /// Gets the name of the hooking backend used for the hook. + /// + public string BackendName { get; } + } } diff --git a/Dalamud/Hooking/Internal/HookInfo.cs b/Dalamud/Hooking/Internal/HookInfo.cs index 0ab05c314..73db6864b 100644 --- a/Dalamud/Hooking/Internal/HookInfo.cs +++ b/Dalamud/Hooking/Internal/HookInfo.cs @@ -2,72 +2,73 @@ using System; using System.Diagnostics; using System.Reflection; -namespace Dalamud.Hooking.Internal; - -/// -/// Class containing information about registered hooks. -/// -internal class HookInfo +namespace Dalamud.Hooking.Internal { - private ulong? inProcessMemory = 0; - /// - /// Initializes a new instance of the class. + /// Class containing information about registered hooks. /// - /// The tracked hook. - /// The hook delegate. - /// The assembly implementing the hook. - public HookInfo(IDalamudHook hook, Delegate hookDelegate, Assembly assembly) + internal class HookInfo { - this.Hook = hook; - this.Delegate = hookDelegate; - this.Assembly = assembly; - } + private ulong? inProcessMemory = 0; - /// - /// Gets the RVA of the hook. - /// - internal ulong? InProcessMemory - { - get + /// + /// Initializes a new instance of the class. + /// + /// The tracked hook. + /// The hook delegate. + /// The assembly implementing the hook. + public HookInfo(IDalamudHook hook, Delegate hookDelegate, Assembly assembly) { - if (this.Hook.IsDisposed) - return 0; + this.Hook = hook; + this.Delegate = hookDelegate; + this.Assembly = assembly; + } - if (this.inProcessMemory == null) - return null; - - if (this.inProcessMemory.Value > 0) - return this.inProcessMemory.Value; - - var p = Process.GetCurrentProcess().MainModule; - var begin = (ulong)p.BaseAddress.ToInt64(); - var end = begin + (ulong)p.ModuleMemorySize; - var hookAddr = (ulong)this.Hook.Address.ToInt64(); - - if (hookAddr >= begin && hookAddr <= end) + /// + /// Gets the RVA of the hook. + /// + internal ulong? InProcessMemory + { + get { - return this.inProcessMemory = hookAddr - begin; - } - else - { - return this.inProcessMemory = null; + if (this.Hook.IsDisposed) + return 0; + + if (this.inProcessMemory == null) + return null; + + if (this.inProcessMemory.Value > 0) + return this.inProcessMemory.Value; + + var p = Process.GetCurrentProcess().MainModule; + var begin = (ulong)p.BaseAddress.ToInt64(); + var end = begin + (ulong)p.ModuleMemorySize; + var hookAddr = (ulong)this.Hook.Address.ToInt64(); + + if (hookAddr >= begin && hookAddr <= end) + { + return this.inProcessMemory = hookAddr - begin; + } + else + { + return this.inProcessMemory = null; + } } } + + /// + /// Gets the tracked hook. + /// + internal IDalamudHook Hook { get; } + + /// + /// Gets the tracked delegate. + /// + internal Delegate Delegate { get; } + + /// + /// Gets the assembly implementing the hook. + /// + internal Assembly Assembly { get; } } - - /// - /// Gets the tracked hook. - /// - internal IDalamudHook Hook { get; } - - /// - /// Gets the tracked delegate. - /// - internal Delegate Delegate { get; } - - /// - /// Gets the assembly implementing the hook. - /// - internal Assembly Assembly { get; } } diff --git a/Dalamud/Hooking/Internal/HookManager.cs b/Dalamud/Hooking/Internal/HookManager.cs index 2b9f94b51..fde7ba3ba 100644 --- a/Dalamud/Hooking/Internal/HookManager.cs +++ b/Dalamud/Hooking/Internal/HookManager.cs @@ -3,140 +3,143 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using Dalamud.Configuration.Internal; using Dalamud.Logging.Internal; using Dalamud.Memory; using Iced.Intel; +using Microsoft.Win32; -namespace Dalamud.Hooking.Internal; - -/// -/// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes. -/// -internal class HookManager : IDisposable +namespace Dalamud.Hooking.Internal { - private static readonly ModuleLog Log = new("HM"); - /// - /// Initializes a new instance of the class. + /// This class manages the final disposition of hooks, cleaning up any that have not reverted their changes. /// - public HookManager() + internal class HookManager : IDisposable { - } + private static readonly ModuleLog Log = new("HM"); - /// - /// Gets a static list of tracked and registered hooks. - /// - internal static List TrackedHooks { get; } = new(); - - /// - /// Gets a static dictionary of original code for a hooked address. - /// - internal static Dictionary Originals { get; } = new(); - - /// - /// Gets a static dictionary of the number of hooks on a given address. - /// - internal static Dictionary> MultiHookTracker { get; } = new(); - - /// - public void Dispose() - { - RevertHooks(); - TrackedHooks.Clear(); - Originals.Clear(); - } - - /// - /// Follow a JMP or Jcc instruction to the next logical location. - /// - /// Address of the instruction. - /// The address referenced by the jmp. - internal static IntPtr FollowJmp(IntPtr address) - { - while (true) + /// + /// Initializes a new instance of the class. + /// + public HookManager() { - var hasOtherHooks = HookManager.Originals.ContainsKey(address); - if (hasOtherHooks) - { - // This address has been hooked already. Do not follow a jmp into a trampoline of our own making. - Log.Verbose($"Detected hook trampoline at {address.ToInt64():X}, stopping jump resolution."); - return address; - } - - var bytes = MemoryHelper.ReadRaw(address, 8); - - var codeReader = new ByteArrayCodeReader(bytes); - var decoder = Decoder.Create(64, codeReader); - decoder.IP = (ulong)address.ToInt64(); - decoder.Decode(out var inst); - - if (inst.Mnemonic == Mnemonic.Jmp) - { - var kind = inst.Op0Kind; - - IntPtr newAddress; - switch (inst.Op0Kind) - { - case OpKind.NearBranch64: - case OpKind.NearBranch32: - case OpKind.NearBranch16: - newAddress = (IntPtr)inst.NearBranchTarget; - break; - case OpKind.Immediate16: - case OpKind.Immediate8to16: - case OpKind.Immediate8to32: - case OpKind.Immediate8to64: - case OpKind.Immediate32to64: - case OpKind.Immediate32 when IntPtr.Size == 4: - case OpKind.Immediate64: - newAddress = (IntPtr)inst.GetImmediate(0); - break; - case OpKind.Memory when inst.IsIPRelativeMemoryOperand: - newAddress = (IntPtr)inst.IPRelativeMemoryAddress; - newAddress = Marshal.ReadIntPtr(newAddress); - break; - case OpKind.Memory: - newAddress = (IntPtr)inst.MemoryDisplacement64; - newAddress = Marshal.ReadIntPtr(newAddress); - break; - default: - var debugBytes = string.Join(" ", bytes.Take(inst.Length).Select(b => $"{b:X2}")); - throw new Exception($"Unknown OpKind {inst.Op0Kind} from {debugBytes}"); - } - - Log.Verbose($"Resolving assembly jump ({kind}) from {address.ToInt64():X} to {newAddress.ToInt64():X}"); - address = newAddress; - } - else - { - break; - } } - return address; - } + /// + /// Gets a static list of tracked and registered hooks. + /// + internal static List TrackedHooks { get; } = new(); - private static unsafe void RevertHooks() - { - foreach (var (address, originalBytes) in Originals) + /// + /// Gets a static dictionary of original code for a hooked address. + /// + internal static Dictionary Originals { get; } = new(); + + /// + /// Gets a static dictionary of the number of hooks on a given address. + /// + internal static Dictionary> MultiHookTracker { get; } = new(); + + /// + public void Dispose() { - var i = 0; - var current = (byte*)address; - // Find how many bytes have been modified by comparing to the saved original - for (; i < originalBytes.Length; i++) + RevertHooks(); + TrackedHooks.Clear(); + Originals.Clear(); + } + + /// + /// Follow a JMP or Jcc instruction to the next logical location. + /// + /// Address of the instruction. + /// The address referenced by the jmp. + internal static IntPtr FollowJmp(IntPtr address) + { + while (true) { - if (current[i] == originalBytes[i]) + var hasOtherHooks = HookManager.Originals.ContainsKey(address); + if (hasOtherHooks) + { + // This address has been hooked already. Do not follow a jmp into a trampoline of our own making. + Log.Verbose($"Detected hook trampoline at {address.ToInt64():X}, stopping jump resolution."); + return address; + } + + var bytes = MemoryHelper.ReadRaw(address, 8); + + var codeReader = new ByteArrayCodeReader(bytes); + var decoder = Decoder.Create(64, codeReader); + decoder.IP = (ulong)address.ToInt64(); + decoder.Decode(out var inst); + + if (inst.Mnemonic == Mnemonic.Jmp) + { + var kind = inst.Op0Kind; + + IntPtr newAddress; + switch (inst.Op0Kind) + { + case OpKind.NearBranch64: + case OpKind.NearBranch32: + case OpKind.NearBranch16: + newAddress = (IntPtr)inst.NearBranchTarget; + break; + case OpKind.Immediate16: + case OpKind.Immediate8to16: + case OpKind.Immediate8to32: + case OpKind.Immediate8to64: + case OpKind.Immediate32to64: + case OpKind.Immediate32 when IntPtr.Size == 4: + case OpKind.Immediate64: + newAddress = (IntPtr)inst.GetImmediate(0); + break; + case OpKind.Memory when inst.IsIPRelativeMemoryOperand: + newAddress = (IntPtr)inst.IPRelativeMemoryAddress; + newAddress = Marshal.ReadIntPtr(newAddress); + break; + case OpKind.Memory: + newAddress = (IntPtr)inst.MemoryDisplacement64; + newAddress = Marshal.ReadIntPtr(newAddress); + break; + default: + var debugBytes = string.Join(" ", bytes.Take(inst.Length).Select(b => $"{b:X2}")); + throw new Exception($"Unknown OpKind {inst.Op0Kind} from {debugBytes}"); + } + + Log.Verbose($"Resolving assembly jump ({kind}) from {address.ToInt64():X} to {newAddress.ToInt64():X}"); + address = newAddress; + } + else + { break; + } } - var snippet = originalBytes[0..i]; + return address; + } - if (i > 0) + private static unsafe void RevertHooks() + { + foreach (var (address, originalBytes) in Originals) { - Log.Verbose($"Reverting hook at 0x{address.ToInt64():X} ({snippet.Length} bytes)"); - MemoryHelper.ChangePermission(address, i, MemoryProtection.ExecuteReadWrite, out var oldPermissions); - MemoryHelper.WriteRaw(address, snippet); - MemoryHelper.ChangePermission(address, i, oldPermissions); + var i = 0; + var current = (byte*)address; + // Find how many bytes have been modified by comparing to the saved original + for (; i < originalBytes.Length; i++) + { + if (current[i] == originalBytes[i]) + break; + } + + var snippet = originalBytes[0..i]; + + if (i > 0) + { + Log.Verbose($"Reverting hook at 0x{address.ToInt64():X} ({snippet.Length} bytes)"); + MemoryHelper.ChangePermission(address, i, MemoryProtection.ExecuteReadWrite, out var oldPermissions); + MemoryHelper.WriteRaw(address, snippet); + MemoryHelper.ChangePermission(address, i, oldPermissions); + } } } } diff --git a/Dalamud/Interface/Animation/AnimUtil.cs b/Dalamud/Interface/Animation/AnimUtil.cs index ba870b9cf..cff36b21c 100644 --- a/Dalamud/Interface/Animation/AnimUtil.cs +++ b/Dalamud/Interface/Animation/AnimUtil.cs @@ -1,35 +1,36 @@ -using System.Numerics; +using System.Numerics; -namespace Dalamud.Interface.Animation; - -/// -/// Class providing helper functions when facilitating animations. -/// -public static class AnimUtil +namespace Dalamud.Interface.Animation { /// - /// Lerp between two floats. + /// Class providing helper functions when facilitating animations. /// - /// The first float. - /// The second float. - /// The point to lerp to. - /// The lerped value. - public static float Lerp(float firstFloat, float secondFloat, float by) + public static class AnimUtil { - return (firstFloat * (1 - @by)) + (secondFloat * by); - } + /// + /// Lerp between two floats. + /// + /// The first float. + /// The second float. + /// The point to lerp to. + /// The lerped value. + public static float Lerp(float firstFloat, float secondFloat, float by) + { + return (firstFloat * (1 - @by)) + (secondFloat * by); + } - /// - /// Lerp between two vectors. - /// - /// The first vector. - /// The second float. - /// The point to lerp to. - /// The lerped vector. - public static Vector2 Lerp(Vector2 firstVector, Vector2 secondVector, float by) - { - var retX = Lerp(firstVector.X, secondVector.X, by); - var retY = Lerp(firstVector.Y, secondVector.Y, by); - return new Vector2(retX, retY); + /// + /// Lerp between two vectors. + /// + /// The first vector. + /// The second float. + /// The point to lerp to. + /// The lerped vector. + public static Vector2 Lerp(Vector2 firstVector, Vector2 secondVector, float by) + { + var retX = Lerp(firstVector.X, secondVector.X, by); + var retY = Lerp(firstVector.Y, secondVector.Y, by); + return new Vector2(retX, retY); + } } } diff --git a/Dalamud/Interface/Animation/Easing.cs b/Dalamud/Interface/Animation/Easing.cs index e8f3deb34..00509169f 100644 --- a/Dalamud/Interface/Animation/Easing.cs +++ b/Dalamud/Interface/Animation/Easing.cs @@ -1,116 +1,117 @@ -using System; +using System; using System.Diagnostics; using System.Numerics; -namespace Dalamud.Interface.Animation; - -/// -/// Base class facilitating the implementation of easing functions. -/// -public abstract class Easing +namespace Dalamud.Interface.Animation { - // TODO: Use game delta time here instead - private readonly Stopwatch animationTimer = new(); - - private double valueInternal; - /// - /// Initializes a new instance of the class with the specified duration. + /// Base class facilitating the implementation of easing functions. /// - /// The animation duration. - protected Easing(TimeSpan duration) + public abstract class Easing { - this.Duration = duration; - } + // TODO: Use game delta time here instead + private readonly Stopwatch animationTimer = new(); - /// - /// Gets or sets the origin point of the animation. - /// - public Vector2? Point1 { get; set; } + private double valueInternal; - /// - /// Gets or sets the destination point of the animation. - /// - public Vector2? Point2 { get; set; } - - /// - /// Gets the resulting point at the current timestep. - /// - public Vector2 EasedPoint { get; private set; } - - /// - /// Gets or sets a value indicating whether the result of the easing should be inversed. - /// - public bool IsInverse { get; set; } - - /// - /// Gets or sets the current value of the animation, from 0 to 1. - /// - public double Value - { - get + /// + /// Initializes a new instance of the class with the specified duration. + /// + /// The animation duration. + protected Easing(TimeSpan duration) { - if (this.IsInverse) - return 1 - this.valueInternal; - - return this.valueInternal; + this.Duration = duration; } - protected set + /// + /// Gets or sets the origin point of the animation. + /// + public Vector2? Point1 { get; set; } + + /// + /// Gets or sets the destination point of the animation. + /// + public Vector2? Point2 { get; set; } + + /// + /// Gets the resulting point at the current timestep. + /// + public Vector2 EasedPoint { get; private set; } + + /// + /// Gets or sets a value indicating whether the result of the easing should be inversed. + /// + public bool IsInverse { get; set; } + + /// + /// Gets or sets the current value of the animation, from 0 to 1. + /// + public double Value { - this.valueInternal = value; + get + { + if (this.IsInverse) + return 1 - this.valueInternal; - if (this.Point1.HasValue && this.Point2.HasValue) - this.EasedPoint = AnimUtil.Lerp(this.Point1.Value, this.Point2.Value, (float)this.valueInternal); + return this.valueInternal; + } + + protected set + { + this.valueInternal = value; + + if (this.Point1.HasValue && this.Point2.HasValue) + this.EasedPoint = AnimUtil.Lerp(this.Point1.Value, this.Point2.Value, (float)this.valueInternal); + } } + + /// + /// Gets or sets the duration of the animation. + /// + public TimeSpan Duration { get; set; } + + /// + /// Gets a value indicating whether or not the animation is running. + /// + public bool IsRunning => this.animationTimer.IsRunning; + + /// + /// Gets a value indicating whether or not the animation is done. + /// + public bool IsDone => this.animationTimer.ElapsedMilliseconds > this.Duration.TotalMilliseconds; + + /// + /// Gets the progress of the animation, from 0 to 1. + /// + protected double Progress => this.animationTimer.ElapsedMilliseconds / this.Duration.TotalMilliseconds; + + /// + /// Starts the animation from where it was last stopped, or from the start if it was never started before. + /// + public void Start() + { + this.animationTimer.Start(); + } + + /// + /// Stops the animation at the current point. + /// + public void Stop() + { + this.animationTimer.Stop(); + } + + /// + /// Restarts the animation. + /// + public void Restart() + { + this.animationTimer.Restart(); + } + + /// + /// Updates the animation. + /// + public abstract void Update(); } - - /// - /// Gets or sets the duration of the animation. - /// - public TimeSpan Duration { get; set; } - - /// - /// Gets a value indicating whether or not the animation is running. - /// - public bool IsRunning => this.animationTimer.IsRunning; - - /// - /// Gets a value indicating whether or not the animation is done. - /// - public bool IsDone => this.animationTimer.ElapsedMilliseconds > this.Duration.TotalMilliseconds; - - /// - /// Gets the progress of the animation, from 0 to 1. - /// - protected double Progress => this.animationTimer.ElapsedMilliseconds / this.Duration.TotalMilliseconds; - - /// - /// Starts the animation from where it was last stopped, or from the start if it was never started before. - /// - public void Start() - { - this.animationTimer.Start(); - } - - /// - /// Stops the animation at the current point. - /// - public void Stop() - { - this.animationTimer.Stop(); - } - - /// - /// Restarts the animation. - /// - public void Restart() - { - this.animationTimer.Restart(); - } - - /// - /// Updates the animation. - /// - public abstract void Update(); } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs index 92ad1d863..952e3539d 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCirc.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InCirc" easing animation. -/// -public class InCirc : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "InCirc" easing animation. /// - /// The duration of the animation. - public InCirc(TimeSpan duration) - : base(duration) + public class InCirc : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InCirc(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); + /// + public override void Update() + { + var p = this.Progress; + this.Value = 1 - Math.Sqrt(1 - Math.Pow(p, 2)); + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs index 52b56e2c3..ebca3203d 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InCubic.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InCubic" easing animation. -/// -public class InCubic : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "InCubic" easing animation. /// - /// The duration of the animation. - public InCubic(TimeSpan duration) - : base(duration) + public class InCubic : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InCubic(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p * p * p; + /// + public override void Update() + { + var p = this.Progress; + this.Value = p * p * p; + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs index e1be03c1c..97159df47 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InElastic.cs @@ -1,32 +1,33 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InElastic" easing animation. -/// -public class InElastic : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { - private const double Constant = (2 * Math.PI) / 3; - /// - /// Initializes a new instance of the class. + /// Class providing an "InElastic" easing animation. /// - /// The duration of the animation. - public InElastic(TimeSpan duration) - : base(duration) + public class InElastic : Easing { - // ignored - } + private const double Constant = (2 * Math.PI) / 3; - /// - public override void Update() - { - var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InElastic(TimeSpan duration) + : base(duration) + { + // ignored + } + + /// + public override void Update() + { + var p = this.Progress; + this.Value = p == 0 + ? 0 + : p == 1 + ? 1 + : -Math.Pow(2, (10 * p) - 10) * Math.Sin(((p * 10) - 10.75) * Constant); + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs index 6766921d9..97fd1a2d2 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCirc.cs @@ -1,28 +1,29 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InOutCirc" easing animation. -/// -public class InOutCirc : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "InOutCirc" easing animation. /// - /// The duration of the animation. - public InOutCirc(TimeSpan duration) - : base(duration) + public class InOutCirc : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InOutCirc(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p < 0.5 - ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 - : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; + /// + public override void Update() + { + var p = this.Progress; + this.Value = p < 0.5 + ? (1 - Math.Sqrt(1 - Math.Pow(2 * p, 2))) / 2 + : (Math.Sqrt(1 - Math.Pow((-2 * p) + 2, 2)) + 1) / 2; + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs index 298cc98de..c075da538 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutCubic.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InOutCubic" easing animation. -/// -public class InOutCubic : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "InOutCubic" easing animation. /// - /// The duration of the animation. - public InOutCubic(TimeSpan duration) - : base(duration) + public class InOutCubic : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InOutCubic(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); + /// + public override void Update() + { + var p = this.Progress; + this.Value = p < 0.5 ? 4 * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 3) / 2); + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs index 3da2e2c58..7bb36dd74 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutElastic.cs @@ -1,34 +1,35 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InOutCirc" easing animation. -/// -public class InOutElastic : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { - private const double Constant = (2 * Math.PI) / 4.5; - /// - /// Initializes a new instance of the class. + /// Class providing an "InOutCirc" easing animation. /// - /// The duration of the animation. - public InOutElastic(TimeSpan duration) - : base(duration) + public class InOutElastic : Easing { - // ignored - } + private const double Constant = (2 * Math.PI) / 4.5; - /// - public override void Update() - { - var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : p < 0.5 - ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 - : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InOutElastic(TimeSpan duration) + : base(duration) + { + // ignored + } + + /// + public override void Update() + { + var p = this.Progress; + this.Value = p == 0 + ? 0 + : p == 1 + ? 1 + : p < 0.5 + ? -(Math.Pow(2, (20 * p) - 10) * Math.Sin(((20 * p) - 11.125) * Constant)) / 2 + : (Math.Pow(2, (-20 * p) + 10) * Math.Sin(((20 * p) - 11.125) * Constant) / 2) + 1; + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs index 4140e946e..70f3123aa 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutQuint.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InOutQuint" easing animation. -/// -public class InOutQuint : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "InOutQuint" easing animation. /// - /// The duration of the animation. - public InOutQuint(TimeSpan duration) - : base(duration) + public class InOutQuint : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InOutQuint(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); + /// + public override void Update() + { + var p = this.Progress; + this.Value = p < 0.5 ? 16 * p * p * p * p * p : 1 - (Math.Pow((-2 * p) + 2, 5) / 2); + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs index 118910205..4808079d3 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InOutSine.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InOutSine" easing animation. -/// -public class InOutSine : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "InOutSine" easing animation. /// - /// The duration of the animation. - public InOutSine(TimeSpan duration) - : base(duration) + public class InOutSine : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InOutSine(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = -(Math.Cos(Math.PI * p) - 1) / 2; + /// + public override void Update() + { + var p = this.Progress; + this.Value = -(Math.Cos(Math.PI * p) - 1) / 2; + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs index b9df11728..42604f136 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InQuint.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InQuint" easing animation. -/// -public class InQuint : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "InQuint" easing animation. /// - /// The duration of the animation. - public InQuint(TimeSpan duration) - : base(duration) + public class InQuint : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InQuint(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = p * p * p * p * p; + /// + public override void Update() + { + var p = this.Progress; + this.Value = p * p * p * p * p; + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs index 35f891d54..aaa19aa40 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/InSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/InSine.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "InSine" easing animation. -/// -public class InSine : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "InSine" easing animation. /// - /// The duration of the animation. - public InSine(TimeSpan duration) - : base(duration) + public class InSine : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public InSine(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = 1 - Math.Cos((p * Math.PI) / 2); + /// + public override void Update() + { + var p = this.Progress; + this.Value = 1 - Math.Cos((p * Math.PI) / 2); + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs index 29f2562e7..da7b0029a 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCirc.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "OutCirc" easing animation. -/// -public class OutCirc : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "OutCirc" easing animation. /// - /// The duration of the animation. - public OutCirc(TimeSpan duration) - : base(duration) + public class OutCirc : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public OutCirc(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = Math.Sqrt(1 - Math.Pow(p - 1, 2)); + /// + public override void Update() + { + var p = this.Progress; + this.Value = Math.Sqrt(1 - Math.Pow(p - 1, 2)); + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs index 6804bcf05..e527b228f 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutCubic.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "OutCubic" easing animation. -/// -public class OutCubic : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "OutCubic" easing animation. /// - /// The duration of the animation. - public OutCubic(TimeSpan duration) - : base(duration) + public class OutCubic : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public OutCubic(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 3); + /// + public override void Update() + { + var p = this.Progress; + this.Value = 1 - Math.Pow(1 - p, 3); + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs index 6aced4403..3475c4a72 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutElastic.cs @@ -1,32 +1,33 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "OutElastic" easing animation. -/// -public class OutElastic : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { - private const double Constant = (2 * Math.PI) / 3; - /// - /// Initializes a new instance of the class. + /// Class providing an "OutElastic" easing animation. /// - /// The duration of the animation. - public OutElastic(TimeSpan duration) - : base(duration) + public class OutElastic : Easing { - // ignored - } + private const double Constant = (2 * Math.PI) / 3; - /// - public override void Update() - { - var p = this.Progress; - this.Value = p == 0 - ? 0 - : p == 1 - ? 1 - : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public OutElastic(TimeSpan duration) + : base(duration) + { + // ignored + } + + /// + public override void Update() + { + var p = this.Progress; + this.Value = p == 0 + ? 0 + : p == 1 + ? 1 + : (Math.Pow(2, -10 * p) * Math.Sin(((p * 10) - 0.75) * Constant)) + 1; + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs index 566a22ffa..c99c77a57 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutQuint.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "OutQuint" easing animation. -/// -public class OutQuint : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "OutQuint" easing animation. /// - /// The duration of the animation. - public OutQuint(TimeSpan duration) - : base(duration) + public class OutQuint : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public OutQuint(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = 1 - Math.Pow(1 - p, 5); + /// + public override void Update() + { + var p = this.Progress; + this.Value = 1 - Math.Pow(1 - p, 5); + } } } diff --git a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs index da9faf5e1..c1becf81c 100644 --- a/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs +++ b/Dalamud/Interface/Animation/EasingFunctions/OutSine.cs @@ -1,26 +1,27 @@ -using System; +using System; -namespace Dalamud.Interface.Animation.EasingFunctions; - -/// -/// Class providing an "OutSine" easing animation. -/// -public class OutSine : Easing +namespace Dalamud.Interface.Animation.EasingFunctions { /// - /// Initializes a new instance of the class. + /// Class providing an "OutSine" easing animation. /// - /// The duration of the animation. - public OutSine(TimeSpan duration) - : base(duration) + public class OutSine : Easing { - // ignored - } + /// + /// Initializes a new instance of the class. + /// + /// The duration of the animation. + public OutSine(TimeSpan duration) + : base(duration) + { + // ignored + } - /// - public override void Update() - { - var p = this.Progress; - this.Value = Math.Sin((p * Math.PI) / 2); + /// + public override void Update() + { + var p = this.Progress; + this.Value = Math.Sin((p * Math.PI) / 2); + } } } diff --git a/Dalamud/Interface/Colors/ImGuiColors.cs b/Dalamud/Interface/Colors/ImGuiColors.cs index 866211d8e..a8cb2f6fb 100644 --- a/Dalamud/Interface/Colors/ImGuiColors.cs +++ b/Dalamud/Interface/Colors/ImGuiColors.cs @@ -1,104 +1,105 @@ using System.Numerics; -namespace Dalamud.Interface.Colors; - -/// -/// Class containing frequently used colors for easier reference. -/// -public static class ImGuiColors +namespace Dalamud.Interface.Colors { /// - /// Gets red used in dalamud. + /// Class containing frequently used colors for easier reference. /// - public static Vector4 DalamudRed { get; internal set; } = new(1f, 0f, 0f, 1f); + public static class ImGuiColors + { + /// + /// Gets red used in dalamud. + /// + public static Vector4 DalamudRed { get; internal set; } = new(1f, 0f, 0f, 1f); - /// - /// Gets grey used in dalamud. - /// - public static Vector4 DalamudGrey { get; internal set; } = new(0.7f, 0.7f, 0.7f, 1f); + /// + /// Gets grey used in dalamud. + /// + public static Vector4 DalamudGrey { get; internal set; } = new(0.7f, 0.7f, 0.7f, 1f); - /// - /// Gets grey used in dalamud. - /// - public static Vector4 DalamudGrey2 { get; internal set; } = new(0.7f, 0.7f, 0.7f, 1f); + /// + /// Gets grey used in dalamud. + /// + public static Vector4 DalamudGrey2 { get; internal set; } = new(0.7f, 0.7f, 0.7f, 1f); - /// - /// Gets grey used in dalamud. - /// - public static Vector4 DalamudGrey3 { get; internal set; } = new(0.5f, 0.5f, 0.5f, 1f); + /// + /// Gets grey used in dalamud. + /// + public static Vector4 DalamudGrey3 { get; internal set; } = new(0.5f, 0.5f, 0.5f, 1f); - /// - /// Gets white used in dalamud. - /// - public static Vector4 DalamudWhite { get; internal set; } = new(1f, 1f, 1f, 1f); + /// + /// Gets white used in dalamud. + /// + public static Vector4 DalamudWhite { get; internal set; } = new(1f, 1f, 1f, 1f); - /// - /// Gets white used in dalamud. - /// - public static Vector4 DalamudWhite2 { get; internal set; } = new(0.878f, 0.878f, 0.878f, 1f); + /// + /// Gets white used in dalamud. + /// + public static Vector4 DalamudWhite2 { get; internal set; } = new(0.878f, 0.878f, 0.878f, 1f); - /// - /// Gets orange used in dalamud. - /// - public static Vector4 DalamudOrange { get; internal set; } = new(1f, 0.709f, 0f, 1f); + /// + /// Gets orange used in dalamud. + /// + public static Vector4 DalamudOrange { get; internal set; } = new(1f, 0.709f, 0f, 1f); - /// - /// Gets yellow used in dalamud. - /// - public static Vector4 DalamudYellow { get; } = new(1f, 1f, .4f, 1f); + /// + /// Gets yellow used in dalamud. + /// + public static Vector4 DalamudYellow { get; } = new(1f, 1f, .4f, 1f); - /// - /// Gets violet used in dalamud. - /// - public static Vector4 DalamudViolet { get; } = new(0.770f, 0.700f, 0.965f, 1.000f); + /// + /// Gets violet used in dalamud. + /// + public static Vector4 DalamudViolet { get; } = new(0.770f, 0.700f, 0.965f, 1.000f); - /// - /// Gets tank blue (UIColor37). - /// - public static Vector4 TankBlue { get; internal set; } = new(0f, 0.6f, 1f, 1f); + /// + /// Gets tank blue (UIColor37). + /// + public static Vector4 TankBlue { get; internal set; } = new(0f, 0.6f, 1f, 1f); - /// - /// Gets healer green (UIColor504). - /// - public static Vector4 HealerGreen { get; internal set; } = new(0f, 0.8f, 0.1333333f, 1f); + /// + /// Gets healer green (UIColor504). + /// + public static Vector4 HealerGreen { get; internal set; } = new(0f, 0.8f, 0.1333333f, 1f); - /// - /// Gets dps red (UIColor545). - /// - public static Vector4 DPSRed { get; internal set; } = new(0.7058824f, 0f, 0f, 1f); + /// + /// Gets dps red (UIColor545). + /// + public static Vector4 DPSRed { get; internal set; } = new(0.7058824f, 0f, 0f, 1f); - /// - /// Gets parsed grey. - /// - public static Vector4 ParsedGrey { get; } = new(0.4f, 0.4f, 0.4f, 1f); + /// + /// Gets parsed grey. + /// + public static Vector4 ParsedGrey { get; } = new(0.4f, 0.4f, 0.4f, 1f); - /// - /// Gets parsed green. - /// - public static Vector4 ParsedGreen { get; } = new(0.117f, 1f, 0f, 1f); + /// + /// Gets parsed green. + /// + public static Vector4 ParsedGreen { get; } = new(0.117f, 1f, 0f, 1f); - /// - /// Gets parsed blue. - /// - public static Vector4 ParsedBlue { get; } = new(0f, 0.439f, 1f, 1f); + /// + /// Gets parsed blue. + /// + public static Vector4 ParsedBlue { get; } = new(0f, 0.439f, 1f, 1f); - /// - /// Gets parsed purple. - /// - public static Vector4 ParsedPurple { get; } = new(0.639f, 0.207f, 0.933f, 1f); + /// + /// Gets parsed purple. + /// + public static Vector4 ParsedPurple { get; } = new(0.639f, 0.207f, 0.933f, 1f); - /// - /// Gets parsed orange. - /// - public static Vector4 ParsedOrange { get; } = new(1f, 0.501f, 0f, 1f); + /// + /// Gets parsed orange. + /// + public static Vector4 ParsedOrange { get; } = new(1f, 0.501f, 0f, 1f); - /// - /// Gets parsed pink. - /// - public static Vector4 ParsedPink { get; } = new(0.886f, 0.407f, 0.658f, 1f); + /// + /// Gets parsed pink. + /// + public static Vector4 ParsedPink { get; } = new(0.886f, 0.407f, 0.658f, 1f); - /// - /// Gets parsed gold. - /// - public static Vector4 ParsedGold { get; } = new(0.898f, 0.8f, 0.501f, 1f); + /// + /// Gets parsed gold. + /// + public static Vector4 ParsedGold { get; } = new(0.898f, 0.8f, 0.501f, 1f); + } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs index e9db345cb..447d0cf03 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.ColorPickerWithPalette.cs @@ -2,68 +2,69 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.Components; - -/// -/// Class containing various methods providing ImGui components. -/// -public static partial class ImGuiComponents +namespace Dalamud.Interface.Components { /// - /// ColorPicker with palette. + /// Class containing various methods providing ImGui components. /// - /// Id for the color picker. - /// The description of the color picker. - /// The current color. - /// Selected color. - public static Vector4 ColorPickerWithPalette(int id, string description, Vector4 originalColor) + public static partial class ImGuiComponents { - const ImGuiColorEditFlags flags = ImGuiColorEditFlags.NoSidePreview | ImGuiColorEditFlags.NoSmallPreview; - return ColorPickerWithPalette(id, description, originalColor, flags); - } - - /// - /// ColorPicker with palette with color picker options. - /// - /// Id for the color picker. - /// The description of the color picker. - /// The current color. - /// Flags to customize color picker. - /// Selected color. - public static Vector4 ColorPickerWithPalette(int id, string description, Vector4 originalColor, ImGuiColorEditFlags flags) - { - var existingColor = originalColor; - var selectedColor = originalColor; - var colorPalette = ImGuiHelpers.DefaultColorPalette(36); - if (ImGui.ColorButton($"{description}###ColorPickerButton{id}", originalColor)) + /// + /// ColorPicker with palette. + /// + /// Id for the color picker. + /// The description of the color picker. + /// The current color. + /// Selected color. + public static Vector4 ColorPickerWithPalette(int id, string description, Vector4 originalColor) { - ImGui.OpenPopup($"###ColorPickerPopup{id}"); + const ImGuiColorEditFlags flags = ImGuiColorEditFlags.NoSidePreview | ImGuiColorEditFlags.NoSmallPreview; + return ColorPickerWithPalette(id, description, originalColor, flags); } - if (ImGui.BeginPopup($"###ColorPickerPopup{id}")) + /// + /// ColorPicker with palette with color picker options. + /// + /// Id for the color picker. + /// The description of the color picker. + /// The current color. + /// Flags to customize color picker. + /// Selected color. + public static Vector4 ColorPickerWithPalette(int id, string description, Vector4 originalColor, ImGuiColorEditFlags flags) { - if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags)) + var existingColor = originalColor; + var selectedColor = originalColor; + var colorPalette = ImGuiHelpers.DefaultColorPalette(36); + if (ImGui.ColorButton($"{description}###ColorPickerButton{id}", originalColor)) { - selectedColor = existingColor; + ImGui.OpenPopup($"###ColorPickerPopup{id}"); } - for (var i = 0; i < 4; i++) + if (ImGui.BeginPopup($"###ColorPickerPopup{id}")) { - ImGui.Spacing(); - for (var j = i * 9; j < (i * 9) + 9; j++) + if (ImGui.ColorPicker4($"###ColorPicker{id}", ref existingColor, flags)) { - if (ImGui.ColorButton($"###ColorPickerSwatch{id}{i}{j}", colorPalette[j])) - { - selectedColor = colorPalette[j]; - } - - ImGui.SameLine(); + selectedColor = existingColor; } + + for (var i = 0; i < 4; i++) + { + ImGui.Spacing(); + for (var j = i * 9; j < (i * 9) + 9; j++) + { + if (ImGui.ColorButton($"###ColorPickerSwatch{id}{i}{j}", colorPalette[j])) + { + selectedColor = colorPalette[j]; + } + + ImGui.SameLine(); + } + } + + ImGui.EndPopup(); } - ImGui.EndPopup(); + return selectedColor; } - - return selectedColor; } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs index 181bbbfd7..225b171bb 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.DisabledButton.cs @@ -2,74 +2,75 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.Components; - -/// -/// Class containing various methods providing ImGui components. -/// -public static partial class ImGuiComponents +namespace Dalamud.Interface.Components { /// - /// Alpha modified IconButton component to use an icon as a button with alpha and color options. + /// Class containing various methods providing ImGui components. /// - /// The icon for the button. - /// The ID of the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// A multiplier for the current alpha levels. - /// Indicator if button is clicked. - public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) + public static partial class ImGuiComponents { - ImGui.PushFont(UiBuilder.IconFont); + /// + /// Alpha modified IconButton component to use an icon as a button with alpha and color options. + /// + /// The icon for the button. + /// The ID of the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// A multiplier for the current alpha levels. + /// Indicator if button is clicked. + public static bool DisabledButton(FontAwesomeIcon icon, int? id = null, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) + { + ImGui.PushFont(UiBuilder.IconFont); - var text = icon.ToIconString(); - if (id.HasValue) - text = $"{text}{id}"; + var text = icon.ToIconString(); + if (id.HasValue) + text = $"{text}{id}"; - var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); + var button = DisabledButton(text, defaultColor, activeColor, hoveredColor, alphaMult); - ImGui.PopFont(); + ImGui.PopFont(); - return button; - } + return button; + } - /// - /// Alpha modified Button component to use as a disabled button with alpha and color options. - /// - /// The button label with ID. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// A multiplier for the current alpha levels. - /// Indicator if button is clicked. - public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) - { - if (defaultColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); + /// + /// Alpha modified Button component to use as a disabled button with alpha and color options. + /// + /// The button label with ID. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// A multiplier for the current alpha levels. + /// Indicator if button is clicked. + public static bool DisabledButton(string labelWithId, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null, float alphaMult = .5f) + { + if (defaultColor.HasValue) + ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - if (activeColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); + if (activeColor.HasValue) + ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - if (hoveredColor.HasValue) - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); + if (hoveredColor.HasValue) + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - var style = ImGui.GetStyle(); - ImGui.PushStyleVar(ImGuiStyleVar.Alpha, style.Alpha * alphaMult); + var style = ImGui.GetStyle(); + ImGui.PushStyleVar(ImGuiStyleVar.Alpha, style.Alpha * alphaMult); - var button = ImGui.Button(labelWithId); + var button = ImGui.Button(labelWithId); - ImGui.PopStyleVar(); + ImGui.PopStyleVar(); - if (defaultColor.HasValue) - ImGui.PopStyleColor(); + if (defaultColor.HasValue) + ImGui.PopStyleColor(); - if (activeColor.HasValue) - ImGui.PopStyleColor(); + if (activeColor.HasValue) + ImGui.PopStyleColor(); - if (hoveredColor.HasValue) - ImGui.PopStyleColor(); + if (hoveredColor.HasValue) + ImGui.PopStyleColor(); - return button; + return button; + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs index f57746eca..dfb86cf79 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.HelpMarker.cs @@ -1,27 +1,28 @@ using ImGuiNET; -namespace Dalamud.Interface.Components; - -/// -/// Class containing various methods providing ImGui components. -/// -public static partial class ImGuiComponents +namespace Dalamud.Interface.Components { /// - /// HelpMarker component to add a help icon with text on hover. + /// Class containing various methods providing ImGui components. /// - /// The text to display on hover. - public static void HelpMarker(string helpText) + public static partial class ImGuiComponents { - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); - ImGui.PopFont(); - if (!ImGui.IsItemHovered()) return; - ImGui.BeginTooltip(); - ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); - ImGui.TextUnformatted(helpText); - ImGui.PopTextWrapPos(); - ImGui.EndTooltip(); + /// + /// HelpMarker component to add a help icon with text on hover. + /// + /// The text to display on hover. + public static void HelpMarker(string helpText) + { + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.TextDisabled(FontAwesomeIcon.InfoCircle.ToIconString()); + ImGui.PopFont(); + if (!ImGui.IsItemHovered()) return; + ImGui.BeginTooltip(); + ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); + ImGui.TextUnformatted(helpText); + ImGui.PopTextWrapPos(); + ImGui.EndTooltip(); + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs index 7ef3f56e5..3d65deb74 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.IconButton.cs @@ -2,100 +2,101 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.Components; - -/// -/// Class containing various methods providing ImGui components. -/// -public static partial class ImGuiComponents +namespace Dalamud.Interface.Components { /// - /// IconButton component to use an icon as a button. + /// Class containing various methods providing ImGui components. /// - /// The icon for the button. - /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon) - => IconButton(icon, null, null, null); - - /// - /// IconButton component to use an icon as a button. - /// - /// The ID of the button. - /// The icon for the button. - /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon) - => IconButton(id, icon, null, null, null); - - /// - /// IconButton component to use an icon as a button. - /// - /// Text already containing the icon string. - /// Indicator if button is clicked. - public static bool IconButton(string iconText) - => IconButton(iconText, null, null, null); - - /// - /// IconButton component to use an icon as a button. - /// - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// The ID of the button. - /// The icon for the button. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) - => IconButton($"{icon.ToIconString()}{id}", defaultColor, activeColor, hoveredColor); - - /// - /// IconButton component to use an icon as a button with color options. - /// - /// Text already containing the icon string. - /// The default color of the button. - /// The color of the button when active. - /// The color of the button when hovered. - /// Indicator if button is clicked. - public static bool IconButton(string iconText, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + public static partial class ImGuiComponents { - var numColors = 0; + /// + /// IconButton component to use an icon as a button. + /// + /// The icon for the button. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon) + => IconButton(icon, null, null, null); - if (defaultColor.HasValue) + /// + /// IconButton component to use an icon as a button. + /// + /// The ID of the button. + /// The icon for the button. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon) + => IconButton(id, icon, null, null, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// Text already containing the icon string. + /// Indicator if button is clicked. + public static bool IconButton(string iconText) + => IconButton(iconText, null, null, null); + + /// + /// IconButton component to use an icon as a button. + /// + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Indicator if button is clicked. + public static bool IconButton(FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + => IconButton($"{icon.ToIconString()}", defaultColor, activeColor, hoveredColor); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// The ID of the button. + /// The icon for the button. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Indicator if button is clicked. + public static bool IconButton(int id, FontAwesomeIcon icon, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) + => IconButton($"{icon.ToIconString()}{id}", defaultColor, activeColor, hoveredColor); + + /// + /// IconButton component to use an icon as a button with color options. + /// + /// Text already containing the icon string. + /// The default color of the button. + /// The color of the button when active. + /// The color of the button when hovered. + /// Indicator if button is clicked. + public static bool IconButton(string iconText, Vector4? defaultColor = null, Vector4? activeColor = null, Vector4? hoveredColor = null) { - ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); - numColors++; + var numColors = 0; + + if (defaultColor.HasValue) + { + ImGui.PushStyleColor(ImGuiCol.Button, defaultColor.Value); + numColors++; + } + + if (activeColor.HasValue) + { + ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); + numColors++; + } + + if (hoveredColor.HasValue) + { + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); + numColors++; + } + + ImGui.PushFont(UiBuilder.IconFont); + + var button = ImGui.Button(iconText); + + ImGui.PopFont(); + + if (numColors > 0) + ImGui.PopStyleColor(numColors); + + return button; } - - if (activeColor.HasValue) - { - ImGui.PushStyleColor(ImGuiCol.ButtonActive, activeColor.Value); - numColors++; - } - - if (hoveredColor.HasValue) - { - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, hoveredColor.Value); - numColors++; - } - - ImGui.PushFont(UiBuilder.IconFont); - - var button = ImGui.Button(iconText); - - ImGui.PopFont(); - - if (numColors > 0) - ImGui.PopStyleColor(numColors); - - return button; } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.Test.cs b/Dalamud/Interface/Components/ImGuiComponents.Test.cs index ddc083cd8..54026b379 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.Test.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.Test.cs @@ -1,17 +1,18 @@ using ImGuiNET; -namespace Dalamud.Interface.Components; - -/// -/// Class containing various methods providing ImGui components. -/// -public static partial class ImGuiComponents +namespace Dalamud.Interface.Components { /// - /// Test component to demonstrate how ImGui components work. + /// Class containing various methods providing ImGui components. /// - public static void Test() + public static partial class ImGuiComponents { - ImGui.Text("You are viewing the test component. The test was a success."); + /// + /// Test component to demonstrate how ImGui components work. + /// + public static void Test() + { + ImGui.Text("You are viewing the test component. The test was a success."); + } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs index 597b472c6..991fefb3a 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.TextWithLabel.cs @@ -1,30 +1,31 @@ using ImGuiNET; -namespace Dalamud.Interface.Components; - -/// -/// Class containing various methods providing ImGui components. -/// -public static partial class ImGuiComponents +namespace Dalamud.Interface.Components { /// - /// TextWithLabel component to show labeled text. + /// Class containing various methods providing ImGui components. /// - /// The label for text. - /// The text value. - /// The hint to show on hover. - public static void TextWithLabel(string label, string value, string hint = "") + public static partial class ImGuiComponents { - ImGui.Text(label + ": "); - ImGui.SameLine(); - if (string.IsNullOrEmpty(hint)) + /// + /// TextWithLabel component to show labeled text. + /// + /// The label for text. + /// The text value. + /// The hint to show on hover. + public static void TextWithLabel(string label, string value, string hint = "") { - ImGui.Text(value); - } - else - { - ImGui.Text(value + "*"); - if (ImGui.IsItemHovered()) ImGui.SetTooltip(hint); + ImGui.Text(label + ": "); + ImGui.SameLine(); + if (string.IsNullOrEmpty(hint)) + { + ImGui.Text(value); + } + else + { + ImGui.Text(value + "*"); + if (ImGui.IsItemHovered()) ImGui.SetTooltip(hint); + } } } } diff --git a/Dalamud/Interface/Components/ImGuiComponents.cs b/Dalamud/Interface/Components/ImGuiComponents.cs index e63ebd890..60d6a0e06 100644 --- a/Dalamud/Interface/Components/ImGuiComponents.cs +++ b/Dalamud/Interface/Components/ImGuiComponents.cs @@ -1,8 +1,9 @@ -namespace Dalamud.Interface.Components; - -/// -/// Class containing various methods providing ImGui components. -/// -public static partial class ImGuiComponents +namespace Dalamud.Interface.Components { + /// + /// Class containing various methods providing ImGui components. + /// + public static partial class ImGuiComponents + { + } } diff --git a/Dalamud/Interface/FontAwesomeExtensions.cs b/Dalamud/Interface/FontAwesomeExtensions.cs index d64eb09da..734ec128d 100644 --- a/Dalamud/Interface/FontAwesomeExtensions.cs +++ b/Dalamud/Interface/FontAwesomeExtensions.cs @@ -1,29 +1,30 @@ -// Font-Awesome - Version 5.0.9 +// Font-Awesome - Version 5.0.9 -namespace Dalamud.Interface; - -/// -/// Extension methods for . -/// -public static class FontAwesomeExtensions +namespace Dalamud.Interface { /// - /// Convert the FontAwesomeIcon to a type. + /// Extension methods for . /// - /// The icon to convert. - /// The converted icon. - public static char ToIconChar(this FontAwesomeIcon icon) + public static class FontAwesomeExtensions { - return (char)icon; - } + /// + /// Convert the FontAwesomeIcon to a type. + /// + /// The icon to convert. + /// The converted icon. + public static char ToIconChar(this FontAwesomeIcon icon) + { + return (char)icon; + } - /// - /// Conver the FontAwesomeIcon to a type. - /// - /// The icon to convert. - /// The converted icon. - public static string ToIconString(this FontAwesomeIcon icon) - { - return string.Empty + (char)icon; + /// + /// Conver the FontAwesomeIcon to a type. + /// + /// The icon to convert. + /// The converted icon. + public static string ToIconString(this FontAwesomeIcon icon) + { + return string.Empty + (char)icon; + } } } diff --git a/Dalamud/Interface/FontAwesomeIcon.cs b/Dalamud/Interface/FontAwesomeIcon.cs index eb87cf784..c2267766c 100644 --- a/Dalamud/Interface/FontAwesomeIcon.cs +++ b/Dalamud/Interface/FontAwesomeIcon.cs @@ -1,7049 +1,7050 @@ // Font-Awesome - Version 5.0.9 -namespace Dalamud.Interface; - -/// -/// Font Awesome unicode characters for use with the font. -/// -public enum FontAwesomeIcon +namespace Dalamud.Interface { /// - /// No icon. - /// - None = 0, - - /// - /// The Font Awesome "500px" icon unicode character. - /// - _500Px = 0xF26E, - - /// - /// The Font Awesome "accessible-icon" icon unicode character. - /// - AccessibleIcon = 0xF368, - - /// - /// The Font Awesome "accusoft" icon unicode character. - /// - Accusoft = 0xF369, - - /// - /// The Font Awesome "acquisitions-incorporated" icon unicode character. - /// - AcquisitionsIncorporated = 0xF6AF, - - /// - /// The Font Awesome "ad" icon unicode character. - /// - Ad = 0xF641, - - /// - /// The Font Awesome "address-book" icon unicode character. - /// - AddressBook = 0xF2B9, - - /// - /// The Font Awesome "address-card" icon unicode character. - /// - AddressCard = 0xF2BB, - - /// - /// The Font Awesome "adjust" icon unicode character. - /// - Adjust = 0xF042, - - /// - /// The Font Awesome "adn" icon unicode character. - /// - Adn = 0xF17, - - /// - /// The Font Awesome "adobe" icon unicode character. - /// - Adobe = 0xF778, - - /// - /// The Font Awesome "adversal" icon unicode character. - /// - Adversal = 0xF36A, - - /// - /// The Font Awesome "affiliatetheme" icon unicode character. - /// - Affiliatetheme = 0xF36B, - - /// - /// The Font Awesome "airbnb" icon unicode character. - /// - Airbnb = 0xF834, - - /// - /// The Font Awesome "air-freshener" icon unicode character. - /// - AirFreshener = 0xF5D, - - /// - /// The Font Awesome "algolia" icon unicode character. - /// - Algolia = 0xF36C, - - /// - /// The Font Awesome "align-center" icon unicode character. - /// - AlignCenter = 0xF037, - - /// - /// The Font Awesome "align-justify" icon unicode character. - /// - AlignJustify = 0xF039, - - /// - /// The Font Awesome "align-left" icon unicode character. - /// - AlignLeft = 0xF036, - - /// - /// The Font Awesome "align-right" icon unicode character. - /// - AlignRight = 0xF038, - - /// - /// The Font Awesome "alipay" icon unicode character. - /// - Alipay = 0xF642, - - /// - /// The Font Awesome "allergies" icon unicode character. - /// - Allergies = 0xF461, - - /// - /// The Font Awesome "amazon" icon unicode character. - /// - Amazon = 0xF27, - - /// - /// The Font Awesome "amazon-pay" icon unicode character. - /// - AmazonPay = 0xF42C, - - /// - /// The Font Awesome "ambulance" icon unicode character. - /// - Ambulance = 0xF0F9, - - /// - /// The Font Awesome "american-sign-language-interpreting" icon unicode character. - /// - AmericanSignLanguageInterpreting = 0xF2A3, - - /// - /// The Font Awesome "amilia" icon unicode character. - /// - Amilia = 0xF36D, - - /// - /// The Font Awesome "anchor" icon unicode character. - /// - Anchor = 0xF13D, - - /// - /// The Font Awesome "android" icon unicode character. - /// - Android = 0xF17B, - - /// - /// The Font Awesome "angellist" icon unicode character. - /// - Angellist = 0xF209, - - /// - /// The Font Awesome "angle-double-down" icon unicode character. - /// - AngleDoubleDown = 0xF103, - - /// - /// The Font Awesome "angle-double-left" icon unicode character. - /// - AngleDoubleLeft = 0xF1, - - /// - /// The Font Awesome "angle-double-right" icon unicode character. - /// - AngleDoubleRight = 0xF101, - - /// - /// The Font Awesome "angle-double-up" icon unicode character. - /// - AngleDoubleUp = 0xF102, - - /// - /// The Font Awesome "angle-down" icon unicode character. - /// - AngleDown = 0xF107, - - /// - /// The Font Awesome "angle-left" icon unicode character. - /// - AngleLeft = 0xF104, - - /// - /// The Font Awesome "angle-right" icon unicode character. - /// - AngleRight = 0xF105, - - /// - /// The Font Awesome "angle-up" icon unicode character. - /// - AngleUp = 0xF106, - - /// - /// The Font Awesome "angry" icon unicode character. - /// - Angry = 0xF556, - - /// - /// The Font Awesome "angrycreative" icon unicode character. - /// - Angrycreative = 0xF36E, - - /// - /// The Font Awesome "angular" icon unicode character. - /// - Angular = 0xF42, - - /// - /// The Font Awesome "ankh" icon unicode character. - /// - Ankh = 0xF644, - - /// - /// The Font Awesome "apper" icon unicode character. - /// - Apper = 0xF371, - - /// - /// The Font Awesome "apple" icon unicode character. - /// - Apple = 0xF179, - - /// - /// The Font Awesome "apple-alt" icon unicode character. - /// - AppleAlt = 0xF5D1, - - /// - /// The Font Awesome "apple-pay" icon unicode character. - /// - ApplePay = 0xF415, - - /// - /// The Font Awesome "app-store" icon unicode character. - /// - AppStore = 0xF36F, - - /// - /// The Font Awesome "app-store-ios" icon unicode character. - /// - AppStoreIos = 0xF37, - - /// - /// The Font Awesome "archive" icon unicode character. - /// - Archive = 0xF187, - - /// - /// The Font Awesome "archway" icon unicode character. - /// - Archway = 0xF557, - - /// - /// The Font Awesome "arrow-alt-circle-down" icon unicode character. - /// - ArrowAltCircleDown = 0xF358, - - /// - /// The Font Awesome "arrow-alt-circle-left" icon unicode character. - /// - ArrowAltCircleLeft = 0xF359, - - /// - /// The Font Awesome "arrow-alt-circle-right" icon unicode character. - /// - ArrowAltCircleRight = 0xF35A, - - /// - /// The Font Awesome "arrow-alt-circle-up" icon unicode character. - /// - ArrowAltCircleUp = 0xF35B, - - /// - /// The Font Awesome "arrow-circle-down" icon unicode character. - /// - ArrowCircleDown = 0xF0AB, - - /// - /// The Font Awesome "arrow-circle-left" icon unicode character. - /// - ArrowCircleLeft = 0xF0A8, - - /// - /// The Font Awesome "arrow-circle-right" icon unicode character. - /// - ArrowCircleRight = 0xF0A9, - - /// - /// The Font Awesome "arrow-circle-up" icon unicode character. - /// - ArrowCircleUp = 0xF0AA, - - /// - /// The Font Awesome "arrow-down" icon unicode character. - /// - ArrowDown = 0xF063, - - /// - /// The Font Awesome "arrow-left" icon unicode character. - /// - ArrowLeft = 0xF06, - - /// - /// The Font Awesome "arrow-right" icon unicode character. - /// - ArrowRight = 0xF061, - - /// - /// The Font Awesome "arrows-alt" icon unicode character. - /// - ArrowsAlt = 0xF0B2, - - /// - /// The Font Awesome "arrows-alt-h" icon unicode character. - /// - ArrowsAltH = 0xF337, - - /// - /// The Font Awesome "arrows-alt-v" icon unicode character. - /// - ArrowsAltV = 0xF338, - - /// - /// The Font Awesome "arrow-up" icon unicode character. - /// - ArrowUp = 0xF062, - - /// - /// The Font Awesome "artstation" icon unicode character. - /// - Artstation = 0xF77A, - - /// - /// The Font Awesome "assistive-listening-systems" icon unicode character. - /// - AssistiveListeningSystems = 0xF2A2, - - /// - /// The Font Awesome "asterisk" icon unicode character. - /// - Asterisk = 0xF069, - - /// - /// The Font Awesome "asymmetrik" icon unicode character. - /// - Asymmetrik = 0xF372, - - /// - /// The Font Awesome "at" icon unicode character. - /// - At = 0xF1FA, - - /// - /// The Font Awesome "atlas" icon unicode character. - /// - Atlas = 0xF558, - - /// - /// The Font Awesome "atlassian" icon unicode character. - /// - Atlassian = 0xF77B, - - /// - /// The Font Awesome "atom" icon unicode character. - /// - Atom = 0xF5D2, - - /// - /// The Font Awesome "audible" icon unicode character. - /// - Audible = 0xF373, - - /// - /// The Font Awesome "audio-description" icon unicode character. - /// - AudioDescription = 0xF29E, - - /// - /// The Font Awesome "autoprefixer" icon unicode character. - /// - Autoprefixer = 0xF41C, - - /// - /// The Font Awesome "avianex" icon unicode character. - /// - Avianex = 0xF374, - - /// - /// The Font Awesome "aviato" icon unicode character. - /// - Aviato = 0xF421, - - /// - /// The Font Awesome "award" icon unicode character. - /// - Award = 0xF559, - - /// - /// The Font Awesome "aws" icon unicode character. - /// - Aws = 0xF375, - - /// - /// The Font Awesome "baby" icon unicode character. - /// - Baby = 0xF77C, - - /// - /// The Font Awesome "baby-carriage" icon unicode character. - /// - BabyCarriage = 0xF77D, - - /// - /// The Font Awesome "backspace" icon unicode character. - /// - Backspace = 0xF55A, - - /// - /// The Font Awesome "backward" icon unicode character. - /// - Backward = 0xF04A, - - /// - /// The Font Awesome "bacon" icon unicode character. - /// - Bacon = 0xF7E5, - - /// - /// The Font Awesome "bahai" icon unicode character. - /// - Bahai = 0xF666, - - /// - /// The Font Awesome "balance-scale" icon unicode character. - /// - BalanceScale = 0xF24E, - - /// - /// The Font Awesome "balance-scale-left" icon unicode character. - /// - BalanceScaleLeft = 0xF515, - - /// - /// The Font Awesome "balance-scale-right" icon unicode character. - /// - BalanceScaleRight = 0xF516, - - /// - /// The Font Awesome "ban" icon unicode character. - /// - Ban = 0xF05E, - - /// - /// The Font Awesome "band-aid" icon unicode character. - /// - BandAid = 0xF462, - - /// - /// The Font Awesome "bandcamp" icon unicode character. - /// - Bandcamp = 0xF2D5, - - /// - /// The Font Awesome "barcode" icon unicode character. - /// - Barcode = 0xF02A, - - /// - /// The Font Awesome "bars" icon unicode character. - /// - Bars = 0xF0C9, - - /// - /// The Font Awesome "baseball-ball" icon unicode character. - /// - BaseballBall = 0xF433, - - /// - /// The Font Awesome "basketball-ball" icon unicode character. - /// - BasketballBall = 0xF434, - - /// - /// The Font Awesome "bath" icon unicode character. - /// - Bath = 0xF2CD, - - /// - /// The Font Awesome "battery-empty" icon unicode character. - /// - BatteryEmpty = 0xF244, - - /// - /// The Font Awesome "battery-full" icon unicode character. - /// - BatteryFull = 0xF24, - - /// - /// The Font Awesome "battery-half" icon unicode character. - /// - BatteryHalf = 0xF242, - - /// - /// The Font Awesome "battery-quarter" icon unicode character. - /// - BatteryQuarter = 0xF243, - - /// - /// The Font Awesome "battery-three-quarters" icon unicode character. - /// - BatteryThreeQuarters = 0xF241, - - /// - /// The Font Awesome "battle-net" icon unicode character. - /// - BattleNet = 0xF835, - - /// - /// The Font Awesome "bed" icon unicode character. - /// - Bed = 0xF236, - - /// - /// The Font Awesome "beer" icon unicode character. - /// - Beer = 0xF0FC, - - /// - /// The Font Awesome "behance" icon unicode character. - /// - Behance = 0xF1B4, - - /// - /// The Font Awesome "behance-square" icon unicode character. - /// - BehanceSquare = 0xF1B5, - - /// - /// The Font Awesome "bell" icon unicode character. - /// - Bell = 0xF0F3, - - /// - /// The Font Awesome "bell-slash" icon unicode character. - /// - BellSlash = 0xF1F6, - - /// - /// The Font Awesome "bezier-curve" icon unicode character. - /// - BezierCurve = 0xF55B, - - /// - /// The Font Awesome "bible" icon unicode character. - /// - Bible = 0xF647, - - /// - /// The Font Awesome "bicycle" icon unicode character. - /// - Bicycle = 0xF206, - - /// - /// The Font Awesome "biking" icon unicode character. - /// - Biking = 0xF84A, - - /// - /// The Font Awesome "bimobject" icon unicode character. - /// - Bimobject = 0xF378, - - /// - /// The Font Awesome "binoculars" icon unicode character. - /// - Binoculars = 0xF1E5, - - /// - /// The Font Awesome "biohazard" icon unicode character. - /// - Biohazard = 0xF78, - - /// - /// The Font Awesome "birthday-cake" icon unicode character. - /// - BirthdayCake = 0xF1FD, - - /// - /// The Font Awesome "bitbucket" icon unicode character. - /// - Bitbucket = 0xF171, - - /// - /// The Font Awesome "bitcoin" icon unicode character. - /// - Bitcoin = 0xF379, - - /// - /// The Font Awesome "bity" icon unicode character. - /// - Bity = 0xF37A, - - /// - /// The Font Awesome "blackberry" icon unicode character. - /// - Blackberry = 0xF37B, - - /// - /// The Font Awesome "black-tie" icon unicode character. - /// - BlackTie = 0xF27E, - - /// - /// The Font Awesome "blender" icon unicode character. - /// - Blender = 0xF517, - - /// - /// The Font Awesome "blender-phone" icon unicode character. - /// - BlenderPhone = 0xF6B6, - - /// - /// The Font Awesome "blind" icon unicode character. - /// - Blind = 0xF29D, - - /// - /// The Font Awesome "blog" icon unicode character. - /// - Blog = 0xF781, - - /// - /// The Font Awesome "blogger" icon unicode character. - /// - Blogger = 0xF37C, - - /// - /// The Font Awesome "blogger-b" icon unicode character. - /// - BloggerB = 0xF37D, - - /// - /// The Font Awesome "bluetooth" icon unicode character. - /// - Bluetooth = 0xF293, - - /// - /// The Font Awesome "bluetooth-b" icon unicode character. - /// - BluetoothB = 0xF294, - - /// - /// The Font Awesome "bold" icon unicode character. - /// - Bold = 0xF032, - - /// - /// The Font Awesome "bolt" icon unicode character. - /// - Bolt = 0xF0E7, - - /// - /// The Font Awesome "bomb" icon unicode character. - /// - Bomb = 0xF1E2, - - /// - /// The Font Awesome "bone" icon unicode character. - /// - Bone = 0xF5D7, - - /// - /// The Font Awesome "bong" icon unicode character. - /// - Bong = 0xF55C, - - /// - /// The Font Awesome "book" icon unicode character. - /// - Book = 0xF02D, - - /// - /// The Font Awesome "book-dead" icon unicode character. - /// - BookDead = 0xF6B7, - - /// - /// The Font Awesome "bookmark" icon unicode character. - /// - Bookmark = 0xF02E, - - /// - /// The Font Awesome "book-medical" icon unicode character. - /// - BookMedical = 0xF7E6, - - /// - /// The Font Awesome "book-open" icon unicode character. - /// - BookOpen = 0xF518, - - /// - /// The Font Awesome "book-reader" icon unicode character. - /// - BookReader = 0xF5DA, - - /// - /// The Font Awesome "bootstrap" icon unicode character. - /// - Bootstrap = 0xF836, - - /// - /// The Font Awesome "border-all" icon unicode character. - /// - BorderAll = 0xF84C, - - /// - /// The Font Awesome "border-none" icon unicode character. - /// - BorderNone = 0xF85, - - /// - /// The Font Awesome "border-style" icon unicode character. - /// - BorderStyle = 0xF853, - - /// - /// The Font Awesome "bowling-ball" icon unicode character. - /// - BowlingBall = 0xF436, - - /// - /// The Font Awesome "box" icon unicode character. - /// - Box = 0xF466, - - /// - /// The Font Awesome "boxes" icon unicode character. - /// - Boxes = 0xF468, - - /// - /// The Font Awesome "box-open" icon unicode character. - /// - BoxOpen = 0xF49E, - - /// - /// The Font Awesome "braille" icon unicode character. - /// - Braille = 0xF2A1, - - /// - /// The Font Awesome "brain" icon unicode character. - /// - Brain = 0xF5DC, - - /// - /// The Font Awesome "bread-slice" icon unicode character. - /// - BreadSlice = 0xF7EC, - - /// - /// The Font Awesome "briefcase" icon unicode character. - /// - Briefcase = 0xF0B1, - - /// - /// The Font Awesome "briefcase-medical" icon unicode character. - /// - BriefcaseMedical = 0xF469, - - /// - /// The Font Awesome "broadcast-tower" icon unicode character. - /// - BroadcastTower = 0xF519, - - /// - /// The Font Awesome "broom" icon unicode character. - /// - Broom = 0xF51A, - - /// - /// The Font Awesome "brush" icon unicode character. - /// - Brush = 0xF55D, - - /// - /// The Font Awesome "btc" icon unicode character. - /// - Btc = 0xF15A, - - /// - /// The Font Awesome "buffer" icon unicode character. - /// - Buffer = 0xF837, - - /// - /// The Font Awesome "bug" icon unicode character. - /// - Bug = 0xF188, - - /// - /// The Font Awesome "building" icon unicode character. - /// - Building = 0xF1AD, - - /// - /// The Font Awesome "bullhorn" icon unicode character. - /// - Bullhorn = 0xF0A1, - - /// - /// The Font Awesome "bullseye" icon unicode character. - /// - Bullseye = 0xF14, - - /// - /// The Font Awesome "burn" icon unicode character. - /// - Burn = 0xF46A, - - /// - /// The Font Awesome "buromobelexperte" icon unicode character. - /// - Buromobelexperte = 0xF37F, - - /// - /// The Font Awesome "bus" icon unicode character. - /// - Bus = 0xF207, - - /// - /// The Font Awesome "bus-alt" icon unicode character. - /// - BusAlt = 0xF55E, - - /// - /// The Font Awesome "business-time" icon unicode character. - /// - BusinessTime = 0xF64A, - - /// - /// The Font Awesome "buy-n-large" icon unicode character. - /// - BuyNLarge = 0xF8A6, - - /// - /// The Font Awesome "buysellads" icon unicode character. - /// - Buysellads = 0xF20D, - - /// - /// The Font Awesome "calculator" icon unicode character. - /// - Calculator = 0xF1EC, - - /// - /// The Font Awesome "calendar" icon unicode character. - /// - Calendar = 0xF133, - - /// - /// The Font Awesome "calendar-alt" icon unicode character. - /// - CalendarAlt = 0xF073, - - /// - /// The Font Awesome "calendar-check" icon unicode character. - /// - CalendarCheck = 0xF274, - - /// - /// The Font Awesome "calendar-day" icon unicode character. - /// - CalendarDay = 0xF783, - - /// - /// The Font Awesome "calendar-minus" icon unicode character. - /// - CalendarMinus = 0xF272, - - /// - /// The Font Awesome "calendar-plus" icon unicode character. - /// - CalendarPlus = 0xF271, - - /// - /// The Font Awesome "calendar-times" icon unicode character. - /// - CalendarTimes = 0xF273, - - /// - /// The Font Awesome "calendar-week" icon unicode character. - /// - CalendarWeek = 0xF784, - - /// - /// The Font Awesome "camera" icon unicode character. - /// - Camera = 0xF03, - - /// - /// The Font Awesome "camera-retro" icon unicode character. - /// - CameraRetro = 0xF083, - - /// - /// The Font Awesome "campground" icon unicode character. - /// - Campground = 0xF6BB, - - /// - /// The Font Awesome "canadian-maple-leaf" icon unicode character. - /// - CanadianMapleLeaf = 0xF785, - - /// - /// The Font Awesome "candy-cane" icon unicode character. - /// - CandyCane = 0xF786, - - /// - /// The Font Awesome "cannabis" icon unicode character. - /// - Cannabis = 0xF55F, - - /// - /// The Font Awesome "capsules" icon unicode character. - /// - Capsules = 0xF46B, - - /// - /// The Font Awesome "car" icon unicode character. - /// - Car = 0xF1B9, - - /// - /// The Font Awesome "car-alt" icon unicode character. - /// - CarAlt = 0xF5DE, - - /// - /// The Font Awesome "caravan" icon unicode character. - /// - Caravan = 0xF8FF, - - /// - /// The Font Awesome "car-battery" icon unicode character. - /// - CarBattery = 0xF5DF, - - /// - /// The Font Awesome "car-crash" icon unicode character. - /// - CarCrash = 0xF5E1, - - /// - /// The Font Awesome "caret-down" icon unicode character. - /// - CaretDown = 0xF0D7, - - /// - /// The Font Awesome "caret-left" icon unicode character. - /// - CaretLeft = 0xF0D9, - - /// - /// The Font Awesome "caret-right" icon unicode character. - /// - CaretRight = 0xF0DA, - - /// - /// The Font Awesome "caret-square-down" icon unicode character. - /// - CaretSquareDown = 0xF15, - - /// - /// The Font Awesome "caret-square-left" icon unicode character. - /// - CaretSquareLeft = 0xF191, - - /// - /// The Font Awesome "caret-square-right" icon unicode character. - /// - CaretSquareRight = 0xF152, - - /// - /// The Font Awesome "caret-square-up" icon unicode character. - /// - CaretSquareUp = 0xF151, - - /// - /// The Font Awesome "caret-up" icon unicode character. - /// - CaretUp = 0xF0D8, - - /// - /// The Font Awesome "carrot" icon unicode character. - /// - Carrot = 0xF787, - - /// - /// The Font Awesome "car-side" icon unicode character. - /// - CarSide = 0xF5E4, - - /// - /// The Font Awesome "cart-arrow-down" icon unicode character. - /// - CartArrowDown = 0xF218, - - /// - /// The Font Awesome "cart-plus" icon unicode character. - /// - CartPlus = 0xF217, - - /// - /// The Font Awesome "cash-register" icon unicode character. - /// - CashRegister = 0xF788, - - /// - /// The Font Awesome "cat" icon unicode character. - /// - Cat = 0xF6BE, - - /// - /// The Font Awesome "cc-amazon-pay" icon unicode character. - /// - CcAmazonPay = 0xF42D, - - /// - /// The Font Awesome "cc-amex" icon unicode character. - /// - CcAmex = 0xF1F3, - - /// - /// The Font Awesome "cc-apple-pay" icon unicode character. - /// - CcApplePay = 0xF416, - - /// - /// The Font Awesome "cc-diners-club" icon unicode character. - /// - CcDinersClub = 0xF24C, - - /// - /// The Font Awesome "cc-discover" icon unicode character. - /// - CcDiscover = 0xF1F2, - - /// - /// The Font Awesome "cc-jcb" icon unicode character. - /// - CcJcb = 0xF24B, - - /// - /// The Font Awesome "cc-mastercard" icon unicode character. - /// - CcMastercard = 0xF1F1, - - /// - /// The Font Awesome "cc-paypal" icon unicode character. - /// - CcPaypal = 0xF1F4, - - /// - /// The Font Awesome "cc-stripe" icon unicode character. - /// - CcStripe = 0xF1F5, - - /// - /// The Font Awesome "cc-visa" icon unicode character. - /// - CcVisa = 0xF1F, - - /// - /// The Font Awesome "centercode" icon unicode character. - /// - Centercode = 0xF38, - - /// - /// The Font Awesome "centos" icon unicode character. - /// - Centos = 0xF789, - - /// - /// The Font Awesome "certificate" icon unicode character. - /// - Certificate = 0xF0A3, - - /// - /// The Font Awesome "chair" icon unicode character. - /// - Chair = 0xF6C, - - /// - /// The Font Awesome "chalkboard" icon unicode character. - /// - Chalkboard = 0xF51B, - - /// - /// The Font Awesome "chalkboard-teacher" icon unicode character. - /// - ChalkboardTeacher = 0xF51C, - - /// - /// The Font Awesome "charging-station" icon unicode character. - /// - ChargingStation = 0xF5E7, - - /// - /// The Font Awesome "chart-area" icon unicode character. - /// - ChartArea = 0xF1FE, - - /// - /// The Font Awesome "chart-bar" icon unicode character. - /// - ChartBar = 0xF08, - - /// - /// The Font Awesome "chart-line" icon unicode character. - /// - ChartLine = 0xF201, - - /// - /// The Font Awesome "chart-pie" icon unicode character. - /// - ChartPie = 0xF2, - - /// - /// The Font Awesome "check" icon unicode character. - /// - Check = 0xF00C, - - /// - /// The Font Awesome "check-circle" icon unicode character. - /// - CheckCircle = 0xF058, - - /// - /// The Font Awesome "check-double" icon unicode character. - /// - CheckDouble = 0xF56, - - /// - /// The Font Awesome "check-square" icon unicode character. - /// - CheckSquare = 0xF14A, - - /// - /// The Font Awesome "cheese" icon unicode character. - /// - Cheese = 0xF7EF, - - /// - /// The Font Awesome "chess" icon unicode character. - /// - Chess = 0xF439, - - /// - /// The Font Awesome "chess-bishop" icon unicode character. - /// - ChessBishop = 0xF43A, - - /// - /// The Font Awesome "chess-board" icon unicode character. - /// - ChessBoard = 0xF43C, - - /// - /// The Font Awesome "chess-king" icon unicode character. - /// - ChessKing = 0xF43F, - - /// - /// The Font Awesome "chess-knight" icon unicode character. - /// - ChessKnight = 0xF441, - - /// - /// The Font Awesome "chess-pawn" icon unicode character. - /// - ChessPawn = 0xF443, - - /// - /// The Font Awesome "chess-queen" icon unicode character. - /// - ChessQueen = 0xF445, - - /// - /// The Font Awesome "chess-rook" icon unicode character. - /// - ChessRook = 0xF447, - - /// - /// The Font Awesome "chevron-circle-down" icon unicode character. - /// - ChevronCircleDown = 0xF13A, - - /// - /// The Font Awesome "chevron-circle-left" icon unicode character. - /// - ChevronCircleLeft = 0xF137, - - /// - /// The Font Awesome "chevron-circle-right" icon unicode character. - /// - ChevronCircleRight = 0xF138, - - /// - /// The Font Awesome "chevron-circle-up" icon unicode character. - /// - ChevronCircleUp = 0xF139, - - /// - /// The Font Awesome "chevron-down" icon unicode character. - /// - ChevronDown = 0xF078, - - /// - /// The Font Awesome "chevron-left" icon unicode character. - /// - ChevronLeft = 0xF053, - - /// - /// The Font Awesome "chevron-right" icon unicode character. - /// - ChevronRight = 0xF054, - - /// - /// The Font Awesome "chevron-up" icon unicode character. - /// - ChevronUp = 0xF077, - - /// - /// The Font Awesome "child" icon unicode character. - /// - Child = 0xF1AE, - - /// - /// The Font Awesome "chrome" icon unicode character. - /// - Chrome = 0xF268, - - /// - /// The Font Awesome "chromecast" icon unicode character. - /// - Chromecast = 0xF838, - - /// - /// The Font Awesome "church" icon unicode character. - /// - Church = 0xF51D, - - /// - /// The Font Awesome "circle" icon unicode character. - /// - Circle = 0xF111, - - /// - /// The Font Awesome "circle-notch" icon unicode character. - /// - CircleNotch = 0xF1CE, - - /// - /// The Font Awesome "city" icon unicode character. - /// - City = 0xF64F, - - /// - /// The Font Awesome "clinic-medical" icon unicode character. - /// - ClinicMedical = 0xF7F2, - - /// - /// The Font Awesome "clipboard" icon unicode character. - /// - Clipboard = 0xF328, - - /// - /// The Font Awesome "clipboard-check" icon unicode character. - /// - ClipboardCheck = 0xF46C, - - /// - /// The Font Awesome "clipboard-list" icon unicode character. - /// - ClipboardList = 0xF46D, - - /// - /// The Font Awesome "clock" icon unicode character. - /// - Clock = 0xF017, - - /// - /// The Font Awesome "clone" icon unicode character. - /// - Clone = 0xF24D, - - /// - /// The Font Awesome "closed-captioning" icon unicode character. - /// - ClosedCaptioning = 0xF20A, - - /// - /// The Font Awesome "cloud" icon unicode character. - /// - Cloud = 0xF0C2, - - /// - /// The Font Awesome "cloud-download-alt" icon unicode character. - /// - CloudDownloadAlt = 0xF381, - - /// - /// The Font Awesome "cloud-meatball" icon unicode character. - /// - CloudMeatball = 0xF73B, - - /// - /// The Font Awesome "cloud-moon" icon unicode character. - /// - CloudMoon = 0xF6C3, - - /// - /// The Font Awesome "cloud-moon-rain" icon unicode character. - /// - CloudMoonRain = 0xF73C, - - /// - /// The Font Awesome "cloud-rain" icon unicode character. - /// - CloudRain = 0xF73D, - - /// - /// The Font Awesome "cloudscale" icon unicode character. - /// - Cloudscale = 0xF383, - - /// - /// The Font Awesome "cloud-showers-heavy" icon unicode character. - /// - CloudShowersHeavy = 0xF74, - - /// - /// The Font Awesome "cloudsmith" icon unicode character. - /// - Cloudsmith = 0xF384, - - /// - /// The Font Awesome "cloud-sun" icon unicode character. - /// - CloudSun = 0xF6C4, - - /// - /// The Font Awesome "cloud-sun-rain" icon unicode character. - /// - CloudSunRain = 0xF743, - - /// - /// The Font Awesome "cloud-upload-alt" icon unicode character. - /// - CloudUploadAlt = 0xF382, - - /// - /// The Font Awesome "cloudversify" icon unicode character. - /// - Cloudversify = 0xF385, - - /// - /// The Font Awesome "cocktail" icon unicode character. - /// - Cocktail = 0xF561, - - /// - /// The Font Awesome "code" icon unicode character. - /// - Code = 0xF121, - - /// - /// The Font Awesome "code-branch" icon unicode character. - /// - CodeBranch = 0xF126, - - /// - /// The Font Awesome "codepen" icon unicode character. - /// - Codepen = 0xF1CB, - - /// - /// The Font Awesome "codiepie" icon unicode character. - /// - Codiepie = 0xF284, - - /// - /// The Font Awesome "coffee" icon unicode character. - /// - Coffee = 0xF0F4, - - /// - /// The Font Awesome "cog" icon unicode character. - /// - Cog = 0xF013, - - /// - /// The Font Awesome "cogs" icon unicode character. - /// - Cogs = 0xF085, - - /// - /// The Font Awesome "coins" icon unicode character. - /// - Coins = 0xF51E, - - /// - /// The Font Awesome "columns" icon unicode character. - /// - Columns = 0xF0DB, - - /// - /// The Font Awesome "comment" icon unicode character. - /// - Comment = 0xF075, - - /// - /// The Font Awesome "comment-alt" icon unicode character. - /// - CommentAlt = 0xF27A, - - /// - /// The Font Awesome "comment-dollar" icon unicode character. - /// - CommentDollar = 0xF651, - - /// - /// The Font Awesome "comment-dots" icon unicode character. - /// - CommentDots = 0xF4AD, - - /// - /// The Font Awesome "comment-medical" icon unicode character. - /// - CommentMedical = 0xF7F5, - - /// - /// The Font Awesome "comments" icon unicode character. - /// - Comments = 0xF086, - - /// - /// The Font Awesome "comments-dollar" icon unicode character. - /// - CommentsDollar = 0xF653, - - /// - /// The Font Awesome "comment-slash" icon unicode character. - /// - CommentSlash = 0xF4B3, - - /// - /// The Font Awesome "compact-disc" icon unicode character. - /// - CompactDisc = 0xF51F, - - /// - /// The Font Awesome "compass" icon unicode character. - /// - Compass = 0xF14E, - - /// - /// The Font Awesome "compress" icon unicode character. - /// - Compress = 0xF066, - - /// - /// The Font Awesome "compress-alt" icon unicode character. - /// - CompressAlt = 0xF422, - - /// - /// The Font Awesome "compress-arrows-alt" icon unicode character. - /// - CompressArrowsAlt = 0xF78C, - - /// - /// The Font Awesome "concierge-bell" icon unicode character. - /// - ConciergeBell = 0xF562, - - /// - /// The Font Awesome "confluence" icon unicode character. - /// - Confluence = 0xF78D, - - /// - /// The Font Awesome "connectdevelop" icon unicode character. - /// - Connectdevelop = 0xF20E, - - /// - /// The Font Awesome "contao" icon unicode character. - /// - Contao = 0xF26D, - - /// - /// The Font Awesome "cookie" icon unicode character. - /// - Cookie = 0xF563, - - /// - /// The Font Awesome "cookie-bite" icon unicode character. - /// - CookieBite = 0xF564, - - /// - /// The Font Awesome "copy" icon unicode character. - /// - Copy = 0xF0C5, - - /// - /// The Font Awesome "copyright" icon unicode character. - /// - Copyright = 0xF1F9, - - /// - /// The Font Awesome "cotton-bureau" icon unicode character. - /// - CottonBureau = 0xF89E, - - /// - /// The Font Awesome "couch" icon unicode character. - /// - Couch = 0xF4B8, - - /// - /// The Font Awesome "cpanel" icon unicode character. - /// - Cpanel = 0xF388, - - /// - /// The Font Awesome "creative-commons" icon unicode character. - /// - CreativeCommons = 0xF25E, - - /// - /// The Font Awesome "creative-commons-by" icon unicode character. - /// - CreativeCommonsBy = 0xF4E7, - - /// - /// The Font Awesome "creative-commons-nc" icon unicode character. - /// - CreativeCommonsNc = 0xF4E8, - - /// - /// The Font Awesome "creative-commons-nc-eu" icon unicode character. - /// - CreativeCommonsNcEu = 0xF4E9, - - /// - /// The Font Awesome "creative-commons-nc-jp" icon unicode character. - /// - CreativeCommonsNcJp = 0xF4EA, - - /// - /// The Font Awesome "creative-commons-nd" icon unicode character. - /// - CreativeCommonsNd = 0xF4EB, - - /// - /// The Font Awesome "creative-commons-pd" icon unicode character. - /// - CreativeCommonsPd = 0xF4EC, - - /// - /// The Font Awesome "creative-commons-pd-alt" icon unicode character. - /// - CreativeCommonsPdAlt = 0xF4ED, - - /// - /// The Font Awesome "creative-commons-remix" icon unicode character. - /// - CreativeCommonsRemix = 0xF4EE, - - /// - /// The Font Awesome "creative-commons-sa" icon unicode character. - /// - CreativeCommonsSa = 0xF4EF, - - /// - /// The Font Awesome "creative-commons-sampling" icon unicode character. - /// - CreativeCommonsSampling = 0xF4F, - - /// - /// The Font Awesome "creative-commons-sampling-plus" icon unicode character. - /// - CreativeCommonsSamplingPlus = 0xF4F1, - - /// - /// The Font Awesome "creative-commons-share" icon unicode character. - /// - CreativeCommonsShare = 0xF4F2, - - /// - /// The Font Awesome "creative-commons-zero" icon unicode character. - /// - CreativeCommonsZero = 0xF4F3, - - /// - /// The Font Awesome "credit-card" icon unicode character. - /// - CreditCard = 0xF09D, - - /// - /// The Font Awesome "critical-role" icon unicode character. - /// - CriticalRole = 0xF6C9, - - /// - /// The Font Awesome "crop" icon unicode character. - /// - Crop = 0xF125, - - /// - /// The Font Awesome "crop-alt" icon unicode character. - /// - CropAlt = 0xF565, - - /// - /// The Font Awesome "cross" icon unicode character. - /// - Cross = 0xF654, - - /// - /// The Font Awesome "crosshairs" icon unicode character. - /// - Crosshairs = 0xF05B, - - /// - /// The Font Awesome "crow" icon unicode character. - /// - Crow = 0xF52, - - /// - /// The Font Awesome "crown" icon unicode character. - /// - Crown = 0xF521, - - /// - /// The Font Awesome "crutch" icon unicode character. - /// - Crutch = 0xF7F7, - - /// - /// The Font Awesome "css3" icon unicode character. - /// - Css3 = 0xF13C, - - /// - /// The Font Awesome "css3-alt" icon unicode character. - /// - Css3Alt = 0xF38B, - - /// - /// The Font Awesome "cube" icon unicode character. - /// - Cube = 0xF1B2, - - /// - /// The Font Awesome "cubes" icon unicode character. - /// - Cubes = 0xF1B3, - - /// - /// The Font Awesome "cut" icon unicode character. - /// - Cut = 0xF0C4, - - /// - /// The Font Awesome "cuttlefish" icon unicode character. - /// - Cuttlefish = 0xF38C, - - /// - /// The Font Awesome "dailymotion" icon unicode character. - /// - Dailymotion = 0xF952, - - /// - /// The Font Awesome "d-and-d" icon unicode character. - /// - DAndD = 0xF38D, - - /// - /// The Font Awesome "d-and-d-beyond" icon unicode character. - /// - DAndDBeyond = 0xF6CA, - - /// - /// The Font Awesome "dashcube" icon unicode character. - /// - Dashcube = 0xF21, - - /// - /// The Font Awesome "database" icon unicode character. - /// - Database = 0xF1C, - - /// - /// The Font Awesome "deaf" icon unicode character. - /// - Deaf = 0xF2A4, - - /// - /// The Font Awesome "delicious" icon unicode character. - /// - Delicious = 0xF1A5, - - /// - /// The Font Awesome "democrat" icon unicode character. - /// - Democrat = 0xF747, - - /// - /// The Font Awesome "deploydog" icon unicode character. - /// - Deploydog = 0xF38E, - - /// - /// The Font Awesome "deskpro" icon unicode character. - /// - Deskpro = 0xF38F, - - /// - /// The Font Awesome "desktop" icon unicode character. - /// - Desktop = 0xF108, - - /// - /// The Font Awesome "dev" icon unicode character. - /// - Dev = 0xF6CC, - - /// - /// The Font Awesome "deviantart" icon unicode character. - /// - Deviantart = 0xF1BD, - - /// - /// The Font Awesome "dharmachakra" icon unicode character. - /// - Dharmachakra = 0xF655, - - /// - /// The Font Awesome "dhl" icon unicode character. - /// - Dhl = 0xF79, - - /// - /// The Font Awesome "diagnoses" icon unicode character. - /// - Diagnoses = 0xF47, - - /// - /// The Font Awesome "diaspora" icon unicode character. - /// - Diaspora = 0xF791, - - /// - /// The Font Awesome "dice" icon unicode character. - /// - Dice = 0xF522, - - /// - /// The Font Awesome "dice-d20" icon unicode character. - /// - DiceD20 = 0xF6CF, - - /// - /// The Font Awesome "dice-d6" icon unicode character. - /// - DiceD6 = 0xF6D1, - - /// - /// The Font Awesome "dice-five" icon unicode character. - /// - DiceFive = 0xF523, - - /// - /// The Font Awesome "dice-four" icon unicode character. - /// - DiceFour = 0xF524, - - /// - /// The Font Awesome "dice-one" icon unicode character. - /// - DiceOne = 0xF525, - - /// - /// The Font Awesome "dice-six" icon unicode character. - /// - DiceSix = 0xF526, - - /// - /// The Font Awesome "dice-three" icon unicode character. - /// - DiceThree = 0xF527, - - /// - /// The Font Awesome "dice-two" icon unicode character. - /// - DiceTwo = 0xF528, - - /// - /// The Font Awesome "digg" icon unicode character. - /// - Digg = 0xF1A6, - - /// - /// The Font Awesome "digital-ocean" icon unicode character. - /// - DigitalOcean = 0xF391, - - /// - /// The Font Awesome "digital-tachograph" icon unicode character. - /// - DigitalTachograph = 0xF566, - - /// - /// The Font Awesome "directions" icon unicode character. - /// - Directions = 0xF5EB, - - /// - /// The Font Awesome "discord" icon unicode character. - /// - Discord = 0xF392, - - /// - /// The Font Awesome "discourse" icon unicode character. - /// - Discourse = 0xF393, - - /// - /// The Font Awesome "divide" icon unicode character. - /// - Divide = 0xF529, - - /// - /// The Font Awesome "dizzy" icon unicode character. - /// - Dizzy = 0xF567, - - /// - /// The Font Awesome "dna" icon unicode character. - /// - Dna = 0xF471, - - /// - /// The Font Awesome "dochub" icon unicode character. - /// - Dochub = 0xF394, - - /// - /// The Font Awesome "docker" icon unicode character. - /// - Docker = 0xF395, - - /// - /// The Font Awesome "dog" icon unicode character. - /// - Dog = 0xF6D3, - - /// - /// The Font Awesome "dollar-sign" icon unicode character. - /// - DollarSign = 0xF155, - - /// - /// The Font Awesome "dolly" icon unicode character. - /// - Dolly = 0xF472, - - /// - /// The Font Awesome "dolly-flatbed" icon unicode character. - /// - DollyFlatbed = 0xF474, - - /// - /// The Font Awesome "donate" icon unicode character. - /// - Donate = 0xF4B9, - - /// - /// The Font Awesome "door-closed" icon unicode character. - /// - DoorClosed = 0xF52A, - - /// - /// The Font Awesome "door-open" icon unicode character. - /// - DoorOpen = 0xF52B, - - /// - /// The Font Awesome "dot-circle" icon unicode character. - /// - DotCircle = 0xF192, - - /// - /// The Font Awesome "dove" icon unicode character. - /// - Dove = 0xF4BA, - - /// - /// The Font Awesome "download" icon unicode character. - /// - Download = 0xF019, - - /// - /// The Font Awesome "draft2digital" icon unicode character. - /// - Draft2digital = 0xF396, - - /// - /// The Font Awesome "drafting-compass" icon unicode character. - /// - DraftingCompass = 0xF568, - - /// - /// The Font Awesome "dragon" icon unicode character. - /// - Dragon = 0xF6D5, - - /// - /// The Font Awesome "draw-polygon" icon unicode character. - /// - DrawPolygon = 0xF5EE, - - /// - /// The Font Awesome "dribbble" icon unicode character. - /// - Dribbble = 0xF17D, - - /// - /// The Font Awesome "dribbble-square" icon unicode character. - /// - DribbbleSquare = 0xF397, - - /// - /// The Font Awesome "dropbox" icon unicode character. - /// - Dropbox = 0xF16B, - - /// - /// The Font Awesome "drum" icon unicode character. - /// - Drum = 0xF569, - - /// - /// The Font Awesome "drum-steelpan" icon unicode character. - /// - DrumSteelpan = 0xF56A, - - /// - /// The Font Awesome "drumstick-bite" icon unicode character. - /// - DrumstickBite = 0xF6D7, - - /// - /// The Font Awesome "drupal" icon unicode character. - /// - Drupal = 0xF1A9, - - /// - /// The Font Awesome "dumbbell" icon unicode character. - /// - Dumbbell = 0xF44B, - - /// - /// The Font Awesome "dumpster" icon unicode character. - /// - Dumpster = 0xF793, - - /// - /// The Font Awesome "dumpster-fire" icon unicode character. - /// - DumpsterFire = 0xF794, - - /// - /// The Font Awesome "dungeon" icon unicode character. - /// - Dungeon = 0xF6D9, - - /// - /// The Font Awesome "dyalog" icon unicode character. - /// - Dyalog = 0xF399, - - /// - /// The Font Awesome "earlybirds" icon unicode character. - /// - Earlybirds = 0xF39A, - - /// - /// The Font Awesome "ebay" icon unicode character. - /// - Ebay = 0xF4F4, - - /// - /// The Font Awesome "edge" icon unicode character. - /// - Edge = 0xF282, - - /// - /// The Font Awesome "edit" icon unicode character. - /// - Edit = 0xF044, - - /// - /// The Font Awesome "egg" icon unicode character. - /// - Egg = 0xF7FB, - - /// - /// The Font Awesome "eject" icon unicode character. - /// - Eject = 0xF052, - - /// - /// The Font Awesome "elementor" icon unicode character. - /// - Elementor = 0xF43, - - /// - /// The Font Awesome "ellipsis-h" icon unicode character. - /// - EllipsisH = 0xF141, - - /// - /// The Font Awesome "ellipsis-v" icon unicode character. - /// - EllipsisV = 0xF142, - - /// - /// The Font Awesome "ello" icon unicode character. - /// - Ello = 0xF5F1, - - /// - /// The Font Awesome "ember" icon unicode character. - /// - Ember = 0xF423, - - /// - /// The Font Awesome "empire" icon unicode character. - /// - Empire = 0xF1D1, - - /// - /// The Font Awesome "envelope" icon unicode character. - /// - Envelope = 0xF0E, - - /// - /// The Font Awesome "envelope-open" icon unicode character. - /// - EnvelopeOpen = 0xF2B6, - - /// - /// The Font Awesome "envelope-open-text" icon unicode character. - /// - EnvelopeOpenText = 0xF658, - - /// - /// The Font Awesome "envelope-square" icon unicode character. - /// - EnvelopeSquare = 0xF199, - - /// - /// The Font Awesome "envira" icon unicode character. - /// - Envira = 0xF299, - - /// - /// The Font Awesome "equals" icon unicode character. - /// - Equals = 0xF52C, - - /// - /// The Font Awesome "eraser" icon unicode character. - /// - Eraser = 0xF12D, - - /// - /// The Font Awesome "erlang" icon unicode character. - /// - Erlang = 0xF39D, - - /// - /// The Font Awesome "ethereum" icon unicode character. - /// - Ethereum = 0xF42E, - - /// - /// The Font Awesome "ethernet" icon unicode character. - /// - Ethernet = 0xF796, - - /// - /// The Font Awesome "etsy" icon unicode character. - /// - Etsy = 0xF2D7, - - /// - /// The Font Awesome "euro-sign" icon unicode character. - /// - EuroSign = 0xF153, - - /// - /// The Font Awesome "evernote" icon unicode character. - /// - Evernote = 0xF839, - - /// - /// The Font Awesome "exchange-alt" icon unicode character. - /// - ExchangeAlt = 0xF362, - - /// - /// The Font Awesome "exclamation" icon unicode character. - /// - Exclamation = 0xF12A, - - /// - /// The Font Awesome "exclamation-circle" icon unicode character. - /// - ExclamationCircle = 0xF06A, - - /// - /// The Font Awesome "exclamation-triangle" icon unicode character. - /// - ExclamationTriangle = 0xF071, - - /// - /// The Font Awesome "expand" icon unicode character. - /// - Expand = 0xF065, - - /// - /// The Font Awesome "expand-alt" icon unicode character. - /// - ExpandAlt = 0xF424, - - /// - /// The Font Awesome "expand-arrows-alt" icon unicode character. - /// - ExpandArrowsAlt = 0xF31E, - - /// - /// The Font Awesome "expeditedssl" icon unicode character. - /// - Expeditedssl = 0xF23E, - - /// - /// The Font Awesome "external-link-alt" icon unicode character. - /// - ExternalLinkAlt = 0xF35D, - - /// - /// The Font Awesome "external-link-square-alt" icon unicode character. - /// - ExternalLinkSquareAlt = 0xF36, - - /// - /// The Font Awesome "eye" icon unicode character. - /// - Eye = 0xF06E, - - /// - /// The Font Awesome "eye-dropper" icon unicode character. - /// - EyeDropper = 0xF1FB, - - /// - /// The Font Awesome "eye-slash" icon unicode character. - /// - EyeSlash = 0xF07, - - /// - /// The Font Awesome "facebook" icon unicode character. - /// - Facebook = 0xF09A, - - /// - /// The Font Awesome "facebook-f" icon unicode character. - /// - FacebookF = 0xF39E, - - /// - /// The Font Awesome "facebook-messenger" icon unicode character. - /// - FacebookMessenger = 0xF39F, - - /// - /// The Font Awesome "facebook-square" icon unicode character. - /// - FacebookSquare = 0xF082, - - /// - /// The Font Awesome "fan" icon unicode character. - /// - Fan = 0xF863, - - /// - /// The Font Awesome "fantasy-flight-games" icon unicode character. - /// - FantasyFlightGames = 0xF6DC, - - /// - /// The Font Awesome "fast-backward" icon unicode character. - /// - FastBackward = 0xF049, - - /// - /// The Font Awesome "fast-forward" icon unicode character. - /// - FastForward = 0xF05, - - /// - /// The Font Awesome "fax" icon unicode character. - /// - Fax = 0xF1AC, - - /// - /// The Font Awesome "feather" icon unicode character. - /// - Feather = 0xF52D, - - /// - /// The Font Awesome "feather-alt" icon unicode character. - /// - FeatherAlt = 0xF56B, - - /// - /// The Font Awesome "fedex" icon unicode character. - /// - Fedex = 0xF797, - - /// - /// The Font Awesome "fedora" icon unicode character. - /// - Fedora = 0xF798, - - /// - /// The Font Awesome "female" icon unicode character. - /// - Female = 0xF182, - - /// - /// The Font Awesome "fighter-jet" icon unicode character. - /// - FighterJet = 0xF0FB, - - /// - /// The Font Awesome "figma" icon unicode character. - /// - Figma = 0xF799, - - /// - /// The Font Awesome "file" icon unicode character. - /// - File = 0xF15B, - - /// - /// The Font Awesome "file-alt" icon unicode character. - /// - FileAlt = 0xF15C, - - /// - /// The Font Awesome "file-archive" icon unicode character. - /// - FileArchive = 0xF1C6, - - /// - /// The Font Awesome "file-audio" icon unicode character. - /// - FileAudio = 0xF1C7, - - /// - /// The Font Awesome "file-code" icon unicode character. - /// - FileCode = 0xF1C9, - - /// - /// The Font Awesome "file-contract" icon unicode character. - /// - FileContract = 0xF56C, - - /// - /// The Font Awesome "file-csv" icon unicode character. - /// - FileCsv = 0xF6DD, - - /// - /// The Font Awesome "file-download" icon unicode character. - /// - FileDownload = 0xF56D, - - /// - /// The Font Awesome "file-excel" icon unicode character. - /// - FileExcel = 0xF1C3, - - /// - /// The Font Awesome "file-export" icon unicode character. - /// - FileExport = 0xF56E, - - /// - /// The Font Awesome "file-image" icon unicode character. - /// - FileImage = 0xF1C5, - - /// - /// The Font Awesome "file-import" icon unicode character. - /// - FileImport = 0xF56F, - - /// - /// The Font Awesome "file-invoice" icon unicode character. - /// - FileInvoice = 0xF57, - - /// - /// The Font Awesome "file-invoice-dollar" icon unicode character. - /// - FileInvoiceDollar = 0xF571, - - /// - /// The Font Awesome "file-medical" icon unicode character. - /// - FileMedical = 0xF477, - - /// - /// The Font Awesome "file-medical-alt" icon unicode character. - /// - FileMedicalAlt = 0xF478, - - /// - /// The Font Awesome "file-pdf" icon unicode character. - /// - FilePdf = 0xF1C1, - - /// - /// The Font Awesome "file-powerpoint" icon unicode character. - /// - FilePowerpoint = 0xF1C4, - - /// - /// The Font Awesome "file-prescription" icon unicode character. - /// - FilePrescription = 0xF572, - - /// - /// The Font Awesome "file-signature" icon unicode character. - /// - FileSignature = 0xF573, - - /// - /// The Font Awesome "file-upload" icon unicode character. - /// - FileUpload = 0xF574, - - /// - /// The Font Awesome "file-video" icon unicode character. - /// - FileVideo = 0xF1C8, - - /// - /// The Font Awesome "file-word" icon unicode character. - /// - FileWord = 0xF1C2, - - /// - /// The Font Awesome "fill" icon unicode character. - /// - Fill = 0xF575, - - /// - /// The Font Awesome "fill-drip" icon unicode character. - /// - FillDrip = 0xF576, - - /// - /// The Font Awesome "film" icon unicode character. - /// - Film = 0xF008, - - /// - /// The Font Awesome "filter" icon unicode character. - /// - Filter = 0xF0B, - - /// - /// The Font Awesome "fingerprint" icon unicode character. - /// - Fingerprint = 0xF577, - - /// - /// The Font Awesome "fire" icon unicode character. - /// - Fire = 0xF06D, - - /// - /// The Font Awesome "fire-alt" icon unicode character. - /// - FireAlt = 0xF7E4, - - /// - /// The Font Awesome "fire-extinguisher" icon unicode character. - /// - FireExtinguisher = 0xF134, - - /// - /// The Font Awesome "firefox" icon unicode character. - /// - Firefox = 0xF269, - - /// - /// The Font Awesome "firefox-browser" icon unicode character. - /// - FirefoxBrowser = 0xF907, - - /// - /// The Font Awesome "first-aid" icon unicode character. - /// - FirstAid = 0xF479, - - /// - /// The Font Awesome "firstdraft" icon unicode character. - /// - Firstdraft = 0xF3A1, - - /// - /// The Font Awesome "first-order" icon unicode character. - /// - FirstOrder = 0xF2B, - - /// - /// The Font Awesome "first-order-alt" icon unicode character. - /// - FirstOrderAlt = 0xF50A, - - /// - /// The Font Awesome "fish" icon unicode character. - /// - Fish = 0xF578, - - /// - /// The Font Awesome "fist-raised" icon unicode character. - /// - FistRaised = 0xF6DE, - - /// - /// The Font Awesome "flag" icon unicode character. - /// - Flag = 0xF024, - - /// - /// The Font Awesome "flag-checkered" icon unicode character. - /// - FlagCheckered = 0xF11E, - - /// - /// The Font Awesome "flag-usa" icon unicode character. - /// - FlagUsa = 0xF74D, - - /// - /// The Font Awesome "flask" icon unicode character. - /// - Flask = 0xF0C3, - - /// - /// The Font Awesome "flickr" icon unicode character. - /// - Flickr = 0xF16E, - - /// - /// The Font Awesome "flipboard" icon unicode character. - /// - Flipboard = 0xF44D, - - /// - /// The Font Awesome "flushed" icon unicode character. - /// - Flushed = 0xF579, - - /// - /// The Font Awesome "fly" icon unicode character. - /// - Fly = 0xF417, - - /// - /// The Font Awesome "folder" icon unicode character. - /// - Folder = 0xF07B, - - /// - /// The Font Awesome "folder-minus" icon unicode character. - /// - FolderMinus = 0xF65D, - - /// - /// The Font Awesome "folder-open" icon unicode character. - /// - FolderOpen = 0xF07C, - - /// - /// The Font Awesome "folder-plus" icon unicode character. - /// - FolderPlus = 0xF65E, - - /// - /// The Font Awesome "font" icon unicode character. - /// - Font = 0xF031, - - /// - /// The Font Awesome "font-awesome" icon unicode character. - /// - FontAwesome = 0xF2B4, - - /// - /// The Font Awesome "font-awesome-alt" icon unicode character. - /// - FontAwesomeAlt = 0xF35C, - - /// - /// The Font Awesome "font-awesome-flag" icon unicode character. - /// - FontAwesomeFlag = 0xF425, - - /// - /// The Font Awesome "font-awesome-logo-full" icon unicode character. - /// - FontAwesomeLogoFull = 0xF4E6, - - /// - /// The Font Awesome "fonticons" icon unicode character. - /// - Fonticons = 0xF28, - - /// - /// The Font Awesome "fonticons-fi" icon unicode character. - /// - FonticonsFi = 0xF3A2, - - /// - /// The Font Awesome "football-ball" icon unicode character. - /// - FootballBall = 0xF44E, - - /// - /// The Font Awesome "fort-awesome" icon unicode character. - /// - FortAwesome = 0xF286, - - /// - /// The Font Awesome "fort-awesome-alt" icon unicode character. - /// - FortAwesomeAlt = 0xF3A3, - - /// - /// The Font Awesome "forumbee" icon unicode character. - /// - Forumbee = 0xF211, - - /// - /// The Font Awesome "forward" icon unicode character. - /// - Forward = 0xF04E, - - /// - /// The Font Awesome "foursquare" icon unicode character. - /// - Foursquare = 0xF18, - - /// - /// The Font Awesome "freebsd" icon unicode character. - /// - Freebsd = 0xF3A4, - - /// - /// The Font Awesome "free-code-camp" icon unicode character. - /// - FreeCodeCamp = 0xF2C5, - - /// - /// The Font Awesome "frog" icon unicode character. - /// - Frog = 0xF52E, - - /// - /// The Font Awesome "frown" icon unicode character. - /// - Frown = 0xF119, - - /// - /// The Font Awesome "frown-open" icon unicode character. - /// - FrownOpen = 0xF57A, - - /// - /// The Font Awesome "fulcrum" icon unicode character. - /// - Fulcrum = 0xF50B, - - /// - /// The Font Awesome "funnel-dollar" icon unicode character. - /// - FunnelDollar = 0xF662, - - /// - /// The Font Awesome "futbol" icon unicode character. - /// - Futbol = 0xF1E3, - - /// - /// The Font Awesome "galactic-republic" icon unicode character. - /// - GalacticRepublic = 0xF50C, - - /// - /// The Font Awesome "galactic-senate" icon unicode character. - /// - GalacticSenate = 0xF50D, - - /// - /// The Font Awesome "gamepad" icon unicode character. - /// - Gamepad = 0xF11B, - - /// - /// The Font Awesome "gas-pump" icon unicode character. - /// - GasPump = 0xF52F, - - /// - /// The Font Awesome "gavel" icon unicode character. - /// - Gavel = 0xF0E3, - - /// - /// The Font Awesome "gem" icon unicode character. - /// - Gem = 0xF3A5, - - /// - /// The Font Awesome "genderless" icon unicode character. - /// - Genderless = 0xF22D, - - /// - /// The Font Awesome "get-pocket" icon unicode character. - /// - GetPocket = 0xF265, - - /// - /// The Font Awesome "gg" icon unicode character. - /// - Gg = 0xF26, - - /// - /// The Font Awesome "gg-circle" icon unicode character. - /// - GgCircle = 0xF261, - - /// - /// The Font Awesome "ghost" icon unicode character. - /// - Ghost = 0xF6E2, - - /// - /// The Font Awesome "gift" icon unicode character. - /// - Gift = 0xF06B, - - /// - /// The Font Awesome "gifts" icon unicode character. - /// - Gifts = 0xF79C, - - /// - /// The Font Awesome "git" icon unicode character. - /// - Git = 0xF1D3, - - /// - /// The Font Awesome "git-alt" icon unicode character. - /// - GitAlt = 0xF841, - - /// - /// The Font Awesome "github" icon unicode character. - /// - Github = 0xF09B, - - /// - /// The Font Awesome "github-alt" icon unicode character. - /// - GithubAlt = 0xF113, - - /// - /// The Font Awesome "github-square" icon unicode character. - /// - GithubSquare = 0xF092, - - /// - /// The Font Awesome "gitkraken" icon unicode character. - /// - Gitkraken = 0xF3A6, - - /// - /// The Font Awesome "gitlab" icon unicode character. - /// - Gitlab = 0xF296, - - /// - /// The Font Awesome "git-square" icon unicode character. - /// - GitSquare = 0xF1D2, - - /// - /// The Font Awesome "gitter" icon unicode character. - /// - Gitter = 0xF426, - - /// - /// The Font Awesome "glass-cheers" icon unicode character. - /// - GlassCheers = 0xF79F, - - /// - /// The Font Awesome "glasses" icon unicode character. - /// - Glasses = 0xF53, - - /// - /// The Font Awesome "glass-martini" icon unicode character. - /// - GlassMartini = 0xF, - - /// - /// The Font Awesome "glass-martini-alt" icon unicode character. - /// - GlassMartiniAlt = 0xF57B, - - /// - /// The Font Awesome "glass-whiskey" icon unicode character. - /// - GlassWhiskey = 0xF7A, - - /// - /// The Font Awesome "glide" icon unicode character. - /// - Glide = 0xF2A5, - - /// - /// The Font Awesome "glide-g" icon unicode character. - /// - GlideG = 0xF2A6, - - /// - /// The Font Awesome "globe" icon unicode character. - /// - Globe = 0xF0AC, - - /// - /// The Font Awesome "globe-africa" icon unicode character. - /// - GlobeAfrica = 0xF57C, - - /// - /// The Font Awesome "globe-americas" icon unicode character. - /// - GlobeAmericas = 0xF57D, - - /// - /// The Font Awesome "globe-asia" icon unicode character. - /// - GlobeAsia = 0xF57E, - - /// - /// The Font Awesome "globe-europe" icon unicode character. - /// - GlobeEurope = 0xF7A2, - - /// - /// The Font Awesome "gofore" icon unicode character. - /// - Gofore = 0xF3A7, - - /// - /// The Font Awesome "golf-ball" icon unicode character. - /// - GolfBall = 0xF45, - - /// - /// The Font Awesome "goodreads" icon unicode character. - /// - Goodreads = 0xF3A8, - - /// - /// The Font Awesome "goodreads-g" icon unicode character. - /// - GoodreadsG = 0xF3A9, - - /// - /// The Font Awesome "google" icon unicode character. - /// - Google = 0xF1A, - - /// - /// The Font Awesome "google-drive" icon unicode character. - /// - GoogleDrive = 0xF3AA, - - /// - /// The Font Awesome "google-play" icon unicode character. - /// - GooglePlay = 0xF3AB, - - /// - /// The Font Awesome "google-plus" icon unicode character. - /// - GooglePlus = 0xF2B3, - - /// - /// The Font Awesome "google-plus-g" icon unicode character. - /// - GooglePlusG = 0xF0D5, - - /// - /// The Font Awesome "google-plus-square" icon unicode character. - /// - GooglePlusSquare = 0xF0D4, - - /// - /// The Font Awesome "google-wallet" icon unicode character. - /// - GoogleWallet = 0xF1EE, - - /// - /// The Font Awesome "gopuram" icon unicode character. - /// - Gopuram = 0xF664, - - /// - /// The Font Awesome "graduation-cap" icon unicode character. - /// - GraduationCap = 0xF19D, - - /// - /// The Font Awesome "gratipay" icon unicode character. - /// - Gratipay = 0xF184, - - /// - /// The Font Awesome "grav" icon unicode character. - /// - Grav = 0xF2D6, - - /// - /// The Font Awesome "greater-than" icon unicode character. - /// - GreaterThan = 0xF531, - - /// - /// The Font Awesome "greater-than-equal" icon unicode character. - /// - GreaterThanEqual = 0xF532, - - /// - /// The Font Awesome "grimace" icon unicode character. - /// - Grimace = 0xF57F, - - /// - /// The Font Awesome "grin" icon unicode character. - /// - Grin = 0xF58, - - /// - /// The Font Awesome "grin-alt" icon unicode character. - /// - GrinAlt = 0xF581, - - /// - /// The Font Awesome "grin-beam" icon unicode character. - /// - GrinBeam = 0xF582, - - /// - /// The Font Awesome "grin-beam-sweat" icon unicode character. - /// - GrinBeamSweat = 0xF583, - - /// - /// The Font Awesome "grin-hearts" icon unicode character. - /// - GrinHearts = 0xF584, - - /// - /// The Font Awesome "grin-squint" icon unicode character. - /// - GrinSquint = 0xF585, - - /// - /// The Font Awesome "grin-squint-tears" icon unicode character. - /// - GrinSquintTears = 0xF586, - - /// - /// The Font Awesome "grin-stars" icon unicode character. - /// - GrinStars = 0xF587, - - /// - /// The Font Awesome "grin-tears" icon unicode character. - /// - GrinTears = 0xF588, - - /// - /// The Font Awesome "grin-tongue" icon unicode character. - /// - GrinTongue = 0xF589, - - /// - /// The Font Awesome "grin-tongue-squint" icon unicode character. - /// - GrinTongueSquint = 0xF58A, - - /// - /// The Font Awesome "grin-tongue-wink" icon unicode character. - /// - GrinTongueWink = 0xF58B, - - /// - /// The Font Awesome "grin-wink" icon unicode character. - /// - GrinWink = 0xF58C, - - /// - /// The Font Awesome "gripfire" icon unicode character. - /// - Gripfire = 0xF3AC, - - /// - /// The Font Awesome "grip-horizontal" icon unicode character. - /// - GripHorizontal = 0xF58D, - - /// - /// The Font Awesome "grip-lines" icon unicode character. - /// - GripLines = 0xF7A4, - - /// - /// The Font Awesome "grip-lines-vertical" icon unicode character. - /// - GripLinesVertical = 0xF7A5, - - /// - /// The Font Awesome "grip-vertical" icon unicode character. - /// - GripVertical = 0xF58E, - - /// - /// The Font Awesome "grunt" icon unicode character. - /// - Grunt = 0xF3AD, - - /// - /// The Font Awesome "guitar" icon unicode character. - /// - Guitar = 0xF7A6, - - /// - /// The Font Awesome "gulp" icon unicode character. - /// - Gulp = 0xF3AE, - - /// - /// The Font Awesome "hacker-news" icon unicode character. - /// - HackerNews = 0xF1D4, - - /// - /// The Font Awesome "hacker-news-square" icon unicode character. - /// - HackerNewsSquare = 0xF3AF, - - /// - /// The Font Awesome "hackerrank" icon unicode character. - /// - Hackerrank = 0xF5F7, - - /// - /// The Font Awesome "hamburger" icon unicode character. - /// - Hamburger = 0xF805, - - /// - /// The Font Awesome "hammer" icon unicode character. - /// - Hammer = 0xF6E3, - - /// - /// The Font Awesome "hamsa" icon unicode character. - /// - Hamsa = 0xF665, - - /// - /// The Font Awesome "hand-holding" icon unicode character. - /// - HandHolding = 0xF4BD, - - /// - /// The Font Awesome "hand-holding-heart" icon unicode character. - /// - HandHoldingHeart = 0xF4BE, - - /// - /// The Font Awesome "hand-holding-usd" icon unicode character. - /// - HandHoldingUsd = 0xF4C, - - /// - /// The Font Awesome "hand-lizard" icon unicode character. - /// - HandLizard = 0xF258, - - /// - /// The Font Awesome "hand-middle-finger" icon unicode character. - /// - HandMiddleFinger = 0xF806, - - /// - /// The Font Awesome "hand-paper" icon unicode character. - /// - HandPaper = 0xF256, - - /// - /// The Font Awesome "hand-peace" icon unicode character. - /// - HandPeace = 0xF25B, - - /// - /// The Font Awesome "hand-point-down" icon unicode character. - /// - HandPointDown = 0xF0A7, - - /// - /// The Font Awesome "hand-pointer" icon unicode character. - /// - HandPointer = 0xF25A, - - /// - /// The Font Awesome "hand-point-left" icon unicode character. - /// - HandPointLeft = 0xF0A5, - - /// - /// The Font Awesome "hand-point-right" icon unicode character. - /// - HandPointRight = 0xF0A4, - - /// - /// The Font Awesome "hand-point-up" icon unicode character. - /// - HandPointUp = 0xF0A6, - - /// - /// The Font Awesome "hand-rock" icon unicode character. - /// - HandRock = 0xF255, - - /// - /// The Font Awesome "hands" icon unicode character. - /// - Hands = 0xF4C2, - - /// - /// The Font Awesome "hand-scissors" icon unicode character. - /// - HandScissors = 0xF257, - - /// - /// The Font Awesome "handshake" icon unicode character. - /// - Handshake = 0xF2B5, - - /// - /// The Font Awesome "hands-helping" icon unicode character. - /// - HandsHelping = 0xF4C4, - - /// - /// The Font Awesome "hand-spock" icon unicode character. - /// - HandSpock = 0xF259, - - /// - /// The Font Awesome "hanukiah" icon unicode character. - /// - Hanukiah = 0xF6E6, - - /// - /// The Font Awesome "hard-hat" icon unicode character. - /// - HardHat = 0xF807, - - /// - /// The Font Awesome "hashtag" icon unicode character. - /// - Hashtag = 0xF292, - - /// - /// The Font Awesome "hat-cowboy" icon unicode character. - /// - HatCowboy = 0xF8C, - - /// - /// The Font Awesome "hat-cowboy-side" icon unicode character. - /// - HatCowboySide = 0xF8C1, - - /// - /// The Font Awesome "hat-wizard" icon unicode character. - /// - HatWizard = 0xF6E8, - - /// - /// The Font Awesome "hdd" icon unicode character. - /// - Hdd = 0xF0A, - - /// - /// The Font Awesome "heading" icon unicode character. - /// - Heading = 0xF1DC, - - /// - /// The Font Awesome "headphones" icon unicode character. - /// - Headphones = 0xF025, - - /// - /// The Font Awesome "headphones-alt" icon unicode character. - /// - HeadphonesAlt = 0xF58F, - - /// - /// The Font Awesome "headset" icon unicode character. - /// - Headset = 0xF59, - - /// - /// The Font Awesome "heart" icon unicode character. - /// - Heart = 0xF004, - - /// - /// The Font Awesome "heartbeat" icon unicode character. - /// - Heartbeat = 0xF21E, - - /// - /// The Font Awesome "heart-broken" icon unicode character. - /// - HeartBroken = 0xF7A9, - - /// - /// The Font Awesome "helicopter" icon unicode character. - /// - Helicopter = 0xF533, - - /// - /// The Font Awesome "highlighter" icon unicode character. - /// - Highlighter = 0xF591, - - /// - /// The Font Awesome "hiking" icon unicode character. - /// - Hiking = 0xF6EC, - - /// - /// The Font Awesome "hippo" icon unicode character. - /// - Hippo = 0xF6ED, - - /// - /// The Font Awesome "hips" icon unicode character. - /// - Hips = 0xF452, - - /// - /// The Font Awesome "hire-a-helper" icon unicode character. - /// - HireAHelper = 0xF3B, - - /// - /// The Font Awesome "history" icon unicode character. - /// - History = 0xF1DA, - - /// - /// The Font Awesome "hockey-puck" icon unicode character. - /// - HockeyPuck = 0xF453, - - /// - /// The Font Awesome "holly-berry" icon unicode character. - /// - HollyBerry = 0xF7AA, - - /// - /// The Font Awesome "home" icon unicode character. - /// - Home = 0xF015, - - /// - /// The Font Awesome "hooli" icon unicode character. - /// - Hooli = 0xF427, - - /// - /// The Font Awesome "hornbill" icon unicode character. - /// - Hornbill = 0xF592, - - /// - /// The Font Awesome "horse" icon unicode character. - /// - Horse = 0xF6F, - - /// - /// The Font Awesome "horse-head" icon unicode character. - /// - HorseHead = 0xF7AB, - - /// - /// The Font Awesome "hospital" icon unicode character. - /// - Hospital = 0xF0F8, - - /// - /// The Font Awesome "hospital-alt" icon unicode character. - /// - HospitalAlt = 0xF47D, - - /// - /// The Font Awesome "hospital-symbol" icon unicode character. - /// - HospitalSymbol = 0xF47E, - - /// - /// The Font Awesome "hotdog" icon unicode character. - /// - Hotdog = 0xF80F, - - /// - /// The Font Awesome "hotel" icon unicode character. - /// - Hotel = 0xF594, - - /// - /// The Font Awesome "hotjar" icon unicode character. - /// - Hotjar = 0xF3B1, - - /// - /// The Font Awesome "hot-tub" icon unicode character. - /// - HotTub = 0xF593, - - /// - /// The Font Awesome "hourglass" icon unicode character. - /// - Hourglass = 0xF254, - - /// - /// The Font Awesome "hourglass-end" icon unicode character. - /// - HourglassEnd = 0xF253, - - /// - /// The Font Awesome "hourglass-half" icon unicode character. - /// - HourglassHalf = 0xF252, - - /// - /// The Font Awesome "hourglass-start" icon unicode character. - /// - HourglassStart = 0xF251, - - /// - /// The Font Awesome "house-damage" icon unicode character. - /// - HouseDamage = 0xF6F1, - - /// - /// The Font Awesome "houzz" icon unicode character. - /// - Houzz = 0xF27C, - - /// - /// The Font Awesome "hryvnia" icon unicode character. - /// - Hryvnia = 0xF6F2, - - /// - /// The Font Awesome "h-square" icon unicode character. - /// - HSquare = 0xF0FD, - - /// - /// The Font Awesome "html5" icon unicode character. - /// - Html5 = 0xF13B, - - /// - /// The Font Awesome "hubspot" icon unicode character. - /// - Hubspot = 0xF3B2, - - /// - /// The Font Awesome "ice-cream" icon unicode character. - /// - IceCream = 0xF81, - - /// - /// The Font Awesome "icicles" icon unicode character. - /// - Icicles = 0xF7AD, - - /// - /// The Font Awesome "icons" icon unicode character. - /// - Icons = 0xF86D, - - /// - /// The Font Awesome "i-cursor" icon unicode character. - /// - ICursor = 0xF246, - - /// - /// The Font Awesome "id-badge" icon unicode character. - /// - IdBadge = 0xF2C1, - - /// - /// The Font Awesome "id-card" icon unicode character. - /// - IdCard = 0xF2C2, - - /// - /// The Font Awesome "id-card-alt" icon unicode character. - /// - IdCardAlt = 0xF47F, - - /// - /// The Font Awesome "ideal" icon unicode character. - /// - Ideal = 0xF913, - - /// - /// The Font Awesome "igloo" icon unicode character. - /// - Igloo = 0xF7AE, - - /// - /// The Font Awesome "image" icon unicode character. - /// - Image = 0xF03E, - - /// - /// The Font Awesome "images" icon unicode character. - /// - Images = 0xF302, - - /// - /// The Font Awesome "imdb" icon unicode character. - /// - Imdb = 0xF2D8, - - /// - /// The Font Awesome "inbox" icon unicode character. - /// - Inbox = 0xF01C, - - /// - /// The Font Awesome "indent" icon unicode character. - /// - Indent = 0xF03C, - - /// - /// The Font Awesome "industry" icon unicode character. - /// - Industry = 0xF275, - - /// - /// The Font Awesome "infinity" icon unicode character. - /// - Infinity = 0xF534, - - /// - /// The Font Awesome "info" icon unicode character. - /// - Info = 0xF129, - - /// - /// The Font Awesome "info-circle" icon unicode character. - /// - InfoCircle = 0xF05A, - - /// - /// The Font Awesome "instagram" icon unicode character. - /// - Instagram = 0xF16D, - - /// - /// The Font Awesome "instagram-square" icon unicode character. - /// - InstagramSquare = 0xF955, - - /// - /// The Font Awesome "intercom" icon unicode character. - /// - Intercom = 0xF7AF, - - /// - /// The Font Awesome "internet-explorer" icon unicode character. - /// - InternetExplorer = 0xF26B, - - /// - /// The Font Awesome "invision" icon unicode character. - /// - Invision = 0xF7B, - - /// - /// The Font Awesome "ioxhost" icon unicode character. - /// - Ioxhost = 0xF208, - - /// - /// The Font Awesome "italic" icon unicode character. - /// - Italic = 0xF033, - - /// - /// The Font Awesome "itch-io" icon unicode character. - /// - ItchIo = 0xF83A, - - /// - /// The Font Awesome "itunes" icon unicode character. - /// - Itunes = 0xF3B4, - - /// - /// The Font Awesome "itunes-note" icon unicode character. - /// - ItunesNote = 0xF3B5, - - /// - /// The Font Awesome "java" icon unicode character. - /// - Java = 0xF4E4, - - /// - /// The Font Awesome "jedi" icon unicode character. - /// - Jedi = 0xF669, - - /// - /// The Font Awesome "jedi-order" icon unicode character. - /// - JediOrder = 0xF50E, - - /// - /// The Font Awesome "jenkins" icon unicode character. - /// - Jenkins = 0xF3B6, - - /// - /// The Font Awesome "jira" icon unicode character. - /// - Jira = 0xF7B1, - - /// - /// The Font Awesome "joget" icon unicode character. - /// - Joget = 0xF3B7, - - /// - /// The Font Awesome "joint" icon unicode character. - /// - Joint = 0xF595, - - /// - /// The Font Awesome "joomla" icon unicode character. - /// - Joomla = 0xF1AA, - - /// - /// The Font Awesome "journal-whills" icon unicode character. - /// - JournalWhills = 0xF66A, - - /// - /// The Font Awesome "js" icon unicode character. - /// - Js = 0xF3B8, - - /// - /// The Font Awesome "jsfiddle" icon unicode character. - /// - Jsfiddle = 0xF1CC, - - /// - /// The Font Awesome "js-square" icon unicode character. - /// - JsSquare = 0xF3B9, - - /// - /// The Font Awesome "kaaba" icon unicode character. - /// - Kaaba = 0xF66B, - - /// - /// The Font Awesome "kaggle" icon unicode character. - /// - Kaggle = 0xF5FA, - - /// - /// The Font Awesome "key" icon unicode character. - /// - Key = 0xF084, - - /// - /// The Font Awesome "keybase" icon unicode character. - /// - Keybase = 0xF4F5, - - /// - /// The Font Awesome "keyboard" icon unicode character. - /// - Keyboard = 0xF11C, - - /// - /// The Font Awesome "keycdn" icon unicode character. - /// - Keycdn = 0xF3BA, - - /// - /// The Font Awesome "khanda" icon unicode character. - /// - Khanda = 0xF66D, - - /// - /// The Font Awesome "kickstarter" icon unicode character. - /// - Kickstarter = 0xF3BB, - - /// - /// The Font Awesome "kickstarter-k" icon unicode character. - /// - KickstarterK = 0xF3BC, - - /// - /// The Font Awesome "kiss" icon unicode character. - /// - Kiss = 0xF596, - - /// - /// The Font Awesome "kiss-beam" icon unicode character. - /// - KissBeam = 0xF597, - - /// - /// The Font Awesome "kiss-wink-heart" icon unicode character. - /// - KissWinkHeart = 0xF598, - - /// - /// The Font Awesome "kiwi-bird" icon unicode character. - /// - KiwiBird = 0xF535, - - /// - /// The Font Awesome "korvue" icon unicode character. - /// - Korvue = 0xF42F, - - /// - /// The Font Awesome "landmark" icon unicode character. - /// - Landmark = 0xF66F, - - /// - /// The Font Awesome "language" icon unicode character. - /// - Language = 0xF1AB, - - /// - /// The Font Awesome "laptop" icon unicode character. - /// - Laptop = 0xF109, - - /// - /// The Font Awesome "laptop-code" icon unicode character. - /// - LaptopCode = 0xF5FC, - - /// - /// The Font Awesome "laptop-medical" icon unicode character. - /// - LaptopMedical = 0xF812, - - /// - /// The Font Awesome "laravel" icon unicode character. - /// - Laravel = 0xF3BD, - - /// - /// The Font Awesome "lastfm" icon unicode character. - /// - Lastfm = 0xF202, - - /// - /// The Font Awesome "lastfm-square" icon unicode character. - /// - LastfmSquare = 0xF203, - - /// - /// The Font Awesome "laugh" icon unicode character. - /// - Laugh = 0xF599, - - /// - /// The Font Awesome "laugh-beam" icon unicode character. - /// - LaughBeam = 0xF59A, - - /// - /// The Font Awesome "laugh-squint" icon unicode character. - /// - LaughSquint = 0xF59B, - - /// - /// The Font Awesome "laugh-wink" icon unicode character. - /// - LaughWink = 0xF59C, - - /// - /// The Font Awesome "layer-group" icon unicode character. - /// - LayerGroup = 0xF5FD, - - /// - /// The Font Awesome "leaf" icon unicode character. - /// - Leaf = 0xF06C, - - /// - /// The Font Awesome "leanpub" icon unicode character. - /// - Leanpub = 0xF212, - - /// - /// The Font Awesome "lemon" icon unicode character. - /// - Lemon = 0xF094, - - /// - /// The Font Awesome "less" icon unicode character. - /// - Less = 0xF41D, - - /// - /// The Font Awesome "less-than" icon unicode character. - /// - LessThan = 0xF536, - - /// - /// The Font Awesome "less-than-equal" icon unicode character. - /// - LessThanEqual = 0xF537, - - /// - /// The Font Awesome "level-down-alt" icon unicode character. - /// - LevelDownAlt = 0xF3BE, - - /// - /// The Font Awesome "level-up-alt" icon unicode character. - /// - LevelUpAlt = 0xF3BF, - - /// - /// The Font Awesome "life-ring" icon unicode character. - /// - LifeRing = 0xF1CD, - - /// - /// The Font Awesome "lightbulb" icon unicode character. - /// - Lightbulb = 0xF0EB, - - /// - /// The Font Awesome "line" icon unicode character. - /// - Line = 0xF3C, - - /// - /// The Font Awesome "link" icon unicode character. - /// - Link = 0xF0C1, - - /// - /// The Font Awesome "linkedin" icon unicode character. - /// - Linkedin = 0xF08C, - - /// - /// The Font Awesome "linkedin-in" icon unicode character. - /// - LinkedinIn = 0xF0E1, - - /// - /// The Font Awesome "linode" icon unicode character. - /// - Linode = 0xF2B8, - - /// - /// The Font Awesome "linux" icon unicode character. - /// - Linux = 0xF17C, - - /// - /// The Font Awesome "lira-sign" icon unicode character. - /// - LiraSign = 0xF195, - - /// - /// The Font Awesome "list" icon unicode character. - /// - List = 0xF03A, - - /// - /// The Font Awesome "list-alt" icon unicode character. - /// - ListAlt = 0xF022, - - /// - /// The Font Awesome "list-ol" icon unicode character. - /// - ListOl = 0xF0CB, - - /// - /// The Font Awesome "list-ul" icon unicode character. - /// - ListUl = 0xF0CA, - - /// - /// The Font Awesome "location-arrow" icon unicode character. - /// - LocationArrow = 0xF124, - - /// - /// The Font Awesome "lock" icon unicode character. - /// - Lock = 0xF023, - - /// - /// The Font Awesome "lock-open" icon unicode character. - /// - LockOpen = 0xF3C1, - - /// - /// The Font Awesome "long-arrow-alt-down" icon unicode character. - /// - LongArrowAltDown = 0xF309, - - /// - /// The Font Awesome "long-arrow-alt-left" icon unicode character. - /// - LongArrowAltLeft = 0xF30A, - - /// - /// The Font Awesome "long-arrow-alt-right" icon unicode character. - /// - LongArrowAltRight = 0xF30B, - - /// - /// The Font Awesome "long-arrow-alt-up" icon unicode character. - /// - LongArrowAltUp = 0xF30C, - - /// - /// The Font Awesome "low-vision" icon unicode character. - /// - LowVision = 0xF2A8, - - /// - /// The Font Awesome "luggage-cart" icon unicode character. - /// - LuggageCart = 0xF59D, - - /// - /// The Font Awesome "lyft" icon unicode character. - /// - Lyft = 0xF3C3, - - /// - /// The Font Awesome "magento" icon unicode character. - /// - Magento = 0xF3C4, - - /// - /// The Font Awesome "magic" icon unicode character. - /// - Magic = 0xF0D, - - /// - /// The Font Awesome "magnet" icon unicode character. - /// - Magnet = 0xF076, - - /// - /// The Font Awesome "mail-bulk" icon unicode character. - /// - MailBulk = 0xF674, - - /// - /// The Font Awesome "mailchimp" icon unicode character. - /// - Mailchimp = 0xF59E, - - /// - /// The Font Awesome "male" icon unicode character. - /// - Male = 0xF183, - - /// - /// The Font Awesome "mandalorian" icon unicode character. - /// - Mandalorian = 0xF50F, - - /// - /// The Font Awesome "map" icon unicode character. - /// - Map = 0xF279, - - /// - /// The Font Awesome "map-marked" icon unicode character. - /// - MapMarked = 0xF59F, - - /// - /// The Font Awesome "map-marked-alt" icon unicode character. - /// - MapMarkedAlt = 0xF5A, - - /// - /// The Font Awesome "map-marker" icon unicode character. - /// - MapMarker = 0xF041, - - /// - /// The Font Awesome "map-marker-alt" icon unicode character. - /// - MapMarkerAlt = 0xF3C5, - - /// - /// The Font Awesome "map-pin" icon unicode character. - /// - MapPin = 0xF276, - - /// - /// The Font Awesome "map-signs" icon unicode character. - /// - MapSigns = 0xF277, - - /// - /// The Font Awesome "markdown" icon unicode character. - /// - Markdown = 0xF60F, - - /// - /// The Font Awesome "marker" icon unicode character. - /// - Marker = 0xF5A1, - - /// - /// The Font Awesome "mars" icon unicode character. - /// - Mars = 0xF222, - - /// - /// The Font Awesome "mars-double" icon unicode character. - /// - MarsDouble = 0xF227, - - /// - /// The Font Awesome "mars-stroke" icon unicode character. - /// - MarsStroke = 0xF229, - - /// - /// The Font Awesome "mars-stroke-h" icon unicode character. - /// - MarsStrokeH = 0xF22B, - - /// - /// The Font Awesome "mars-stroke-v" icon unicode character. - /// - MarsStrokeV = 0xF22A, - - /// - /// The Font Awesome "mask" icon unicode character. - /// - Mask = 0xF6FA, - - /// - /// The Font Awesome "mastodon" icon unicode character. - /// - Mastodon = 0xF4F6, - - /// - /// The Font Awesome "maxcdn" icon unicode character. - /// - Maxcdn = 0xF136, - - /// - /// The Font Awesome "mdb" icon unicode character. - /// - Mdb = 0xF8CA, - - /// - /// The Font Awesome "medal" icon unicode character. - /// - Medal = 0xF5A2, - - /// - /// The Font Awesome "medapps" icon unicode character. - /// - Medapps = 0xF3C6, - - /// - /// The Font Awesome "medium" icon unicode character. - /// - Medium = 0xF23A, - - /// - /// The Font Awesome "medium-m" icon unicode character. - /// - MediumM = 0xF3C7, - - /// - /// The Font Awesome "medkit" icon unicode character. - /// - Medkit = 0xF0FA, - - /// - /// The Font Awesome "medrt" icon unicode character. - /// - Medrt = 0xF3C8, - - /// - /// The Font Awesome "meetup" icon unicode character. - /// - Meetup = 0xF2E, - - /// - /// The Font Awesome "megaport" icon unicode character. - /// - Megaport = 0xF5A3, - - /// - /// The Font Awesome "meh" icon unicode character. - /// - Meh = 0xF11A, - - /// - /// The Font Awesome "meh-blank" icon unicode character. - /// - MehBlank = 0xF5A4, - - /// - /// The Font Awesome "meh-rolling-eyes" icon unicode character. - /// - MehRollingEyes = 0xF5A5, - - /// - /// The Font Awesome "memory" icon unicode character. - /// - Memory = 0xF538, - - /// - /// The Font Awesome "mendeley" icon unicode character. - /// - Mendeley = 0xF7B3, - - /// - /// The Font Awesome "menorah" icon unicode character. - /// - Menorah = 0xF676, - - /// - /// The Font Awesome "mercury" icon unicode character. - /// - Mercury = 0xF223, - - /// - /// The Font Awesome "meteor" icon unicode character. - /// - Meteor = 0xF753, - - /// - /// The Font Awesome "microblog" icon unicode character. - /// - Microblog = 0xF91A, - - /// - /// The Font Awesome "microchip" icon unicode character. - /// - Microchip = 0xF2DB, - - /// - /// The Font Awesome "microphone" icon unicode character. - /// - Microphone = 0xF13, - - /// - /// The Font Awesome "microphone-alt" icon unicode character. - /// - MicrophoneAlt = 0xF3C9, - - /// - /// The Font Awesome "microphone-alt-slash" icon unicode character. - /// - MicrophoneAltSlash = 0xF539, - - /// - /// The Font Awesome "microphone-slash" icon unicode character. - /// - MicrophoneSlash = 0xF131, - - /// - /// The Font Awesome "microscope" icon unicode character. - /// - Microscope = 0xF61, - - /// - /// The Font Awesome "microsoft" icon unicode character. - /// - Microsoft = 0xF3CA, - - /// - /// The Font Awesome "minus" icon unicode character. - /// - Minus = 0xF068, - - /// - /// The Font Awesome "minus-circle" icon unicode character. - /// - MinusCircle = 0xF056, - - /// - /// The Font Awesome "minus-square" icon unicode character. - /// - MinusSquare = 0xF146, - - /// - /// The Font Awesome "mitten" icon unicode character. - /// - Mitten = 0xF7B5, - - /// - /// The Font Awesome "mix" icon unicode character. - /// - Mix = 0xF3CB, - - /// - /// The Font Awesome "mixcloud" icon unicode character. - /// - Mixcloud = 0xF289, - - /// - /// The Font Awesome "mixer" icon unicode character. - /// - Mixer = 0xF956, - - /// - /// The Font Awesome "mizuni" icon unicode character. - /// - Mizuni = 0xF3CC, - - /// - /// The Font Awesome "mobile" icon unicode character. - /// - Mobile = 0xF10B, - - /// - /// The Font Awesome "mobile-alt" icon unicode character. - /// - MobileAlt = 0xF3CD, - - /// - /// The Font Awesome "modx" icon unicode character. - /// - Modx = 0xF285, - - /// - /// The Font Awesome "monero" icon unicode character. - /// - Monero = 0xF3D, - - /// - /// The Font Awesome "money-bill" icon unicode character. - /// - MoneyBill = 0xF0D6, - - /// - /// The Font Awesome "money-bill-alt" icon unicode character. - /// - MoneyBillAlt = 0xF3D1, - - /// - /// The Font Awesome "money-bill-wave" icon unicode character. - /// - MoneyBillWave = 0xF53A, - - /// - /// The Font Awesome "money-bill-wave-alt" icon unicode character. - /// - MoneyBillWaveAlt = 0xF53B, - - /// - /// The Font Awesome "money-check" icon unicode character. - /// - MoneyCheck = 0xF53C, - - /// - /// The Font Awesome "money-check-alt" icon unicode character. - /// - MoneyCheckAlt = 0xF53D, - - /// - /// The Font Awesome "monument" icon unicode character. - /// - Monument = 0xF5A6, - - /// - /// The Font Awesome "moon" icon unicode character. - /// - Moon = 0xF186, - - /// - /// The Font Awesome "mortar-pestle" icon unicode character. - /// - MortarPestle = 0xF5A7, - - /// - /// The Font Awesome "mosque" icon unicode character. - /// - Mosque = 0xF678, - - /// - /// The Font Awesome "motorcycle" icon unicode character. - /// - Motorcycle = 0xF21C, - - /// - /// The Font Awesome "mountain" icon unicode character. - /// - Mountain = 0xF6FC, - - /// - /// The Font Awesome "mouse" icon unicode character. - /// - Mouse = 0xF8CC, - - /// - /// The Font Awesome "mouse-pointer" icon unicode character. - /// - MousePointer = 0xF245, - - /// - /// The Font Awesome "mug-hot" icon unicode character. - /// - MugHot = 0xF7B6, - - /// - /// The Font Awesome "music" icon unicode character. - /// - Music = 0xF001, - - /// - /// The Font Awesome "napster" icon unicode character. - /// - Napster = 0xF3D2, - - /// - /// The Font Awesome "neos" icon unicode character. - /// - Neos = 0xF612, - - /// - /// The Font Awesome "network-wired" icon unicode character. - /// - NetworkWired = 0xF6FF, - - /// - /// The Font Awesome "neuter" icon unicode character. - /// - Neuter = 0xF22C, - - /// - /// The Font Awesome "newspaper" icon unicode character. - /// - Newspaper = 0xF1EA, - - /// - /// The Font Awesome "nimblr" icon unicode character. - /// - Nimblr = 0xF5A8, - - /// - /// The Font Awesome "node" icon unicode character. - /// - Node = 0xF419, - - /// - /// The Font Awesome "node-js" icon unicode character. - /// - NodeJs = 0xF3D3, - - /// - /// The Font Awesome "not-equal" icon unicode character. - /// - NotEqual = 0xF53E, - - /// - /// The Font Awesome "notes-medical" icon unicode character. - /// - NotesMedical = 0xF481, - - /// - /// The Font Awesome "npm" icon unicode character. - /// - Npm = 0xF3D4, - - /// - /// The Font Awesome "ns8" icon unicode character. - /// - Ns8 = 0xF3D5, - - /// - /// The Font Awesome "nutritionix" icon unicode character. - /// - Nutritionix = 0xF3D6, - - /// - /// The Font Awesome "object-group" icon unicode character. - /// - ObjectGroup = 0xF247, - - /// - /// The Font Awesome "object-ungroup" icon unicode character. - /// - ObjectUngroup = 0xF248, - - /// - /// The Font Awesome "odnoklassniki" icon unicode character. - /// - Odnoklassniki = 0xF263, - - /// - /// The Font Awesome "odnoklassniki-square" icon unicode character. - /// - OdnoklassnikiSquare = 0xF264, - - /// - /// The Font Awesome "oil-can" icon unicode character. - /// - OilCan = 0xF613, - - /// - /// The Font Awesome "old-republic" icon unicode character. - /// - OldRepublic = 0xF51, - - /// - /// The Font Awesome "om" icon unicode character. - /// - Om = 0xF679, - - /// - /// The Font Awesome "opencart" icon unicode character. - /// - Opencart = 0xF23D, - - /// - /// The Font Awesome "openid" icon unicode character. - /// - Openid = 0xF19B, - - /// - /// The Font Awesome "opera" icon unicode character. - /// - Opera = 0xF26A, - - /// - /// The Font Awesome "optin-monster" icon unicode character. - /// - OptinMonster = 0xF23C, - - /// - /// The Font Awesome "orcid" icon unicode character. - /// - Orcid = 0xF8D2, - - /// - /// The Font Awesome "osi" icon unicode character. - /// - Osi = 0xF41A, - - /// - /// The Font Awesome "otter" icon unicode character. - /// - Otter = 0xF7, - - /// - /// The Font Awesome "outdent" icon unicode character. - /// - Outdent = 0xF03B, - - /// - /// The Font Awesome "page4" icon unicode character. - /// - Page4 = 0xF3D7, - - /// - /// The Font Awesome "pagelines" icon unicode character. - /// - Pagelines = 0xF18C, - - /// - /// The Font Awesome "pager" icon unicode character. - /// - Pager = 0xF815, - - /// - /// The Font Awesome "paint-brush" icon unicode character. - /// - PaintBrush = 0xF1FC, - - /// - /// The Font Awesome "paint-roller" icon unicode character. - /// - PaintRoller = 0xF5AA, - - /// - /// The Font Awesome "palette" icon unicode character. - /// - Palette = 0xF53F, - - /// - /// The Font Awesome "palfed" icon unicode character. - /// - Palfed = 0xF3D8, - - /// - /// The Font Awesome "pallet" icon unicode character. - /// - Pallet = 0xF482, - - /// - /// The Font Awesome "paperclip" icon unicode character. - /// - Paperclip = 0xF0C6, - - /// - /// The Font Awesome "paper-plane" icon unicode character. - /// - PaperPlane = 0xF1D8, - - /// - /// The Font Awesome "parachute-box" icon unicode character. - /// - ParachuteBox = 0xF4CD, - - /// - /// The Font Awesome "paragraph" icon unicode character. - /// - Paragraph = 0xF1DD, - - /// - /// The Font Awesome "parking" icon unicode character. - /// - Parking = 0xF54, - - /// - /// The Font Awesome "passport" icon unicode character. - /// - Passport = 0xF5AB, - - /// - /// The Font Awesome "pastafarianism" icon unicode character. - /// - Pastafarianism = 0xF67B, - - /// - /// The Font Awesome "paste" icon unicode character. - /// - Paste = 0xF0EA, - - /// - /// The Font Awesome "patreon" icon unicode character. - /// - Patreon = 0xF3D9, - - /// - /// The Font Awesome "pause" icon unicode character. - /// - Pause = 0xF04C, - - /// - /// The Font Awesome "pause-circle" icon unicode character. - /// - PauseCircle = 0xF28B, - - /// - /// The Font Awesome "paw" icon unicode character. - /// - Paw = 0xF1B, - - /// - /// The Font Awesome "paypal" icon unicode character. - /// - Paypal = 0xF1ED, - - /// - /// The Font Awesome "peace" icon unicode character. - /// - Peace = 0xF67C, - - /// - /// The Font Awesome "pen" icon unicode character. - /// - Pen = 0xF304, - - /// - /// The Font Awesome "pen-alt" icon unicode character. - /// - PenAlt = 0xF305, - - /// - /// The Font Awesome "pencil-alt" icon unicode character. - /// - PencilAlt = 0xF303, - - /// - /// The Font Awesome "pencil-ruler" icon unicode character. - /// - PencilRuler = 0xF5AE, - - /// - /// The Font Awesome "pen-fancy" icon unicode character. - /// - PenFancy = 0xF5AC, - - /// - /// The Font Awesome "pen-nib" icon unicode character. - /// - PenNib = 0xF5AD, - - /// - /// The Font Awesome "penny-arcade" icon unicode character. - /// - PennyArcade = 0xF704, - - /// - /// The Font Awesome "pen-square" icon unicode character. - /// - PenSquare = 0xF14B, - - /// - /// The Font Awesome "people-carry" icon unicode character. - /// - PeopleCarry = 0xF4CE, - - /// - /// The Font Awesome "pepper-hot" icon unicode character. - /// - PepperHot = 0xF816, - - /// - /// The Font Awesome "percent" icon unicode character. - /// - Percent = 0xF295, - - /// - /// The Font Awesome "percentage" icon unicode character. - /// - Percentage = 0xF541, - - /// - /// The Font Awesome "periscope" icon unicode character. - /// - Periscope = 0xF3DA, - - /// - /// The Font Awesome "person-booth" icon unicode character. - /// - PersonBooth = 0xF756, - - /// - /// The Font Awesome "phabricator" icon unicode character. - /// - Phabricator = 0xF3DB, - - /// - /// The Font Awesome "phoenix-framework" icon unicode character. - /// - PhoenixFramework = 0xF3DC, - - /// - /// The Font Awesome "phoenix-squadron" icon unicode character. - /// - PhoenixSquadron = 0xF511, - - /// - /// The Font Awesome "phone" icon unicode character. - /// - Phone = 0xF095, - - /// - /// The Font Awesome "phone-alt" icon unicode character. - /// - PhoneAlt = 0xF879, - - /// - /// The Font Awesome "phone-slash" icon unicode character. - /// - PhoneSlash = 0xF3DD, - - /// - /// The Font Awesome "phone-square" icon unicode character. - /// - PhoneSquare = 0xF098, - - /// - /// The Font Awesome "phone-square-alt" icon unicode character. - /// - PhoneSquareAlt = 0xF87B, - - /// - /// The Font Awesome "phone-volume" icon unicode character. - /// - PhoneVolume = 0xF2A, - - /// - /// The Font Awesome "photo-video" icon unicode character. - /// - PhotoVideo = 0xF87C, - - /// - /// The Font Awesome "php" icon unicode character. - /// - Php = 0xF457, - - /// - /// The Font Awesome "pied-piper" icon unicode character. - /// - PiedPiper = 0xF2AE, - - /// - /// The Font Awesome "pied-piper-alt" icon unicode character. - /// - PiedPiperAlt = 0xF1A8, - - /// - /// The Font Awesome "pied-piper-hat" icon unicode character. - /// - PiedPiperHat = 0xF4E5, - - /// - /// The Font Awesome "pied-piper-pp" icon unicode character. - /// - PiedPiperPp = 0xF1A7, - - /// - /// The Font Awesome "pied-piper-square" icon unicode character. - /// - PiedPiperSquare = 0xF91E, - - /// - /// The Font Awesome "piggy-bank" icon unicode character. - /// - PiggyBank = 0xF4D3, - - /// - /// The Font Awesome "pills" icon unicode character. - /// - Pills = 0xF484, - - /// - /// The Font Awesome "pinterest" icon unicode character. - /// - Pinterest = 0xF0D2, - - /// - /// The Font Awesome "pinterest-p" icon unicode character. - /// - PinterestP = 0xF231, - - /// - /// The Font Awesome "pinterest-square" icon unicode character. - /// - PinterestSquare = 0xF0D3, - - /// - /// The Font Awesome "pizza-slice" icon unicode character. - /// - PizzaSlice = 0xF818, - - /// - /// The Font Awesome "place-of-worship" icon unicode character. - /// - PlaceOfWorship = 0xF67F, - - /// - /// The Font Awesome "plane" icon unicode character. - /// - Plane = 0xF072, - - /// - /// The Font Awesome "plane-arrival" icon unicode character. - /// - PlaneArrival = 0xF5AF, - - /// - /// The Font Awesome "plane-departure" icon unicode character. - /// - PlaneDeparture = 0xF5B, - - /// - /// The Font Awesome "play" icon unicode character. - /// - Play = 0xF04B, - - /// - /// The Font Awesome "play-circle" icon unicode character. - /// - PlayCircle = 0xF144, - - /// - /// The Font Awesome "playstation" icon unicode character. - /// - Playstation = 0xF3DF, - - /// - /// The Font Awesome "plug" icon unicode character. - /// - Plug = 0xF1E6, - - /// - /// The Font Awesome "plus" icon unicode character. - /// - Plus = 0xF067, - - /// - /// The Font Awesome "plus-circle" icon unicode character. - /// - PlusCircle = 0xF055, - - /// - /// The Font Awesome "plus-square" icon unicode character. - /// - PlusSquare = 0xF0FE, - - /// - /// The Font Awesome "podcast" icon unicode character. - /// - Podcast = 0xF2CE, - - /// - /// The Font Awesome "poll" icon unicode character. - /// - Poll = 0xF681, - - /// - /// The Font Awesome "poll-h" icon unicode character. - /// - PollH = 0xF682, - - /// - /// The Font Awesome "poo" icon unicode character. - /// - Poo = 0xF2FE, - - /// - /// The Font Awesome "poop" icon unicode character. - /// - Poop = 0xF619, - - /// - /// The Font Awesome "poo-storm" icon unicode character. - /// - PooStorm = 0xF75A, - - /// - /// The Font Awesome "portrait" icon unicode character. - /// - Portrait = 0xF3E, - - /// - /// The Font Awesome "pound-sign" icon unicode character. - /// - PoundSign = 0xF154, - - /// - /// The Font Awesome "power-off" icon unicode character. - /// - PowerOff = 0xF011, - - /// - /// The Font Awesome "pray" icon unicode character. - /// - Pray = 0xF683, - - /// - /// The Font Awesome "praying-hands" icon unicode character. - /// - PrayingHands = 0xF684, - - /// - /// The Font Awesome "prescription" icon unicode character. - /// - Prescription = 0xF5B1, - - /// - /// The Font Awesome "prescription-bottle" icon unicode character. - /// - PrescriptionBottle = 0xF485, - - /// - /// The Font Awesome "prescription-bottle-alt" icon unicode character. - /// - PrescriptionBottleAlt = 0xF486, - - /// - /// The Font Awesome "print" icon unicode character. - /// - Print = 0xF02F, - - /// - /// The Font Awesome "procedures" icon unicode character. - /// - Procedures = 0xF487, - - /// - /// The Font Awesome "product-hunt" icon unicode character. - /// - ProductHunt = 0xF288, - - /// - /// The Font Awesome "project-diagram" icon unicode character. - /// - ProjectDiagram = 0xF542, - - /// - /// The Font Awesome "pushed" icon unicode character. - /// - Pushed = 0xF3E1, - - /// - /// The Font Awesome "puzzle-piece" icon unicode character. - /// - PuzzlePiece = 0xF12E, - - /// - /// The Font Awesome "python" icon unicode character. - /// - Python = 0xF3E2, - - /// - /// The Font Awesome "qq" icon unicode character. - /// - Qq = 0xF1D6, - - /// - /// The Font Awesome "qrcode" icon unicode character. - /// - Qrcode = 0xF029, - - /// - /// The Font Awesome "question" icon unicode character. - /// - Question = 0xF128, - - /// - /// The Font Awesome "question-circle" icon unicode character. - /// - QuestionCircle = 0xF059, - - /// - /// The Font Awesome "quidditch" icon unicode character. - /// - Quidditch = 0xF458, - - /// - /// The Font Awesome "quinscape" icon unicode character. - /// - Quinscape = 0xF459, - - /// - /// The Font Awesome "quora" icon unicode character. - /// - Quora = 0xF2C4, - - /// - /// The Font Awesome "quote-left" icon unicode character. - /// - QuoteLeft = 0xF10D, - - /// - /// The Font Awesome "quote-right" icon unicode character. - /// - QuoteRight = 0xF10E, - - /// - /// The Font Awesome "quran" icon unicode character. - /// - Quran = 0xF687, - - /// - /// The Font Awesome "radiation" icon unicode character. - /// - Radiation = 0xF7B9, - - /// - /// The Font Awesome "radiation-alt" icon unicode character. - /// - RadiationAlt = 0xF7BA, - - /// - /// The Font Awesome "rainbow" icon unicode character. - /// - Rainbow = 0xF75B, - - /// - /// The Font Awesome "random" icon unicode character. - /// - Random = 0xF074, - - /// - /// The Font Awesome "raspberry-pi" icon unicode character. - /// - RaspberryPi = 0xF7BB, - - /// - /// The Font Awesome "ravelry" icon unicode character. - /// - Ravelry = 0xF2D9, - - /// - /// The Font Awesome "react" icon unicode character. - /// - React = 0xF41B, - - /// - /// The Font Awesome "reacteurope" icon unicode character. - /// - Reacteurope = 0xF75D, - - /// - /// The Font Awesome "readme" icon unicode character. - /// - Readme = 0xF4D5, - - /// - /// The Font Awesome "rebel" icon unicode character. - /// - Rebel = 0xF1D, - - /// - /// The Font Awesome "receipt" icon unicode character. - /// - Receipt = 0xF543, - - /// - /// The Font Awesome "record-vinyl" icon unicode character. - /// - RecordVinyl = 0xF8D9, - - /// - /// The Font Awesome "recycle" icon unicode character. - /// - Recycle = 0xF1B8, - - /// - /// The Font Awesome "reddit" icon unicode character. - /// - Reddit = 0xF1A1, - - /// - /// The Font Awesome "reddit-alien" icon unicode character. - /// - RedditAlien = 0xF281, - - /// - /// The Font Awesome "reddit-square" icon unicode character. - /// - RedditSquare = 0xF1A2, - - /// - /// The Font Awesome "redhat" icon unicode character. - /// - Redhat = 0xF7BC, - - /// - /// The Font Awesome "redo" icon unicode character. - /// - Redo = 0xF01E, - - /// - /// The Font Awesome "redo-alt" icon unicode character. - /// - RedoAlt = 0xF2F9, - - /// - /// The Font Awesome "red-river" icon unicode character. - /// - RedRiver = 0xF3E3, - - /// - /// The Font Awesome "registered" icon unicode character. - /// - Registered = 0xF25D, - - /// - /// The Font Awesome "remove-format" icon unicode character. - /// - RemoveFormat = 0xF87D, - - /// - /// The Font Awesome "renren" icon unicode character. - /// - Renren = 0xF18B, - - /// - /// The Font Awesome "reply" icon unicode character. - /// - Reply = 0xF3E5, - - /// - /// The Font Awesome "reply-all" icon unicode character. - /// - ReplyAll = 0xF122, - - /// - /// The Font Awesome "replyd" icon unicode character. - /// - Replyd = 0xF3E6, - - /// - /// The Font Awesome "republican" icon unicode character. - /// - Republican = 0xF75E, - - /// - /// The Font Awesome "researchgate" icon unicode character. - /// - Researchgate = 0xF4F8, - - /// - /// The Font Awesome "resolving" icon unicode character. - /// - Resolving = 0xF3E7, - - /// - /// The Font Awesome "restroom" icon unicode character. - /// - Restroom = 0xF7BD, - - /// - /// The Font Awesome "retweet" icon unicode character. - /// - Retweet = 0xF079, - - /// - /// The Font Awesome "rev" icon unicode character. - /// - Rev = 0xF5B2, - - /// - /// The Font Awesome "ribbon" icon unicode character. - /// - Ribbon = 0xF4D6, - - /// - /// The Font Awesome "ring" icon unicode character. - /// - Ring = 0xF70B, - - /// - /// The Font Awesome "road" icon unicode character. - /// - Road = 0xF018, - - /// - /// The Font Awesome "robot" icon unicode character. - /// - Robot = 0xF544, - - /// - /// The Font Awesome "rocket" icon unicode character. - /// - Rocket = 0xF135, - - /// - /// The Font Awesome "rocketchat" icon unicode character. - /// - Rocketchat = 0xF3E8, - - /// - /// The Font Awesome "rockrms" icon unicode character. - /// - Rockrms = 0xF3E9, - - /// - /// The Font Awesome "route" icon unicode character. - /// - Route = 0xF4D7, - - /// - /// The Font Awesome "r-project" icon unicode character. - /// - RProject = 0xF4F7, - - /// - /// The Font Awesome "rss" icon unicode character. - /// - Rss = 0xF09E, - - /// - /// The Font Awesome "rss-square" icon unicode character. - /// - RssSquare = 0xF143, - - /// - /// The Font Awesome "ruble-sign" icon unicode character. - /// - RubleSign = 0xF158, - - /// - /// The Font Awesome "ruler" icon unicode character. - /// - Ruler = 0xF545, - - /// - /// The Font Awesome "ruler-combined" icon unicode character. - /// - RulerCombined = 0xF546, - - /// - /// The Font Awesome "ruler-horizontal" icon unicode character. - /// - RulerHorizontal = 0xF547, - - /// - /// The Font Awesome "ruler-vertical" icon unicode character. - /// - RulerVertical = 0xF548, - - /// - /// The Font Awesome "running" icon unicode character. - /// - Running = 0xF70C, - - /// - /// The Font Awesome "rupee-sign" icon unicode character. - /// - RupeeSign = 0xF156, - - /// - /// The Font Awesome "sad-cry" icon unicode character. - /// - SadCry = 0xF5B3, - - /// - /// The Font Awesome "sad-tear" icon unicode character. - /// - SadTear = 0xF5B4, - - /// - /// The Font Awesome "safari" icon unicode character. - /// - Safari = 0xF267, - - /// - /// The Font Awesome "salesforce" icon unicode character. - /// - Salesforce = 0xF83B, - - /// - /// The Font Awesome "sass" icon unicode character. - /// - Sass = 0xF41E, - - /// - /// The Font Awesome "satellite" icon unicode character. - /// - Satellite = 0xF7BF, - - /// - /// The Font Awesome "satellite-dish" icon unicode character. - /// - SatelliteDish = 0xF7C, - - /// - /// The Font Awesome "save" icon unicode character. - /// - Save = 0xF0C7, - - /// - /// The Font Awesome "schlix" icon unicode character. - /// - Schlix = 0xF3EA, - - /// - /// The Font Awesome "school" icon unicode character. - /// - School = 0xF549, - - /// - /// The Font Awesome "screwdriver" icon unicode character. - /// - Screwdriver = 0xF54A, - - /// - /// The Font Awesome "scribd" icon unicode character. - /// - Scribd = 0xF28A, - - /// - /// The Font Awesome "scroll" icon unicode character. - /// - Scroll = 0xF70E, - - /// - /// The Font Awesome "sd-card" icon unicode character. - /// - SdCard = 0xF7C2, - - /// - /// The Font Awesome "search" icon unicode character. - /// - Search = 0xF002, - - /// - /// The Font Awesome "search-dollar" icon unicode character. - /// - SearchDollar = 0xF688, - - /// - /// The Font Awesome "searchengin" icon unicode character. - /// - Searchengin = 0xF3EB, - - /// - /// The Font Awesome "search-location" icon unicode character. - /// - SearchLocation = 0xF689, - - /// - /// The Font Awesome "search-minus" icon unicode character. - /// - SearchMinus = 0xF01, - - /// - /// The Font Awesome "search-plus" icon unicode character. - /// - SearchPlus = 0xF00E, - - /// - /// The Font Awesome "seedling" icon unicode character. - /// - Seedling = 0xF4D8, - - /// - /// The Font Awesome "sellcast" icon unicode character. - /// - Sellcast = 0xF2DA, - - /// - /// The Font Awesome "sellsy" icon unicode character. - /// - Sellsy = 0xF213, - - /// - /// The Font Awesome "server" icon unicode character. - /// - Server = 0xF233, - - /// - /// The Font Awesome "servicestack" icon unicode character. - /// - Servicestack = 0xF3EC, - - /// - /// The Font Awesome "shapes" icon unicode character. - /// - Shapes = 0xF61F, - - /// - /// The Font Awesome "share" icon unicode character. - /// - Share = 0xF064, - - /// - /// The Font Awesome "share-alt" icon unicode character. - /// - ShareAlt = 0xF1E, - - /// - /// The Font Awesome "share-alt-square" icon unicode character. - /// - ShareAltSquare = 0xF1E1, - - /// - /// The Font Awesome "share-square" icon unicode character. - /// - ShareSquare = 0xF14D, - - /// - /// The Font Awesome "shekel-sign" icon unicode character. - /// - ShekelSign = 0xF20B, - - /// - /// The Font Awesome "shield-alt" icon unicode character. - /// - ShieldAlt = 0xF3ED, - - /// - /// The Font Awesome "ship" icon unicode character. - /// - Ship = 0xF21A, - - /// - /// The Font Awesome "shipping-fast" icon unicode character. - /// - ShippingFast = 0xF48B, - - /// - /// The Font Awesome "shirtsinbulk" icon unicode character. - /// - Shirtsinbulk = 0xF214, - - /// - /// The Font Awesome "shoe-prints" icon unicode character. - /// - ShoePrints = 0xF54B, - - /// - /// The Font Awesome "shopify" icon unicode character. - /// - Shopify = 0xF957, - - /// - /// The Font Awesome "shopping-bag" icon unicode character. - /// - ShoppingBag = 0xF29, - - /// - /// The Font Awesome "shopping-basket" icon unicode character. - /// - ShoppingBasket = 0xF291, - - /// - /// The Font Awesome "shopping-cart" icon unicode character. - /// - ShoppingCart = 0xF07A, - - /// - /// The Font Awesome "shopware" icon unicode character. - /// - Shopware = 0xF5B5, - - /// - /// The Font Awesome "shower" icon unicode character. - /// - Shower = 0xF2CC, - - /// - /// The Font Awesome "shuttle-van" icon unicode character. - /// - ShuttleVan = 0xF5B6, - - /// - /// The Font Awesome "sign" icon unicode character. - /// - Sign = 0xF4D9, - - /// - /// The Font Awesome "signal" icon unicode character. - /// - Signal = 0xF012, - - /// - /// The Font Awesome "signature" icon unicode character. - /// - Signature = 0xF5B7, - - /// - /// The Font Awesome "sign-in-alt" icon unicode character. - /// - SignInAlt = 0xF2F6, - - /// - /// The Font Awesome "sign-language" icon unicode character. - /// - SignLanguage = 0xF2A7, - - /// - /// The Font Awesome "sign-out-alt" icon unicode character. - /// - SignOutAlt = 0xF2F5, - - /// - /// The Font Awesome "sim-card" icon unicode character. - /// - SimCard = 0xF7C4, - - /// - /// The Font Awesome "simplybuilt" icon unicode character. - /// - Simplybuilt = 0xF215, - - /// - /// The Font Awesome "sistrix" icon unicode character. - /// - Sistrix = 0xF3EE, - - /// - /// The Font Awesome "sitemap" icon unicode character. - /// - Sitemap = 0xF0E8, - - /// - /// The Font Awesome "sith" icon unicode character. - /// - Sith = 0xF512, - - /// - /// The Font Awesome "skating" icon unicode character. - /// - Skating = 0xF7C5, - - /// - /// The Font Awesome "sketch" icon unicode character. - /// - Sketch = 0xF7C6, - - /// - /// The Font Awesome "skiing" icon unicode character. - /// - Skiing = 0xF7C9, - - /// - /// The Font Awesome "skiing-nordic" icon unicode character. - /// - SkiingNordic = 0xF7CA, - - /// - /// The Font Awesome "skull" icon unicode character. - /// - Skull = 0xF54C, - - /// - /// The Font Awesome "skull-crossbones" icon unicode character. - /// - SkullCrossbones = 0xF714, - - /// - /// The Font Awesome "skyatlas" icon unicode character. - /// - Skyatlas = 0xF216, - - /// - /// The Font Awesome "skype" icon unicode character. - /// - Skype = 0xF17E, - - /// - /// The Font Awesome "slack" icon unicode character. - /// - Slack = 0xF198, - - /// - /// The Font Awesome "slack-hash" icon unicode character. - /// - SlackHash = 0xF3EF, - - /// - /// The Font Awesome "slash" icon unicode character. - /// - Slash = 0xF715, - - /// - /// The Font Awesome "sleigh" icon unicode character. - /// - Sleigh = 0xF7CC, - - /// - /// The Font Awesome "sliders-h" icon unicode character. - /// - SlidersH = 0xF1DE, - - /// - /// The Font Awesome "slideshare" icon unicode character. - /// - Slideshare = 0xF1E7, - - /// - /// The Font Awesome "smile" icon unicode character. - /// - Smile = 0xF118, - - /// - /// The Font Awesome "smile-beam" icon unicode character. - /// - SmileBeam = 0xF5B8, - - /// - /// The Font Awesome "smile-wink" icon unicode character. - /// - SmileWink = 0xF4DA, - - /// - /// The Font Awesome "smog" icon unicode character. - /// - Smog = 0xF75F, - - /// - /// The Font Awesome "smoking" icon unicode character. - /// - Smoking = 0xF48D, - - /// - /// The Font Awesome "smoking-ban" icon unicode character. - /// - SmokingBan = 0xF54D, - - /// - /// The Font Awesome "sms" icon unicode character. - /// - Sms = 0xF7CD, - - /// - /// The Font Awesome "snapchat" icon unicode character. - /// - Snapchat = 0xF2AB, - - /// - /// The Font Awesome "snapchat-ghost" icon unicode character. - /// - SnapchatGhost = 0xF2AC, - - /// - /// The Font Awesome "snapchat-square" icon unicode character. - /// - SnapchatSquare = 0xF2AD, - - /// - /// The Font Awesome "snowboarding" icon unicode character. - /// - Snowboarding = 0xF7CE, - - /// - /// The Font Awesome "snowflake" icon unicode character. - /// - Snowflake = 0xF2DC, - - /// - /// The Font Awesome "snowman" icon unicode character. - /// - Snowman = 0xF7D, - - /// - /// The Font Awesome "snowplow" icon unicode character. - /// - Snowplow = 0xF7D2, - - /// - /// The Font Awesome "socks" icon unicode character. - /// - Socks = 0xF696, - - /// - /// The Font Awesome "solar-panel" icon unicode character. - /// - SolarPanel = 0xF5BA, - - /// - /// The Font Awesome "sort" icon unicode character. - /// - Sort = 0xF0DC, - - /// - /// The Font Awesome "sort-alpha-down" icon unicode character. - /// - SortAlphaDown = 0xF15D, - - /// - /// The Font Awesome "sort-alpha-down-alt" icon unicode character. - /// - SortAlphaDownAlt = 0xF881, - - /// - /// The Font Awesome "sort-alpha-up" icon unicode character. - /// - SortAlphaUp = 0xF15E, - - /// - /// The Font Awesome "sort-alpha-up-alt" icon unicode character. - /// - SortAlphaUpAlt = 0xF882, - - /// - /// The Font Awesome "sort-amount-down" icon unicode character. - /// - SortAmountDown = 0xF16, - - /// - /// The Font Awesome "sort-amount-down-alt" icon unicode character. - /// - SortAmountDownAlt = 0xF884, - - /// - /// The Font Awesome "sort-amount-up" icon unicode character. - /// - SortAmountUp = 0xF161, - - /// - /// The Font Awesome "sort-amount-up-alt" icon unicode character. - /// - SortAmountUpAlt = 0xF885, - - /// - /// The Font Awesome "sort-down" icon unicode character. - /// - SortDown = 0xF0DD, - - /// - /// The Font Awesome "sort-numeric-down" icon unicode character. - /// - SortNumericDown = 0xF162, - - /// - /// The Font Awesome "sort-numeric-down-alt" icon unicode character. - /// - SortNumericDownAlt = 0xF886, - - /// - /// The Font Awesome "sort-numeric-up" icon unicode character. - /// - SortNumericUp = 0xF163, - - /// - /// The Font Awesome "sort-numeric-up-alt" icon unicode character. - /// - SortNumericUpAlt = 0xF887, - - /// - /// The Font Awesome "sort-up" icon unicode character. - /// - SortUp = 0xF0DE, - - /// - /// The Font Awesome "soundcloud" icon unicode character. - /// - Soundcloud = 0xF1BE, - - /// - /// The Font Awesome "sourcetree" icon unicode character. - /// - Sourcetree = 0xF7D3, - - /// - /// The Font Awesome "spa" icon unicode character. - /// - Spa = 0xF5BB, - - /// - /// The Font Awesome "space-shuttle" icon unicode character. - /// - SpaceShuttle = 0xF197, - - /// - /// The Font Awesome "speakap" icon unicode character. - /// - Speakap = 0xF3F3, - - /// - /// The Font Awesome "speaker-deck" icon unicode character. - /// - SpeakerDeck = 0xF83C, - - /// - /// The Font Awesome "spell-check" icon unicode character. - /// - SpellCheck = 0xF891, - - /// - /// The Font Awesome "spider" icon unicode character. - /// - Spider = 0xF717, - - /// - /// The Font Awesome "spinner" icon unicode character. - /// - Spinner = 0xF11, - - /// - /// The Font Awesome "splotch" icon unicode character. - /// - Splotch = 0xF5BC, - - /// - /// The Font Awesome "spotify" icon unicode character. - /// - Spotify = 0xF1BC, - - /// - /// The Font Awesome "spray-can" icon unicode character. - /// - SprayCan = 0xF5BD, - - /// - /// The Font Awesome "square" icon unicode character. - /// - Square = 0xF0C8, - - /// - /// The Font Awesome "square-full" icon unicode character. - /// - SquareFull = 0xF45C, - - /// - /// The Font Awesome "square-root-alt" icon unicode character. - /// - SquareRootAlt = 0xF698, - - /// - /// The Font Awesome "squarespace" icon unicode character. - /// - Squarespace = 0xF5BE, - - /// - /// The Font Awesome "stack-exchange" icon unicode character. - /// - StackExchange = 0xF18D, - - /// - /// The Font Awesome "stack-overflow" icon unicode character. - /// - StackOverflow = 0xF16C, - - /// - /// The Font Awesome "stackpath" icon unicode character. - /// - Stackpath = 0xF842, - - /// - /// The Font Awesome "stamp" icon unicode character. - /// - Stamp = 0xF5BF, - - /// - /// The Font Awesome "star" icon unicode character. - /// - Star = 0xF005, - - /// - /// The Font Awesome "star-and-crescent" icon unicode character. - /// - StarAndCrescent = 0xF699, - - /// - /// The Font Awesome "star-half" icon unicode character. - /// - StarHalf = 0xF089, - - /// - /// The Font Awesome "star-half-alt" icon unicode character. - /// - StarHalfAlt = 0xF5C, - - /// - /// The Font Awesome "star-of-david" icon unicode character. - /// - StarOfDavid = 0xF69A, - - /// - /// The Font Awesome "star-of-life" icon unicode character. - /// - StarOfLife = 0xF621, - - /// - /// The Font Awesome "staylinked" icon unicode character. - /// - Staylinked = 0xF3F5, - - /// - /// The Font Awesome "steam" icon unicode character. - /// - Steam = 0xF1B6, - - /// - /// The Font Awesome "steam-square" icon unicode character. - /// - SteamSquare = 0xF1B7, - - /// - /// The Font Awesome "steam-symbol" icon unicode character. - /// - SteamSymbol = 0xF3F6, - - /// - /// The Font Awesome "step-backward" icon unicode character. - /// - StepBackward = 0xF048, - - /// - /// The Font Awesome "step-forward" icon unicode character. - /// - StepForward = 0xF051, - - /// - /// The Font Awesome "stethoscope" icon unicode character. - /// - Stethoscope = 0xF0F1, - - /// - /// The Font Awesome "sticker-mule" icon unicode character. - /// - StickerMule = 0xF3F7, - - /// - /// The Font Awesome "sticky-note" icon unicode character. - /// - StickyNote = 0xF249, - - /// - /// The Font Awesome "stop" icon unicode character. - /// - Stop = 0xF04D, - - /// - /// The Font Awesome "stop-circle" icon unicode character. - /// - StopCircle = 0xF28D, - - /// - /// The Font Awesome "stopwatch" icon unicode character. - /// - Stopwatch = 0xF2F2, - - /// - /// The Font Awesome "store" icon unicode character. - /// - Store = 0xF54E, - - /// - /// The Font Awesome "store-alt" icon unicode character. - /// - StoreAlt = 0xF54F, - - /// - /// The Font Awesome "strava" icon unicode character. - /// - Strava = 0xF428, - - /// - /// The Font Awesome "stream" icon unicode character. - /// - Stream = 0xF55, - - /// - /// The Font Awesome "street-view" icon unicode character. - /// - StreetView = 0xF21D, - - /// - /// The Font Awesome "strikethrough" icon unicode character. - /// - Strikethrough = 0xF0CC, - - /// - /// The Font Awesome "stripe" icon unicode character. - /// - Stripe = 0xF429, - - /// - /// The Font Awesome "stripe-s" icon unicode character. - /// - StripeS = 0xF42A, - - /// - /// The Font Awesome "stroopwafel" icon unicode character. - /// - Stroopwafel = 0xF551, - - /// - /// The Font Awesome "studiovinari" icon unicode character. - /// - Studiovinari = 0xF3F8, - - /// - /// The Font Awesome "stumbleupon" icon unicode character. - /// - Stumbleupon = 0xF1A4, - - /// - /// The Font Awesome "stumbleupon-circle" icon unicode character. - /// - StumbleuponCircle = 0xF1A3, - - /// - /// The Font Awesome "subscript" icon unicode character. - /// - Subscript = 0xF12C, - - /// - /// The Font Awesome "subway" icon unicode character. - /// - Subway = 0xF239, - - /// - /// The Font Awesome "suitcase" icon unicode character. - /// - Suitcase = 0xF0F2, - - /// - /// The Font Awesome "suitcase-rolling" icon unicode character. - /// - SuitcaseRolling = 0xF5C1, - - /// - /// The Font Awesome "sun" icon unicode character. - /// - Sun = 0xF185, - - /// - /// The Font Awesome "superpowers" icon unicode character. - /// - Superpowers = 0xF2DD, - - /// - /// The Font Awesome "superscript" icon unicode character. - /// - Superscript = 0xF12B, - - /// - /// The Font Awesome "supple" icon unicode character. - /// - Supple = 0xF3F9, - - /// - /// The Font Awesome "surprise" icon unicode character. - /// - Surprise = 0xF5C2, - - /// - /// The Font Awesome "suse" icon unicode character. - /// - Suse = 0xF7D6, - - /// - /// The Font Awesome "swatchbook" icon unicode character. - /// - Swatchbook = 0xF5C3, - - /// - /// The Font Awesome "swift" icon unicode character. - /// - Swift = 0xF8E1, - - /// - /// The Font Awesome "swimmer" icon unicode character. - /// - Swimmer = 0xF5C4, - - /// - /// The Font Awesome "swimming-pool" icon unicode character. - /// - SwimmingPool = 0xF5C5, - - /// - /// The Font Awesome "symfony" icon unicode character. - /// - Symfony = 0xF83D, - - /// - /// The Font Awesome "synagogue" icon unicode character. - /// - Synagogue = 0xF69B, - - /// - /// The Font Awesome "sync" icon unicode character. - /// - Sync = 0xF021, - - /// - /// The Font Awesome "sync-alt" icon unicode character. - /// - SyncAlt = 0xF2F1, - - /// - /// The Font Awesome "syringe" icon unicode character. - /// - Syringe = 0xF48E, - - /// - /// The Font Awesome "table" icon unicode character. - /// - Table = 0xF0CE, - - /// - /// The Font Awesome "tablet" icon unicode character. - /// - Tablet = 0xF10A, - - /// - /// The Font Awesome "tablet-alt" icon unicode character. - /// - TabletAlt = 0xF3FA, - - /// - /// The Font Awesome "table-tennis" icon unicode character. - /// - TableTennis = 0xF45D, - - /// - /// The Font Awesome "tablets" icon unicode character. - /// - Tablets = 0xF49, - - /// - /// The Font Awesome "tachometer-alt" icon unicode character. - /// - TachometerAlt = 0xF3FD, - - /// - /// The Font Awesome "tag" icon unicode character. - /// - Tag = 0xF02B, - - /// - /// The Font Awesome "tags" icon unicode character. - /// - Tags = 0xF02C, - - /// - /// The Font Awesome "tape" icon unicode character. - /// - Tape = 0xF4DB, - - /// - /// The Font Awesome "tasks" icon unicode character. - /// - Tasks = 0xF0AE, - - /// - /// The Font Awesome "taxi" icon unicode character. - /// - Taxi = 0xF1BA, - - /// - /// The Font Awesome "teamspeak" icon unicode character. - /// - Teamspeak = 0xF4F9, - - /// - /// The Font Awesome "teeth" icon unicode character. - /// - Teeth = 0xF62E, - - /// - /// The Font Awesome "teeth-open" icon unicode character. - /// - TeethOpen = 0xF62F, - - /// - /// The Font Awesome "telegram" icon unicode character. - /// - Telegram = 0xF2C6, - - /// - /// The Font Awesome "telegram-plane" icon unicode character. - /// - TelegramPlane = 0xF3FE, - - /// - /// The Font Awesome "temperature-high" icon unicode character. - /// - TemperatureHigh = 0xF769, - - /// - /// The Font Awesome "temperature-low" icon unicode character. - /// - TemperatureLow = 0xF76B, - - /// - /// The Font Awesome "tencent-weibo" icon unicode character. - /// - TencentWeibo = 0xF1D5, - - /// - /// The Font Awesome "tenge" icon unicode character. - /// - Tenge = 0xF7D7, - - /// - /// The Font Awesome "terminal" icon unicode character. - /// - Terminal = 0xF12, - - /// - /// The Font Awesome "text-height" icon unicode character. - /// - TextHeight = 0xF034, - - /// - /// The Font Awesome "text-width" icon unicode character. - /// - TextWidth = 0xF035, - - /// - /// The Font Awesome "th" icon unicode character. - /// - Th = 0xF00A, - - /// - /// The Font Awesome "theater-masks" icon unicode character. - /// - TheaterMasks = 0xF63, - - /// - /// The Font Awesome "themeco" icon unicode character. - /// - Themeco = 0xF5C6, - - /// - /// The Font Awesome "themeisle" icon unicode character. - /// - Themeisle = 0xF2B2, - - /// - /// The Font Awesome "the-red-yeti" icon unicode character. - /// - TheRedYeti = 0xF69D, - - /// - /// The Font Awesome "thermometer" icon unicode character. - /// - Thermometer = 0xF491, - - /// - /// The Font Awesome "thermometer-empty" icon unicode character. - /// - ThermometerEmpty = 0xF2CB, - - /// - /// The Font Awesome "thermometer-full" icon unicode character. - /// - ThermometerFull = 0xF2C7, - - /// - /// The Font Awesome "thermometer-half" icon unicode character. - /// - ThermometerHalf = 0xF2C9, - - /// - /// The Font Awesome "thermometer-quarter" icon unicode character. - /// - ThermometerQuarter = 0xF2CA, - - /// - /// The Font Awesome "thermometer-three-quarters" icon unicode character. - /// - ThermometerThreeQuarters = 0xF2C8, - - /// - /// The Font Awesome "think-peaks" icon unicode character. - /// - ThinkPeaks = 0xF731, - - /// - /// The Font Awesome "th-large" icon unicode character. - /// - ThLarge = 0xF009, - - /// - /// The Font Awesome "th-list" icon unicode character. - /// - ThList = 0xF00B, - - /// - /// The Font Awesome "thumbs-down" icon unicode character. - /// - ThumbsDown = 0xF165, - - /// - /// The Font Awesome "thumbs-up" icon unicode character. - /// - ThumbsUp = 0xF164, - - /// - /// The Font Awesome "thumbtack" icon unicode character. - /// - Thumbtack = 0xF08D, - - /// - /// The Font Awesome "ticket-alt" icon unicode character. - /// - TicketAlt = 0xF3FF, - - /// - /// The Font Awesome "times" icon unicode character. - /// - Times = 0xF00D, - - /// - /// The Font Awesome "times-circle" icon unicode character. - /// - TimesCircle = 0xF057, - - /// - /// The Font Awesome "tint" icon unicode character. - /// - Tint = 0xF043, - - /// - /// The Font Awesome "tint-slash" icon unicode character. - /// - TintSlash = 0xF5C7, - - /// - /// The Font Awesome "tired" icon unicode character. - /// - Tired = 0xF5C8, - - /// - /// The Font Awesome "toggle-off" icon unicode character. - /// - ToggleOff = 0xF204, - - /// - /// The Font Awesome "toggle-on" icon unicode character. - /// - ToggleOn = 0xF205, - - /// - /// The Font Awesome "toilet" icon unicode character. - /// - Toilet = 0xF7D8, - - /// - /// The Font Awesome "toilet-paper" icon unicode character. - /// - ToiletPaper = 0xF71E, - - /// - /// The Font Awesome "toolbox" icon unicode character. - /// - Toolbox = 0xF552, - - /// - /// The Font Awesome "tools" icon unicode character. - /// - Tools = 0xF7D9, - - /// - /// The Font Awesome "tooth" icon unicode character. - /// - Tooth = 0xF5C9, - - /// - /// The Font Awesome "torah" icon unicode character. - /// - Torah = 0xF6A, - - /// - /// The Font Awesome "torii-gate" icon unicode character. - /// - ToriiGate = 0xF6A1, - - /// - /// The Font Awesome "tractor" icon unicode character. - /// - Tractor = 0xF722, - - /// - /// The Font Awesome "trade-federation" icon unicode character. - /// - TradeFederation = 0xF513, - - /// - /// The Font Awesome "trademark" icon unicode character. - /// - Trademark = 0xF25C, - - /// - /// The Font Awesome "traffic-light" icon unicode character. - /// - TrafficLight = 0xF637, - - /// - /// The Font Awesome "trailer" icon unicode character. - /// - Trailer = 0xF941, - - /// - /// The Font Awesome "train" icon unicode character. - /// - Train = 0xF238, - - /// - /// The Font Awesome "tram" icon unicode character. - /// - Tram = 0xF7DA, - - /// - /// The Font Awesome "transgender" icon unicode character. - /// - Transgender = 0xF224, - - /// - /// The Font Awesome "transgender-alt" icon unicode character. - /// - TransgenderAlt = 0xF225, - - /// - /// The Font Awesome "trash" icon unicode character. - /// - Trash = 0xF1F8, - - /// - /// The Font Awesome "trash-alt" icon unicode character. - /// - TrashAlt = 0xF2ED, - - /// - /// The Font Awesome "trash-restore" icon unicode character. - /// - TrashRestore = 0xF829, - - /// - /// The Font Awesome "trash-restore-alt" icon unicode character. - /// - TrashRestoreAlt = 0xF82A, - - /// - /// The Font Awesome "tree" icon unicode character. - /// - Tree = 0xF1BB, - - /// - /// The Font Awesome "trello" icon unicode character. - /// - Trello = 0xF181, - - /// - /// The Font Awesome "tripadvisor" icon unicode character. - /// - Tripadvisor = 0xF262, - - /// - /// The Font Awesome "trophy" icon unicode character. - /// - Trophy = 0xF091, - - /// - /// The Font Awesome "truck" icon unicode character. - /// - Truck = 0xF0D1, - - /// - /// The Font Awesome "truck-loading" icon unicode character. - /// - TruckLoading = 0xF4DE, - - /// - /// The Font Awesome "truck-monster" icon unicode character. - /// - TruckMonster = 0xF63B, - - /// - /// The Font Awesome "truck-moving" icon unicode character. - /// - TruckMoving = 0xF4DF, - - /// - /// The Font Awesome "truck-pickup" icon unicode character. - /// - TruckPickup = 0xF63C, - - /// - /// The Font Awesome "tshirt" icon unicode character. - /// - Tshirt = 0xF553, - - /// - /// The Font Awesome "tty" icon unicode character. - /// - Tty = 0xF1E4, - - /// - /// The Font Awesome "tumblr" icon unicode character. - /// - Tumblr = 0xF173, - - /// - /// The Font Awesome "tumblr-square" icon unicode character. - /// - TumblrSquare = 0xF174, - - /// - /// The Font Awesome "tv" icon unicode character. - /// - Tv = 0xF26C, - - /// - /// The Font Awesome "twitch" icon unicode character. - /// - Twitch = 0xF1E8, - - /// - /// The Font Awesome "twitter" icon unicode character. - /// - Twitter = 0xF099, - - /// - /// The Font Awesome "twitter-square" icon unicode character. - /// - TwitterSquare = 0xF081, - - /// - /// The Font Awesome "typo3" icon unicode character. - /// - Typo3 = 0xF42B, - - /// - /// The Font Awesome "uber" icon unicode character. - /// - Uber = 0xF402, - - /// - /// The Font Awesome "ubuntu" icon unicode character. - /// - Ubuntu = 0xF7DF, - - /// - /// The Font Awesome "uikit" icon unicode character. - /// - Uikit = 0xF403, - - /// - /// The Font Awesome "umbraco" icon unicode character. - /// - Umbraco = 0xF8E8, - - /// - /// The Font Awesome "umbrella" icon unicode character. - /// - Umbrella = 0xF0E9, - - /// - /// The Font Awesome "umbrella-beach" icon unicode character. - /// - UmbrellaBeach = 0xF5CA, - - /// - /// The Font Awesome "underline" icon unicode character. - /// - Underline = 0xF0CD, - - /// - /// The Font Awesome "undo" icon unicode character. - /// - Undo = 0xF0E2, - - /// - /// The Font Awesome "undo-alt" icon unicode character. - /// - UndoAlt = 0xF2EA, - - /// - /// The Font Awesome "uniregistry" icon unicode character. - /// - Uniregistry = 0xF404, - - /// - /// The Font Awesome "unity" icon unicode character. - /// - Unity = 0xF949, - - /// - /// The Font Awesome "universal-access" icon unicode character. - /// - UniversalAccess = 0xF29A, - - /// - /// The Font Awesome "university" icon unicode character. - /// - University = 0xF19C, - - /// - /// The Font Awesome "unlink" icon unicode character. - /// - Unlink = 0xF127, - - /// - /// The Font Awesome "unlock" icon unicode character. - /// - Unlock = 0xF09C, - - /// - /// The Font Awesome "unlock-alt" icon unicode character. - /// - UnlockAlt = 0xF13E, - - /// - /// The Font Awesome "untappd" icon unicode character. - /// - Untappd = 0xF405, - - /// - /// The Font Awesome "upload" icon unicode character. - /// - Upload = 0xF093, - - /// - /// The Font Awesome "ups" icon unicode character. - /// - Ups = 0xF7E, - - /// - /// The Font Awesome "usb" icon unicode character. - /// - Usb = 0xF287, - - /// - /// The Font Awesome "user" icon unicode character. - /// - User = 0xF007, - - /// - /// The Font Awesome "user-alt" icon unicode character. - /// - UserAlt = 0xF406, - - /// - /// The Font Awesome "user-alt-slash" icon unicode character. - /// - UserAltSlash = 0xF4FA, - - /// - /// The Font Awesome "user-astronaut" icon unicode character. - /// - UserAstronaut = 0xF4FB, - - /// - /// The Font Awesome "user-check" icon unicode character. - /// - UserCheck = 0xF4FC, - - /// - /// The Font Awesome "user-circle" icon unicode character. - /// - UserCircle = 0xF2BD, - - /// - /// The Font Awesome "user-clock" icon unicode character. - /// - UserClock = 0xF4FD, - - /// - /// The Font Awesome "user-cog" icon unicode character. - /// - UserCog = 0xF4FE, - - /// - /// The Font Awesome "user-edit" icon unicode character. - /// - UserEdit = 0xF4FF, - - /// - /// The Font Awesome "user-friends" icon unicode character. - /// - UserFriends = 0xF5, - - /// - /// The Font Awesome "user-graduate" icon unicode character. - /// - UserGraduate = 0xF501, - - /// - /// The Font Awesome "user-injured" icon unicode character. - /// - UserInjured = 0xF728, - - /// - /// The Font Awesome "user-lock" icon unicode character. - /// - UserLock = 0xF502, - - /// - /// The Font Awesome "user-md" icon unicode character. - /// - UserMd = 0xF0F, - - /// - /// The Font Awesome "user-minus" icon unicode character. - /// - UserMinus = 0xF503, - - /// - /// The Font Awesome "user-ninja" icon unicode character. - /// - UserNinja = 0xF504, - - /// - /// The Font Awesome "user-nurse" icon unicode character. - /// - UserNurse = 0xF82F, - - /// - /// The Font Awesome "user-plus" icon unicode character. - /// - UserPlus = 0xF234, - - /// - /// The Font Awesome "users" icon unicode character. - /// - Users = 0xF0C, - - /// - /// The Font Awesome "users-cog" icon unicode character. - /// - UsersCog = 0xF509, - - /// - /// The Font Awesome "user-secret" icon unicode character. - /// - UserSecret = 0xF21B, - - /// - /// The Font Awesome "user-shield" icon unicode character. - /// - UserShield = 0xF505, - - /// - /// The Font Awesome "user-slash" icon unicode character. - /// - UserSlash = 0xF506, - - /// - /// The Font Awesome "user-tag" icon unicode character. - /// - UserTag = 0xF507, - - /// - /// The Font Awesome "user-tie" icon unicode character. - /// - UserTie = 0xF508, - - /// - /// The Font Awesome "user-times" icon unicode character. - /// - UserTimes = 0xF235, - - /// - /// The Font Awesome "usps" icon unicode character. - /// - Usps = 0xF7E1, - - /// - /// The Font Awesome "ussunnah" icon unicode character. - /// - Ussunnah = 0xF407, - - /// - /// The Font Awesome "utensils" icon unicode character. - /// - Utensils = 0xF2E7, - - /// - /// The Font Awesome "utensil-spoon" icon unicode character. - /// - UtensilSpoon = 0xF2E5, - - /// - /// The Font Awesome "vaadin" icon unicode character. - /// - Vaadin = 0xF408, - - /// - /// The Font Awesome "vector-square" icon unicode character. - /// - VectorSquare = 0xF5CB, - - /// - /// The Font Awesome "venus" icon unicode character. - /// - Venus = 0xF221, - - /// - /// The Font Awesome "venus-double" icon unicode character. - /// - VenusDouble = 0xF226, - - /// - /// The Font Awesome "venus-mars" icon unicode character. - /// - VenusMars = 0xF228, - - /// - /// The Font Awesome "viacoin" icon unicode character. - /// - Viacoin = 0xF237, - - /// - /// The Font Awesome "viadeo" icon unicode character. - /// - Viadeo = 0xF2A9, - - /// - /// The Font Awesome "viadeo-square" icon unicode character. - /// - ViadeoSquare = 0xF2AA, - - /// - /// The Font Awesome "vial" icon unicode character. - /// - Vial = 0xF492, - - /// - /// The Font Awesome "vials" icon unicode character. - /// - Vials = 0xF493, - - /// - /// The Font Awesome "viber" icon unicode character. - /// - Viber = 0xF409, - - /// - /// The Font Awesome "video" icon unicode character. - /// - Video = 0xF03D, - - /// - /// The Font Awesome "video-slash" icon unicode character. - /// - VideoSlash = 0xF4E2, - - /// - /// The Font Awesome "vihara" icon unicode character. - /// - Vihara = 0xF6A7, - - /// - /// The Font Awesome "vimeo" icon unicode character. - /// - Vimeo = 0xF40A, - - /// - /// The Font Awesome "vimeo-square" icon unicode character. - /// - VimeoSquare = 0xF194, - - /// - /// The Font Awesome "vimeo-v" icon unicode character. - /// - VimeoV = 0xF27D, - - /// - /// The Font Awesome "vine" icon unicode character. - /// - Vine = 0xF1CA, - - /// - /// The Font Awesome "vk" icon unicode character. - /// - Vk = 0xF189, - - /// - /// The Font Awesome "vnv" icon unicode character. - /// - Vnv = 0xF40B, - - /// - /// The Font Awesome "voicemail" icon unicode character. - /// - Voicemail = 0xF897, - - /// - /// The Font Awesome "volleyball-ball" icon unicode character. - /// - VolleyballBall = 0xF45F, - - /// - /// The Font Awesome "volume-down" icon unicode character. - /// - VolumeDown = 0xF027, - - /// - /// The Font Awesome "volume-mute" icon unicode character. - /// - VolumeMute = 0xF6A9, - - /// - /// The Font Awesome "volume-off" icon unicode character. - /// - VolumeOff = 0xF026, - - /// - /// The Font Awesome "volume-up" icon unicode character. - /// - VolumeUp = 0xF028, - - /// - /// The Font Awesome "vote-yea" icon unicode character. - /// - VoteYea = 0xF772, - - /// - /// The Font Awesome "vr-cardboard" icon unicode character. - /// - VrCardboard = 0xF729, - - /// - /// The Font Awesome "vuejs" icon unicode character. - /// - Vuejs = 0xF41F, - - /// - /// The Font Awesome "walking" icon unicode character. - /// - Walking = 0xF554, - - /// - /// The Font Awesome "wallet" icon unicode character. - /// - Wallet = 0xF555, - - /// - /// The Font Awesome "warehouse" icon unicode character. - /// - Warehouse = 0xF494, - - /// - /// The Font Awesome "water" icon unicode character. - /// - Water = 0xF773, - - /// - /// The Font Awesome "wave-square" icon unicode character. - /// - WaveSquare = 0xF83E, - - /// - /// The Font Awesome "waze" icon unicode character. - /// - Waze = 0xF83F, - - /// - /// The Font Awesome "weebly" icon unicode character. - /// - Weebly = 0xF5CC, - - /// - /// The Font Awesome "weibo" icon unicode character. - /// - Weibo = 0xF18A, - - /// - /// The Font Awesome "weight" icon unicode character. - /// - Weight = 0xF496, - - /// - /// The Font Awesome "weight-hanging" icon unicode character. - /// - WeightHanging = 0xF5CD, - - /// - /// The Font Awesome "weixin" icon unicode character. - /// - Weixin = 0xF1D7, - - /// - /// The Font Awesome "whatsapp" icon unicode character. - /// - Whatsapp = 0xF232, - - /// - /// The Font Awesome "whatsapp-square" icon unicode character. - /// - WhatsappSquare = 0xF40C, - - /// - /// The Font Awesome "wheelchair" icon unicode character. - /// - Wheelchair = 0xF193, - - /// - /// The Font Awesome "whmcs" icon unicode character. - /// - Whmcs = 0xF40D, - - /// - /// The Font Awesome "wifi" icon unicode character. - /// - Wifi = 0xF1EB, - - /// - /// The Font Awesome "wikipedia-w" icon unicode character. - /// - WikipediaW = 0xF266, - - /// - /// The Font Awesome "wind" icon unicode character. - /// - Wind = 0xF72E, - - /// - /// The Font Awesome "window-close" icon unicode character. - /// - WindowClose = 0xF41, - - /// - /// The Font Awesome "window-maximize" icon unicode character. - /// - WindowMaximize = 0xF2D, - - /// - /// The Font Awesome "window-minimize" icon unicode character. - /// - WindowMinimize = 0xF2D1, - - /// - /// The Font Awesome "window-restore" icon unicode character. - /// - WindowRestore = 0xF2D2, - - /// - /// The Font Awesome "windows" icon unicode character. - /// - Windows = 0xF17A, - - /// - /// The Font Awesome "wine-bottle" icon unicode character. - /// - WineBottle = 0xF72F, - - /// - /// The Font Awesome "wine-glass" icon unicode character. - /// - WineGlass = 0xF4E3, - - /// - /// The Font Awesome "wine-glass-alt" icon unicode character. - /// - WineGlassAlt = 0xF5CE, - - /// - /// The Font Awesome "wix" icon unicode character. - /// - Wix = 0xF5CF, - - /// - /// The Font Awesome "wizards-of-the-coast" icon unicode character. - /// - WizardsOfTheCoast = 0xF73, - - /// - /// The Font Awesome "wolf-pack-battalion" icon unicode character. - /// - WolfPackBattalion = 0xF514, - - /// - /// The Font Awesome "won-sign" icon unicode character. - /// - WonSign = 0xF159, - - /// - /// The Font Awesome "wordpress" icon unicode character. - /// - Wordpress = 0xF19A, - - /// - /// The Font Awesome "wordpress-simple" icon unicode character. - /// - WordpressSimple = 0xF411, - - /// - /// The Font Awesome "wpbeginner" icon unicode character. - /// - Wpbeginner = 0xF297, - - /// - /// The Font Awesome "wpexplorer" icon unicode character. - /// - Wpexplorer = 0xF2DE, - - /// - /// The Font Awesome "wpforms" icon unicode character. - /// - Wpforms = 0xF298, - - /// - /// The Font Awesome "wpressr" icon unicode character. - /// - Wpressr = 0xF3E4, - - /// - /// The Font Awesome "wrench" icon unicode character. - /// - Wrench = 0xF0AD, - - /// - /// The Font Awesome "xbox" icon unicode character. - /// - Xbox = 0xF412, - - /// - /// The Font Awesome "xing" icon unicode character. - /// - Xing = 0xF168, - - /// - /// The Font Awesome "xing-square" icon unicode character. - /// - XingSquare = 0xF169, - - /// - /// The Font Awesome "x-ray" icon unicode character. - /// - XRay = 0xF497, - - /// - /// The Font Awesome "yahoo" icon unicode character. - /// - Yahoo = 0xF19E, - - /// - /// The Font Awesome "yammer" icon unicode character. - /// - Yammer = 0xF84, - - /// - /// The Font Awesome "yandex" icon unicode character. - /// - Yandex = 0xF413, - - /// - /// The Font Awesome "yandex-international" icon unicode character. - /// - YandexInternational = 0xF414, - - /// - /// The Font Awesome "yarn" icon unicode character. - /// - Yarn = 0xF7E3, - - /// - /// The Font Awesome "y-combinator" icon unicode character. - /// - YCombinator = 0xF23B, - - /// - /// The Font Awesome "yelp" icon unicode character. - /// - Yelp = 0xF1E9, - - /// - /// The Font Awesome "yen-sign" icon unicode character. - /// - YenSign = 0xF157, - - /// - /// The Font Awesome "yin-yang" icon unicode character. - /// - YinYang = 0xF6AD, - - /// - /// The Font Awesome "yoast" icon unicode character. - /// - Yoast = 0xF2B1, - - /// - /// The Font Awesome "youtube" icon unicode character. - /// - Youtube = 0xF167, - - /// - /// The Font Awesome "youtube-square" icon unicode character. - /// - YoutubeSquare = 0xF431, - - /// - /// The Font Awesome "zhihu" icon unicode character. - /// - Zhihu = 0xF63F, + /// Font Awesome unicode characters for use with the font. + /// + public enum FontAwesomeIcon + { + /// + /// No icon. + /// + None = 0, + + /// + /// The Font Awesome "500px" icon unicode character. + /// + _500Px = 0xF26E, + + /// + /// The Font Awesome "accessible-icon" icon unicode character. + /// + AccessibleIcon = 0xF368, + + /// + /// The Font Awesome "accusoft" icon unicode character. + /// + Accusoft = 0xF369, + + /// + /// The Font Awesome "acquisitions-incorporated" icon unicode character. + /// + AcquisitionsIncorporated = 0xF6AF, + + /// + /// The Font Awesome "ad" icon unicode character. + /// + Ad = 0xF641, + + /// + /// The Font Awesome "address-book" icon unicode character. + /// + AddressBook = 0xF2B9, + + /// + /// The Font Awesome "address-card" icon unicode character. + /// + AddressCard = 0xF2BB, + + /// + /// The Font Awesome "adjust" icon unicode character. + /// + Adjust = 0xF042, + + /// + /// The Font Awesome "adn" icon unicode character. + /// + Adn = 0xF17, + + /// + /// The Font Awesome "adobe" icon unicode character. + /// + Adobe = 0xF778, + + /// + /// The Font Awesome "adversal" icon unicode character. + /// + Adversal = 0xF36A, + + /// + /// The Font Awesome "affiliatetheme" icon unicode character. + /// + Affiliatetheme = 0xF36B, + + /// + /// The Font Awesome "airbnb" icon unicode character. + /// + Airbnb = 0xF834, + + /// + /// The Font Awesome "air-freshener" icon unicode character. + /// + AirFreshener = 0xF5D, + + /// + /// The Font Awesome "algolia" icon unicode character. + /// + Algolia = 0xF36C, + + /// + /// The Font Awesome "align-center" icon unicode character. + /// + AlignCenter = 0xF037, + + /// + /// The Font Awesome "align-justify" icon unicode character. + /// + AlignJustify = 0xF039, + + /// + /// The Font Awesome "align-left" icon unicode character. + /// + AlignLeft = 0xF036, + + /// + /// The Font Awesome "align-right" icon unicode character. + /// + AlignRight = 0xF038, + + /// + /// The Font Awesome "alipay" icon unicode character. + /// + Alipay = 0xF642, + + /// + /// The Font Awesome "allergies" icon unicode character. + /// + Allergies = 0xF461, + + /// + /// The Font Awesome "amazon" icon unicode character. + /// + Amazon = 0xF27, + + /// + /// The Font Awesome "amazon-pay" icon unicode character. + /// + AmazonPay = 0xF42C, + + /// + /// The Font Awesome "ambulance" icon unicode character. + /// + Ambulance = 0xF0F9, + + /// + /// The Font Awesome "american-sign-language-interpreting" icon unicode character. + /// + AmericanSignLanguageInterpreting = 0xF2A3, + + /// + /// The Font Awesome "amilia" icon unicode character. + /// + Amilia = 0xF36D, + + /// + /// The Font Awesome "anchor" icon unicode character. + /// + Anchor = 0xF13D, + + /// + /// The Font Awesome "android" icon unicode character. + /// + Android = 0xF17B, + + /// + /// The Font Awesome "angellist" icon unicode character. + /// + Angellist = 0xF209, + + /// + /// The Font Awesome "angle-double-down" icon unicode character. + /// + AngleDoubleDown = 0xF103, + + /// + /// The Font Awesome "angle-double-left" icon unicode character. + /// + AngleDoubleLeft = 0xF1, + + /// + /// The Font Awesome "angle-double-right" icon unicode character. + /// + AngleDoubleRight = 0xF101, + + /// + /// The Font Awesome "angle-double-up" icon unicode character. + /// + AngleDoubleUp = 0xF102, + + /// + /// The Font Awesome "angle-down" icon unicode character. + /// + AngleDown = 0xF107, + + /// + /// The Font Awesome "angle-left" icon unicode character. + /// + AngleLeft = 0xF104, + + /// + /// The Font Awesome "angle-right" icon unicode character. + /// + AngleRight = 0xF105, + + /// + /// The Font Awesome "angle-up" icon unicode character. + /// + AngleUp = 0xF106, + + /// + /// The Font Awesome "angry" icon unicode character. + /// + Angry = 0xF556, + + /// + /// The Font Awesome "angrycreative" icon unicode character. + /// + Angrycreative = 0xF36E, + + /// + /// The Font Awesome "angular" icon unicode character. + /// + Angular = 0xF42, + + /// + /// The Font Awesome "ankh" icon unicode character. + /// + Ankh = 0xF644, + + /// + /// The Font Awesome "apper" icon unicode character. + /// + Apper = 0xF371, + + /// + /// The Font Awesome "apple" icon unicode character. + /// + Apple = 0xF179, + + /// + /// The Font Awesome "apple-alt" icon unicode character. + /// + AppleAlt = 0xF5D1, + + /// + /// The Font Awesome "apple-pay" icon unicode character. + /// + ApplePay = 0xF415, + + /// + /// The Font Awesome "app-store" icon unicode character. + /// + AppStore = 0xF36F, + + /// + /// The Font Awesome "app-store-ios" icon unicode character. + /// + AppStoreIos = 0xF37, + + /// + /// The Font Awesome "archive" icon unicode character. + /// + Archive = 0xF187, + + /// + /// The Font Awesome "archway" icon unicode character. + /// + Archway = 0xF557, + + /// + /// The Font Awesome "arrow-alt-circle-down" icon unicode character. + /// + ArrowAltCircleDown = 0xF358, + + /// + /// The Font Awesome "arrow-alt-circle-left" icon unicode character. + /// + ArrowAltCircleLeft = 0xF359, + + /// + /// The Font Awesome "arrow-alt-circle-right" icon unicode character. + /// + ArrowAltCircleRight = 0xF35A, + + /// + /// The Font Awesome "arrow-alt-circle-up" icon unicode character. + /// + ArrowAltCircleUp = 0xF35B, + + /// + /// The Font Awesome "arrow-circle-down" icon unicode character. + /// + ArrowCircleDown = 0xF0AB, + + /// + /// The Font Awesome "arrow-circle-left" icon unicode character. + /// + ArrowCircleLeft = 0xF0A8, + + /// + /// The Font Awesome "arrow-circle-right" icon unicode character. + /// + ArrowCircleRight = 0xF0A9, + + /// + /// The Font Awesome "arrow-circle-up" icon unicode character. + /// + ArrowCircleUp = 0xF0AA, + + /// + /// The Font Awesome "arrow-down" icon unicode character. + /// + ArrowDown = 0xF063, + + /// + /// The Font Awesome "arrow-left" icon unicode character. + /// + ArrowLeft = 0xF06, + + /// + /// The Font Awesome "arrow-right" icon unicode character. + /// + ArrowRight = 0xF061, + + /// + /// The Font Awesome "arrows-alt" icon unicode character. + /// + ArrowsAlt = 0xF0B2, + + /// + /// The Font Awesome "arrows-alt-h" icon unicode character. + /// + ArrowsAltH = 0xF337, + + /// + /// The Font Awesome "arrows-alt-v" icon unicode character. + /// + ArrowsAltV = 0xF338, + + /// + /// The Font Awesome "arrow-up" icon unicode character. + /// + ArrowUp = 0xF062, + + /// + /// The Font Awesome "artstation" icon unicode character. + /// + Artstation = 0xF77A, + + /// + /// The Font Awesome "assistive-listening-systems" icon unicode character. + /// + AssistiveListeningSystems = 0xF2A2, + + /// + /// The Font Awesome "asterisk" icon unicode character. + /// + Asterisk = 0xF069, + + /// + /// The Font Awesome "asymmetrik" icon unicode character. + /// + Asymmetrik = 0xF372, + + /// + /// The Font Awesome "at" icon unicode character. + /// + At = 0xF1FA, + + /// + /// The Font Awesome "atlas" icon unicode character. + /// + Atlas = 0xF558, + + /// + /// The Font Awesome "atlassian" icon unicode character. + /// + Atlassian = 0xF77B, + + /// + /// The Font Awesome "atom" icon unicode character. + /// + Atom = 0xF5D2, + + /// + /// The Font Awesome "audible" icon unicode character. + /// + Audible = 0xF373, + + /// + /// The Font Awesome "audio-description" icon unicode character. + /// + AudioDescription = 0xF29E, + + /// + /// The Font Awesome "autoprefixer" icon unicode character. + /// + Autoprefixer = 0xF41C, + + /// + /// The Font Awesome "avianex" icon unicode character. + /// + Avianex = 0xF374, + + /// + /// The Font Awesome "aviato" icon unicode character. + /// + Aviato = 0xF421, + + /// + /// The Font Awesome "award" icon unicode character. + /// + Award = 0xF559, + + /// + /// The Font Awesome "aws" icon unicode character. + /// + Aws = 0xF375, + + /// + /// The Font Awesome "baby" icon unicode character. + /// + Baby = 0xF77C, + + /// + /// The Font Awesome "baby-carriage" icon unicode character. + /// + BabyCarriage = 0xF77D, + + /// + /// The Font Awesome "backspace" icon unicode character. + /// + Backspace = 0xF55A, + + /// + /// The Font Awesome "backward" icon unicode character. + /// + Backward = 0xF04A, + + /// + /// The Font Awesome "bacon" icon unicode character. + /// + Bacon = 0xF7E5, + + /// + /// The Font Awesome "bahai" icon unicode character. + /// + Bahai = 0xF666, + + /// + /// The Font Awesome "balance-scale" icon unicode character. + /// + BalanceScale = 0xF24E, + + /// + /// The Font Awesome "balance-scale-left" icon unicode character. + /// + BalanceScaleLeft = 0xF515, + + /// + /// The Font Awesome "balance-scale-right" icon unicode character. + /// + BalanceScaleRight = 0xF516, + + /// + /// The Font Awesome "ban" icon unicode character. + /// + Ban = 0xF05E, + + /// + /// The Font Awesome "band-aid" icon unicode character. + /// + BandAid = 0xF462, + + /// + /// The Font Awesome "bandcamp" icon unicode character. + /// + Bandcamp = 0xF2D5, + + /// + /// The Font Awesome "barcode" icon unicode character. + /// + Barcode = 0xF02A, + + /// + /// The Font Awesome "bars" icon unicode character. + /// + Bars = 0xF0C9, + + /// + /// The Font Awesome "baseball-ball" icon unicode character. + /// + BaseballBall = 0xF433, + + /// + /// The Font Awesome "basketball-ball" icon unicode character. + /// + BasketballBall = 0xF434, + + /// + /// The Font Awesome "bath" icon unicode character. + /// + Bath = 0xF2CD, + + /// + /// The Font Awesome "battery-empty" icon unicode character. + /// + BatteryEmpty = 0xF244, + + /// + /// The Font Awesome "battery-full" icon unicode character. + /// + BatteryFull = 0xF24, + + /// + /// The Font Awesome "battery-half" icon unicode character. + /// + BatteryHalf = 0xF242, + + /// + /// The Font Awesome "battery-quarter" icon unicode character. + /// + BatteryQuarter = 0xF243, + + /// + /// The Font Awesome "battery-three-quarters" icon unicode character. + /// + BatteryThreeQuarters = 0xF241, + + /// + /// The Font Awesome "battle-net" icon unicode character. + /// + BattleNet = 0xF835, + + /// + /// The Font Awesome "bed" icon unicode character. + /// + Bed = 0xF236, + + /// + /// The Font Awesome "beer" icon unicode character. + /// + Beer = 0xF0FC, + + /// + /// The Font Awesome "behance" icon unicode character. + /// + Behance = 0xF1B4, + + /// + /// The Font Awesome "behance-square" icon unicode character. + /// + BehanceSquare = 0xF1B5, + + /// + /// The Font Awesome "bell" icon unicode character. + /// + Bell = 0xF0F3, + + /// + /// The Font Awesome "bell-slash" icon unicode character. + /// + BellSlash = 0xF1F6, + + /// + /// The Font Awesome "bezier-curve" icon unicode character. + /// + BezierCurve = 0xF55B, + + /// + /// The Font Awesome "bible" icon unicode character. + /// + Bible = 0xF647, + + /// + /// The Font Awesome "bicycle" icon unicode character. + /// + Bicycle = 0xF206, + + /// + /// The Font Awesome "biking" icon unicode character. + /// + Biking = 0xF84A, + + /// + /// The Font Awesome "bimobject" icon unicode character. + /// + Bimobject = 0xF378, + + /// + /// The Font Awesome "binoculars" icon unicode character. + /// + Binoculars = 0xF1E5, + + /// + /// The Font Awesome "biohazard" icon unicode character. + /// + Biohazard = 0xF78, + + /// + /// The Font Awesome "birthday-cake" icon unicode character. + /// + BirthdayCake = 0xF1FD, + + /// + /// The Font Awesome "bitbucket" icon unicode character. + /// + Bitbucket = 0xF171, + + /// + /// The Font Awesome "bitcoin" icon unicode character. + /// + Bitcoin = 0xF379, + + /// + /// The Font Awesome "bity" icon unicode character. + /// + Bity = 0xF37A, + + /// + /// The Font Awesome "blackberry" icon unicode character. + /// + Blackberry = 0xF37B, + + /// + /// The Font Awesome "black-tie" icon unicode character. + /// + BlackTie = 0xF27E, + + /// + /// The Font Awesome "blender" icon unicode character. + /// + Blender = 0xF517, + + /// + /// The Font Awesome "blender-phone" icon unicode character. + /// + BlenderPhone = 0xF6B6, + + /// + /// The Font Awesome "blind" icon unicode character. + /// + Blind = 0xF29D, + + /// + /// The Font Awesome "blog" icon unicode character. + /// + Blog = 0xF781, + + /// + /// The Font Awesome "blogger" icon unicode character. + /// + Blogger = 0xF37C, + + /// + /// The Font Awesome "blogger-b" icon unicode character. + /// + BloggerB = 0xF37D, + + /// + /// The Font Awesome "bluetooth" icon unicode character. + /// + Bluetooth = 0xF293, + + /// + /// The Font Awesome "bluetooth-b" icon unicode character. + /// + BluetoothB = 0xF294, + + /// + /// The Font Awesome "bold" icon unicode character. + /// + Bold = 0xF032, + + /// + /// The Font Awesome "bolt" icon unicode character. + /// + Bolt = 0xF0E7, + + /// + /// The Font Awesome "bomb" icon unicode character. + /// + Bomb = 0xF1E2, + + /// + /// The Font Awesome "bone" icon unicode character. + /// + Bone = 0xF5D7, + + /// + /// The Font Awesome "bong" icon unicode character. + /// + Bong = 0xF55C, + + /// + /// The Font Awesome "book" icon unicode character. + /// + Book = 0xF02D, + + /// + /// The Font Awesome "book-dead" icon unicode character. + /// + BookDead = 0xF6B7, + + /// + /// The Font Awesome "bookmark" icon unicode character. + /// + Bookmark = 0xF02E, + + /// + /// The Font Awesome "book-medical" icon unicode character. + /// + BookMedical = 0xF7E6, + + /// + /// The Font Awesome "book-open" icon unicode character. + /// + BookOpen = 0xF518, + + /// + /// The Font Awesome "book-reader" icon unicode character. + /// + BookReader = 0xF5DA, + + /// + /// The Font Awesome "bootstrap" icon unicode character. + /// + Bootstrap = 0xF836, + + /// + /// The Font Awesome "border-all" icon unicode character. + /// + BorderAll = 0xF84C, + + /// + /// The Font Awesome "border-none" icon unicode character. + /// + BorderNone = 0xF85, + + /// + /// The Font Awesome "border-style" icon unicode character. + /// + BorderStyle = 0xF853, + + /// + /// The Font Awesome "bowling-ball" icon unicode character. + /// + BowlingBall = 0xF436, + + /// + /// The Font Awesome "box" icon unicode character. + /// + Box = 0xF466, + + /// + /// The Font Awesome "boxes" icon unicode character. + /// + Boxes = 0xF468, + + /// + /// The Font Awesome "box-open" icon unicode character. + /// + BoxOpen = 0xF49E, + + /// + /// The Font Awesome "braille" icon unicode character. + /// + Braille = 0xF2A1, + + /// + /// The Font Awesome "brain" icon unicode character. + /// + Brain = 0xF5DC, + + /// + /// The Font Awesome "bread-slice" icon unicode character. + /// + BreadSlice = 0xF7EC, + + /// + /// The Font Awesome "briefcase" icon unicode character. + /// + Briefcase = 0xF0B1, + + /// + /// The Font Awesome "briefcase-medical" icon unicode character. + /// + BriefcaseMedical = 0xF469, + + /// + /// The Font Awesome "broadcast-tower" icon unicode character. + /// + BroadcastTower = 0xF519, + + /// + /// The Font Awesome "broom" icon unicode character. + /// + Broom = 0xF51A, + + /// + /// The Font Awesome "brush" icon unicode character. + /// + Brush = 0xF55D, + + /// + /// The Font Awesome "btc" icon unicode character. + /// + Btc = 0xF15A, + + /// + /// The Font Awesome "buffer" icon unicode character. + /// + Buffer = 0xF837, + + /// + /// The Font Awesome "bug" icon unicode character. + /// + Bug = 0xF188, + + /// + /// The Font Awesome "building" icon unicode character. + /// + Building = 0xF1AD, + + /// + /// The Font Awesome "bullhorn" icon unicode character. + /// + Bullhorn = 0xF0A1, + + /// + /// The Font Awesome "bullseye" icon unicode character. + /// + Bullseye = 0xF14, + + /// + /// The Font Awesome "burn" icon unicode character. + /// + Burn = 0xF46A, + + /// + /// The Font Awesome "buromobelexperte" icon unicode character. + /// + Buromobelexperte = 0xF37F, + + /// + /// The Font Awesome "bus" icon unicode character. + /// + Bus = 0xF207, + + /// + /// The Font Awesome "bus-alt" icon unicode character. + /// + BusAlt = 0xF55E, + + /// + /// The Font Awesome "business-time" icon unicode character. + /// + BusinessTime = 0xF64A, + + /// + /// The Font Awesome "buy-n-large" icon unicode character. + /// + BuyNLarge = 0xF8A6, + + /// + /// The Font Awesome "buysellads" icon unicode character. + /// + Buysellads = 0xF20D, + + /// + /// The Font Awesome "calculator" icon unicode character. + /// + Calculator = 0xF1EC, + + /// + /// The Font Awesome "calendar" icon unicode character. + /// + Calendar = 0xF133, + + /// + /// The Font Awesome "calendar-alt" icon unicode character. + /// + CalendarAlt = 0xF073, + + /// + /// The Font Awesome "calendar-check" icon unicode character. + /// + CalendarCheck = 0xF274, + + /// + /// The Font Awesome "calendar-day" icon unicode character. + /// + CalendarDay = 0xF783, + + /// + /// The Font Awesome "calendar-minus" icon unicode character. + /// + CalendarMinus = 0xF272, + + /// + /// The Font Awesome "calendar-plus" icon unicode character. + /// + CalendarPlus = 0xF271, + + /// + /// The Font Awesome "calendar-times" icon unicode character. + /// + CalendarTimes = 0xF273, + + /// + /// The Font Awesome "calendar-week" icon unicode character. + /// + CalendarWeek = 0xF784, + + /// + /// The Font Awesome "camera" icon unicode character. + /// + Camera = 0xF03, + + /// + /// The Font Awesome "camera-retro" icon unicode character. + /// + CameraRetro = 0xF083, + + /// + /// The Font Awesome "campground" icon unicode character. + /// + Campground = 0xF6BB, + + /// + /// The Font Awesome "canadian-maple-leaf" icon unicode character. + /// + CanadianMapleLeaf = 0xF785, + + /// + /// The Font Awesome "candy-cane" icon unicode character. + /// + CandyCane = 0xF786, + + /// + /// The Font Awesome "cannabis" icon unicode character. + /// + Cannabis = 0xF55F, + + /// + /// The Font Awesome "capsules" icon unicode character. + /// + Capsules = 0xF46B, + + /// + /// The Font Awesome "car" icon unicode character. + /// + Car = 0xF1B9, + + /// + /// The Font Awesome "car-alt" icon unicode character. + /// + CarAlt = 0xF5DE, + + /// + /// The Font Awesome "caravan" icon unicode character. + /// + Caravan = 0xF8FF, + + /// + /// The Font Awesome "car-battery" icon unicode character. + /// + CarBattery = 0xF5DF, + + /// + /// The Font Awesome "car-crash" icon unicode character. + /// + CarCrash = 0xF5E1, + + /// + /// The Font Awesome "caret-down" icon unicode character. + /// + CaretDown = 0xF0D7, + + /// + /// The Font Awesome "caret-left" icon unicode character. + /// + CaretLeft = 0xF0D9, + + /// + /// The Font Awesome "caret-right" icon unicode character. + /// + CaretRight = 0xF0DA, + + /// + /// The Font Awesome "caret-square-down" icon unicode character. + /// + CaretSquareDown = 0xF15, + + /// + /// The Font Awesome "caret-square-left" icon unicode character. + /// + CaretSquareLeft = 0xF191, + + /// + /// The Font Awesome "caret-square-right" icon unicode character. + /// + CaretSquareRight = 0xF152, + + /// + /// The Font Awesome "caret-square-up" icon unicode character. + /// + CaretSquareUp = 0xF151, + + /// + /// The Font Awesome "caret-up" icon unicode character. + /// + CaretUp = 0xF0D8, + + /// + /// The Font Awesome "carrot" icon unicode character. + /// + Carrot = 0xF787, + + /// + /// The Font Awesome "car-side" icon unicode character. + /// + CarSide = 0xF5E4, + + /// + /// The Font Awesome "cart-arrow-down" icon unicode character. + /// + CartArrowDown = 0xF218, + + /// + /// The Font Awesome "cart-plus" icon unicode character. + /// + CartPlus = 0xF217, + + /// + /// The Font Awesome "cash-register" icon unicode character. + /// + CashRegister = 0xF788, + + /// + /// The Font Awesome "cat" icon unicode character. + /// + Cat = 0xF6BE, + + /// + /// The Font Awesome "cc-amazon-pay" icon unicode character. + /// + CcAmazonPay = 0xF42D, + + /// + /// The Font Awesome "cc-amex" icon unicode character. + /// + CcAmex = 0xF1F3, + + /// + /// The Font Awesome "cc-apple-pay" icon unicode character. + /// + CcApplePay = 0xF416, + + /// + /// The Font Awesome "cc-diners-club" icon unicode character. + /// + CcDinersClub = 0xF24C, + + /// + /// The Font Awesome "cc-discover" icon unicode character. + /// + CcDiscover = 0xF1F2, + + /// + /// The Font Awesome "cc-jcb" icon unicode character. + /// + CcJcb = 0xF24B, + + /// + /// The Font Awesome "cc-mastercard" icon unicode character. + /// + CcMastercard = 0xF1F1, + + /// + /// The Font Awesome "cc-paypal" icon unicode character. + /// + CcPaypal = 0xF1F4, + + /// + /// The Font Awesome "cc-stripe" icon unicode character. + /// + CcStripe = 0xF1F5, + + /// + /// The Font Awesome "cc-visa" icon unicode character. + /// + CcVisa = 0xF1F, + + /// + /// The Font Awesome "centercode" icon unicode character. + /// + Centercode = 0xF38, + + /// + /// The Font Awesome "centos" icon unicode character. + /// + Centos = 0xF789, + + /// + /// The Font Awesome "certificate" icon unicode character. + /// + Certificate = 0xF0A3, + + /// + /// The Font Awesome "chair" icon unicode character. + /// + Chair = 0xF6C, + + /// + /// The Font Awesome "chalkboard" icon unicode character. + /// + Chalkboard = 0xF51B, + + /// + /// The Font Awesome "chalkboard-teacher" icon unicode character. + /// + ChalkboardTeacher = 0xF51C, + + /// + /// The Font Awesome "charging-station" icon unicode character. + /// + ChargingStation = 0xF5E7, + + /// + /// The Font Awesome "chart-area" icon unicode character. + /// + ChartArea = 0xF1FE, + + /// + /// The Font Awesome "chart-bar" icon unicode character. + /// + ChartBar = 0xF08, + + /// + /// The Font Awesome "chart-line" icon unicode character. + /// + ChartLine = 0xF201, + + /// + /// The Font Awesome "chart-pie" icon unicode character. + /// + ChartPie = 0xF2, + + /// + /// The Font Awesome "check" icon unicode character. + /// + Check = 0xF00C, + + /// + /// The Font Awesome "check-circle" icon unicode character. + /// + CheckCircle = 0xF058, + + /// + /// The Font Awesome "check-double" icon unicode character. + /// + CheckDouble = 0xF56, + + /// + /// The Font Awesome "check-square" icon unicode character. + /// + CheckSquare = 0xF14A, + + /// + /// The Font Awesome "cheese" icon unicode character. + /// + Cheese = 0xF7EF, + + /// + /// The Font Awesome "chess" icon unicode character. + /// + Chess = 0xF439, + + /// + /// The Font Awesome "chess-bishop" icon unicode character. + /// + ChessBishop = 0xF43A, + + /// + /// The Font Awesome "chess-board" icon unicode character. + /// + ChessBoard = 0xF43C, + + /// + /// The Font Awesome "chess-king" icon unicode character. + /// + ChessKing = 0xF43F, + + /// + /// The Font Awesome "chess-knight" icon unicode character. + /// + ChessKnight = 0xF441, + + /// + /// The Font Awesome "chess-pawn" icon unicode character. + /// + ChessPawn = 0xF443, + + /// + /// The Font Awesome "chess-queen" icon unicode character. + /// + ChessQueen = 0xF445, + + /// + /// The Font Awesome "chess-rook" icon unicode character. + /// + ChessRook = 0xF447, + + /// + /// The Font Awesome "chevron-circle-down" icon unicode character. + /// + ChevronCircleDown = 0xF13A, + + /// + /// The Font Awesome "chevron-circle-left" icon unicode character. + /// + ChevronCircleLeft = 0xF137, + + /// + /// The Font Awesome "chevron-circle-right" icon unicode character. + /// + ChevronCircleRight = 0xF138, + + /// + /// The Font Awesome "chevron-circle-up" icon unicode character. + /// + ChevronCircleUp = 0xF139, + + /// + /// The Font Awesome "chevron-down" icon unicode character. + /// + ChevronDown = 0xF078, + + /// + /// The Font Awesome "chevron-left" icon unicode character. + /// + ChevronLeft = 0xF053, + + /// + /// The Font Awesome "chevron-right" icon unicode character. + /// + ChevronRight = 0xF054, + + /// + /// The Font Awesome "chevron-up" icon unicode character. + /// + ChevronUp = 0xF077, + + /// + /// The Font Awesome "child" icon unicode character. + /// + Child = 0xF1AE, + + /// + /// The Font Awesome "chrome" icon unicode character. + /// + Chrome = 0xF268, + + /// + /// The Font Awesome "chromecast" icon unicode character. + /// + Chromecast = 0xF838, + + /// + /// The Font Awesome "church" icon unicode character. + /// + Church = 0xF51D, + + /// + /// The Font Awesome "circle" icon unicode character. + /// + Circle = 0xF111, + + /// + /// The Font Awesome "circle-notch" icon unicode character. + /// + CircleNotch = 0xF1CE, + + /// + /// The Font Awesome "city" icon unicode character. + /// + City = 0xF64F, + + /// + /// The Font Awesome "clinic-medical" icon unicode character. + /// + ClinicMedical = 0xF7F2, + + /// + /// The Font Awesome "clipboard" icon unicode character. + /// + Clipboard = 0xF328, + + /// + /// The Font Awesome "clipboard-check" icon unicode character. + /// + ClipboardCheck = 0xF46C, + + /// + /// The Font Awesome "clipboard-list" icon unicode character. + /// + ClipboardList = 0xF46D, + + /// + /// The Font Awesome "clock" icon unicode character. + /// + Clock = 0xF017, + + /// + /// The Font Awesome "clone" icon unicode character. + /// + Clone = 0xF24D, + + /// + /// The Font Awesome "closed-captioning" icon unicode character. + /// + ClosedCaptioning = 0xF20A, + + /// + /// The Font Awesome "cloud" icon unicode character. + /// + Cloud = 0xF0C2, + + /// + /// The Font Awesome "cloud-download-alt" icon unicode character. + /// + CloudDownloadAlt = 0xF381, + + /// + /// The Font Awesome "cloud-meatball" icon unicode character. + /// + CloudMeatball = 0xF73B, + + /// + /// The Font Awesome "cloud-moon" icon unicode character. + /// + CloudMoon = 0xF6C3, + + /// + /// The Font Awesome "cloud-moon-rain" icon unicode character. + /// + CloudMoonRain = 0xF73C, + + /// + /// The Font Awesome "cloud-rain" icon unicode character. + /// + CloudRain = 0xF73D, + + /// + /// The Font Awesome "cloudscale" icon unicode character. + /// + Cloudscale = 0xF383, + + /// + /// The Font Awesome "cloud-showers-heavy" icon unicode character. + /// + CloudShowersHeavy = 0xF74, + + /// + /// The Font Awesome "cloudsmith" icon unicode character. + /// + Cloudsmith = 0xF384, + + /// + /// The Font Awesome "cloud-sun" icon unicode character. + /// + CloudSun = 0xF6C4, + + /// + /// The Font Awesome "cloud-sun-rain" icon unicode character. + /// + CloudSunRain = 0xF743, + + /// + /// The Font Awesome "cloud-upload-alt" icon unicode character. + /// + CloudUploadAlt = 0xF382, + + /// + /// The Font Awesome "cloudversify" icon unicode character. + /// + Cloudversify = 0xF385, + + /// + /// The Font Awesome "cocktail" icon unicode character. + /// + Cocktail = 0xF561, + + /// + /// The Font Awesome "code" icon unicode character. + /// + Code = 0xF121, + + /// + /// The Font Awesome "code-branch" icon unicode character. + /// + CodeBranch = 0xF126, + + /// + /// The Font Awesome "codepen" icon unicode character. + /// + Codepen = 0xF1CB, + + /// + /// The Font Awesome "codiepie" icon unicode character. + /// + Codiepie = 0xF284, + + /// + /// The Font Awesome "coffee" icon unicode character. + /// + Coffee = 0xF0F4, + + /// + /// The Font Awesome "cog" icon unicode character. + /// + Cog = 0xF013, + + /// + /// The Font Awesome "cogs" icon unicode character. + /// + Cogs = 0xF085, + + /// + /// The Font Awesome "coins" icon unicode character. + /// + Coins = 0xF51E, + + /// + /// The Font Awesome "columns" icon unicode character. + /// + Columns = 0xF0DB, + + /// + /// The Font Awesome "comment" icon unicode character. + /// + Comment = 0xF075, + + /// + /// The Font Awesome "comment-alt" icon unicode character. + /// + CommentAlt = 0xF27A, + + /// + /// The Font Awesome "comment-dollar" icon unicode character. + /// + CommentDollar = 0xF651, + + /// + /// The Font Awesome "comment-dots" icon unicode character. + /// + CommentDots = 0xF4AD, + + /// + /// The Font Awesome "comment-medical" icon unicode character. + /// + CommentMedical = 0xF7F5, + + /// + /// The Font Awesome "comments" icon unicode character. + /// + Comments = 0xF086, + + /// + /// The Font Awesome "comments-dollar" icon unicode character. + /// + CommentsDollar = 0xF653, + + /// + /// The Font Awesome "comment-slash" icon unicode character. + /// + CommentSlash = 0xF4B3, + + /// + /// The Font Awesome "compact-disc" icon unicode character. + /// + CompactDisc = 0xF51F, + + /// + /// The Font Awesome "compass" icon unicode character. + /// + Compass = 0xF14E, + + /// + /// The Font Awesome "compress" icon unicode character. + /// + Compress = 0xF066, + + /// + /// The Font Awesome "compress-alt" icon unicode character. + /// + CompressAlt = 0xF422, + + /// + /// The Font Awesome "compress-arrows-alt" icon unicode character. + /// + CompressArrowsAlt = 0xF78C, + + /// + /// The Font Awesome "concierge-bell" icon unicode character. + /// + ConciergeBell = 0xF562, + + /// + /// The Font Awesome "confluence" icon unicode character. + /// + Confluence = 0xF78D, + + /// + /// The Font Awesome "connectdevelop" icon unicode character. + /// + Connectdevelop = 0xF20E, + + /// + /// The Font Awesome "contao" icon unicode character. + /// + Contao = 0xF26D, + + /// + /// The Font Awesome "cookie" icon unicode character. + /// + Cookie = 0xF563, + + /// + /// The Font Awesome "cookie-bite" icon unicode character. + /// + CookieBite = 0xF564, + + /// + /// The Font Awesome "copy" icon unicode character. + /// + Copy = 0xF0C5, + + /// + /// The Font Awesome "copyright" icon unicode character. + /// + Copyright = 0xF1F9, + + /// + /// The Font Awesome "cotton-bureau" icon unicode character. + /// + CottonBureau = 0xF89E, + + /// + /// The Font Awesome "couch" icon unicode character. + /// + Couch = 0xF4B8, + + /// + /// The Font Awesome "cpanel" icon unicode character. + /// + Cpanel = 0xF388, + + /// + /// The Font Awesome "creative-commons" icon unicode character. + /// + CreativeCommons = 0xF25E, + + /// + /// The Font Awesome "creative-commons-by" icon unicode character. + /// + CreativeCommonsBy = 0xF4E7, + + /// + /// The Font Awesome "creative-commons-nc" icon unicode character. + /// + CreativeCommonsNc = 0xF4E8, + + /// + /// The Font Awesome "creative-commons-nc-eu" icon unicode character. + /// + CreativeCommonsNcEu = 0xF4E9, + + /// + /// The Font Awesome "creative-commons-nc-jp" icon unicode character. + /// + CreativeCommonsNcJp = 0xF4EA, + + /// + /// The Font Awesome "creative-commons-nd" icon unicode character. + /// + CreativeCommonsNd = 0xF4EB, + + /// + /// The Font Awesome "creative-commons-pd" icon unicode character. + /// + CreativeCommonsPd = 0xF4EC, + + /// + /// The Font Awesome "creative-commons-pd-alt" icon unicode character. + /// + CreativeCommonsPdAlt = 0xF4ED, + + /// + /// The Font Awesome "creative-commons-remix" icon unicode character. + /// + CreativeCommonsRemix = 0xF4EE, + + /// + /// The Font Awesome "creative-commons-sa" icon unicode character. + /// + CreativeCommonsSa = 0xF4EF, + + /// + /// The Font Awesome "creative-commons-sampling" icon unicode character. + /// + CreativeCommonsSampling = 0xF4F, + + /// + /// The Font Awesome "creative-commons-sampling-plus" icon unicode character. + /// + CreativeCommonsSamplingPlus = 0xF4F1, + + /// + /// The Font Awesome "creative-commons-share" icon unicode character. + /// + CreativeCommonsShare = 0xF4F2, + + /// + /// The Font Awesome "creative-commons-zero" icon unicode character. + /// + CreativeCommonsZero = 0xF4F3, + + /// + /// The Font Awesome "credit-card" icon unicode character. + /// + CreditCard = 0xF09D, + + /// + /// The Font Awesome "critical-role" icon unicode character. + /// + CriticalRole = 0xF6C9, + + /// + /// The Font Awesome "crop" icon unicode character. + /// + Crop = 0xF125, + + /// + /// The Font Awesome "crop-alt" icon unicode character. + /// + CropAlt = 0xF565, + + /// + /// The Font Awesome "cross" icon unicode character. + /// + Cross = 0xF654, + + /// + /// The Font Awesome "crosshairs" icon unicode character. + /// + Crosshairs = 0xF05B, + + /// + /// The Font Awesome "crow" icon unicode character. + /// + Crow = 0xF52, + + /// + /// The Font Awesome "crown" icon unicode character. + /// + Crown = 0xF521, + + /// + /// The Font Awesome "crutch" icon unicode character. + /// + Crutch = 0xF7F7, + + /// + /// The Font Awesome "css3" icon unicode character. + /// + Css3 = 0xF13C, + + /// + /// The Font Awesome "css3-alt" icon unicode character. + /// + Css3Alt = 0xF38B, + + /// + /// The Font Awesome "cube" icon unicode character. + /// + Cube = 0xF1B2, + + /// + /// The Font Awesome "cubes" icon unicode character. + /// + Cubes = 0xF1B3, + + /// + /// The Font Awesome "cut" icon unicode character. + /// + Cut = 0xF0C4, + + /// + /// The Font Awesome "cuttlefish" icon unicode character. + /// + Cuttlefish = 0xF38C, + + /// + /// The Font Awesome "dailymotion" icon unicode character. + /// + Dailymotion = 0xF952, + + /// + /// The Font Awesome "d-and-d" icon unicode character. + /// + DAndD = 0xF38D, + + /// + /// The Font Awesome "d-and-d-beyond" icon unicode character. + /// + DAndDBeyond = 0xF6CA, + + /// + /// The Font Awesome "dashcube" icon unicode character. + /// + Dashcube = 0xF21, + + /// + /// The Font Awesome "database" icon unicode character. + /// + Database = 0xF1C, + + /// + /// The Font Awesome "deaf" icon unicode character. + /// + Deaf = 0xF2A4, + + /// + /// The Font Awesome "delicious" icon unicode character. + /// + Delicious = 0xF1A5, + + /// + /// The Font Awesome "democrat" icon unicode character. + /// + Democrat = 0xF747, + + /// + /// The Font Awesome "deploydog" icon unicode character. + /// + Deploydog = 0xF38E, + + /// + /// The Font Awesome "deskpro" icon unicode character. + /// + Deskpro = 0xF38F, + + /// + /// The Font Awesome "desktop" icon unicode character. + /// + Desktop = 0xF108, + + /// + /// The Font Awesome "dev" icon unicode character. + /// + Dev = 0xF6CC, + + /// + /// The Font Awesome "deviantart" icon unicode character. + /// + Deviantart = 0xF1BD, + + /// + /// The Font Awesome "dharmachakra" icon unicode character. + /// + Dharmachakra = 0xF655, + + /// + /// The Font Awesome "dhl" icon unicode character. + /// + Dhl = 0xF79, + + /// + /// The Font Awesome "diagnoses" icon unicode character. + /// + Diagnoses = 0xF47, + + /// + /// The Font Awesome "diaspora" icon unicode character. + /// + Diaspora = 0xF791, + + /// + /// The Font Awesome "dice" icon unicode character. + /// + Dice = 0xF522, + + /// + /// The Font Awesome "dice-d20" icon unicode character. + /// + DiceD20 = 0xF6CF, + + /// + /// The Font Awesome "dice-d6" icon unicode character. + /// + DiceD6 = 0xF6D1, + + /// + /// The Font Awesome "dice-five" icon unicode character. + /// + DiceFive = 0xF523, + + /// + /// The Font Awesome "dice-four" icon unicode character. + /// + DiceFour = 0xF524, + + /// + /// The Font Awesome "dice-one" icon unicode character. + /// + DiceOne = 0xF525, + + /// + /// The Font Awesome "dice-six" icon unicode character. + /// + DiceSix = 0xF526, + + /// + /// The Font Awesome "dice-three" icon unicode character. + /// + DiceThree = 0xF527, + + /// + /// The Font Awesome "dice-two" icon unicode character. + /// + DiceTwo = 0xF528, + + /// + /// The Font Awesome "digg" icon unicode character. + /// + Digg = 0xF1A6, + + /// + /// The Font Awesome "digital-ocean" icon unicode character. + /// + DigitalOcean = 0xF391, + + /// + /// The Font Awesome "digital-tachograph" icon unicode character. + /// + DigitalTachograph = 0xF566, + + /// + /// The Font Awesome "directions" icon unicode character. + /// + Directions = 0xF5EB, + + /// + /// The Font Awesome "discord" icon unicode character. + /// + Discord = 0xF392, + + /// + /// The Font Awesome "discourse" icon unicode character. + /// + Discourse = 0xF393, + + /// + /// The Font Awesome "divide" icon unicode character. + /// + Divide = 0xF529, + + /// + /// The Font Awesome "dizzy" icon unicode character. + /// + Dizzy = 0xF567, + + /// + /// The Font Awesome "dna" icon unicode character. + /// + Dna = 0xF471, + + /// + /// The Font Awesome "dochub" icon unicode character. + /// + Dochub = 0xF394, + + /// + /// The Font Awesome "docker" icon unicode character. + /// + Docker = 0xF395, + + /// + /// The Font Awesome "dog" icon unicode character. + /// + Dog = 0xF6D3, + + /// + /// The Font Awesome "dollar-sign" icon unicode character. + /// + DollarSign = 0xF155, + + /// + /// The Font Awesome "dolly" icon unicode character. + /// + Dolly = 0xF472, + + /// + /// The Font Awesome "dolly-flatbed" icon unicode character. + /// + DollyFlatbed = 0xF474, + + /// + /// The Font Awesome "donate" icon unicode character. + /// + Donate = 0xF4B9, + + /// + /// The Font Awesome "door-closed" icon unicode character. + /// + DoorClosed = 0xF52A, + + /// + /// The Font Awesome "door-open" icon unicode character. + /// + DoorOpen = 0xF52B, + + /// + /// The Font Awesome "dot-circle" icon unicode character. + /// + DotCircle = 0xF192, + + /// + /// The Font Awesome "dove" icon unicode character. + /// + Dove = 0xF4BA, + + /// + /// The Font Awesome "download" icon unicode character. + /// + Download = 0xF019, + + /// + /// The Font Awesome "draft2digital" icon unicode character. + /// + Draft2digital = 0xF396, + + /// + /// The Font Awesome "drafting-compass" icon unicode character. + /// + DraftingCompass = 0xF568, + + /// + /// The Font Awesome "dragon" icon unicode character. + /// + Dragon = 0xF6D5, + + /// + /// The Font Awesome "draw-polygon" icon unicode character. + /// + DrawPolygon = 0xF5EE, + + /// + /// The Font Awesome "dribbble" icon unicode character. + /// + Dribbble = 0xF17D, + + /// + /// The Font Awesome "dribbble-square" icon unicode character. + /// + DribbbleSquare = 0xF397, + + /// + /// The Font Awesome "dropbox" icon unicode character. + /// + Dropbox = 0xF16B, + + /// + /// The Font Awesome "drum" icon unicode character. + /// + Drum = 0xF569, + + /// + /// The Font Awesome "drum-steelpan" icon unicode character. + /// + DrumSteelpan = 0xF56A, + + /// + /// The Font Awesome "drumstick-bite" icon unicode character. + /// + DrumstickBite = 0xF6D7, + + /// + /// The Font Awesome "drupal" icon unicode character. + /// + Drupal = 0xF1A9, + + /// + /// The Font Awesome "dumbbell" icon unicode character. + /// + Dumbbell = 0xF44B, + + /// + /// The Font Awesome "dumpster" icon unicode character. + /// + Dumpster = 0xF793, + + /// + /// The Font Awesome "dumpster-fire" icon unicode character. + /// + DumpsterFire = 0xF794, + + /// + /// The Font Awesome "dungeon" icon unicode character. + /// + Dungeon = 0xF6D9, + + /// + /// The Font Awesome "dyalog" icon unicode character. + /// + Dyalog = 0xF399, + + /// + /// The Font Awesome "earlybirds" icon unicode character. + /// + Earlybirds = 0xF39A, + + /// + /// The Font Awesome "ebay" icon unicode character. + /// + Ebay = 0xF4F4, + + /// + /// The Font Awesome "edge" icon unicode character. + /// + Edge = 0xF282, + + /// + /// The Font Awesome "edit" icon unicode character. + /// + Edit = 0xF044, + + /// + /// The Font Awesome "egg" icon unicode character. + /// + Egg = 0xF7FB, + + /// + /// The Font Awesome "eject" icon unicode character. + /// + Eject = 0xF052, + + /// + /// The Font Awesome "elementor" icon unicode character. + /// + Elementor = 0xF43, + + /// + /// The Font Awesome "ellipsis-h" icon unicode character. + /// + EllipsisH = 0xF141, + + /// + /// The Font Awesome "ellipsis-v" icon unicode character. + /// + EllipsisV = 0xF142, + + /// + /// The Font Awesome "ello" icon unicode character. + /// + Ello = 0xF5F1, + + /// + /// The Font Awesome "ember" icon unicode character. + /// + Ember = 0xF423, + + /// + /// The Font Awesome "empire" icon unicode character. + /// + Empire = 0xF1D1, + + /// + /// The Font Awesome "envelope" icon unicode character. + /// + Envelope = 0xF0E, + + /// + /// The Font Awesome "envelope-open" icon unicode character. + /// + EnvelopeOpen = 0xF2B6, + + /// + /// The Font Awesome "envelope-open-text" icon unicode character. + /// + EnvelopeOpenText = 0xF658, + + /// + /// The Font Awesome "envelope-square" icon unicode character. + /// + EnvelopeSquare = 0xF199, + + /// + /// The Font Awesome "envira" icon unicode character. + /// + Envira = 0xF299, + + /// + /// The Font Awesome "equals" icon unicode character. + /// + Equals = 0xF52C, + + /// + /// The Font Awesome "eraser" icon unicode character. + /// + Eraser = 0xF12D, + + /// + /// The Font Awesome "erlang" icon unicode character. + /// + Erlang = 0xF39D, + + /// + /// The Font Awesome "ethereum" icon unicode character. + /// + Ethereum = 0xF42E, + + /// + /// The Font Awesome "ethernet" icon unicode character. + /// + Ethernet = 0xF796, + + /// + /// The Font Awesome "etsy" icon unicode character. + /// + Etsy = 0xF2D7, + + /// + /// The Font Awesome "euro-sign" icon unicode character. + /// + EuroSign = 0xF153, + + /// + /// The Font Awesome "evernote" icon unicode character. + /// + Evernote = 0xF839, + + /// + /// The Font Awesome "exchange-alt" icon unicode character. + /// + ExchangeAlt = 0xF362, + + /// + /// The Font Awesome "exclamation" icon unicode character. + /// + Exclamation = 0xF12A, + + /// + /// The Font Awesome "exclamation-circle" icon unicode character. + /// + ExclamationCircle = 0xF06A, + + /// + /// The Font Awesome "exclamation-triangle" icon unicode character. + /// + ExclamationTriangle = 0xF071, + + /// + /// The Font Awesome "expand" icon unicode character. + /// + Expand = 0xF065, + + /// + /// The Font Awesome "expand-alt" icon unicode character. + /// + ExpandAlt = 0xF424, + + /// + /// The Font Awesome "expand-arrows-alt" icon unicode character. + /// + ExpandArrowsAlt = 0xF31E, + + /// + /// The Font Awesome "expeditedssl" icon unicode character. + /// + Expeditedssl = 0xF23E, + + /// + /// The Font Awesome "external-link-alt" icon unicode character. + /// + ExternalLinkAlt = 0xF35D, + + /// + /// The Font Awesome "external-link-square-alt" icon unicode character. + /// + ExternalLinkSquareAlt = 0xF36, + + /// + /// The Font Awesome "eye" icon unicode character. + /// + Eye = 0xF06E, + + /// + /// The Font Awesome "eye-dropper" icon unicode character. + /// + EyeDropper = 0xF1FB, + + /// + /// The Font Awesome "eye-slash" icon unicode character. + /// + EyeSlash = 0xF07, + + /// + /// The Font Awesome "facebook" icon unicode character. + /// + Facebook = 0xF09A, + + /// + /// The Font Awesome "facebook-f" icon unicode character. + /// + FacebookF = 0xF39E, + + /// + /// The Font Awesome "facebook-messenger" icon unicode character. + /// + FacebookMessenger = 0xF39F, + + /// + /// The Font Awesome "facebook-square" icon unicode character. + /// + FacebookSquare = 0xF082, + + /// + /// The Font Awesome "fan" icon unicode character. + /// + Fan = 0xF863, + + /// + /// The Font Awesome "fantasy-flight-games" icon unicode character. + /// + FantasyFlightGames = 0xF6DC, + + /// + /// The Font Awesome "fast-backward" icon unicode character. + /// + FastBackward = 0xF049, + + /// + /// The Font Awesome "fast-forward" icon unicode character. + /// + FastForward = 0xF05, + + /// + /// The Font Awesome "fax" icon unicode character. + /// + Fax = 0xF1AC, + + /// + /// The Font Awesome "feather" icon unicode character. + /// + Feather = 0xF52D, + + /// + /// The Font Awesome "feather-alt" icon unicode character. + /// + FeatherAlt = 0xF56B, + + /// + /// The Font Awesome "fedex" icon unicode character. + /// + Fedex = 0xF797, + + /// + /// The Font Awesome "fedora" icon unicode character. + /// + Fedora = 0xF798, + + /// + /// The Font Awesome "female" icon unicode character. + /// + Female = 0xF182, + + /// + /// The Font Awesome "fighter-jet" icon unicode character. + /// + FighterJet = 0xF0FB, + + /// + /// The Font Awesome "figma" icon unicode character. + /// + Figma = 0xF799, + + /// + /// The Font Awesome "file" icon unicode character. + /// + File = 0xF15B, + + /// + /// The Font Awesome "file-alt" icon unicode character. + /// + FileAlt = 0xF15C, + + /// + /// The Font Awesome "file-archive" icon unicode character. + /// + FileArchive = 0xF1C6, + + /// + /// The Font Awesome "file-audio" icon unicode character. + /// + FileAudio = 0xF1C7, + + /// + /// The Font Awesome "file-code" icon unicode character. + /// + FileCode = 0xF1C9, + + /// + /// The Font Awesome "file-contract" icon unicode character. + /// + FileContract = 0xF56C, + + /// + /// The Font Awesome "file-csv" icon unicode character. + /// + FileCsv = 0xF6DD, + + /// + /// The Font Awesome "file-download" icon unicode character. + /// + FileDownload = 0xF56D, + + /// + /// The Font Awesome "file-excel" icon unicode character. + /// + FileExcel = 0xF1C3, + + /// + /// The Font Awesome "file-export" icon unicode character. + /// + FileExport = 0xF56E, + + /// + /// The Font Awesome "file-image" icon unicode character. + /// + FileImage = 0xF1C5, + + /// + /// The Font Awesome "file-import" icon unicode character. + /// + FileImport = 0xF56F, + + /// + /// The Font Awesome "file-invoice" icon unicode character. + /// + FileInvoice = 0xF57, + + /// + /// The Font Awesome "file-invoice-dollar" icon unicode character. + /// + FileInvoiceDollar = 0xF571, + + /// + /// The Font Awesome "file-medical" icon unicode character. + /// + FileMedical = 0xF477, + + /// + /// The Font Awesome "file-medical-alt" icon unicode character. + /// + FileMedicalAlt = 0xF478, + + /// + /// The Font Awesome "file-pdf" icon unicode character. + /// + FilePdf = 0xF1C1, + + /// + /// The Font Awesome "file-powerpoint" icon unicode character. + /// + FilePowerpoint = 0xF1C4, + + /// + /// The Font Awesome "file-prescription" icon unicode character. + /// + FilePrescription = 0xF572, + + /// + /// The Font Awesome "file-signature" icon unicode character. + /// + FileSignature = 0xF573, + + /// + /// The Font Awesome "file-upload" icon unicode character. + /// + FileUpload = 0xF574, + + /// + /// The Font Awesome "file-video" icon unicode character. + /// + FileVideo = 0xF1C8, + + /// + /// The Font Awesome "file-word" icon unicode character. + /// + FileWord = 0xF1C2, + + /// + /// The Font Awesome "fill" icon unicode character. + /// + Fill = 0xF575, + + /// + /// The Font Awesome "fill-drip" icon unicode character. + /// + FillDrip = 0xF576, + + /// + /// The Font Awesome "film" icon unicode character. + /// + Film = 0xF008, + + /// + /// The Font Awesome "filter" icon unicode character. + /// + Filter = 0xF0B, + + /// + /// The Font Awesome "fingerprint" icon unicode character. + /// + Fingerprint = 0xF577, + + /// + /// The Font Awesome "fire" icon unicode character. + /// + Fire = 0xF06D, + + /// + /// The Font Awesome "fire-alt" icon unicode character. + /// + FireAlt = 0xF7E4, + + /// + /// The Font Awesome "fire-extinguisher" icon unicode character. + /// + FireExtinguisher = 0xF134, + + /// + /// The Font Awesome "firefox" icon unicode character. + /// + Firefox = 0xF269, + + /// + /// The Font Awesome "firefox-browser" icon unicode character. + /// + FirefoxBrowser = 0xF907, + + /// + /// The Font Awesome "first-aid" icon unicode character. + /// + FirstAid = 0xF479, + + /// + /// The Font Awesome "firstdraft" icon unicode character. + /// + Firstdraft = 0xF3A1, + + /// + /// The Font Awesome "first-order" icon unicode character. + /// + FirstOrder = 0xF2B, + + /// + /// The Font Awesome "first-order-alt" icon unicode character. + /// + FirstOrderAlt = 0xF50A, + + /// + /// The Font Awesome "fish" icon unicode character. + /// + Fish = 0xF578, + + /// + /// The Font Awesome "fist-raised" icon unicode character. + /// + FistRaised = 0xF6DE, + + /// + /// The Font Awesome "flag" icon unicode character. + /// + Flag = 0xF024, + + /// + /// The Font Awesome "flag-checkered" icon unicode character. + /// + FlagCheckered = 0xF11E, + + /// + /// The Font Awesome "flag-usa" icon unicode character. + /// + FlagUsa = 0xF74D, + + /// + /// The Font Awesome "flask" icon unicode character. + /// + Flask = 0xF0C3, + + /// + /// The Font Awesome "flickr" icon unicode character. + /// + Flickr = 0xF16E, + + /// + /// The Font Awesome "flipboard" icon unicode character. + /// + Flipboard = 0xF44D, + + /// + /// The Font Awesome "flushed" icon unicode character. + /// + Flushed = 0xF579, + + /// + /// The Font Awesome "fly" icon unicode character. + /// + Fly = 0xF417, + + /// + /// The Font Awesome "folder" icon unicode character. + /// + Folder = 0xF07B, + + /// + /// The Font Awesome "folder-minus" icon unicode character. + /// + FolderMinus = 0xF65D, + + /// + /// The Font Awesome "folder-open" icon unicode character. + /// + FolderOpen = 0xF07C, + + /// + /// The Font Awesome "folder-plus" icon unicode character. + /// + FolderPlus = 0xF65E, + + /// + /// The Font Awesome "font" icon unicode character. + /// + Font = 0xF031, + + /// + /// The Font Awesome "font-awesome" icon unicode character. + /// + FontAwesome = 0xF2B4, + + /// + /// The Font Awesome "font-awesome-alt" icon unicode character. + /// + FontAwesomeAlt = 0xF35C, + + /// + /// The Font Awesome "font-awesome-flag" icon unicode character. + /// + FontAwesomeFlag = 0xF425, + + /// + /// The Font Awesome "font-awesome-logo-full" icon unicode character. + /// + FontAwesomeLogoFull = 0xF4E6, + + /// + /// The Font Awesome "fonticons" icon unicode character. + /// + Fonticons = 0xF28, + + /// + /// The Font Awesome "fonticons-fi" icon unicode character. + /// + FonticonsFi = 0xF3A2, + + /// + /// The Font Awesome "football-ball" icon unicode character. + /// + FootballBall = 0xF44E, + + /// + /// The Font Awesome "fort-awesome" icon unicode character. + /// + FortAwesome = 0xF286, + + /// + /// The Font Awesome "fort-awesome-alt" icon unicode character. + /// + FortAwesomeAlt = 0xF3A3, + + /// + /// The Font Awesome "forumbee" icon unicode character. + /// + Forumbee = 0xF211, + + /// + /// The Font Awesome "forward" icon unicode character. + /// + Forward = 0xF04E, + + /// + /// The Font Awesome "foursquare" icon unicode character. + /// + Foursquare = 0xF18, + + /// + /// The Font Awesome "freebsd" icon unicode character. + /// + Freebsd = 0xF3A4, + + /// + /// The Font Awesome "free-code-camp" icon unicode character. + /// + FreeCodeCamp = 0xF2C5, + + /// + /// The Font Awesome "frog" icon unicode character. + /// + Frog = 0xF52E, + + /// + /// The Font Awesome "frown" icon unicode character. + /// + Frown = 0xF119, + + /// + /// The Font Awesome "frown-open" icon unicode character. + /// + FrownOpen = 0xF57A, + + /// + /// The Font Awesome "fulcrum" icon unicode character. + /// + Fulcrum = 0xF50B, + + /// + /// The Font Awesome "funnel-dollar" icon unicode character. + /// + FunnelDollar = 0xF662, + + /// + /// The Font Awesome "futbol" icon unicode character. + /// + Futbol = 0xF1E3, + + /// + /// The Font Awesome "galactic-republic" icon unicode character. + /// + GalacticRepublic = 0xF50C, + + /// + /// The Font Awesome "galactic-senate" icon unicode character. + /// + GalacticSenate = 0xF50D, + + /// + /// The Font Awesome "gamepad" icon unicode character. + /// + Gamepad = 0xF11B, + + /// + /// The Font Awesome "gas-pump" icon unicode character. + /// + GasPump = 0xF52F, + + /// + /// The Font Awesome "gavel" icon unicode character. + /// + Gavel = 0xF0E3, + + /// + /// The Font Awesome "gem" icon unicode character. + /// + Gem = 0xF3A5, + + /// + /// The Font Awesome "genderless" icon unicode character. + /// + Genderless = 0xF22D, + + /// + /// The Font Awesome "get-pocket" icon unicode character. + /// + GetPocket = 0xF265, + + /// + /// The Font Awesome "gg" icon unicode character. + /// + Gg = 0xF26, + + /// + /// The Font Awesome "gg-circle" icon unicode character. + /// + GgCircle = 0xF261, + + /// + /// The Font Awesome "ghost" icon unicode character. + /// + Ghost = 0xF6E2, + + /// + /// The Font Awesome "gift" icon unicode character. + /// + Gift = 0xF06B, + + /// + /// The Font Awesome "gifts" icon unicode character. + /// + Gifts = 0xF79C, + + /// + /// The Font Awesome "git" icon unicode character. + /// + Git = 0xF1D3, + + /// + /// The Font Awesome "git-alt" icon unicode character. + /// + GitAlt = 0xF841, + + /// + /// The Font Awesome "github" icon unicode character. + /// + Github = 0xF09B, + + /// + /// The Font Awesome "github-alt" icon unicode character. + /// + GithubAlt = 0xF113, + + /// + /// The Font Awesome "github-square" icon unicode character. + /// + GithubSquare = 0xF092, + + /// + /// The Font Awesome "gitkraken" icon unicode character. + /// + Gitkraken = 0xF3A6, + + /// + /// The Font Awesome "gitlab" icon unicode character. + /// + Gitlab = 0xF296, + + /// + /// The Font Awesome "git-square" icon unicode character. + /// + GitSquare = 0xF1D2, + + /// + /// The Font Awesome "gitter" icon unicode character. + /// + Gitter = 0xF426, + + /// + /// The Font Awesome "glass-cheers" icon unicode character. + /// + GlassCheers = 0xF79F, + + /// + /// The Font Awesome "glasses" icon unicode character. + /// + Glasses = 0xF53, + + /// + /// The Font Awesome "glass-martini" icon unicode character. + /// + GlassMartini = 0xF, + + /// + /// The Font Awesome "glass-martini-alt" icon unicode character. + /// + GlassMartiniAlt = 0xF57B, + + /// + /// The Font Awesome "glass-whiskey" icon unicode character. + /// + GlassWhiskey = 0xF7A, + + /// + /// The Font Awesome "glide" icon unicode character. + /// + Glide = 0xF2A5, + + /// + /// The Font Awesome "glide-g" icon unicode character. + /// + GlideG = 0xF2A6, + + /// + /// The Font Awesome "globe" icon unicode character. + /// + Globe = 0xF0AC, + + /// + /// The Font Awesome "globe-africa" icon unicode character. + /// + GlobeAfrica = 0xF57C, + + /// + /// The Font Awesome "globe-americas" icon unicode character. + /// + GlobeAmericas = 0xF57D, + + /// + /// The Font Awesome "globe-asia" icon unicode character. + /// + GlobeAsia = 0xF57E, + + /// + /// The Font Awesome "globe-europe" icon unicode character. + /// + GlobeEurope = 0xF7A2, + + /// + /// The Font Awesome "gofore" icon unicode character. + /// + Gofore = 0xF3A7, + + /// + /// The Font Awesome "golf-ball" icon unicode character. + /// + GolfBall = 0xF45, + + /// + /// The Font Awesome "goodreads" icon unicode character. + /// + Goodreads = 0xF3A8, + + /// + /// The Font Awesome "goodreads-g" icon unicode character. + /// + GoodreadsG = 0xF3A9, + + /// + /// The Font Awesome "google" icon unicode character. + /// + Google = 0xF1A, + + /// + /// The Font Awesome "google-drive" icon unicode character. + /// + GoogleDrive = 0xF3AA, + + /// + /// The Font Awesome "google-play" icon unicode character. + /// + GooglePlay = 0xF3AB, + + /// + /// The Font Awesome "google-plus" icon unicode character. + /// + GooglePlus = 0xF2B3, + + /// + /// The Font Awesome "google-plus-g" icon unicode character. + /// + GooglePlusG = 0xF0D5, + + /// + /// The Font Awesome "google-plus-square" icon unicode character. + /// + GooglePlusSquare = 0xF0D4, + + /// + /// The Font Awesome "google-wallet" icon unicode character. + /// + GoogleWallet = 0xF1EE, + + /// + /// The Font Awesome "gopuram" icon unicode character. + /// + Gopuram = 0xF664, + + /// + /// The Font Awesome "graduation-cap" icon unicode character. + /// + GraduationCap = 0xF19D, + + /// + /// The Font Awesome "gratipay" icon unicode character. + /// + Gratipay = 0xF184, + + /// + /// The Font Awesome "grav" icon unicode character. + /// + Grav = 0xF2D6, + + /// + /// The Font Awesome "greater-than" icon unicode character. + /// + GreaterThan = 0xF531, + + /// + /// The Font Awesome "greater-than-equal" icon unicode character. + /// + GreaterThanEqual = 0xF532, + + /// + /// The Font Awesome "grimace" icon unicode character. + /// + Grimace = 0xF57F, + + /// + /// The Font Awesome "grin" icon unicode character. + /// + Grin = 0xF58, + + /// + /// The Font Awesome "grin-alt" icon unicode character. + /// + GrinAlt = 0xF581, + + /// + /// The Font Awesome "grin-beam" icon unicode character. + /// + GrinBeam = 0xF582, + + /// + /// The Font Awesome "grin-beam-sweat" icon unicode character. + /// + GrinBeamSweat = 0xF583, + + /// + /// The Font Awesome "grin-hearts" icon unicode character. + /// + GrinHearts = 0xF584, + + /// + /// The Font Awesome "grin-squint" icon unicode character. + /// + GrinSquint = 0xF585, + + /// + /// The Font Awesome "grin-squint-tears" icon unicode character. + /// + GrinSquintTears = 0xF586, + + /// + /// The Font Awesome "grin-stars" icon unicode character. + /// + GrinStars = 0xF587, + + /// + /// The Font Awesome "grin-tears" icon unicode character. + /// + GrinTears = 0xF588, + + /// + /// The Font Awesome "grin-tongue" icon unicode character. + /// + GrinTongue = 0xF589, + + /// + /// The Font Awesome "grin-tongue-squint" icon unicode character. + /// + GrinTongueSquint = 0xF58A, + + /// + /// The Font Awesome "grin-tongue-wink" icon unicode character. + /// + GrinTongueWink = 0xF58B, + + /// + /// The Font Awesome "grin-wink" icon unicode character. + /// + GrinWink = 0xF58C, + + /// + /// The Font Awesome "gripfire" icon unicode character. + /// + Gripfire = 0xF3AC, + + /// + /// The Font Awesome "grip-horizontal" icon unicode character. + /// + GripHorizontal = 0xF58D, + + /// + /// The Font Awesome "grip-lines" icon unicode character. + /// + GripLines = 0xF7A4, + + /// + /// The Font Awesome "grip-lines-vertical" icon unicode character. + /// + GripLinesVertical = 0xF7A5, + + /// + /// The Font Awesome "grip-vertical" icon unicode character. + /// + GripVertical = 0xF58E, + + /// + /// The Font Awesome "grunt" icon unicode character. + /// + Grunt = 0xF3AD, + + /// + /// The Font Awesome "guitar" icon unicode character. + /// + Guitar = 0xF7A6, + + /// + /// The Font Awesome "gulp" icon unicode character. + /// + Gulp = 0xF3AE, + + /// + /// The Font Awesome "hacker-news" icon unicode character. + /// + HackerNews = 0xF1D4, + + /// + /// The Font Awesome "hacker-news-square" icon unicode character. + /// + HackerNewsSquare = 0xF3AF, + + /// + /// The Font Awesome "hackerrank" icon unicode character. + /// + Hackerrank = 0xF5F7, + + /// + /// The Font Awesome "hamburger" icon unicode character. + /// + Hamburger = 0xF805, + + /// + /// The Font Awesome "hammer" icon unicode character. + /// + Hammer = 0xF6E3, + + /// + /// The Font Awesome "hamsa" icon unicode character. + /// + Hamsa = 0xF665, + + /// + /// The Font Awesome "hand-holding" icon unicode character. + /// + HandHolding = 0xF4BD, + + /// + /// The Font Awesome "hand-holding-heart" icon unicode character. + /// + HandHoldingHeart = 0xF4BE, + + /// + /// The Font Awesome "hand-holding-usd" icon unicode character. + /// + HandHoldingUsd = 0xF4C, + + /// + /// The Font Awesome "hand-lizard" icon unicode character. + /// + HandLizard = 0xF258, + + /// + /// The Font Awesome "hand-middle-finger" icon unicode character. + /// + HandMiddleFinger = 0xF806, + + /// + /// The Font Awesome "hand-paper" icon unicode character. + /// + HandPaper = 0xF256, + + /// + /// The Font Awesome "hand-peace" icon unicode character. + /// + HandPeace = 0xF25B, + + /// + /// The Font Awesome "hand-point-down" icon unicode character. + /// + HandPointDown = 0xF0A7, + + /// + /// The Font Awesome "hand-pointer" icon unicode character. + /// + HandPointer = 0xF25A, + + /// + /// The Font Awesome "hand-point-left" icon unicode character. + /// + HandPointLeft = 0xF0A5, + + /// + /// The Font Awesome "hand-point-right" icon unicode character. + /// + HandPointRight = 0xF0A4, + + /// + /// The Font Awesome "hand-point-up" icon unicode character. + /// + HandPointUp = 0xF0A6, + + /// + /// The Font Awesome "hand-rock" icon unicode character. + /// + HandRock = 0xF255, + + /// + /// The Font Awesome "hands" icon unicode character. + /// + Hands = 0xF4C2, + + /// + /// The Font Awesome "hand-scissors" icon unicode character. + /// + HandScissors = 0xF257, + + /// + /// The Font Awesome "handshake" icon unicode character. + /// + Handshake = 0xF2B5, + + /// + /// The Font Awesome "hands-helping" icon unicode character. + /// + HandsHelping = 0xF4C4, + + /// + /// The Font Awesome "hand-spock" icon unicode character. + /// + HandSpock = 0xF259, + + /// + /// The Font Awesome "hanukiah" icon unicode character. + /// + Hanukiah = 0xF6E6, + + /// + /// The Font Awesome "hard-hat" icon unicode character. + /// + HardHat = 0xF807, + + /// + /// The Font Awesome "hashtag" icon unicode character. + /// + Hashtag = 0xF292, + + /// + /// The Font Awesome "hat-cowboy" icon unicode character. + /// + HatCowboy = 0xF8C, + + /// + /// The Font Awesome "hat-cowboy-side" icon unicode character. + /// + HatCowboySide = 0xF8C1, + + /// + /// The Font Awesome "hat-wizard" icon unicode character. + /// + HatWizard = 0xF6E8, + + /// + /// The Font Awesome "hdd" icon unicode character. + /// + Hdd = 0xF0A, + + /// + /// The Font Awesome "heading" icon unicode character. + /// + Heading = 0xF1DC, + + /// + /// The Font Awesome "headphones" icon unicode character. + /// + Headphones = 0xF025, + + /// + /// The Font Awesome "headphones-alt" icon unicode character. + /// + HeadphonesAlt = 0xF58F, + + /// + /// The Font Awesome "headset" icon unicode character. + /// + Headset = 0xF59, + + /// + /// The Font Awesome "heart" icon unicode character. + /// + Heart = 0xF004, + + /// + /// The Font Awesome "heartbeat" icon unicode character. + /// + Heartbeat = 0xF21E, + + /// + /// The Font Awesome "heart-broken" icon unicode character. + /// + HeartBroken = 0xF7A9, + + /// + /// The Font Awesome "helicopter" icon unicode character. + /// + Helicopter = 0xF533, + + /// + /// The Font Awesome "highlighter" icon unicode character. + /// + Highlighter = 0xF591, + + /// + /// The Font Awesome "hiking" icon unicode character. + /// + Hiking = 0xF6EC, + + /// + /// The Font Awesome "hippo" icon unicode character. + /// + Hippo = 0xF6ED, + + /// + /// The Font Awesome "hips" icon unicode character. + /// + Hips = 0xF452, + + /// + /// The Font Awesome "hire-a-helper" icon unicode character. + /// + HireAHelper = 0xF3B, + + /// + /// The Font Awesome "history" icon unicode character. + /// + History = 0xF1DA, + + /// + /// The Font Awesome "hockey-puck" icon unicode character. + /// + HockeyPuck = 0xF453, + + /// + /// The Font Awesome "holly-berry" icon unicode character. + /// + HollyBerry = 0xF7AA, + + /// + /// The Font Awesome "home" icon unicode character. + /// + Home = 0xF015, + + /// + /// The Font Awesome "hooli" icon unicode character. + /// + Hooli = 0xF427, + + /// + /// The Font Awesome "hornbill" icon unicode character. + /// + Hornbill = 0xF592, + + /// + /// The Font Awesome "horse" icon unicode character. + /// + Horse = 0xF6F, + + /// + /// The Font Awesome "horse-head" icon unicode character. + /// + HorseHead = 0xF7AB, + + /// + /// The Font Awesome "hospital" icon unicode character. + /// + Hospital = 0xF0F8, + + /// + /// The Font Awesome "hospital-alt" icon unicode character. + /// + HospitalAlt = 0xF47D, + + /// + /// The Font Awesome "hospital-symbol" icon unicode character. + /// + HospitalSymbol = 0xF47E, + + /// + /// The Font Awesome "hotdog" icon unicode character. + /// + Hotdog = 0xF80F, + + /// + /// The Font Awesome "hotel" icon unicode character. + /// + Hotel = 0xF594, + + /// + /// The Font Awesome "hotjar" icon unicode character. + /// + Hotjar = 0xF3B1, + + /// + /// The Font Awesome "hot-tub" icon unicode character. + /// + HotTub = 0xF593, + + /// + /// The Font Awesome "hourglass" icon unicode character. + /// + Hourglass = 0xF254, + + /// + /// The Font Awesome "hourglass-end" icon unicode character. + /// + HourglassEnd = 0xF253, + + /// + /// The Font Awesome "hourglass-half" icon unicode character. + /// + HourglassHalf = 0xF252, + + /// + /// The Font Awesome "hourglass-start" icon unicode character. + /// + HourglassStart = 0xF251, + + /// + /// The Font Awesome "house-damage" icon unicode character. + /// + HouseDamage = 0xF6F1, + + /// + /// The Font Awesome "houzz" icon unicode character. + /// + Houzz = 0xF27C, + + /// + /// The Font Awesome "hryvnia" icon unicode character. + /// + Hryvnia = 0xF6F2, + + /// + /// The Font Awesome "h-square" icon unicode character. + /// + HSquare = 0xF0FD, + + /// + /// The Font Awesome "html5" icon unicode character. + /// + Html5 = 0xF13B, + + /// + /// The Font Awesome "hubspot" icon unicode character. + /// + Hubspot = 0xF3B2, + + /// + /// The Font Awesome "ice-cream" icon unicode character. + /// + IceCream = 0xF81, + + /// + /// The Font Awesome "icicles" icon unicode character. + /// + Icicles = 0xF7AD, + + /// + /// The Font Awesome "icons" icon unicode character. + /// + Icons = 0xF86D, + + /// + /// The Font Awesome "i-cursor" icon unicode character. + /// + ICursor = 0xF246, + + /// + /// The Font Awesome "id-badge" icon unicode character. + /// + IdBadge = 0xF2C1, + + /// + /// The Font Awesome "id-card" icon unicode character. + /// + IdCard = 0xF2C2, + + /// + /// The Font Awesome "id-card-alt" icon unicode character. + /// + IdCardAlt = 0xF47F, + + /// + /// The Font Awesome "ideal" icon unicode character. + /// + Ideal = 0xF913, + + /// + /// The Font Awesome "igloo" icon unicode character. + /// + Igloo = 0xF7AE, + + /// + /// The Font Awesome "image" icon unicode character. + /// + Image = 0xF03E, + + /// + /// The Font Awesome "images" icon unicode character. + /// + Images = 0xF302, + + /// + /// The Font Awesome "imdb" icon unicode character. + /// + Imdb = 0xF2D8, + + /// + /// The Font Awesome "inbox" icon unicode character. + /// + Inbox = 0xF01C, + + /// + /// The Font Awesome "indent" icon unicode character. + /// + Indent = 0xF03C, + + /// + /// The Font Awesome "industry" icon unicode character. + /// + Industry = 0xF275, + + /// + /// The Font Awesome "infinity" icon unicode character. + /// + Infinity = 0xF534, + + /// + /// The Font Awesome "info" icon unicode character. + /// + Info = 0xF129, + + /// + /// The Font Awesome "info-circle" icon unicode character. + /// + InfoCircle = 0xF05A, + + /// + /// The Font Awesome "instagram" icon unicode character. + /// + Instagram = 0xF16D, + + /// + /// The Font Awesome "instagram-square" icon unicode character. + /// + InstagramSquare = 0xF955, + + /// + /// The Font Awesome "intercom" icon unicode character. + /// + Intercom = 0xF7AF, + + /// + /// The Font Awesome "internet-explorer" icon unicode character. + /// + InternetExplorer = 0xF26B, + + /// + /// The Font Awesome "invision" icon unicode character. + /// + Invision = 0xF7B, + + /// + /// The Font Awesome "ioxhost" icon unicode character. + /// + Ioxhost = 0xF208, + + /// + /// The Font Awesome "italic" icon unicode character. + /// + Italic = 0xF033, + + /// + /// The Font Awesome "itch-io" icon unicode character. + /// + ItchIo = 0xF83A, + + /// + /// The Font Awesome "itunes" icon unicode character. + /// + Itunes = 0xF3B4, + + /// + /// The Font Awesome "itunes-note" icon unicode character. + /// + ItunesNote = 0xF3B5, + + /// + /// The Font Awesome "java" icon unicode character. + /// + Java = 0xF4E4, + + /// + /// The Font Awesome "jedi" icon unicode character. + /// + Jedi = 0xF669, + + /// + /// The Font Awesome "jedi-order" icon unicode character. + /// + JediOrder = 0xF50E, + + /// + /// The Font Awesome "jenkins" icon unicode character. + /// + Jenkins = 0xF3B6, + + /// + /// The Font Awesome "jira" icon unicode character. + /// + Jira = 0xF7B1, + + /// + /// The Font Awesome "joget" icon unicode character. + /// + Joget = 0xF3B7, + + /// + /// The Font Awesome "joint" icon unicode character. + /// + Joint = 0xF595, + + /// + /// The Font Awesome "joomla" icon unicode character. + /// + Joomla = 0xF1AA, + + /// + /// The Font Awesome "journal-whills" icon unicode character. + /// + JournalWhills = 0xF66A, + + /// + /// The Font Awesome "js" icon unicode character. + /// + Js = 0xF3B8, + + /// + /// The Font Awesome "jsfiddle" icon unicode character. + /// + Jsfiddle = 0xF1CC, + + /// + /// The Font Awesome "js-square" icon unicode character. + /// + JsSquare = 0xF3B9, + + /// + /// The Font Awesome "kaaba" icon unicode character. + /// + Kaaba = 0xF66B, + + /// + /// The Font Awesome "kaggle" icon unicode character. + /// + Kaggle = 0xF5FA, + + /// + /// The Font Awesome "key" icon unicode character. + /// + Key = 0xF084, + + /// + /// The Font Awesome "keybase" icon unicode character. + /// + Keybase = 0xF4F5, + + /// + /// The Font Awesome "keyboard" icon unicode character. + /// + Keyboard = 0xF11C, + + /// + /// The Font Awesome "keycdn" icon unicode character. + /// + Keycdn = 0xF3BA, + + /// + /// The Font Awesome "khanda" icon unicode character. + /// + Khanda = 0xF66D, + + /// + /// The Font Awesome "kickstarter" icon unicode character. + /// + Kickstarter = 0xF3BB, + + /// + /// The Font Awesome "kickstarter-k" icon unicode character. + /// + KickstarterK = 0xF3BC, + + /// + /// The Font Awesome "kiss" icon unicode character. + /// + Kiss = 0xF596, + + /// + /// The Font Awesome "kiss-beam" icon unicode character. + /// + KissBeam = 0xF597, + + /// + /// The Font Awesome "kiss-wink-heart" icon unicode character. + /// + KissWinkHeart = 0xF598, + + /// + /// The Font Awesome "kiwi-bird" icon unicode character. + /// + KiwiBird = 0xF535, + + /// + /// The Font Awesome "korvue" icon unicode character. + /// + Korvue = 0xF42F, + + /// + /// The Font Awesome "landmark" icon unicode character. + /// + Landmark = 0xF66F, + + /// + /// The Font Awesome "language" icon unicode character. + /// + Language = 0xF1AB, + + /// + /// The Font Awesome "laptop" icon unicode character. + /// + Laptop = 0xF109, + + /// + /// The Font Awesome "laptop-code" icon unicode character. + /// + LaptopCode = 0xF5FC, + + /// + /// The Font Awesome "laptop-medical" icon unicode character. + /// + LaptopMedical = 0xF812, + + /// + /// The Font Awesome "laravel" icon unicode character. + /// + Laravel = 0xF3BD, + + /// + /// The Font Awesome "lastfm" icon unicode character. + /// + Lastfm = 0xF202, + + /// + /// The Font Awesome "lastfm-square" icon unicode character. + /// + LastfmSquare = 0xF203, + + /// + /// The Font Awesome "laugh" icon unicode character. + /// + Laugh = 0xF599, + + /// + /// The Font Awesome "laugh-beam" icon unicode character. + /// + LaughBeam = 0xF59A, + + /// + /// The Font Awesome "laugh-squint" icon unicode character. + /// + LaughSquint = 0xF59B, + + /// + /// The Font Awesome "laugh-wink" icon unicode character. + /// + LaughWink = 0xF59C, + + /// + /// The Font Awesome "layer-group" icon unicode character. + /// + LayerGroup = 0xF5FD, + + /// + /// The Font Awesome "leaf" icon unicode character. + /// + Leaf = 0xF06C, + + /// + /// The Font Awesome "leanpub" icon unicode character. + /// + Leanpub = 0xF212, + + /// + /// The Font Awesome "lemon" icon unicode character. + /// + Lemon = 0xF094, + + /// + /// The Font Awesome "less" icon unicode character. + /// + Less = 0xF41D, + + /// + /// The Font Awesome "less-than" icon unicode character. + /// + LessThan = 0xF536, + + /// + /// The Font Awesome "less-than-equal" icon unicode character. + /// + LessThanEqual = 0xF537, + + /// + /// The Font Awesome "level-down-alt" icon unicode character. + /// + LevelDownAlt = 0xF3BE, + + /// + /// The Font Awesome "level-up-alt" icon unicode character. + /// + LevelUpAlt = 0xF3BF, + + /// + /// The Font Awesome "life-ring" icon unicode character. + /// + LifeRing = 0xF1CD, + + /// + /// The Font Awesome "lightbulb" icon unicode character. + /// + Lightbulb = 0xF0EB, + + /// + /// The Font Awesome "line" icon unicode character. + /// + Line = 0xF3C, + + /// + /// The Font Awesome "link" icon unicode character. + /// + Link = 0xF0C1, + + /// + /// The Font Awesome "linkedin" icon unicode character. + /// + Linkedin = 0xF08C, + + /// + /// The Font Awesome "linkedin-in" icon unicode character. + /// + LinkedinIn = 0xF0E1, + + /// + /// The Font Awesome "linode" icon unicode character. + /// + Linode = 0xF2B8, + + /// + /// The Font Awesome "linux" icon unicode character. + /// + Linux = 0xF17C, + + /// + /// The Font Awesome "lira-sign" icon unicode character. + /// + LiraSign = 0xF195, + + /// + /// The Font Awesome "list" icon unicode character. + /// + List = 0xF03A, + + /// + /// The Font Awesome "list-alt" icon unicode character. + /// + ListAlt = 0xF022, + + /// + /// The Font Awesome "list-ol" icon unicode character. + /// + ListOl = 0xF0CB, + + /// + /// The Font Awesome "list-ul" icon unicode character. + /// + ListUl = 0xF0CA, + + /// + /// The Font Awesome "location-arrow" icon unicode character. + /// + LocationArrow = 0xF124, + + /// + /// The Font Awesome "lock" icon unicode character. + /// + Lock = 0xF023, + + /// + /// The Font Awesome "lock-open" icon unicode character. + /// + LockOpen = 0xF3C1, + + /// + /// The Font Awesome "long-arrow-alt-down" icon unicode character. + /// + LongArrowAltDown = 0xF309, + + /// + /// The Font Awesome "long-arrow-alt-left" icon unicode character. + /// + LongArrowAltLeft = 0xF30A, + + /// + /// The Font Awesome "long-arrow-alt-right" icon unicode character. + /// + LongArrowAltRight = 0xF30B, + + /// + /// The Font Awesome "long-arrow-alt-up" icon unicode character. + /// + LongArrowAltUp = 0xF30C, + + /// + /// The Font Awesome "low-vision" icon unicode character. + /// + LowVision = 0xF2A8, + + /// + /// The Font Awesome "luggage-cart" icon unicode character. + /// + LuggageCart = 0xF59D, + + /// + /// The Font Awesome "lyft" icon unicode character. + /// + Lyft = 0xF3C3, + + /// + /// The Font Awesome "magento" icon unicode character. + /// + Magento = 0xF3C4, + + /// + /// The Font Awesome "magic" icon unicode character. + /// + Magic = 0xF0D, + + /// + /// The Font Awesome "magnet" icon unicode character. + /// + Magnet = 0xF076, + + /// + /// The Font Awesome "mail-bulk" icon unicode character. + /// + MailBulk = 0xF674, + + /// + /// The Font Awesome "mailchimp" icon unicode character. + /// + Mailchimp = 0xF59E, + + /// + /// The Font Awesome "male" icon unicode character. + /// + Male = 0xF183, + + /// + /// The Font Awesome "mandalorian" icon unicode character. + /// + Mandalorian = 0xF50F, + + /// + /// The Font Awesome "map" icon unicode character. + /// + Map = 0xF279, + + /// + /// The Font Awesome "map-marked" icon unicode character. + /// + MapMarked = 0xF59F, + + /// + /// The Font Awesome "map-marked-alt" icon unicode character. + /// + MapMarkedAlt = 0xF5A, + + /// + /// The Font Awesome "map-marker" icon unicode character. + /// + MapMarker = 0xF041, + + /// + /// The Font Awesome "map-marker-alt" icon unicode character. + /// + MapMarkerAlt = 0xF3C5, + + /// + /// The Font Awesome "map-pin" icon unicode character. + /// + MapPin = 0xF276, + + /// + /// The Font Awesome "map-signs" icon unicode character. + /// + MapSigns = 0xF277, + + /// + /// The Font Awesome "markdown" icon unicode character. + /// + Markdown = 0xF60F, + + /// + /// The Font Awesome "marker" icon unicode character. + /// + Marker = 0xF5A1, + + /// + /// The Font Awesome "mars" icon unicode character. + /// + Mars = 0xF222, + + /// + /// The Font Awesome "mars-double" icon unicode character. + /// + MarsDouble = 0xF227, + + /// + /// The Font Awesome "mars-stroke" icon unicode character. + /// + MarsStroke = 0xF229, + + /// + /// The Font Awesome "mars-stroke-h" icon unicode character. + /// + MarsStrokeH = 0xF22B, + + /// + /// The Font Awesome "mars-stroke-v" icon unicode character. + /// + MarsStrokeV = 0xF22A, + + /// + /// The Font Awesome "mask" icon unicode character. + /// + Mask = 0xF6FA, + + /// + /// The Font Awesome "mastodon" icon unicode character. + /// + Mastodon = 0xF4F6, + + /// + /// The Font Awesome "maxcdn" icon unicode character. + /// + Maxcdn = 0xF136, + + /// + /// The Font Awesome "mdb" icon unicode character. + /// + Mdb = 0xF8CA, + + /// + /// The Font Awesome "medal" icon unicode character. + /// + Medal = 0xF5A2, + + /// + /// The Font Awesome "medapps" icon unicode character. + /// + Medapps = 0xF3C6, + + /// + /// The Font Awesome "medium" icon unicode character. + /// + Medium = 0xF23A, + + /// + /// The Font Awesome "medium-m" icon unicode character. + /// + MediumM = 0xF3C7, + + /// + /// The Font Awesome "medkit" icon unicode character. + /// + Medkit = 0xF0FA, + + /// + /// The Font Awesome "medrt" icon unicode character. + /// + Medrt = 0xF3C8, + + /// + /// The Font Awesome "meetup" icon unicode character. + /// + Meetup = 0xF2E, + + /// + /// The Font Awesome "megaport" icon unicode character. + /// + Megaport = 0xF5A3, + + /// + /// The Font Awesome "meh" icon unicode character. + /// + Meh = 0xF11A, + + /// + /// The Font Awesome "meh-blank" icon unicode character. + /// + MehBlank = 0xF5A4, + + /// + /// The Font Awesome "meh-rolling-eyes" icon unicode character. + /// + MehRollingEyes = 0xF5A5, + + /// + /// The Font Awesome "memory" icon unicode character. + /// + Memory = 0xF538, + + /// + /// The Font Awesome "mendeley" icon unicode character. + /// + Mendeley = 0xF7B3, + + /// + /// The Font Awesome "menorah" icon unicode character. + /// + Menorah = 0xF676, + + /// + /// The Font Awesome "mercury" icon unicode character. + /// + Mercury = 0xF223, + + /// + /// The Font Awesome "meteor" icon unicode character. + /// + Meteor = 0xF753, + + /// + /// The Font Awesome "microblog" icon unicode character. + /// + Microblog = 0xF91A, + + /// + /// The Font Awesome "microchip" icon unicode character. + /// + Microchip = 0xF2DB, + + /// + /// The Font Awesome "microphone" icon unicode character. + /// + Microphone = 0xF13, + + /// + /// The Font Awesome "microphone-alt" icon unicode character. + /// + MicrophoneAlt = 0xF3C9, + + /// + /// The Font Awesome "microphone-alt-slash" icon unicode character. + /// + MicrophoneAltSlash = 0xF539, + + /// + /// The Font Awesome "microphone-slash" icon unicode character. + /// + MicrophoneSlash = 0xF131, + + /// + /// The Font Awesome "microscope" icon unicode character. + /// + Microscope = 0xF61, + + /// + /// The Font Awesome "microsoft" icon unicode character. + /// + Microsoft = 0xF3CA, + + /// + /// The Font Awesome "minus" icon unicode character. + /// + Minus = 0xF068, + + /// + /// The Font Awesome "minus-circle" icon unicode character. + /// + MinusCircle = 0xF056, + + /// + /// The Font Awesome "minus-square" icon unicode character. + /// + MinusSquare = 0xF146, + + /// + /// The Font Awesome "mitten" icon unicode character. + /// + Mitten = 0xF7B5, + + /// + /// The Font Awesome "mix" icon unicode character. + /// + Mix = 0xF3CB, + + /// + /// The Font Awesome "mixcloud" icon unicode character. + /// + Mixcloud = 0xF289, + + /// + /// The Font Awesome "mixer" icon unicode character. + /// + Mixer = 0xF956, + + /// + /// The Font Awesome "mizuni" icon unicode character. + /// + Mizuni = 0xF3CC, + + /// + /// The Font Awesome "mobile" icon unicode character. + /// + Mobile = 0xF10B, + + /// + /// The Font Awesome "mobile-alt" icon unicode character. + /// + MobileAlt = 0xF3CD, + + /// + /// The Font Awesome "modx" icon unicode character. + /// + Modx = 0xF285, + + /// + /// The Font Awesome "monero" icon unicode character. + /// + Monero = 0xF3D, + + /// + /// The Font Awesome "money-bill" icon unicode character. + /// + MoneyBill = 0xF0D6, + + /// + /// The Font Awesome "money-bill-alt" icon unicode character. + /// + MoneyBillAlt = 0xF3D1, + + /// + /// The Font Awesome "money-bill-wave" icon unicode character. + /// + MoneyBillWave = 0xF53A, + + /// + /// The Font Awesome "money-bill-wave-alt" icon unicode character. + /// + MoneyBillWaveAlt = 0xF53B, + + /// + /// The Font Awesome "money-check" icon unicode character. + /// + MoneyCheck = 0xF53C, + + /// + /// The Font Awesome "money-check-alt" icon unicode character. + /// + MoneyCheckAlt = 0xF53D, + + /// + /// The Font Awesome "monument" icon unicode character. + /// + Monument = 0xF5A6, + + /// + /// The Font Awesome "moon" icon unicode character. + /// + Moon = 0xF186, + + /// + /// The Font Awesome "mortar-pestle" icon unicode character. + /// + MortarPestle = 0xF5A7, + + /// + /// The Font Awesome "mosque" icon unicode character. + /// + Mosque = 0xF678, + + /// + /// The Font Awesome "motorcycle" icon unicode character. + /// + Motorcycle = 0xF21C, + + /// + /// The Font Awesome "mountain" icon unicode character. + /// + Mountain = 0xF6FC, + + /// + /// The Font Awesome "mouse" icon unicode character. + /// + Mouse = 0xF8CC, + + /// + /// The Font Awesome "mouse-pointer" icon unicode character. + /// + MousePointer = 0xF245, + + /// + /// The Font Awesome "mug-hot" icon unicode character. + /// + MugHot = 0xF7B6, + + /// + /// The Font Awesome "music" icon unicode character. + /// + Music = 0xF001, + + /// + /// The Font Awesome "napster" icon unicode character. + /// + Napster = 0xF3D2, + + /// + /// The Font Awesome "neos" icon unicode character. + /// + Neos = 0xF612, + + /// + /// The Font Awesome "network-wired" icon unicode character. + /// + NetworkWired = 0xF6FF, + + /// + /// The Font Awesome "neuter" icon unicode character. + /// + Neuter = 0xF22C, + + /// + /// The Font Awesome "newspaper" icon unicode character. + /// + Newspaper = 0xF1EA, + + /// + /// The Font Awesome "nimblr" icon unicode character. + /// + Nimblr = 0xF5A8, + + /// + /// The Font Awesome "node" icon unicode character. + /// + Node = 0xF419, + + /// + /// The Font Awesome "node-js" icon unicode character. + /// + NodeJs = 0xF3D3, + + /// + /// The Font Awesome "not-equal" icon unicode character. + /// + NotEqual = 0xF53E, + + /// + /// The Font Awesome "notes-medical" icon unicode character. + /// + NotesMedical = 0xF481, + + /// + /// The Font Awesome "npm" icon unicode character. + /// + Npm = 0xF3D4, + + /// + /// The Font Awesome "ns8" icon unicode character. + /// + Ns8 = 0xF3D5, + + /// + /// The Font Awesome "nutritionix" icon unicode character. + /// + Nutritionix = 0xF3D6, + + /// + /// The Font Awesome "object-group" icon unicode character. + /// + ObjectGroup = 0xF247, + + /// + /// The Font Awesome "object-ungroup" icon unicode character. + /// + ObjectUngroup = 0xF248, + + /// + /// The Font Awesome "odnoklassniki" icon unicode character. + /// + Odnoklassniki = 0xF263, + + /// + /// The Font Awesome "odnoklassniki-square" icon unicode character. + /// + OdnoklassnikiSquare = 0xF264, + + /// + /// The Font Awesome "oil-can" icon unicode character. + /// + OilCan = 0xF613, + + /// + /// The Font Awesome "old-republic" icon unicode character. + /// + OldRepublic = 0xF51, + + /// + /// The Font Awesome "om" icon unicode character. + /// + Om = 0xF679, + + /// + /// The Font Awesome "opencart" icon unicode character. + /// + Opencart = 0xF23D, + + /// + /// The Font Awesome "openid" icon unicode character. + /// + Openid = 0xF19B, + + /// + /// The Font Awesome "opera" icon unicode character. + /// + Opera = 0xF26A, + + /// + /// The Font Awesome "optin-monster" icon unicode character. + /// + OptinMonster = 0xF23C, + + /// + /// The Font Awesome "orcid" icon unicode character. + /// + Orcid = 0xF8D2, + + /// + /// The Font Awesome "osi" icon unicode character. + /// + Osi = 0xF41A, + + /// + /// The Font Awesome "otter" icon unicode character. + /// + Otter = 0xF7, + + /// + /// The Font Awesome "outdent" icon unicode character. + /// + Outdent = 0xF03B, + + /// + /// The Font Awesome "page4" icon unicode character. + /// + Page4 = 0xF3D7, + + /// + /// The Font Awesome "pagelines" icon unicode character. + /// + Pagelines = 0xF18C, + + /// + /// The Font Awesome "pager" icon unicode character. + /// + Pager = 0xF815, + + /// + /// The Font Awesome "paint-brush" icon unicode character. + /// + PaintBrush = 0xF1FC, + + /// + /// The Font Awesome "paint-roller" icon unicode character. + /// + PaintRoller = 0xF5AA, + + /// + /// The Font Awesome "palette" icon unicode character. + /// + Palette = 0xF53F, + + /// + /// The Font Awesome "palfed" icon unicode character. + /// + Palfed = 0xF3D8, + + /// + /// The Font Awesome "pallet" icon unicode character. + /// + Pallet = 0xF482, + + /// + /// The Font Awesome "paperclip" icon unicode character. + /// + Paperclip = 0xF0C6, + + /// + /// The Font Awesome "paper-plane" icon unicode character. + /// + PaperPlane = 0xF1D8, + + /// + /// The Font Awesome "parachute-box" icon unicode character. + /// + ParachuteBox = 0xF4CD, + + /// + /// The Font Awesome "paragraph" icon unicode character. + /// + Paragraph = 0xF1DD, + + /// + /// The Font Awesome "parking" icon unicode character. + /// + Parking = 0xF54, + + /// + /// The Font Awesome "passport" icon unicode character. + /// + Passport = 0xF5AB, + + /// + /// The Font Awesome "pastafarianism" icon unicode character. + /// + Pastafarianism = 0xF67B, + + /// + /// The Font Awesome "paste" icon unicode character. + /// + Paste = 0xF0EA, + + /// + /// The Font Awesome "patreon" icon unicode character. + /// + Patreon = 0xF3D9, + + /// + /// The Font Awesome "pause" icon unicode character. + /// + Pause = 0xF04C, + + /// + /// The Font Awesome "pause-circle" icon unicode character. + /// + PauseCircle = 0xF28B, + + /// + /// The Font Awesome "paw" icon unicode character. + /// + Paw = 0xF1B, + + /// + /// The Font Awesome "paypal" icon unicode character. + /// + Paypal = 0xF1ED, + + /// + /// The Font Awesome "peace" icon unicode character. + /// + Peace = 0xF67C, + + /// + /// The Font Awesome "pen" icon unicode character. + /// + Pen = 0xF304, + + /// + /// The Font Awesome "pen-alt" icon unicode character. + /// + PenAlt = 0xF305, + + /// + /// The Font Awesome "pencil-alt" icon unicode character. + /// + PencilAlt = 0xF303, + + /// + /// The Font Awesome "pencil-ruler" icon unicode character. + /// + PencilRuler = 0xF5AE, + + /// + /// The Font Awesome "pen-fancy" icon unicode character. + /// + PenFancy = 0xF5AC, + + /// + /// The Font Awesome "pen-nib" icon unicode character. + /// + PenNib = 0xF5AD, + + /// + /// The Font Awesome "penny-arcade" icon unicode character. + /// + PennyArcade = 0xF704, + + /// + /// The Font Awesome "pen-square" icon unicode character. + /// + PenSquare = 0xF14B, + + /// + /// The Font Awesome "people-carry" icon unicode character. + /// + PeopleCarry = 0xF4CE, + + /// + /// The Font Awesome "pepper-hot" icon unicode character. + /// + PepperHot = 0xF816, + + /// + /// The Font Awesome "percent" icon unicode character. + /// + Percent = 0xF295, + + /// + /// The Font Awesome "percentage" icon unicode character. + /// + Percentage = 0xF541, + + /// + /// The Font Awesome "periscope" icon unicode character. + /// + Periscope = 0xF3DA, + + /// + /// The Font Awesome "person-booth" icon unicode character. + /// + PersonBooth = 0xF756, + + /// + /// The Font Awesome "phabricator" icon unicode character. + /// + Phabricator = 0xF3DB, + + /// + /// The Font Awesome "phoenix-framework" icon unicode character. + /// + PhoenixFramework = 0xF3DC, + + /// + /// The Font Awesome "phoenix-squadron" icon unicode character. + /// + PhoenixSquadron = 0xF511, + + /// + /// The Font Awesome "phone" icon unicode character. + /// + Phone = 0xF095, + + /// + /// The Font Awesome "phone-alt" icon unicode character. + /// + PhoneAlt = 0xF879, + + /// + /// The Font Awesome "phone-slash" icon unicode character. + /// + PhoneSlash = 0xF3DD, + + /// + /// The Font Awesome "phone-square" icon unicode character. + /// + PhoneSquare = 0xF098, + + /// + /// The Font Awesome "phone-square-alt" icon unicode character. + /// + PhoneSquareAlt = 0xF87B, + + /// + /// The Font Awesome "phone-volume" icon unicode character. + /// + PhoneVolume = 0xF2A, + + /// + /// The Font Awesome "photo-video" icon unicode character. + /// + PhotoVideo = 0xF87C, + + /// + /// The Font Awesome "php" icon unicode character. + /// + Php = 0xF457, + + /// + /// The Font Awesome "pied-piper" icon unicode character. + /// + PiedPiper = 0xF2AE, + + /// + /// The Font Awesome "pied-piper-alt" icon unicode character. + /// + PiedPiperAlt = 0xF1A8, + + /// + /// The Font Awesome "pied-piper-hat" icon unicode character. + /// + PiedPiperHat = 0xF4E5, + + /// + /// The Font Awesome "pied-piper-pp" icon unicode character. + /// + PiedPiperPp = 0xF1A7, + + /// + /// The Font Awesome "pied-piper-square" icon unicode character. + /// + PiedPiperSquare = 0xF91E, + + /// + /// The Font Awesome "piggy-bank" icon unicode character. + /// + PiggyBank = 0xF4D3, + + /// + /// The Font Awesome "pills" icon unicode character. + /// + Pills = 0xF484, + + /// + /// The Font Awesome "pinterest" icon unicode character. + /// + Pinterest = 0xF0D2, + + /// + /// The Font Awesome "pinterest-p" icon unicode character. + /// + PinterestP = 0xF231, + + /// + /// The Font Awesome "pinterest-square" icon unicode character. + /// + PinterestSquare = 0xF0D3, + + /// + /// The Font Awesome "pizza-slice" icon unicode character. + /// + PizzaSlice = 0xF818, + + /// + /// The Font Awesome "place-of-worship" icon unicode character. + /// + PlaceOfWorship = 0xF67F, + + /// + /// The Font Awesome "plane" icon unicode character. + /// + Plane = 0xF072, + + /// + /// The Font Awesome "plane-arrival" icon unicode character. + /// + PlaneArrival = 0xF5AF, + + /// + /// The Font Awesome "plane-departure" icon unicode character. + /// + PlaneDeparture = 0xF5B, + + /// + /// The Font Awesome "play" icon unicode character. + /// + Play = 0xF04B, + + /// + /// The Font Awesome "play-circle" icon unicode character. + /// + PlayCircle = 0xF144, + + /// + /// The Font Awesome "playstation" icon unicode character. + /// + Playstation = 0xF3DF, + + /// + /// The Font Awesome "plug" icon unicode character. + /// + Plug = 0xF1E6, + + /// + /// The Font Awesome "plus" icon unicode character. + /// + Plus = 0xF067, + + /// + /// The Font Awesome "plus-circle" icon unicode character. + /// + PlusCircle = 0xF055, + + /// + /// The Font Awesome "plus-square" icon unicode character. + /// + PlusSquare = 0xF0FE, + + /// + /// The Font Awesome "podcast" icon unicode character. + /// + Podcast = 0xF2CE, + + /// + /// The Font Awesome "poll" icon unicode character. + /// + Poll = 0xF681, + + /// + /// The Font Awesome "poll-h" icon unicode character. + /// + PollH = 0xF682, + + /// + /// The Font Awesome "poo" icon unicode character. + /// + Poo = 0xF2FE, + + /// + /// The Font Awesome "poop" icon unicode character. + /// + Poop = 0xF619, + + /// + /// The Font Awesome "poo-storm" icon unicode character. + /// + PooStorm = 0xF75A, + + /// + /// The Font Awesome "portrait" icon unicode character. + /// + Portrait = 0xF3E, + + /// + /// The Font Awesome "pound-sign" icon unicode character. + /// + PoundSign = 0xF154, + + /// + /// The Font Awesome "power-off" icon unicode character. + /// + PowerOff = 0xF011, + + /// + /// The Font Awesome "pray" icon unicode character. + /// + Pray = 0xF683, + + /// + /// The Font Awesome "praying-hands" icon unicode character. + /// + PrayingHands = 0xF684, + + /// + /// The Font Awesome "prescription" icon unicode character. + /// + Prescription = 0xF5B1, + + /// + /// The Font Awesome "prescription-bottle" icon unicode character. + /// + PrescriptionBottle = 0xF485, + + /// + /// The Font Awesome "prescription-bottle-alt" icon unicode character. + /// + PrescriptionBottleAlt = 0xF486, + + /// + /// The Font Awesome "print" icon unicode character. + /// + Print = 0xF02F, + + /// + /// The Font Awesome "procedures" icon unicode character. + /// + Procedures = 0xF487, + + /// + /// The Font Awesome "product-hunt" icon unicode character. + /// + ProductHunt = 0xF288, + + /// + /// The Font Awesome "project-diagram" icon unicode character. + /// + ProjectDiagram = 0xF542, + + /// + /// The Font Awesome "pushed" icon unicode character. + /// + Pushed = 0xF3E1, + + /// + /// The Font Awesome "puzzle-piece" icon unicode character. + /// + PuzzlePiece = 0xF12E, + + /// + /// The Font Awesome "python" icon unicode character. + /// + Python = 0xF3E2, + + /// + /// The Font Awesome "qq" icon unicode character. + /// + Qq = 0xF1D6, + + /// + /// The Font Awesome "qrcode" icon unicode character. + /// + Qrcode = 0xF029, + + /// + /// The Font Awesome "question" icon unicode character. + /// + Question = 0xF128, + + /// + /// The Font Awesome "question-circle" icon unicode character. + /// + QuestionCircle = 0xF059, + + /// + /// The Font Awesome "quidditch" icon unicode character. + /// + Quidditch = 0xF458, + + /// + /// The Font Awesome "quinscape" icon unicode character. + /// + Quinscape = 0xF459, + + /// + /// The Font Awesome "quora" icon unicode character. + /// + Quora = 0xF2C4, + + /// + /// The Font Awesome "quote-left" icon unicode character. + /// + QuoteLeft = 0xF10D, + + /// + /// The Font Awesome "quote-right" icon unicode character. + /// + QuoteRight = 0xF10E, + + /// + /// The Font Awesome "quran" icon unicode character. + /// + Quran = 0xF687, + + /// + /// The Font Awesome "radiation" icon unicode character. + /// + Radiation = 0xF7B9, + + /// + /// The Font Awesome "radiation-alt" icon unicode character. + /// + RadiationAlt = 0xF7BA, + + /// + /// The Font Awesome "rainbow" icon unicode character. + /// + Rainbow = 0xF75B, + + /// + /// The Font Awesome "random" icon unicode character. + /// + Random = 0xF074, + + /// + /// The Font Awesome "raspberry-pi" icon unicode character. + /// + RaspberryPi = 0xF7BB, + + /// + /// The Font Awesome "ravelry" icon unicode character. + /// + Ravelry = 0xF2D9, + + /// + /// The Font Awesome "react" icon unicode character. + /// + React = 0xF41B, + + /// + /// The Font Awesome "reacteurope" icon unicode character. + /// + Reacteurope = 0xF75D, + + /// + /// The Font Awesome "readme" icon unicode character. + /// + Readme = 0xF4D5, + + /// + /// The Font Awesome "rebel" icon unicode character. + /// + Rebel = 0xF1D, + + /// + /// The Font Awesome "receipt" icon unicode character. + /// + Receipt = 0xF543, + + /// + /// The Font Awesome "record-vinyl" icon unicode character. + /// + RecordVinyl = 0xF8D9, + + /// + /// The Font Awesome "recycle" icon unicode character. + /// + Recycle = 0xF1B8, + + /// + /// The Font Awesome "reddit" icon unicode character. + /// + Reddit = 0xF1A1, + + /// + /// The Font Awesome "reddit-alien" icon unicode character. + /// + RedditAlien = 0xF281, + + /// + /// The Font Awesome "reddit-square" icon unicode character. + /// + RedditSquare = 0xF1A2, + + /// + /// The Font Awesome "redhat" icon unicode character. + /// + Redhat = 0xF7BC, + + /// + /// The Font Awesome "redo" icon unicode character. + /// + Redo = 0xF01E, + + /// + /// The Font Awesome "redo-alt" icon unicode character. + /// + RedoAlt = 0xF2F9, + + /// + /// The Font Awesome "red-river" icon unicode character. + /// + RedRiver = 0xF3E3, + + /// + /// The Font Awesome "registered" icon unicode character. + /// + Registered = 0xF25D, + + /// + /// The Font Awesome "remove-format" icon unicode character. + /// + RemoveFormat = 0xF87D, + + /// + /// The Font Awesome "renren" icon unicode character. + /// + Renren = 0xF18B, + + /// + /// The Font Awesome "reply" icon unicode character. + /// + Reply = 0xF3E5, + + /// + /// The Font Awesome "reply-all" icon unicode character. + /// + ReplyAll = 0xF122, + + /// + /// The Font Awesome "replyd" icon unicode character. + /// + Replyd = 0xF3E6, + + /// + /// The Font Awesome "republican" icon unicode character. + /// + Republican = 0xF75E, + + /// + /// The Font Awesome "researchgate" icon unicode character. + /// + Researchgate = 0xF4F8, + + /// + /// The Font Awesome "resolving" icon unicode character. + /// + Resolving = 0xF3E7, + + /// + /// The Font Awesome "restroom" icon unicode character. + /// + Restroom = 0xF7BD, + + /// + /// The Font Awesome "retweet" icon unicode character. + /// + Retweet = 0xF079, + + /// + /// The Font Awesome "rev" icon unicode character. + /// + Rev = 0xF5B2, + + /// + /// The Font Awesome "ribbon" icon unicode character. + /// + Ribbon = 0xF4D6, + + /// + /// The Font Awesome "ring" icon unicode character. + /// + Ring = 0xF70B, + + /// + /// The Font Awesome "road" icon unicode character. + /// + Road = 0xF018, + + /// + /// The Font Awesome "robot" icon unicode character. + /// + Robot = 0xF544, + + /// + /// The Font Awesome "rocket" icon unicode character. + /// + Rocket = 0xF135, + + /// + /// The Font Awesome "rocketchat" icon unicode character. + /// + Rocketchat = 0xF3E8, + + /// + /// The Font Awesome "rockrms" icon unicode character. + /// + Rockrms = 0xF3E9, + + /// + /// The Font Awesome "route" icon unicode character. + /// + Route = 0xF4D7, + + /// + /// The Font Awesome "r-project" icon unicode character. + /// + RProject = 0xF4F7, + + /// + /// The Font Awesome "rss" icon unicode character. + /// + Rss = 0xF09E, + + /// + /// The Font Awesome "rss-square" icon unicode character. + /// + RssSquare = 0xF143, + + /// + /// The Font Awesome "ruble-sign" icon unicode character. + /// + RubleSign = 0xF158, + + /// + /// The Font Awesome "ruler" icon unicode character. + /// + Ruler = 0xF545, + + /// + /// The Font Awesome "ruler-combined" icon unicode character. + /// + RulerCombined = 0xF546, + + /// + /// The Font Awesome "ruler-horizontal" icon unicode character. + /// + RulerHorizontal = 0xF547, + + /// + /// The Font Awesome "ruler-vertical" icon unicode character. + /// + RulerVertical = 0xF548, + + /// + /// The Font Awesome "running" icon unicode character. + /// + Running = 0xF70C, + + /// + /// The Font Awesome "rupee-sign" icon unicode character. + /// + RupeeSign = 0xF156, + + /// + /// The Font Awesome "sad-cry" icon unicode character. + /// + SadCry = 0xF5B3, + + /// + /// The Font Awesome "sad-tear" icon unicode character. + /// + SadTear = 0xF5B4, + + /// + /// The Font Awesome "safari" icon unicode character. + /// + Safari = 0xF267, + + /// + /// The Font Awesome "salesforce" icon unicode character. + /// + Salesforce = 0xF83B, + + /// + /// The Font Awesome "sass" icon unicode character. + /// + Sass = 0xF41E, + + /// + /// The Font Awesome "satellite" icon unicode character. + /// + Satellite = 0xF7BF, + + /// + /// The Font Awesome "satellite-dish" icon unicode character. + /// + SatelliteDish = 0xF7C, + + /// + /// The Font Awesome "save" icon unicode character. + /// + Save = 0xF0C7, + + /// + /// The Font Awesome "schlix" icon unicode character. + /// + Schlix = 0xF3EA, + + /// + /// The Font Awesome "school" icon unicode character. + /// + School = 0xF549, + + /// + /// The Font Awesome "screwdriver" icon unicode character. + /// + Screwdriver = 0xF54A, + + /// + /// The Font Awesome "scribd" icon unicode character. + /// + Scribd = 0xF28A, + + /// + /// The Font Awesome "scroll" icon unicode character. + /// + Scroll = 0xF70E, + + /// + /// The Font Awesome "sd-card" icon unicode character. + /// + SdCard = 0xF7C2, + + /// + /// The Font Awesome "search" icon unicode character. + /// + Search = 0xF002, + + /// + /// The Font Awesome "search-dollar" icon unicode character. + /// + SearchDollar = 0xF688, + + /// + /// The Font Awesome "searchengin" icon unicode character. + /// + Searchengin = 0xF3EB, + + /// + /// The Font Awesome "search-location" icon unicode character. + /// + SearchLocation = 0xF689, + + /// + /// The Font Awesome "search-minus" icon unicode character. + /// + SearchMinus = 0xF01, + + /// + /// The Font Awesome "search-plus" icon unicode character. + /// + SearchPlus = 0xF00E, + + /// + /// The Font Awesome "seedling" icon unicode character. + /// + Seedling = 0xF4D8, + + /// + /// The Font Awesome "sellcast" icon unicode character. + /// + Sellcast = 0xF2DA, + + /// + /// The Font Awesome "sellsy" icon unicode character. + /// + Sellsy = 0xF213, + + /// + /// The Font Awesome "server" icon unicode character. + /// + Server = 0xF233, + + /// + /// The Font Awesome "servicestack" icon unicode character. + /// + Servicestack = 0xF3EC, + + /// + /// The Font Awesome "shapes" icon unicode character. + /// + Shapes = 0xF61F, + + /// + /// The Font Awesome "share" icon unicode character. + /// + Share = 0xF064, + + /// + /// The Font Awesome "share-alt" icon unicode character. + /// + ShareAlt = 0xF1E, + + /// + /// The Font Awesome "share-alt-square" icon unicode character. + /// + ShareAltSquare = 0xF1E1, + + /// + /// The Font Awesome "share-square" icon unicode character. + /// + ShareSquare = 0xF14D, + + /// + /// The Font Awesome "shekel-sign" icon unicode character. + /// + ShekelSign = 0xF20B, + + /// + /// The Font Awesome "shield-alt" icon unicode character. + /// + ShieldAlt = 0xF3ED, + + /// + /// The Font Awesome "ship" icon unicode character. + /// + Ship = 0xF21A, + + /// + /// The Font Awesome "shipping-fast" icon unicode character. + /// + ShippingFast = 0xF48B, + + /// + /// The Font Awesome "shirtsinbulk" icon unicode character. + /// + Shirtsinbulk = 0xF214, + + /// + /// The Font Awesome "shoe-prints" icon unicode character. + /// + ShoePrints = 0xF54B, + + /// + /// The Font Awesome "shopify" icon unicode character. + /// + Shopify = 0xF957, + + /// + /// The Font Awesome "shopping-bag" icon unicode character. + /// + ShoppingBag = 0xF29, + + /// + /// The Font Awesome "shopping-basket" icon unicode character. + /// + ShoppingBasket = 0xF291, + + /// + /// The Font Awesome "shopping-cart" icon unicode character. + /// + ShoppingCart = 0xF07A, + + /// + /// The Font Awesome "shopware" icon unicode character. + /// + Shopware = 0xF5B5, + + /// + /// The Font Awesome "shower" icon unicode character. + /// + Shower = 0xF2CC, + + /// + /// The Font Awesome "shuttle-van" icon unicode character. + /// + ShuttleVan = 0xF5B6, + + /// + /// The Font Awesome "sign" icon unicode character. + /// + Sign = 0xF4D9, + + /// + /// The Font Awesome "signal" icon unicode character. + /// + Signal = 0xF012, + + /// + /// The Font Awesome "signature" icon unicode character. + /// + Signature = 0xF5B7, + + /// + /// The Font Awesome "sign-in-alt" icon unicode character. + /// + SignInAlt = 0xF2F6, + + /// + /// The Font Awesome "sign-language" icon unicode character. + /// + SignLanguage = 0xF2A7, + + /// + /// The Font Awesome "sign-out-alt" icon unicode character. + /// + SignOutAlt = 0xF2F5, + + /// + /// The Font Awesome "sim-card" icon unicode character. + /// + SimCard = 0xF7C4, + + /// + /// The Font Awesome "simplybuilt" icon unicode character. + /// + Simplybuilt = 0xF215, + + /// + /// The Font Awesome "sistrix" icon unicode character. + /// + Sistrix = 0xF3EE, + + /// + /// The Font Awesome "sitemap" icon unicode character. + /// + Sitemap = 0xF0E8, + + /// + /// The Font Awesome "sith" icon unicode character. + /// + Sith = 0xF512, + + /// + /// The Font Awesome "skating" icon unicode character. + /// + Skating = 0xF7C5, + + /// + /// The Font Awesome "sketch" icon unicode character. + /// + Sketch = 0xF7C6, + + /// + /// The Font Awesome "skiing" icon unicode character. + /// + Skiing = 0xF7C9, + + /// + /// The Font Awesome "skiing-nordic" icon unicode character. + /// + SkiingNordic = 0xF7CA, + + /// + /// The Font Awesome "skull" icon unicode character. + /// + Skull = 0xF54C, + + /// + /// The Font Awesome "skull-crossbones" icon unicode character. + /// + SkullCrossbones = 0xF714, + + /// + /// The Font Awesome "skyatlas" icon unicode character. + /// + Skyatlas = 0xF216, + + /// + /// The Font Awesome "skype" icon unicode character. + /// + Skype = 0xF17E, + + /// + /// The Font Awesome "slack" icon unicode character. + /// + Slack = 0xF198, + + /// + /// The Font Awesome "slack-hash" icon unicode character. + /// + SlackHash = 0xF3EF, + + /// + /// The Font Awesome "slash" icon unicode character. + /// + Slash = 0xF715, + + /// + /// The Font Awesome "sleigh" icon unicode character. + /// + Sleigh = 0xF7CC, + + /// + /// The Font Awesome "sliders-h" icon unicode character. + /// + SlidersH = 0xF1DE, + + /// + /// The Font Awesome "slideshare" icon unicode character. + /// + Slideshare = 0xF1E7, + + /// + /// The Font Awesome "smile" icon unicode character. + /// + Smile = 0xF118, + + /// + /// The Font Awesome "smile-beam" icon unicode character. + /// + SmileBeam = 0xF5B8, + + /// + /// The Font Awesome "smile-wink" icon unicode character. + /// + SmileWink = 0xF4DA, + + /// + /// The Font Awesome "smog" icon unicode character. + /// + Smog = 0xF75F, + + /// + /// The Font Awesome "smoking" icon unicode character. + /// + Smoking = 0xF48D, + + /// + /// The Font Awesome "smoking-ban" icon unicode character. + /// + SmokingBan = 0xF54D, + + /// + /// The Font Awesome "sms" icon unicode character. + /// + Sms = 0xF7CD, + + /// + /// The Font Awesome "snapchat" icon unicode character. + /// + Snapchat = 0xF2AB, + + /// + /// The Font Awesome "snapchat-ghost" icon unicode character. + /// + SnapchatGhost = 0xF2AC, + + /// + /// The Font Awesome "snapchat-square" icon unicode character. + /// + SnapchatSquare = 0xF2AD, + + /// + /// The Font Awesome "snowboarding" icon unicode character. + /// + Snowboarding = 0xF7CE, + + /// + /// The Font Awesome "snowflake" icon unicode character. + /// + Snowflake = 0xF2DC, + + /// + /// The Font Awesome "snowman" icon unicode character. + /// + Snowman = 0xF7D, + + /// + /// The Font Awesome "snowplow" icon unicode character. + /// + Snowplow = 0xF7D2, + + /// + /// The Font Awesome "socks" icon unicode character. + /// + Socks = 0xF696, + + /// + /// The Font Awesome "solar-panel" icon unicode character. + /// + SolarPanel = 0xF5BA, + + /// + /// The Font Awesome "sort" icon unicode character. + /// + Sort = 0xF0DC, + + /// + /// The Font Awesome "sort-alpha-down" icon unicode character. + /// + SortAlphaDown = 0xF15D, + + /// + /// The Font Awesome "sort-alpha-down-alt" icon unicode character. + /// + SortAlphaDownAlt = 0xF881, + + /// + /// The Font Awesome "sort-alpha-up" icon unicode character. + /// + SortAlphaUp = 0xF15E, + + /// + /// The Font Awesome "sort-alpha-up-alt" icon unicode character. + /// + SortAlphaUpAlt = 0xF882, + + /// + /// The Font Awesome "sort-amount-down" icon unicode character. + /// + SortAmountDown = 0xF16, + + /// + /// The Font Awesome "sort-amount-down-alt" icon unicode character. + /// + SortAmountDownAlt = 0xF884, + + /// + /// The Font Awesome "sort-amount-up" icon unicode character. + /// + SortAmountUp = 0xF161, + + /// + /// The Font Awesome "sort-amount-up-alt" icon unicode character. + /// + SortAmountUpAlt = 0xF885, + + /// + /// The Font Awesome "sort-down" icon unicode character. + /// + SortDown = 0xF0DD, + + /// + /// The Font Awesome "sort-numeric-down" icon unicode character. + /// + SortNumericDown = 0xF162, + + /// + /// The Font Awesome "sort-numeric-down-alt" icon unicode character. + /// + SortNumericDownAlt = 0xF886, + + /// + /// The Font Awesome "sort-numeric-up" icon unicode character. + /// + SortNumericUp = 0xF163, + + /// + /// The Font Awesome "sort-numeric-up-alt" icon unicode character. + /// + SortNumericUpAlt = 0xF887, + + /// + /// The Font Awesome "sort-up" icon unicode character. + /// + SortUp = 0xF0DE, + + /// + /// The Font Awesome "soundcloud" icon unicode character. + /// + Soundcloud = 0xF1BE, + + /// + /// The Font Awesome "sourcetree" icon unicode character. + /// + Sourcetree = 0xF7D3, + + /// + /// The Font Awesome "spa" icon unicode character. + /// + Spa = 0xF5BB, + + /// + /// The Font Awesome "space-shuttle" icon unicode character. + /// + SpaceShuttle = 0xF197, + + /// + /// The Font Awesome "speakap" icon unicode character. + /// + Speakap = 0xF3F3, + + /// + /// The Font Awesome "speaker-deck" icon unicode character. + /// + SpeakerDeck = 0xF83C, + + /// + /// The Font Awesome "spell-check" icon unicode character. + /// + SpellCheck = 0xF891, + + /// + /// The Font Awesome "spider" icon unicode character. + /// + Spider = 0xF717, + + /// + /// The Font Awesome "spinner" icon unicode character. + /// + Spinner = 0xF11, + + /// + /// The Font Awesome "splotch" icon unicode character. + /// + Splotch = 0xF5BC, + + /// + /// The Font Awesome "spotify" icon unicode character. + /// + Spotify = 0xF1BC, + + /// + /// The Font Awesome "spray-can" icon unicode character. + /// + SprayCan = 0xF5BD, + + /// + /// The Font Awesome "square" icon unicode character. + /// + Square = 0xF0C8, + + /// + /// The Font Awesome "square-full" icon unicode character. + /// + SquareFull = 0xF45C, + + /// + /// The Font Awesome "square-root-alt" icon unicode character. + /// + SquareRootAlt = 0xF698, + + /// + /// The Font Awesome "squarespace" icon unicode character. + /// + Squarespace = 0xF5BE, + + /// + /// The Font Awesome "stack-exchange" icon unicode character. + /// + StackExchange = 0xF18D, + + /// + /// The Font Awesome "stack-overflow" icon unicode character. + /// + StackOverflow = 0xF16C, + + /// + /// The Font Awesome "stackpath" icon unicode character. + /// + Stackpath = 0xF842, + + /// + /// The Font Awesome "stamp" icon unicode character. + /// + Stamp = 0xF5BF, + + /// + /// The Font Awesome "star" icon unicode character. + /// + Star = 0xF005, + + /// + /// The Font Awesome "star-and-crescent" icon unicode character. + /// + StarAndCrescent = 0xF699, + + /// + /// The Font Awesome "star-half" icon unicode character. + /// + StarHalf = 0xF089, + + /// + /// The Font Awesome "star-half-alt" icon unicode character. + /// + StarHalfAlt = 0xF5C, + + /// + /// The Font Awesome "star-of-david" icon unicode character. + /// + StarOfDavid = 0xF69A, + + /// + /// The Font Awesome "star-of-life" icon unicode character. + /// + StarOfLife = 0xF621, + + /// + /// The Font Awesome "staylinked" icon unicode character. + /// + Staylinked = 0xF3F5, + + /// + /// The Font Awesome "steam" icon unicode character. + /// + Steam = 0xF1B6, + + /// + /// The Font Awesome "steam-square" icon unicode character. + /// + SteamSquare = 0xF1B7, + + /// + /// The Font Awesome "steam-symbol" icon unicode character. + /// + SteamSymbol = 0xF3F6, + + /// + /// The Font Awesome "step-backward" icon unicode character. + /// + StepBackward = 0xF048, + + /// + /// The Font Awesome "step-forward" icon unicode character. + /// + StepForward = 0xF051, + + /// + /// The Font Awesome "stethoscope" icon unicode character. + /// + Stethoscope = 0xF0F1, + + /// + /// The Font Awesome "sticker-mule" icon unicode character. + /// + StickerMule = 0xF3F7, + + /// + /// The Font Awesome "sticky-note" icon unicode character. + /// + StickyNote = 0xF249, + + /// + /// The Font Awesome "stop" icon unicode character. + /// + Stop = 0xF04D, + + /// + /// The Font Awesome "stop-circle" icon unicode character. + /// + StopCircle = 0xF28D, + + /// + /// The Font Awesome "stopwatch" icon unicode character. + /// + Stopwatch = 0xF2F2, + + /// + /// The Font Awesome "store" icon unicode character. + /// + Store = 0xF54E, + + /// + /// The Font Awesome "store-alt" icon unicode character. + /// + StoreAlt = 0xF54F, + + /// + /// The Font Awesome "strava" icon unicode character. + /// + Strava = 0xF428, + + /// + /// The Font Awesome "stream" icon unicode character. + /// + Stream = 0xF55, + + /// + /// The Font Awesome "street-view" icon unicode character. + /// + StreetView = 0xF21D, + + /// + /// The Font Awesome "strikethrough" icon unicode character. + /// + Strikethrough = 0xF0CC, + + /// + /// The Font Awesome "stripe" icon unicode character. + /// + Stripe = 0xF429, + + /// + /// The Font Awesome "stripe-s" icon unicode character. + /// + StripeS = 0xF42A, + + /// + /// The Font Awesome "stroopwafel" icon unicode character. + /// + Stroopwafel = 0xF551, + + /// + /// The Font Awesome "studiovinari" icon unicode character. + /// + Studiovinari = 0xF3F8, + + /// + /// The Font Awesome "stumbleupon" icon unicode character. + /// + Stumbleupon = 0xF1A4, + + /// + /// The Font Awesome "stumbleupon-circle" icon unicode character. + /// + StumbleuponCircle = 0xF1A3, + + /// + /// The Font Awesome "subscript" icon unicode character. + /// + Subscript = 0xF12C, + + /// + /// The Font Awesome "subway" icon unicode character. + /// + Subway = 0xF239, + + /// + /// The Font Awesome "suitcase" icon unicode character. + /// + Suitcase = 0xF0F2, + + /// + /// The Font Awesome "suitcase-rolling" icon unicode character. + /// + SuitcaseRolling = 0xF5C1, + + /// + /// The Font Awesome "sun" icon unicode character. + /// + Sun = 0xF185, + + /// + /// The Font Awesome "superpowers" icon unicode character. + /// + Superpowers = 0xF2DD, + + /// + /// The Font Awesome "superscript" icon unicode character. + /// + Superscript = 0xF12B, + + /// + /// The Font Awesome "supple" icon unicode character. + /// + Supple = 0xF3F9, + + /// + /// The Font Awesome "surprise" icon unicode character. + /// + Surprise = 0xF5C2, + + /// + /// The Font Awesome "suse" icon unicode character. + /// + Suse = 0xF7D6, + + /// + /// The Font Awesome "swatchbook" icon unicode character. + /// + Swatchbook = 0xF5C3, + + /// + /// The Font Awesome "swift" icon unicode character. + /// + Swift = 0xF8E1, + + /// + /// The Font Awesome "swimmer" icon unicode character. + /// + Swimmer = 0xF5C4, + + /// + /// The Font Awesome "swimming-pool" icon unicode character. + /// + SwimmingPool = 0xF5C5, + + /// + /// The Font Awesome "symfony" icon unicode character. + /// + Symfony = 0xF83D, + + /// + /// The Font Awesome "synagogue" icon unicode character. + /// + Synagogue = 0xF69B, + + /// + /// The Font Awesome "sync" icon unicode character. + /// + Sync = 0xF021, + + /// + /// The Font Awesome "sync-alt" icon unicode character. + /// + SyncAlt = 0xF2F1, + + /// + /// The Font Awesome "syringe" icon unicode character. + /// + Syringe = 0xF48E, + + /// + /// The Font Awesome "table" icon unicode character. + /// + Table = 0xF0CE, + + /// + /// The Font Awesome "tablet" icon unicode character. + /// + Tablet = 0xF10A, + + /// + /// The Font Awesome "tablet-alt" icon unicode character. + /// + TabletAlt = 0xF3FA, + + /// + /// The Font Awesome "table-tennis" icon unicode character. + /// + TableTennis = 0xF45D, + + /// + /// The Font Awesome "tablets" icon unicode character. + /// + Tablets = 0xF49, + + /// + /// The Font Awesome "tachometer-alt" icon unicode character. + /// + TachometerAlt = 0xF3FD, + + /// + /// The Font Awesome "tag" icon unicode character. + /// + Tag = 0xF02B, + + /// + /// The Font Awesome "tags" icon unicode character. + /// + Tags = 0xF02C, + + /// + /// The Font Awesome "tape" icon unicode character. + /// + Tape = 0xF4DB, + + /// + /// The Font Awesome "tasks" icon unicode character. + /// + Tasks = 0xF0AE, + + /// + /// The Font Awesome "taxi" icon unicode character. + /// + Taxi = 0xF1BA, + + /// + /// The Font Awesome "teamspeak" icon unicode character. + /// + Teamspeak = 0xF4F9, + + /// + /// The Font Awesome "teeth" icon unicode character. + /// + Teeth = 0xF62E, + + /// + /// The Font Awesome "teeth-open" icon unicode character. + /// + TeethOpen = 0xF62F, + + /// + /// The Font Awesome "telegram" icon unicode character. + /// + Telegram = 0xF2C6, + + /// + /// The Font Awesome "telegram-plane" icon unicode character. + /// + TelegramPlane = 0xF3FE, + + /// + /// The Font Awesome "temperature-high" icon unicode character. + /// + TemperatureHigh = 0xF769, + + /// + /// The Font Awesome "temperature-low" icon unicode character. + /// + TemperatureLow = 0xF76B, + + /// + /// The Font Awesome "tencent-weibo" icon unicode character. + /// + TencentWeibo = 0xF1D5, + + /// + /// The Font Awesome "tenge" icon unicode character. + /// + Tenge = 0xF7D7, + + /// + /// The Font Awesome "terminal" icon unicode character. + /// + Terminal = 0xF12, + + /// + /// The Font Awesome "text-height" icon unicode character. + /// + TextHeight = 0xF034, + + /// + /// The Font Awesome "text-width" icon unicode character. + /// + TextWidth = 0xF035, + + /// + /// The Font Awesome "th" icon unicode character. + /// + Th = 0xF00A, + + /// + /// The Font Awesome "theater-masks" icon unicode character. + /// + TheaterMasks = 0xF63, + + /// + /// The Font Awesome "themeco" icon unicode character. + /// + Themeco = 0xF5C6, + + /// + /// The Font Awesome "themeisle" icon unicode character. + /// + Themeisle = 0xF2B2, + + /// + /// The Font Awesome "the-red-yeti" icon unicode character. + /// + TheRedYeti = 0xF69D, + + /// + /// The Font Awesome "thermometer" icon unicode character. + /// + Thermometer = 0xF491, + + /// + /// The Font Awesome "thermometer-empty" icon unicode character. + /// + ThermometerEmpty = 0xF2CB, + + /// + /// The Font Awesome "thermometer-full" icon unicode character. + /// + ThermometerFull = 0xF2C7, + + /// + /// The Font Awesome "thermometer-half" icon unicode character. + /// + ThermometerHalf = 0xF2C9, + + /// + /// The Font Awesome "thermometer-quarter" icon unicode character. + /// + ThermometerQuarter = 0xF2CA, + + /// + /// The Font Awesome "thermometer-three-quarters" icon unicode character. + /// + ThermometerThreeQuarters = 0xF2C8, + + /// + /// The Font Awesome "think-peaks" icon unicode character. + /// + ThinkPeaks = 0xF731, + + /// + /// The Font Awesome "th-large" icon unicode character. + /// + ThLarge = 0xF009, + + /// + /// The Font Awesome "th-list" icon unicode character. + /// + ThList = 0xF00B, + + /// + /// The Font Awesome "thumbs-down" icon unicode character. + /// + ThumbsDown = 0xF165, + + /// + /// The Font Awesome "thumbs-up" icon unicode character. + /// + ThumbsUp = 0xF164, + + /// + /// The Font Awesome "thumbtack" icon unicode character. + /// + Thumbtack = 0xF08D, + + /// + /// The Font Awesome "ticket-alt" icon unicode character. + /// + TicketAlt = 0xF3FF, + + /// + /// The Font Awesome "times" icon unicode character. + /// + Times = 0xF00D, + + /// + /// The Font Awesome "times-circle" icon unicode character. + /// + TimesCircle = 0xF057, + + /// + /// The Font Awesome "tint" icon unicode character. + /// + Tint = 0xF043, + + /// + /// The Font Awesome "tint-slash" icon unicode character. + /// + TintSlash = 0xF5C7, + + /// + /// The Font Awesome "tired" icon unicode character. + /// + Tired = 0xF5C8, + + /// + /// The Font Awesome "toggle-off" icon unicode character. + /// + ToggleOff = 0xF204, + + /// + /// The Font Awesome "toggle-on" icon unicode character. + /// + ToggleOn = 0xF205, + + /// + /// The Font Awesome "toilet" icon unicode character. + /// + Toilet = 0xF7D8, + + /// + /// The Font Awesome "toilet-paper" icon unicode character. + /// + ToiletPaper = 0xF71E, + + /// + /// The Font Awesome "toolbox" icon unicode character. + /// + Toolbox = 0xF552, + + /// + /// The Font Awesome "tools" icon unicode character. + /// + Tools = 0xF7D9, + + /// + /// The Font Awesome "tooth" icon unicode character. + /// + Tooth = 0xF5C9, + + /// + /// The Font Awesome "torah" icon unicode character. + /// + Torah = 0xF6A, + + /// + /// The Font Awesome "torii-gate" icon unicode character. + /// + ToriiGate = 0xF6A1, + + /// + /// The Font Awesome "tractor" icon unicode character. + /// + Tractor = 0xF722, + + /// + /// The Font Awesome "trade-federation" icon unicode character. + /// + TradeFederation = 0xF513, + + /// + /// The Font Awesome "trademark" icon unicode character. + /// + Trademark = 0xF25C, + + /// + /// The Font Awesome "traffic-light" icon unicode character. + /// + TrafficLight = 0xF637, + + /// + /// The Font Awesome "trailer" icon unicode character. + /// + Trailer = 0xF941, + + /// + /// The Font Awesome "train" icon unicode character. + /// + Train = 0xF238, + + /// + /// The Font Awesome "tram" icon unicode character. + /// + Tram = 0xF7DA, + + /// + /// The Font Awesome "transgender" icon unicode character. + /// + Transgender = 0xF224, + + /// + /// The Font Awesome "transgender-alt" icon unicode character. + /// + TransgenderAlt = 0xF225, + + /// + /// The Font Awesome "trash" icon unicode character. + /// + Trash = 0xF1F8, + + /// + /// The Font Awesome "trash-alt" icon unicode character. + /// + TrashAlt = 0xF2ED, + + /// + /// The Font Awesome "trash-restore" icon unicode character. + /// + TrashRestore = 0xF829, + + /// + /// The Font Awesome "trash-restore-alt" icon unicode character. + /// + TrashRestoreAlt = 0xF82A, + + /// + /// The Font Awesome "tree" icon unicode character. + /// + Tree = 0xF1BB, + + /// + /// The Font Awesome "trello" icon unicode character. + /// + Trello = 0xF181, + + /// + /// The Font Awesome "tripadvisor" icon unicode character. + /// + Tripadvisor = 0xF262, + + /// + /// The Font Awesome "trophy" icon unicode character. + /// + Trophy = 0xF091, + + /// + /// The Font Awesome "truck" icon unicode character. + /// + Truck = 0xF0D1, + + /// + /// The Font Awesome "truck-loading" icon unicode character. + /// + TruckLoading = 0xF4DE, + + /// + /// The Font Awesome "truck-monster" icon unicode character. + /// + TruckMonster = 0xF63B, + + /// + /// The Font Awesome "truck-moving" icon unicode character. + /// + TruckMoving = 0xF4DF, + + /// + /// The Font Awesome "truck-pickup" icon unicode character. + /// + TruckPickup = 0xF63C, + + /// + /// The Font Awesome "tshirt" icon unicode character. + /// + Tshirt = 0xF553, + + /// + /// The Font Awesome "tty" icon unicode character. + /// + Tty = 0xF1E4, + + /// + /// The Font Awesome "tumblr" icon unicode character. + /// + Tumblr = 0xF173, + + /// + /// The Font Awesome "tumblr-square" icon unicode character. + /// + TumblrSquare = 0xF174, + + /// + /// The Font Awesome "tv" icon unicode character. + /// + Tv = 0xF26C, + + /// + /// The Font Awesome "twitch" icon unicode character. + /// + Twitch = 0xF1E8, + + /// + /// The Font Awesome "twitter" icon unicode character. + /// + Twitter = 0xF099, + + /// + /// The Font Awesome "twitter-square" icon unicode character. + /// + TwitterSquare = 0xF081, + + /// + /// The Font Awesome "typo3" icon unicode character. + /// + Typo3 = 0xF42B, + + /// + /// The Font Awesome "uber" icon unicode character. + /// + Uber = 0xF402, + + /// + /// The Font Awesome "ubuntu" icon unicode character. + /// + Ubuntu = 0xF7DF, + + /// + /// The Font Awesome "uikit" icon unicode character. + /// + Uikit = 0xF403, + + /// + /// The Font Awesome "umbraco" icon unicode character. + /// + Umbraco = 0xF8E8, + + /// + /// The Font Awesome "umbrella" icon unicode character. + /// + Umbrella = 0xF0E9, + + /// + /// The Font Awesome "umbrella-beach" icon unicode character. + /// + UmbrellaBeach = 0xF5CA, + + /// + /// The Font Awesome "underline" icon unicode character. + /// + Underline = 0xF0CD, + + /// + /// The Font Awesome "undo" icon unicode character. + /// + Undo = 0xF0E2, + + /// + /// The Font Awesome "undo-alt" icon unicode character. + /// + UndoAlt = 0xF2EA, + + /// + /// The Font Awesome "uniregistry" icon unicode character. + /// + Uniregistry = 0xF404, + + /// + /// The Font Awesome "unity" icon unicode character. + /// + Unity = 0xF949, + + /// + /// The Font Awesome "universal-access" icon unicode character. + /// + UniversalAccess = 0xF29A, + + /// + /// The Font Awesome "university" icon unicode character. + /// + University = 0xF19C, + + /// + /// The Font Awesome "unlink" icon unicode character. + /// + Unlink = 0xF127, + + /// + /// The Font Awesome "unlock" icon unicode character. + /// + Unlock = 0xF09C, + + /// + /// The Font Awesome "unlock-alt" icon unicode character. + /// + UnlockAlt = 0xF13E, + + /// + /// The Font Awesome "untappd" icon unicode character. + /// + Untappd = 0xF405, + + /// + /// The Font Awesome "upload" icon unicode character. + /// + Upload = 0xF093, + + /// + /// The Font Awesome "ups" icon unicode character. + /// + Ups = 0xF7E, + + /// + /// The Font Awesome "usb" icon unicode character. + /// + Usb = 0xF287, + + /// + /// The Font Awesome "user" icon unicode character. + /// + User = 0xF007, + + /// + /// The Font Awesome "user-alt" icon unicode character. + /// + UserAlt = 0xF406, + + /// + /// The Font Awesome "user-alt-slash" icon unicode character. + /// + UserAltSlash = 0xF4FA, + + /// + /// The Font Awesome "user-astronaut" icon unicode character. + /// + UserAstronaut = 0xF4FB, + + /// + /// The Font Awesome "user-check" icon unicode character. + /// + UserCheck = 0xF4FC, + + /// + /// The Font Awesome "user-circle" icon unicode character. + /// + UserCircle = 0xF2BD, + + /// + /// The Font Awesome "user-clock" icon unicode character. + /// + UserClock = 0xF4FD, + + /// + /// The Font Awesome "user-cog" icon unicode character. + /// + UserCog = 0xF4FE, + + /// + /// The Font Awesome "user-edit" icon unicode character. + /// + UserEdit = 0xF4FF, + + /// + /// The Font Awesome "user-friends" icon unicode character. + /// + UserFriends = 0xF5, + + /// + /// The Font Awesome "user-graduate" icon unicode character. + /// + UserGraduate = 0xF501, + + /// + /// The Font Awesome "user-injured" icon unicode character. + /// + UserInjured = 0xF728, + + /// + /// The Font Awesome "user-lock" icon unicode character. + /// + UserLock = 0xF502, + + /// + /// The Font Awesome "user-md" icon unicode character. + /// + UserMd = 0xF0F, + + /// + /// The Font Awesome "user-minus" icon unicode character. + /// + UserMinus = 0xF503, + + /// + /// The Font Awesome "user-ninja" icon unicode character. + /// + UserNinja = 0xF504, + + /// + /// The Font Awesome "user-nurse" icon unicode character. + /// + UserNurse = 0xF82F, + + /// + /// The Font Awesome "user-plus" icon unicode character. + /// + UserPlus = 0xF234, + + /// + /// The Font Awesome "users" icon unicode character. + /// + Users = 0xF0C, + + /// + /// The Font Awesome "users-cog" icon unicode character. + /// + UsersCog = 0xF509, + + /// + /// The Font Awesome "user-secret" icon unicode character. + /// + UserSecret = 0xF21B, + + /// + /// The Font Awesome "user-shield" icon unicode character. + /// + UserShield = 0xF505, + + /// + /// The Font Awesome "user-slash" icon unicode character. + /// + UserSlash = 0xF506, + + /// + /// The Font Awesome "user-tag" icon unicode character. + /// + UserTag = 0xF507, + + /// + /// The Font Awesome "user-tie" icon unicode character. + /// + UserTie = 0xF508, + + /// + /// The Font Awesome "user-times" icon unicode character. + /// + UserTimes = 0xF235, + + /// + /// The Font Awesome "usps" icon unicode character. + /// + Usps = 0xF7E1, + + /// + /// The Font Awesome "ussunnah" icon unicode character. + /// + Ussunnah = 0xF407, + + /// + /// The Font Awesome "utensils" icon unicode character. + /// + Utensils = 0xF2E7, + + /// + /// The Font Awesome "utensil-spoon" icon unicode character. + /// + UtensilSpoon = 0xF2E5, + + /// + /// The Font Awesome "vaadin" icon unicode character. + /// + Vaadin = 0xF408, + + /// + /// The Font Awesome "vector-square" icon unicode character. + /// + VectorSquare = 0xF5CB, + + /// + /// The Font Awesome "venus" icon unicode character. + /// + Venus = 0xF221, + + /// + /// The Font Awesome "venus-double" icon unicode character. + /// + VenusDouble = 0xF226, + + /// + /// The Font Awesome "venus-mars" icon unicode character. + /// + VenusMars = 0xF228, + + /// + /// The Font Awesome "viacoin" icon unicode character. + /// + Viacoin = 0xF237, + + /// + /// The Font Awesome "viadeo" icon unicode character. + /// + Viadeo = 0xF2A9, + + /// + /// The Font Awesome "viadeo-square" icon unicode character. + /// + ViadeoSquare = 0xF2AA, + + /// + /// The Font Awesome "vial" icon unicode character. + /// + Vial = 0xF492, + + /// + /// The Font Awesome "vials" icon unicode character. + /// + Vials = 0xF493, + + /// + /// The Font Awesome "viber" icon unicode character. + /// + Viber = 0xF409, + + /// + /// The Font Awesome "video" icon unicode character. + /// + Video = 0xF03D, + + /// + /// The Font Awesome "video-slash" icon unicode character. + /// + VideoSlash = 0xF4E2, + + /// + /// The Font Awesome "vihara" icon unicode character. + /// + Vihara = 0xF6A7, + + /// + /// The Font Awesome "vimeo" icon unicode character. + /// + Vimeo = 0xF40A, + + /// + /// The Font Awesome "vimeo-square" icon unicode character. + /// + VimeoSquare = 0xF194, + + /// + /// The Font Awesome "vimeo-v" icon unicode character. + /// + VimeoV = 0xF27D, + + /// + /// The Font Awesome "vine" icon unicode character. + /// + Vine = 0xF1CA, + + /// + /// The Font Awesome "vk" icon unicode character. + /// + Vk = 0xF189, + + /// + /// The Font Awesome "vnv" icon unicode character. + /// + Vnv = 0xF40B, + + /// + /// The Font Awesome "voicemail" icon unicode character. + /// + Voicemail = 0xF897, + + /// + /// The Font Awesome "volleyball-ball" icon unicode character. + /// + VolleyballBall = 0xF45F, + + /// + /// The Font Awesome "volume-down" icon unicode character. + /// + VolumeDown = 0xF027, + + /// + /// The Font Awesome "volume-mute" icon unicode character. + /// + VolumeMute = 0xF6A9, + + /// + /// The Font Awesome "volume-off" icon unicode character. + /// + VolumeOff = 0xF026, + + /// + /// The Font Awesome "volume-up" icon unicode character. + /// + VolumeUp = 0xF028, + + /// + /// The Font Awesome "vote-yea" icon unicode character. + /// + VoteYea = 0xF772, + + /// + /// The Font Awesome "vr-cardboard" icon unicode character. + /// + VrCardboard = 0xF729, + + /// + /// The Font Awesome "vuejs" icon unicode character. + /// + Vuejs = 0xF41F, + + /// + /// The Font Awesome "walking" icon unicode character. + /// + Walking = 0xF554, + + /// + /// The Font Awesome "wallet" icon unicode character. + /// + Wallet = 0xF555, + + /// + /// The Font Awesome "warehouse" icon unicode character. + /// + Warehouse = 0xF494, + + /// + /// The Font Awesome "water" icon unicode character. + /// + Water = 0xF773, + + /// + /// The Font Awesome "wave-square" icon unicode character. + /// + WaveSquare = 0xF83E, + + /// + /// The Font Awesome "waze" icon unicode character. + /// + Waze = 0xF83F, + + /// + /// The Font Awesome "weebly" icon unicode character. + /// + Weebly = 0xF5CC, + + /// + /// The Font Awesome "weibo" icon unicode character. + /// + Weibo = 0xF18A, + + /// + /// The Font Awesome "weight" icon unicode character. + /// + Weight = 0xF496, + + /// + /// The Font Awesome "weight-hanging" icon unicode character. + /// + WeightHanging = 0xF5CD, + + /// + /// The Font Awesome "weixin" icon unicode character. + /// + Weixin = 0xF1D7, + + /// + /// The Font Awesome "whatsapp" icon unicode character. + /// + Whatsapp = 0xF232, + + /// + /// The Font Awesome "whatsapp-square" icon unicode character. + /// + WhatsappSquare = 0xF40C, + + /// + /// The Font Awesome "wheelchair" icon unicode character. + /// + Wheelchair = 0xF193, + + /// + /// The Font Awesome "whmcs" icon unicode character. + /// + Whmcs = 0xF40D, + + /// + /// The Font Awesome "wifi" icon unicode character. + /// + Wifi = 0xF1EB, + + /// + /// The Font Awesome "wikipedia-w" icon unicode character. + /// + WikipediaW = 0xF266, + + /// + /// The Font Awesome "wind" icon unicode character. + /// + Wind = 0xF72E, + + /// + /// The Font Awesome "window-close" icon unicode character. + /// + WindowClose = 0xF41, + + /// + /// The Font Awesome "window-maximize" icon unicode character. + /// + WindowMaximize = 0xF2D, + + /// + /// The Font Awesome "window-minimize" icon unicode character. + /// + WindowMinimize = 0xF2D1, + + /// + /// The Font Awesome "window-restore" icon unicode character. + /// + WindowRestore = 0xF2D2, + + /// + /// The Font Awesome "windows" icon unicode character. + /// + Windows = 0xF17A, + + /// + /// The Font Awesome "wine-bottle" icon unicode character. + /// + WineBottle = 0xF72F, + + /// + /// The Font Awesome "wine-glass" icon unicode character. + /// + WineGlass = 0xF4E3, + + /// + /// The Font Awesome "wine-glass-alt" icon unicode character. + /// + WineGlassAlt = 0xF5CE, + + /// + /// The Font Awesome "wix" icon unicode character. + /// + Wix = 0xF5CF, + + /// + /// The Font Awesome "wizards-of-the-coast" icon unicode character. + /// + WizardsOfTheCoast = 0xF73, + + /// + /// The Font Awesome "wolf-pack-battalion" icon unicode character. + /// + WolfPackBattalion = 0xF514, + + /// + /// The Font Awesome "won-sign" icon unicode character. + /// + WonSign = 0xF159, + + /// + /// The Font Awesome "wordpress" icon unicode character. + /// + Wordpress = 0xF19A, + + /// + /// The Font Awesome "wordpress-simple" icon unicode character. + /// + WordpressSimple = 0xF411, + + /// + /// The Font Awesome "wpbeginner" icon unicode character. + /// + Wpbeginner = 0xF297, + + /// + /// The Font Awesome "wpexplorer" icon unicode character. + /// + Wpexplorer = 0xF2DE, + + /// + /// The Font Awesome "wpforms" icon unicode character. + /// + Wpforms = 0xF298, + + /// + /// The Font Awesome "wpressr" icon unicode character. + /// + Wpressr = 0xF3E4, + + /// + /// The Font Awesome "wrench" icon unicode character. + /// + Wrench = 0xF0AD, + + /// + /// The Font Awesome "xbox" icon unicode character. + /// + Xbox = 0xF412, + + /// + /// The Font Awesome "xing" icon unicode character. + /// + Xing = 0xF168, + + /// + /// The Font Awesome "xing-square" icon unicode character. + /// + XingSquare = 0xF169, + + /// + /// The Font Awesome "x-ray" icon unicode character. + /// + XRay = 0xF497, + + /// + /// The Font Awesome "yahoo" icon unicode character. + /// + Yahoo = 0xF19E, + + /// + /// The Font Awesome "yammer" icon unicode character. + /// + Yammer = 0xF84, + + /// + /// The Font Awesome "yandex" icon unicode character. + /// + Yandex = 0xF413, + + /// + /// The Font Awesome "yandex-international" icon unicode character. + /// + YandexInternational = 0xF414, + + /// + /// The Font Awesome "yarn" icon unicode character. + /// + Yarn = 0xF7E3, + + /// + /// The Font Awesome "y-combinator" icon unicode character. + /// + YCombinator = 0xF23B, + + /// + /// The Font Awesome "yelp" icon unicode character. + /// + Yelp = 0xF1E9, + + /// + /// The Font Awesome "yen-sign" icon unicode character. + /// + YenSign = 0xF157, + + /// + /// The Font Awesome "yin-yang" icon unicode character. + /// + YinYang = 0xF6AD, + + /// + /// The Font Awesome "yoast" icon unicode character. + /// + Yoast = 0xF2B1, + + /// + /// The Font Awesome "youtube" icon unicode character. + /// + Youtube = 0xF167, + + /// + /// The Font Awesome "youtube-square" icon unicode character. + /// + YoutubeSquare = 0xF431, + + /// + /// The Font Awesome "zhihu" icon unicode character. + /// + Zhihu = 0xF63F, + } } diff --git a/Dalamud/Interface/GlyphRangesJapanese.cs b/Dalamud/Interface/GlyphRangesJapanese.cs index 5859a7d41..3c44492ff 100644 --- a/Dalamud/Interface/GlyphRangesJapanese.cs +++ b/Dalamud/Interface/GlyphRangesJapanese.cs @@ -1,15 +1,15 @@ -namespace Dalamud.Interface; - -/// -/// Unicode glyph ranges for the Japanese language. -/// -public static class GlyphRangesJapanese +namespace Dalamud.Interface { /// - /// Gets the unicode glyph ranges for the Japanese language. + /// Unicode glyph ranges for the Japanese language. /// - public static ushort[] GlyphRanges => new ushort[] + public static class GlyphRangesJapanese { + /// + /// Gets the unicode glyph ranges for the Japanese language. + /// + public static ushort[] GlyphRanges => new ushort[] + { 0x0020, 0x00FF, 0x0391, 0x03A1, 0x03A3, 0x03A9, 0x03B1, 0x03C1, 0x03C3, 0x03C9, 0x0401, 0x0401, 0x0410, 0x044F, 0x0451, 0x0451, 0x2000, 0x206F, 0x2103, 0x2103, 0x212B, 0x212B, 0x2190, 0x2193, 0x21D2, 0x21D2, 0x21D4, 0x21D4, 0x2200, 0x2200, 0x2202, 0x2203, 0x2207, 0x2208, 0x220B, 0x220B, 0x2212, 0x2212, 0x221A, 0x221A, 0x221D, 0x221E, 0x2220, 0x2220, 0x2227, 0x222C, 0x2234, 0x2235, @@ -524,5 +524,6 @@ public static class GlyphRangesJapanese 0x9F4E, 0x9F4F, 0x9F52, 0x9F52, 0x9F54, 0x9F54, 0x9F5F, 0x9F63, 0x9F66, 0x9F67, 0x9F6A, 0x9F6A, 0x9F6C, 0x9F6C, 0x9F72, 0x9F72, 0x9F76, 0x9F77, 0x9F8D, 0x9F8D, 0x9F95, 0x9F95, 0x9F9C, 0x9F9D, 0x9FA0, 0x9FA0, 0xFF01, 0xFF01, 0xFF03, 0xFF06, 0xFF08, 0xFF0C, 0xFF0E, 0xFF3B, 0xFF3D, 0xFF5D, 0xFF61, 0xFF9F, 0xFFE3, 0xFFE3, 0xFFE5, 0xFFE5, 0xFFFF, 0xFFFF, 0, - }; + }; + } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs index 48031df36..6630c0439 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Files.cs @@ -3,413 +3,416 @@ using System.Collections.Generic; using System.IO; using System.Linq; -namespace Dalamud.Interface.ImGuiFileDialog; +using Dalamud.Interface; -/// -/// A file or folder picker. -/// -public partial class FileDialog +namespace Dalamud.Interface.ImGuiFileDialog { - private readonly object filesLock = new(); - - private List files = new(); - private List filteredFiles = new(); - - private SortingField currentSortingField = SortingField.FileName; - private bool[] sortDescending = new[] { false, false, false, false }; - - private enum FileStructType + /// + /// A file or folder picker. + /// + public partial class FileDialog { - File, - Directory, - } + private readonly object filesLock = new(); - private enum SortingField - { - None, - FileName, - Type, - Size, - Date, - } + private List files = new(); + private List filteredFiles = new(); - private static string ComposeNewPath(List decomp) - { - if (decomp.Count == 1) + private SortingField currentSortingField = SortingField.FileName; + private bool[] sortDescending = new[] { false, false, false, false }; + + private enum FileStructType { - var drivePath = decomp[0]; - if (drivePath[^1] != Path.DirectorySeparatorChar) - { // turn C: into C:\ - drivePath += Path.DirectorySeparatorChar; - } - - return drivePath; + File, + Directory, } - return Path.Combine(decomp.ToArray()); - } - - private static FileStruct GetFile(FileInfo file, string path) - { - return new FileStruct + private enum SortingField { - FileName = file.Name, - FilePath = path, - FileModifiedDate = FormatModifiedDate(file.LastWriteTime), - FileSize = file.Length, - FormattedFileSize = BytesToString(file.Length), - Type = FileStructType.File, - Ext = file.Extension.Trim('.'), - }; - } - - private static FileStruct GetDir(DirectoryInfo dir, string path) - { - return new FileStruct - { - FileName = dir.Name, - FilePath = path, - FileModifiedDate = FormatModifiedDate(dir.LastWriteTime), - FileSize = 0, - FormattedFileSize = string.Empty, - Type = FileStructType.Directory, - Ext = string.Empty, - }; - } - - private static int SortByFileNameDesc(FileStruct a, FileStruct b) - { - if (a.FileName[0] == '.' && b.FileName[0] != '.') - { - return 1; + None, + FileName, + Type, + Size, + Date, } - if (a.FileName[0] != '.' && b.FileName[0] == '.') + private static string ComposeNewPath(List decomp) { - return -1; - } - - if (a.FileName[0] == '.' && b.FileName[0] == '.') - { - if (a.FileName.Length == 1) + if (decomp.Count == 1) { - return -1; + var drivePath = decomp[0]; + if (drivePath[^1] != Path.DirectorySeparatorChar) + { // turn C: into C:\ + drivePath += Path.DirectorySeparatorChar; + } + + return drivePath; } - if (b.FileName.Length == 1) + return Path.Combine(decomp.ToArray()); + } + + private static FileStruct GetFile(FileInfo file, string path) + { + return new FileStruct + { + FileName = file.Name, + FilePath = path, + FileModifiedDate = FormatModifiedDate(file.LastWriteTime), + FileSize = file.Length, + FormattedFileSize = BytesToString(file.Length), + Type = FileStructType.File, + Ext = file.Extension.Trim('.'), + }; + } + + private static FileStruct GetDir(DirectoryInfo dir, string path) + { + return new FileStruct + { + FileName = dir.Name, + FilePath = path, + FileModifiedDate = FormatModifiedDate(dir.LastWriteTime), + FileSize = 0, + FormattedFileSize = string.Empty, + Type = FileStructType.Directory, + Ext = string.Empty, + }; + } + + private static int SortByFileNameDesc(FileStruct a, FileStruct b) + { + if (a.FileName[0] == '.' && b.FileName[0] != '.') { return 1; } - return -1 * string.Compare(a.FileName[1..], b.FileName[1..]); - } - - if (a.Type != b.Type) - { - return a.Type == FileStructType.Directory ? 1 : -1; - } - - return -1 * string.Compare(a.FileName, b.FileName); - } - - private static int SortByFileNameAsc(FileStruct a, FileStruct b) - { - if (a.FileName[0] == '.' && b.FileName[0] != '.') - { - return -1; - } - - if (a.FileName[0] != '.' && b.FileName[0] == '.') - { - return 1; - } - - if (a.FileName[0] == '.' && b.FileName[0] == '.') - { - if (a.FileName.Length == 1) - { - return 1; - } - - if (b.FileName.Length == 1) + if (a.FileName[0] != '.' && b.FileName[0] == '.') { return -1; } - return string.Compare(a.FileName[1..], b.FileName[1..]); - } - - if (a.Type != b.Type) - { - return a.Type == FileStructType.Directory ? -1 : 1; - } - - return string.Compare(a.FileName, b.FileName); - } - - private static int SortByTypeDesc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? 1 : -1; - } - - return string.Compare(a.Ext, b.Ext); - } - - private static int SortByTypeAsc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? -1 : 1; - } - - return -1 * string.Compare(a.Ext, b.Ext); - } - - private static int SortBySizeDesc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? 1 : -1; - } - - return (a.FileSize > b.FileSize) ? 1 : -1; - } - - private static int SortBySizeAsc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? -1 : 1; - } - - return (a.FileSize > b.FileSize) ? -1 : 1; - } - - private static int SortByDateDesc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? 1 : -1; - } - - return string.Compare(a.FileModifiedDate, b.FileModifiedDate); - } - - private static int SortByDateAsc(FileStruct a, FileStruct b) - { - if (a.Type != b.Type) - { - return (a.Type == FileStructType.Directory) ? -1 : 1; - } - - return -1 * string.Compare(a.FileModifiedDate, b.FileModifiedDate); - } - - private bool CreateDir(string dirPath) - { - var newPath = Path.Combine(this.currentPath, dirPath); - if (string.IsNullOrEmpty(newPath)) - { - return false; - } - - Directory.CreateDirectory(newPath); - return true; - } - - private void ScanDir(string path) - { - if (!Directory.Exists(path)) - { - return; - } - - if (this.pathDecomposition.Count == 0) - { - this.SetCurrentDir(path); - } - - if (this.pathDecomposition.Count > 0) - { - this.files.Clear(); - - if (this.pathDecomposition.Count > 1) + if (a.FileName[0] == '.' && b.FileName[0] == '.') { - this.files.Add(new FileStruct + if (a.FileName.Length == 1) { - Type = FileStructType.Directory, - FilePath = path, - FileName = "..", - FileSize = 0, - FileModifiedDate = string.Empty, - FormattedFileSize = string.Empty, - Ext = string.Empty, - }); + return -1; + } + + if (b.FileName.Length == 1) + { + return 1; + } + + return -1 * string.Compare(a.FileName[1..], b.FileName[1..]); } - var dirInfo = new DirectoryInfo(path); - - var dontShowHidden = this.flags.HasFlag(ImGuiFileDialogFlags.DontShowHiddenFiles); - - foreach (var dir in dirInfo.EnumerateDirectories().OrderBy(d => d.Name)) + if (a.Type != b.Type) { - if (string.IsNullOrEmpty(dir.Name)) - { - continue; - } - - if (dontShowHidden && dir.Name[0] == '.') - { - continue; - } - - this.files.Add(GetDir(dir, path)); + return a.Type == FileStructType.Directory ? 1 : -1; } - foreach (var file in dirInfo.EnumerateFiles().OrderBy(f => f.Name)) + return -1 * string.Compare(a.FileName, b.FileName); + } + + private static int SortByFileNameAsc(FileStruct a, FileStruct b) + { + if (a.FileName[0] == '.' && b.FileName[0] != '.') { - if (string.IsNullOrEmpty(file.Name)) + return -1; + } + + if (a.FileName[0] != '.' && b.FileName[0] == '.') + { + return 1; + } + + if (a.FileName[0] == '.' && b.FileName[0] == '.') + { + if (a.FileName.Length == 1) { - continue; + return 1; } - if (dontShowHidden && file.Name[0] == '.') + if (b.FileName.Length == 1) { - continue; + return -1; } - if (!string.IsNullOrEmpty(file.Extension)) + return string.Compare(a.FileName[1..], b.FileName[1..]); + } + + if (a.Type != b.Type) + { + return a.Type == FileStructType.Directory ? -1 : 1; + } + + return string.Compare(a.FileName, b.FileName); + } + + private static int SortByTypeDesc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? 1 : -1; + } + + return string.Compare(a.Ext, b.Ext); + } + + private static int SortByTypeAsc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? -1 : 1; + } + + return -1 * string.Compare(a.Ext, b.Ext); + } + + private static int SortBySizeDesc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? 1 : -1; + } + + return (a.FileSize > b.FileSize) ? 1 : -1; + } + + private static int SortBySizeAsc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? -1 : 1; + } + + return (a.FileSize > b.FileSize) ? -1 : 1; + } + + private static int SortByDateDesc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? 1 : -1; + } + + return string.Compare(a.FileModifiedDate, b.FileModifiedDate); + } + + private static int SortByDateAsc(FileStruct a, FileStruct b) + { + if (a.Type != b.Type) + { + return (a.Type == FileStructType.Directory) ? -1 : 1; + } + + return -1 * string.Compare(a.FileModifiedDate, b.FileModifiedDate); + } + + private bool CreateDir(string dirPath) + { + var newPath = Path.Combine(this.currentPath, dirPath); + if (string.IsNullOrEmpty(newPath)) + { + return false; + } + + Directory.CreateDirectory(newPath); + return true; + } + + private void ScanDir(string path) + { + if (!Directory.Exists(path)) + { + return; + } + + if (this.pathDecomposition.Count == 0) + { + this.SetCurrentDir(path); + } + + if (this.pathDecomposition.Count > 0) + { + this.files.Clear(); + + if (this.pathDecomposition.Count > 1) { - var ext = file.Extension; - if (this.filters.Count > 0 && !this.selectedFilter.Empty() && !this.selectedFilter.FilterExists(ext) && this.selectedFilter.Filter != ".*") + this.files.Add(new FileStruct + { + Type = FileStructType.Directory, + FilePath = path, + FileName = "..", + FileSize = 0, + FileModifiedDate = string.Empty, + FormattedFileSize = string.Empty, + Ext = string.Empty, + }); + } + + var dirInfo = new DirectoryInfo(path); + + var dontShowHidden = this.flags.HasFlag(ImGuiFileDialogFlags.DontShowHiddenFiles); + + foreach (var dir in dirInfo.EnumerateDirectories().OrderBy(d => d.Name)) + { + if (string.IsNullOrEmpty(dir.Name)) { continue; } + + if (dontShowHidden && dir.Name[0] == '.') + { + continue; + } + + this.files.Add(GetDir(dir, path)); } - this.files.Add(GetFile(file, path)); + foreach (var file in dirInfo.EnumerateFiles().OrderBy(f => f.Name)) + { + if (string.IsNullOrEmpty(file.Name)) + { + continue; + } + + if (dontShowHidden && file.Name[0] == '.') + { + continue; + } + + if (!string.IsNullOrEmpty(file.Extension)) + { + var ext = file.Extension; + if (this.filters.Count > 0 && !this.selectedFilter.Empty() && !this.selectedFilter.FilterExists(ext) && this.selectedFilter.Filter != ".*") + { + continue; + } + } + + this.files.Add(GetFile(file, path)); + } + + this.SortFields(this.currentSortingField); + } + } + + private void SetupSideBar() + { + var drives = DriveInfo.GetDrives(); + foreach (var drive in drives) + { + this.drives.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Server, + Location = drive.Name, + Text = drive.Name, + }); } - this.SortFields(this.currentSortingField); - } - } + var personal = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); - private void SetupSideBar() - { - var drives = DriveInfo.GetDrives(); - foreach (var drive in drives) - { - this.drives.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Server, - Location = drive.Name, - Text = drive.Name, - }); - } - - var personal = Path.GetDirectoryName(Environment.GetFolderPath(Environment.SpecialFolder.Personal)); - - this.quickAccess.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Desktop, - Location = Environment.GetFolderPath(Environment.SpecialFolder.Desktop), - Text = "Desktop", - }); - - this.quickAccess.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.File, - Location = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - Text = "Documents", - }); - - if (!string.IsNullOrEmpty(personal)) - { this.quickAccess.Add(new SideBarItem { - Icon = (char)FontAwesomeIcon.Download, - Location = Path.Combine(personal, "Downloads"), - Text = "Downloads", + Icon = (char)FontAwesomeIcon.Desktop, + Location = Environment.GetFolderPath(Environment.SpecialFolder.Desktop), + Text = "Desktop", + }); + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.File, + Location = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + Text = "Documents", + }); + + if (!string.IsNullOrEmpty(personal)) + { + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Download, + Location = Path.Combine(personal, "Downloads"), + Text = "Downloads", + }); + } + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Star, + Location = Environment.GetFolderPath(Environment.SpecialFolder.Favorites), + Text = "Favorites", + }); + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Music, + Location = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), + Text = "Music", + }); + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Image, + Location = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), + Text = "Pictures", + }); + + this.quickAccess.Add(new SideBarItem + { + Icon = (char)FontAwesomeIcon.Video, + Location = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos), + Text = "Videos", }); } - this.quickAccess.Add(new SideBarItem + private void SortFields(SortingField sortingField, bool canChangeOrder = false) { - Icon = (char)FontAwesomeIcon.Star, - Location = Environment.GetFolderPath(Environment.SpecialFolder.Favorites), - Text = "Favorites", - }); + switch (sortingField) + { + case SortingField.FileName: + if (canChangeOrder && sortingField == this.currentSortingField) + { + this.sortDescending[0] = !this.sortDescending[0]; + } - this.quickAccess.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Music, - Location = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic), - Text = "Music", - }); + this.files.Sort(this.sortDescending[0] ? SortByFileNameDesc : SortByFileNameAsc); + break; - this.quickAccess.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Image, - Location = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), - Text = "Pictures", - }); + case SortingField.Type: + if (canChangeOrder && sortingField == this.currentSortingField) + { + this.sortDescending[1] = !this.sortDescending[1]; + } - this.quickAccess.Add(new SideBarItem - { - Icon = (char)FontAwesomeIcon.Video, - Location = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos), - Text = "Videos", - }); - } + this.files.Sort(this.sortDescending[1] ? SortByTypeDesc : SortByTypeAsc); + break; - private void SortFields(SortingField sortingField, bool canChangeOrder = false) - { - switch (sortingField) - { - case SortingField.FileName: - if (canChangeOrder && sortingField == this.currentSortingField) - { - this.sortDescending[0] = !this.sortDescending[0]; - } + case SortingField.Size: + if (canChangeOrder && sortingField == this.currentSortingField) + { + this.sortDescending[2] = !this.sortDescending[2]; + } - this.files.Sort(this.sortDescending[0] ? SortByFileNameDesc : SortByFileNameAsc); - break; + this.files.Sort(this.sortDescending[2] ? SortBySizeDesc : SortBySizeAsc); + break; - case SortingField.Type: - if (canChangeOrder && sortingField == this.currentSortingField) - { - this.sortDescending[1] = !this.sortDescending[1]; - } + case SortingField.Date: + if (canChangeOrder && sortingField == this.currentSortingField) + { + this.sortDescending[3] = !this.sortDescending[3]; + } - this.files.Sort(this.sortDescending[1] ? SortByTypeDesc : SortByTypeAsc); - break; + this.files.Sort(this.sortDescending[3] ? SortByDateDesc : SortByDateAsc); + break; + } - case SortingField.Size: - if (canChangeOrder && sortingField == this.currentSortingField) - { - this.sortDescending[2] = !this.sortDescending[2]; - } + if (sortingField != SortingField.None) + { + this.currentSortingField = sortingField; + } - this.files.Sort(this.sortDescending[2] ? SortBySizeDesc : SortBySizeAsc); - break; - - case SortingField.Date: - if (canChangeOrder && sortingField == this.currentSortingField) - { - this.sortDescending[3] = !this.sortDescending[3]; - } - - this.files.Sort(this.sortDescending[3] ? SortByDateDesc : SortByDateAsc); - break; + this.ApplyFilteringOnFileList(); } - - if (sortingField != SortingField.None) - { - this.currentSortingField = sortingField; - } - - this.ApplyFilteringOnFileList(); } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Filters.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Filters.cs index 919e65592..037155229 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Filters.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Filters.cs @@ -1,107 +1,108 @@ using System.Collections.Generic; using System.Text.RegularExpressions; -namespace Dalamud.Interface.ImGuiFileDialog; - -/// -/// A file or folder picker. -/// -public partial class FileDialog +namespace Dalamud.Interface.ImGuiFileDialog { - private static Regex filterRegex = new(@"[^,{}]+(\{([^{}]*?)\})?", RegexOptions.Compiled); - - private List filters = new(); - private FilterStruct selectedFilter; - - private void ParseFilters(string filters) + /// + /// A file or folder picker. + /// + public partial class FileDialog { - // ".*,.cpp,.h,.hpp" - // "Source files{.cpp,.h,.hpp},Image files{.png,.gif,.jpg,.jpeg},.md" + private static Regex filterRegex = new(@"[^,{}]+(\{([^{}]*?)\})?", RegexOptions.Compiled); - this.filters.Clear(); - if (filters.Length == 0) return; + private List filters = new(); + private FilterStruct selectedFilter; - var currentFilterFound = false; - var matches = filterRegex.Matches(filters); - foreach (Match m in matches) + private void ParseFilters(string filters) { - var match = m.Value; - var filter = default(FilterStruct); + // ".*,.cpp,.h,.hpp" + // "Source files{.cpp,.h,.hpp},Image files{.png,.gif,.jpg,.jpeg},.md" - if (match.Contains("{")) + this.filters.Clear(); + if (filters.Length == 0) return; + + var currentFilterFound = false; + var matches = filterRegex.Matches(filters); + foreach (Match m in matches) { - var exts = m.Groups[2].Value; - filter = new FilterStruct + var match = m.Value; + var filter = default(FilterStruct); + + if (match.Contains("{")) { - Filter = match.Split('{')[0], - CollectionFilters = new HashSet(exts.Split(',')), - }; - } - else - { - filter = new FilterStruct + var exts = m.Groups[2].Value; + filter = new FilterStruct + { + Filter = match.Split('{')[0], + CollectionFilters = new HashSet(exts.Split(',')), + }; + } + else { - Filter = match, - CollectionFilters = new(), - }; - } - - this.filters.Add(filter); - - if (!currentFilterFound && filter.Filter == this.selectedFilter.Filter) - { - currentFilterFound = true; - this.selectedFilter = filter; - } - } - - if (!currentFilterFound && !(this.filters.Count == 0)) - { - this.selectedFilter = this.filters[0]; - } - } - - private void SetSelectedFilterWithExt(string ext) - { - if (this.filters.Count == 0) return; - if (string.IsNullOrEmpty(ext)) return; - - foreach (var filter in this.filters) - { - if (filter.FilterExists(ext)) - { - this.selectedFilter = filter; - } - } - - if (this.selectedFilter.Empty()) - { - this.selectedFilter = this.filters[0]; - } - } - - private void ApplyFilteringOnFileList() - { - lock (this.filesLock) - { - this.filteredFiles.Clear(); - - foreach (var file in this.files) - { - var show = true; - if (!string.IsNullOrEmpty(this.searchBuffer) && !file.FileName.ToLower().Contains(this.searchBuffer.ToLower())) - { - show = false; + filter = new FilterStruct + { + Filter = match, + CollectionFilters = new(), + }; } - if (this.IsDirectoryMode() && file.Type != FileStructType.Directory) - { - show = false; - } + this.filters.Add(filter); - if (show) + if (!currentFilterFound && filter.Filter == this.selectedFilter.Filter) { - this.filteredFiles.Add(file); + currentFilterFound = true; + this.selectedFilter = filter; + } + } + + if (!currentFilterFound && !(this.filters.Count == 0)) + { + this.selectedFilter = this.filters[0]; + } + } + + private void SetSelectedFilterWithExt(string ext) + { + if (this.filters.Count == 0) return; + if (string.IsNullOrEmpty(ext)) return; + + foreach (var filter in this.filters) + { + if (filter.FilterExists(ext)) + { + this.selectedFilter = filter; + } + } + + if (this.selectedFilter.Empty()) + { + this.selectedFilter = this.filters[0]; + } + } + + private void ApplyFilteringOnFileList() + { + lock (this.filesLock) + { + this.filteredFiles.Clear(); + + foreach (var file in this.files) + { + var show = true; + if (!string.IsNullOrEmpty(this.searchBuffer) && !file.FileName.ToLower().Contains(this.searchBuffer.ToLower())) + { + show = false; + } + + if (this.IsDirectoryMode() && file.Type != FileStructType.Directory) + { + show = false; + } + + if (show) + { + this.filteredFiles.Add(file); + } } } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Helpers.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Helpers.cs index 96bdb3172..16bc3e46f 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Helpers.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Helpers.cs @@ -1,25 +1,26 @@ using System; -namespace Dalamud.Interface.ImGuiFileDialog; - -/// -/// A file or folder picker. -/// -public partial class FileDialog +namespace Dalamud.Interface.ImGuiFileDialog { - private static string FormatModifiedDate(DateTime date) + /// + /// A file or folder picker. + /// + public partial class FileDialog { - return date.ToString("yyyy/MM/dd HH:mm"); - } + private static string FormatModifiedDate(DateTime date) + { + return date.ToString("yyyy/MM/dd HH:mm"); + } - private static string BytesToString(long byteCount) - { - string[] suf = { " B", " KB", " MB", " GB", " TB" }; - if (byteCount == 0) - return "0" + suf[0]; - var bytes = Math.Abs(byteCount); - var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); - var num = Math.Round(bytes / Math.Pow(1024, place), 1); - return (Math.Sign(byteCount) * num).ToString() + suf[place]; + private static string BytesToString(long byteCount) + { + string[] suf = { " B", " KB", " MB", " GB", " TB" }; + if (byteCount == 0) + return "0" + suf[0]; + var bytes = Math.Abs(byteCount); + var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + var num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(byteCount) * num).ToString() + suf[place]; + } } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs index 18e8b4cf9..475147518 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.Structs.cs @@ -2,56 +2,57 @@ using System; using System.Collections.Generic; using System.Numerics; -namespace Dalamud.Interface.ImGuiFileDialog; - -/// -/// A file or folder picker. -/// -public partial class FileDialog +namespace Dalamud.Interface.ImGuiFileDialog { - private struct FileStruct + /// + /// A file or folder picker. + /// + public partial class FileDialog { - public FileStructType Type; - public string FilePath; - public string FileName; - public string Ext; - public long FileSize; - public string FormattedFileSize; - public string FileModifiedDate; - } - - private struct SideBarItem - { - public char Icon; - public string Text; - public string Location; - } - - private struct FilterStruct - { - public string Filter; - public HashSet CollectionFilters; - - public void Clear() + private struct FileStruct { - this.Filter = string.Empty; - this.CollectionFilters.Clear(); + public FileStructType Type; + public string FilePath; + public string FileName; + public string Ext; + public long FileSize; + public string FormattedFileSize; + public string FileModifiedDate; } - public bool Empty() + private struct SideBarItem { - return string.IsNullOrEmpty(this.Filter) && ((this.CollectionFilters == null) || (this.CollectionFilters.Count == 0)); + public char Icon; + public string Text; + public string Location; } - public bool FilterExists(string filter) + private struct FilterStruct { - return (this.Filter == filter) || (this.CollectionFilters != null && this.CollectionFilters.Contains(filter)); - } - } + public string Filter; + public HashSet CollectionFilters; - private struct IconColorItem - { - public char Icon; - public Vector4 Color; + public void Clear() + { + this.Filter = string.Empty; + this.CollectionFilters.Clear(); + } + + public bool Empty() + { + return string.IsNullOrEmpty(this.Filter) && ((this.CollectionFilters == null) || (this.CollectionFilters.Count == 0)); + } + + public bool FilterExists(string filter) + { + return (this.Filter == filter) || (this.CollectionFilters != null && this.CollectionFilters.Contains(filter)); + } + } + + private struct IconColorItem + { + public char Icon; + public Vector4 Color; + } } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs index 68b7917b0..d5010f13c 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.UI.cs @@ -4,590 +4,577 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface.ImGuiFileDialog; - -/// -/// A file or folder picker. -/// -public partial class FileDialog +namespace Dalamud.Interface.ImGuiFileDialog { - private static Vector4 pathDecompColor = new(0.188f, 0.188f, 0.2f, 1f); - private static Vector4 selectedTextColor = new(1.00000000000f, 0.33333333333f, 0.33333333333f, 1f); - private static Vector4 dirTextColor = new(0.54509803922f, 0.91372549020f, 0.99215686275f, 1f); - private static Vector4 codeTextColor = new(0.94509803922f, 0.98039215686f, 0.54901960784f, 1f); - private static Vector4 miscTextColor = new(1.00000000000f, 0.47450980392f, 0.77647058824f, 1f); - private static Vector4 imageTextColor = new(0.31372549020f, 0.98039215686f, 0.48235294118f, 1f); - private static Vector4 standardTextColor = new(1f); - - private static Dictionary iconMap; - /// - /// Draws the dialog. + /// A file or folder picker. /// - /// Whether a selection or cancel action was performed. - public bool Draw() + public partial class FileDialog { - if (!this.visible) return false; + private static Vector4 pathDecompColor = new(0.188f, 0.188f, 0.2f, 1f); + private static Vector4 selectedTextColor = new(1.00000000000f, 0.33333333333f, 0.33333333333f, 1f); + private static Vector4 dirTextColor = new(0.54509803922f, 0.91372549020f, 0.99215686275f, 1f); + private static Vector4 codeTextColor = new(0.94509803922f, 0.98039215686f, 0.54901960784f, 1f); + private static Vector4 miscTextColor = new(1.00000000000f, 0.47450980392f, 0.77647058824f, 1f); + private static Vector4 imageTextColor = new(0.31372549020f, 0.98039215686f, 0.48235294118f, 1f); + private static Vector4 standardTextColor = new(1f); - var res = false; - var name = this.title + "###" + this.id; + private static Dictionary iconMap; - bool windowVisible; - this.isOk = false; - this.wantsToQuit = false; - - this.ResetEvents(); - - ImGui.SetNextWindowSize(new Vector2(800, 500), ImGuiCond.FirstUseEver); - - if (this.isModal && !this.okResultToConfirm) + /// + /// Draws the dialog. + /// + /// Whether a selection or cancel action was performed. + public bool Draw() { - ImGui.OpenPopup(name); - windowVisible = ImGui.BeginPopupModal(name, ref this.visible, ImGuiWindowFlags.NoScrollbar); - } - else - { - windowVisible = ImGui.Begin(name, ref this.visible, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoNav); - } + if (!this.visible) return false; - bool wasClosed = false; - if (windowVisible) - { - if (!this.visible) - { // window closed - this.isOk = false; - wasClosed = true; - } - else - { - if (this.selectedFilter.Empty() && (this.filters.Count > 0)) - { - this.selectedFilter = this.filters[0]; - } + var res = false; + var name = this.title + "###" + this.id; - if (this.files.Count == 0) - { - if (!string.IsNullOrEmpty(this.defaultFileName)) - { - this.SetDefaultFileName(); - this.SetSelectedFilterWithExt(this.defaultExtension); - } - else if (this.IsDirectoryMode()) - { - this.SetDefaultFileName(); - } + bool windowVisible; + this.isOk = false; + this.wantsToQuit = false; - this.ScanDir(this.currentPath); - } + this.ResetEvents(); - this.DrawHeader(); - this.DrawContent(); - res = this.DrawFooter(); - } + ImGui.SetNextWindowSize(new Vector2(800, 500), ImGuiCond.FirstUseEver); if (this.isModal && !this.okResultToConfirm) { - ImGui.EndPopup(); - } - } - - if (!this.isModal || this.okResultToConfirm) - { - ImGui.End(); - } - - return wasClosed || this.ConfirmOrOpenOverWriteFileDialogIfNeeded(res); - } - - private static void AddToIconMap(string[] extensions, char icon, Vector4 color) - { - foreach (var ext in extensions) - { - iconMap[ext] = new IconColorItem - { - Icon = icon, - Color = color, - }; - } - } - - private static IconColorItem GetIcon(string ext) - { - if (iconMap == null) - { - iconMap = new(); - AddToIconMap(new[] { "mp4", "gif", "mov", "avi" }, (char)FontAwesomeIcon.FileVideo, miscTextColor); - AddToIconMap(new[] { "pdf" }, (char)FontAwesomeIcon.FilePdf, miscTextColor); - AddToIconMap(new[] { "png", "jpg", "jpeg", "tiff" }, (char)FontAwesomeIcon.FileImage, imageTextColor); - AddToIconMap(new[] { "cs", "json", "cpp", "h", "py", "xml", "yaml", "js", "html", "css", "ts", "java" }, (char)FontAwesomeIcon.FileCode, codeTextColor); - AddToIconMap(new[] { "txt", "md" }, (char)FontAwesomeIcon.FileAlt, standardTextColor); - AddToIconMap(new[] { "zip", "7z", "gz", "tar" }, (char)FontAwesomeIcon.FileArchive, miscTextColor); - AddToIconMap(new[] { "mp3", "m4a", "ogg", "wav" }, (char)FontAwesomeIcon.FileAudio, miscTextColor); - AddToIconMap(new[] { "csv" }, (char)FontAwesomeIcon.FileCsv, miscTextColor); - } - - return iconMap.TryGetValue(ext.ToLower(), out var icon) ? icon : new IconColorItem - { - Icon = (char)FontAwesomeIcon.File, - Color = standardTextColor, - }; - } - - private void DrawHeader() - { - this.DrawPathComposer(); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2); - ImGui.Separator(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2); - - this.DrawSearchBar(); - } - - private void DrawPathComposer() - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button($"{(this.pathInputActivated ? (char)FontAwesomeIcon.Times : (char)FontAwesomeIcon.Edit)}")) - { - this.pathInputActivated = !this.pathInputActivated; - } - - ImGui.PopFont(); - - ImGui.SameLine(); - - if (this.pathDecomposition.Count > 0) - { - ImGui.SameLine(); - - if (this.pathInputActivated) - { - ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); - ImGui.InputText("##pathedit", ref this.pathInputBuffer, 255); - ImGui.PopItemWidth(); + ImGui.OpenPopup(name); + windowVisible = ImGui.BeginPopupModal(name, ref this.visible, ImGuiWindowFlags.NoScrollbar); } else { - for (var idx = 0; idx < this.pathDecomposition.Count; idx++) - { - if (idx > 0) - { - ImGui.SameLine(); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() - 3); - } - - ImGui.PushID(idx); - ImGui.PushStyleColor(ImGuiCol.Button, pathDecompColor); - var click = ImGui.Button(this.pathDecomposition[idx]); - ImGui.PopStyleColor(); - ImGui.PopID(); - - if (click) - { - this.currentPath = ComposeNewPath(this.pathDecomposition.GetRange(0, idx + 1)); - this.pathClicked = true; - break; - } - - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - this.pathInputBuffer = ComposeNewPath(this.pathDecomposition.GetRange(0, idx + 1)); - this.pathInputActivated = true; - break; - } - } + windowVisible = ImGui.Begin(name, ref this.visible, ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoNav); } - } - } - private void DrawSearchBar() - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button($"{(char)FontAwesomeIcon.Home}")) - { - this.SetPath("."); - } - - ImGui.PopFont(); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Reset to current directory"); - } - - ImGui.SameLine(); - - this.DrawDirectoryCreation(); - - if (!this.createDirectoryMode) - { - ImGui.SameLine(); - ImGui.Text("Search :"); - ImGui.SameLine(); - ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); - var edited = ImGui.InputText("##InputImGuiFileDialogSearchField", ref this.searchBuffer, 255); - ImGui.PopItemWidth(); - if (edited) + bool wasClosed = false; + if (windowVisible) { - this.ApplyFilteringOnFileList(); - } - } - } - - private void DrawDirectoryCreation() - { - if (this.flags.HasFlag(ImGuiFileDialogFlags.DisableCreateDirectoryButton)) return; - - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button($"{(char)FontAwesomeIcon.FolderPlus}")) - { - if (!this.createDirectoryMode) - { - this.createDirectoryMode = true; - this.createDirectoryBuffer = string.Empty; - } - } - - ImGui.PopFont(); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip("Create Directory"); - } - - if (this.createDirectoryMode) - { - ImGui.SameLine(); - ImGui.Text("New Directory Name"); - - ImGui.SameLine(); - ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X - 100f); - ImGui.InputText("##DirectoryFileName", ref this.createDirectoryBuffer, 255); - ImGui.PopItemWidth(); - - ImGui.SameLine(); - - if (ImGui.Button("Ok")) - { - if (this.CreateDir(this.createDirectoryBuffer)) - { - this.SetPath(Path.Combine(this.currentPath, this.createDirectoryBuffer)); - } - - this.createDirectoryMode = false; - } - - ImGui.SameLine(); - - if (ImGui.Button("Cancel")) - { - this.createDirectoryMode = false; - } - } - } - - private void DrawContent() - { - var size = ImGui.GetContentRegionAvail() - new Vector2(0, this.footerHeight); - - if (!this.flags.HasFlag(ImGuiFileDialogFlags.HideSideBar)) - { - ImGui.BeginChild("##FileDialog_ColumnChild", size); - ImGui.Columns(2, "##FileDialog_Columns"); - - this.DrawSideBar(new Vector2(150, size.Y)); - - ImGui.SetColumnWidth(0, 150); - ImGui.NextColumn(); - - this.DrawFileListView(size - new Vector2(160, 0)); - - ImGui.Columns(1); - ImGui.EndChild(); - } - else - { - this.DrawFileListView(size); - } - } - - private void DrawSideBar(Vector2 size) - { - ImGui.BeginChild("##FileDialog_SideBar", size); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5); - - foreach (var drive in this.drives) - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Selectable($"{drive.Icon}##{drive.Text}", drive.Text == this.selectedSideBar)) - { - this.SetPath(drive.Location); - this.selectedSideBar = drive.Text; - } - - ImGui.PopFont(); - - ImGui.SameLine(25); - - ImGui.Text(drive.Text); - } - - foreach (var quick in this.quickAccess) - { - if (string.IsNullOrEmpty(quick.Location)) - { - continue; - } - - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Selectable($"{quick.Icon}##{quick.Text}", quick.Text == this.selectedSideBar)) - { - this.SetPath(quick.Location); - this.selectedSideBar = quick.Text; - } - - ImGui.PopFont(); - - ImGui.SameLine(25); - - ImGui.Text(quick.Text); - } - - ImGui.EndChild(); - } - - private unsafe void DrawFileListView(Vector2 size) - { - ImGui.BeginChild("##FileDialog_FileList", size); - - var tableFlags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.Hideable | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoHostExtendX; - if (ImGui.BeginTable("##FileTable", 4, tableFlags, size)) - { - ImGui.TableSetupScrollFreeze(0, 1); - - var hideType = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnType); - var hideSize = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnSize); - var hideDate = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnDate); - - ImGui.TableSetupColumn(" File Name", ImGuiTableColumnFlags.WidthStretch, -1, 0); - ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed | (hideType ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 1); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed | (hideSize ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 2); - ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.WidthFixed | (hideDate ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 3); - - ImGui.TableNextRow(ImGuiTableRowFlags.Headers); - for (var column = 0; column < 4; column++) - { - ImGui.TableSetColumnIndex(column); - var columnName = ImGui.TableGetColumnName(column); - ImGui.PushID(column); - ImGui.TableHeader(columnName); - ImGui.PopID(); - if (ImGui.IsItemClicked()) - { - if (column == 0) - { - this.SortFields(SortingField.FileName, true); - } - else if (column == 1) - { - this.SortFields(SortingField.Type, true); - } - else if (column == 2) - { - this.SortFields(SortingField.Size, true); - } - else - { - this.SortFields(SortingField.Date, true); - } - } - } - - if (this.filteredFiles.Count > 0) - { - ImGuiListClipperPtr clipper; - unsafe - { - clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - } - - lock (this.filesLock) - { - clipper.Begin(this.filteredFiles.Count); - while (clipper.Step()) - { - for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - { - if (i < 0) continue; - - var file = this.filteredFiles[i]; - var selected = this.selectedFileNames.Contains(file.FileName); - var needToBreak = false; - - var dir = file.Type == FileStructType.Directory; - var item = !dir ? GetIcon(file.Ext) : new IconColorItem - { - Color = dirTextColor, - Icon = (char)FontAwesomeIcon.Folder, - }; - - ImGui.PushStyleColor(ImGuiCol.Text, item.Color); - if (selected) ImGui.PushStyleColor(ImGuiCol.Text, selectedTextColor); - - ImGui.TableNextRow(); - - if (ImGui.TableNextColumn()) - { - needToBreak = this.SelectableItem(file, selected, item.Icon); - } - - if (ImGui.TableNextColumn()) - { - ImGui.Text(file.Ext); - } - - if (ImGui.TableNextColumn()) - { - if (file.Type == FileStructType.File) - { - ImGui.Text(file.FormattedFileSize + " "); - } - else - { - ImGui.Text(" "); - } - } - - if (ImGui.TableNextColumn()) - { - var sz = ImGui.CalcTextSize(file.FileModifiedDate); - ImGui.PushItemWidth(sz.X + 5); - ImGui.Text(file.FileModifiedDate + " "); - ImGui.PopItemWidth(); - } - - if (selected) ImGui.PopStyleColor(); - ImGui.PopStyleColor(); - - if (needToBreak) break; - } - } - - clipper.End(); - } - } - - if (this.pathInputActivated) - { - if (ImGui.IsKeyReleased(ImGui.GetKeyIndex(ImGuiKey.Enter))) - { - if (Directory.Exists(this.pathInputBuffer)) this.SetPath(this.pathInputBuffer); - this.pathInputActivated = false; - } - - if (ImGui.IsKeyReleased(ImGui.GetKeyIndex(ImGuiKey.Escape))) - { - this.pathInputActivated = false; - } - } - - ImGui.EndTable(); - } - - if (this.pathClicked) - { - this.SetPath(this.currentPath); - } - - ImGui.EndChild(); - } - - private bool SelectableItem(FileStruct file, bool selected, char icon) - { - var flags = ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.SpanAllColumns; - - ImGui.PushFont(UiBuilder.IconFont); - - ImGui.Text($"{icon}"); - ImGui.PopFont(); - - ImGui.SameLine(25f); - - if (ImGui.Selectable(file.FileName, selected, flags)) - { - if (file.Type == FileStructType.Directory) - { - if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) - { - this.pathClicked = this.SelectDirectory(file); - return true; - } - else if (this.IsDirectoryMode()) - { - this.SelectFileName(file); - } - } - else - { - this.SelectFileName(file); - if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) - { - this.wantsToQuit = true; - this.isOk = true; - } - } - } - - return false; - } - - private bool SelectDirectory(FileStruct file) - { - var pathClick = false; - - if (file.FileName == "..") - { - if (this.pathDecomposition.Count > 1) - { - this.currentPath = ComposeNewPath(this.pathDecomposition.GetRange(0, this.pathDecomposition.Count - 1)); - pathClick = true; - } - } - else - { - var newPath = Path.Combine(this.currentPath, file.FileName); - - if (Directory.Exists(newPath)) - { - this.currentPath = newPath; - } - - pathClick = true; - } - - return pathClick; - } - - private void SelectFileName(FileStruct file) - { - if (ImGui.GetIO().KeyCtrl) - { - if (this.selectionCountMax == 0) - { // infinite select - if (!this.selectedFileNames.Contains(file.FileName)) - { - this.AddFileNameInSelection(file.FileName, true); + if (!this.visible) + { // window closed + this.isOk = false; + wasClosed = true; } else { - this.RemoveFileNameInSelection(file.FileName); + if (this.selectedFilter.Empty() && (this.filters.Count > 0)) + { + this.selectedFilter = this.filters[0]; + } + + if (this.files.Count == 0) + { + if (!string.IsNullOrEmpty(this.defaultFileName)) + { + this.SetDefaultFileName(); + this.SetSelectedFilterWithExt(this.defaultExtension); + } + else if (this.IsDirectoryMode()) + { + this.SetDefaultFileName(); + } + + this.ScanDir(this.currentPath); + } + + this.DrawHeader(); + this.DrawContent(); + res = this.DrawFooter(); + } + + if (this.isModal && !this.okResultToConfirm) + { + ImGui.EndPopup(); + } + } + + if (!this.isModal || this.okResultToConfirm) + { + ImGui.End(); + } + + return wasClosed || this.ConfirmOrOpenOverWriteFileDialogIfNeeded(res); + } + + private static void AddToIconMap(string[] extensions, char icon, Vector4 color) + { + foreach (var ext in extensions) + { + iconMap[ext] = new IconColorItem + { + Icon = icon, + Color = color, + }; + } + } + + private static IconColorItem GetIcon(string ext) + { + if (iconMap == null) + { + iconMap = new(); + AddToIconMap(new[] { "mp4", "gif", "mov", "avi" }, (char)FontAwesomeIcon.FileVideo, miscTextColor); + AddToIconMap(new[] { "pdf" }, (char)FontAwesomeIcon.FilePdf, miscTextColor); + AddToIconMap(new[] { "png", "jpg", "jpeg", "tiff" }, (char)FontAwesomeIcon.FileImage, imageTextColor); + AddToIconMap(new[] { "cs", "json", "cpp", "h", "py", "xml", "yaml", "js", "html", "css", "ts", "java" }, (char)FontAwesomeIcon.FileCode, codeTextColor); + AddToIconMap(new[] { "txt", "md" }, (char)FontAwesomeIcon.FileAlt, standardTextColor); + AddToIconMap(new[] { "zip", "7z", "gz", "tar" }, (char)FontAwesomeIcon.FileArchive, miscTextColor); + AddToIconMap(new[] { "mp3", "m4a", "ogg", "wav" }, (char)FontAwesomeIcon.FileAudio, miscTextColor); + AddToIconMap(new[] { "csv" }, (char)FontAwesomeIcon.FileCsv, miscTextColor); + } + + return iconMap.TryGetValue(ext.ToLower(), out var icon) ? icon : new IconColorItem + { + Icon = (char)FontAwesomeIcon.File, + Color = standardTextColor, + }; + } + + private void DrawHeader() + { + this.DrawPathComposer(); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2); + ImGui.Separator(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 2); + + this.DrawSearchBar(); + } + + private void DrawPathComposer() + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button($"{(this.pathInputActivated ? (char)FontAwesomeIcon.Times : (char)FontAwesomeIcon.Edit)}")) + { + this.pathInputActivated = !this.pathInputActivated; + } + + ImGui.PopFont(); + + ImGui.SameLine(); + + if (this.pathDecomposition.Count > 0) + { + ImGui.SameLine(); + + if (this.pathInputActivated) + { + ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); + ImGui.InputText("##pathedit", ref this.pathInputBuffer, 255); + ImGui.PopItemWidth(); + } + else + { + for (var idx = 0; idx < this.pathDecomposition.Count; idx++) + { + if (idx > 0) + { + ImGui.SameLine(); + ImGui.SetCursorPosX(ImGui.GetCursorPosX() - 3); + } + + ImGui.PushID(idx); + ImGui.PushStyleColor(ImGuiCol.Button, pathDecompColor); + var click = ImGui.Button(this.pathDecomposition[idx]); + ImGui.PopStyleColor(); + ImGui.PopID(); + + if (click) + { + this.currentPath = ComposeNewPath(this.pathDecomposition.GetRange(0, idx + 1)); + this.pathClicked = true; + break; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + this.pathInputBuffer = ComposeNewPath(this.pathDecomposition.GetRange(0, idx + 1)); + this.pathInputActivated = true; + break; + } + } + } + } + } + + private void DrawSearchBar() + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button($"{(char)FontAwesomeIcon.Home}")) + { + this.SetPath("."); + } + + ImGui.PopFont(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Reset to current directory"); + } + + ImGui.SameLine(); + + this.DrawDirectoryCreation(); + + if (!this.createDirectoryMode) + { + ImGui.SameLine(); + ImGui.Text("Search :"); + ImGui.SameLine(); + ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X); + var edited = ImGui.InputText("##InputImGuiFileDialogSearchField", ref this.searchBuffer, 255); + ImGui.PopItemWidth(); + if (edited) + { + this.ApplyFilteringOnFileList(); + } + } + } + + private void DrawDirectoryCreation() + { + if (this.flags.HasFlag(ImGuiFileDialogFlags.DisableCreateDirectoryButton)) return; + + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button($"{(char)FontAwesomeIcon.FolderPlus}")) + { + if (!this.createDirectoryMode) + { + this.createDirectoryMode = true; + this.createDirectoryBuffer = string.Empty; + } + } + + ImGui.PopFont(); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip("Create Directory"); + } + + if (this.createDirectoryMode) + { + ImGui.SameLine(); + ImGui.Text("New Directory Name"); + + ImGui.SameLine(); + ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X - 100f); + ImGui.InputText("##DirectoryFileName", ref this.createDirectoryBuffer, 255); + ImGui.PopItemWidth(); + + ImGui.SameLine(); + + if (ImGui.Button("Ok")) + { + if (this.CreateDir(this.createDirectoryBuffer)) + { + this.SetPath(Path.Combine(this.currentPath, this.createDirectoryBuffer)); + } + + this.createDirectoryMode = false; + } + + ImGui.SameLine(); + + if (ImGui.Button("Cancel")) + { + this.createDirectoryMode = false; + } + } + } + + private void DrawContent() + { + var size = ImGui.GetContentRegionAvail() - new Vector2(0, this.footerHeight); + + if (!this.flags.HasFlag(ImGuiFileDialogFlags.HideSideBar)) + { + ImGui.BeginChild("##FileDialog_ColumnChild", size); + ImGui.Columns(2, "##FileDialog_Columns"); + + this.DrawSideBar(new Vector2(150, size.Y)); + + ImGui.SetColumnWidth(0, 150); + ImGui.NextColumn(); + + this.DrawFileListView(size - new Vector2(160, 0)); + + ImGui.Columns(1); + ImGui.EndChild(); + } + else + { + this.DrawFileListView(size); + } + } + + private void DrawSideBar(Vector2 size) + { + ImGui.BeginChild("##FileDialog_SideBar", size); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5); + + foreach (var drive in this.drives) + { + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Selectable($"{drive.Icon}##{drive.Text}", drive.Text == this.selectedSideBar)) + { + this.SetPath(drive.Location); + this.selectedSideBar = drive.Text; + } + + ImGui.PopFont(); + + ImGui.SameLine(25); + + ImGui.Text(drive.Text); + } + + foreach (var quick in this.quickAccess) + { + if (string.IsNullOrEmpty(quick.Location)) + { + continue; + } + + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Selectable($"{quick.Icon}##{quick.Text}", quick.Text == this.selectedSideBar)) + { + this.SetPath(quick.Location); + this.selectedSideBar = quick.Text; + } + + ImGui.PopFont(); + + ImGui.SameLine(25); + + ImGui.Text(quick.Text); + } + + ImGui.EndChild(); + } + + private unsafe void DrawFileListView(Vector2 size) + { + ImGui.BeginChild("##FileDialog_FileList", size); + + var tableFlags = ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg | ImGuiTableFlags.Hideable | ImGuiTableFlags.ScrollY | ImGuiTableFlags.NoHostExtendX; + if (ImGui.BeginTable("##FileTable", 4, tableFlags, size)) + { + ImGui.TableSetupScrollFreeze(0, 1); + + var hideType = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnType); + var hideSize = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnSize); + var hideDate = this.flags.HasFlag(ImGuiFileDialogFlags.HideColumnDate); + + ImGui.TableSetupColumn(" File Name", ImGuiTableColumnFlags.WidthStretch, -1, 0); + ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed | (hideType ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 1); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed | (hideSize ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 2); + ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.WidthFixed | (hideDate ? ImGuiTableColumnFlags.DefaultHide : ImGuiTableColumnFlags.None), -1, 3); + + ImGui.TableNextRow(ImGuiTableRowFlags.Headers); + for (var column = 0; column < 4; column++) + { + ImGui.TableSetColumnIndex(column); + var columnName = ImGui.TableGetColumnName(column); + ImGui.PushID(column); + ImGui.TableHeader(columnName); + ImGui.PopID(); + if (ImGui.IsItemClicked()) + { + if (column == 0) + { + this.SortFields(SortingField.FileName, true); + } + else if (column == 1) + { + this.SortFields(SortingField.Type, true); + } + else if (column == 2) + { + this.SortFields(SortingField.Size, true); + } + else + { + this.SortFields(SortingField.Date, true); + } + } + } + + if (this.filteredFiles.Count > 0) + { + ImGuiListClipperPtr clipper; + unsafe + { + clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); + } + + lock (this.filesLock) + { + clipper.Begin(this.filteredFiles.Count); + while (clipper.Step()) + { + for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + { + if (i < 0) continue; + + var file = this.filteredFiles[i]; + var selected = this.selectedFileNames.Contains(file.FileName); + var needToBreak = false; + + var dir = file.Type == FileStructType.Directory; + var item = !dir ? GetIcon(file.Ext) : new IconColorItem + { + Color = dirTextColor, + Icon = (char)FontAwesomeIcon.Folder, + }; + + ImGui.PushStyleColor(ImGuiCol.Text, item.Color); + if (selected) ImGui.PushStyleColor(ImGuiCol.Text, selectedTextColor); + + ImGui.TableNextRow(); + + if (ImGui.TableNextColumn()) + { + needToBreak = this.SelectableItem(file, selected, item.Icon); + } + + if (ImGui.TableNextColumn()) + { + ImGui.Text(file.Ext); + } + + if (ImGui.TableNextColumn()) + { + if (file.Type == FileStructType.File) + { + ImGui.Text(file.FormattedFileSize + " "); + } + else + { + ImGui.Text(" "); + } + } + + if (ImGui.TableNextColumn()) + { + var sz = ImGui.CalcTextSize(file.FileModifiedDate); + ImGui.PushItemWidth(sz.X + 5); + ImGui.Text(file.FileModifiedDate + " "); + ImGui.PopItemWidth(); + } + + if (selected) ImGui.PopStyleColor(); + ImGui.PopStyleColor(); + + if (needToBreak) break; + } + } + + clipper.End(); + } + } + + if (this.pathInputActivated) + { + if (ImGui.IsKeyReleased(ImGui.GetKeyIndex(ImGuiKey.Enter))) + { + if (Directory.Exists(this.pathInputBuffer)) this.SetPath(this.pathInputBuffer); + this.pathInputActivated = false; + } + + if (ImGui.IsKeyReleased(ImGui.GetKeyIndex(ImGuiKey.Escape))) + { + this.pathInputActivated = false; + } + } + + ImGui.EndTable(); + } + + if (this.pathClicked) + { + this.SetPath(this.currentPath); + } + + ImGui.EndChild(); + } + + private bool SelectableItem(FileStruct file, bool selected, char icon) + { + var flags = ImGuiSelectableFlags.AllowDoubleClick | ImGuiSelectableFlags.SpanAllColumns; + + ImGui.PushFont(UiBuilder.IconFont); + + ImGui.Text($"{icon}"); + ImGui.PopFont(); + + ImGui.SameLine(25f); + + if (ImGui.Selectable(file.FileName, selected, flags)) + { + if (file.Type == FileStructType.Directory) + { + if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) + { + this.pathClicked = this.SelectDirectory(file); + return true; + } + else if (this.IsDirectoryMode()) + { + this.SelectFileName(file); + } + } + else + { + this.SelectFileName(file); + if (ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left)) + { + this.wantsToQuit = true; + this.isOk = true; + } + } + } + + return false; + } + + private bool SelectDirectory(FileStruct file) + { + var pathClick = false; + + if (file.FileName == "..") + { + if (this.pathDecomposition.Count > 1) + { + this.currentPath = ComposeNewPath(this.pathDecomposition.GetRange(0, this.pathDecomposition.Count - 1)); + pathClick = true; } } else { - if (this.selectedFileNames.Count < this.selectionCountMax) + var newPath = Path.Combine(this.currentPath, file.FileName); + + if (Directory.Exists(newPath)) { + this.currentPath = newPath; + } + + pathClick = true; + } + + return pathClick; + } + + private void SelectFileName(FileStruct file) + { + if (ImGui.GetIO().KeyCtrl) + { + if (this.selectionCountMax == 0) + { // infinite select if (!this.selectedFileNames.Contains(file.FileName)) { this.AddFileNameInSelection(file.FileName, true); @@ -597,39 +584,76 @@ public partial class FileDialog this.RemoveFileNameInSelection(file.FileName); } } - } - } - else if (ImGui.GetIO().KeyShift) - { - if (this.selectionCountMax != 1) - { // can select a block - this.selectedFileNames.Clear(); - - var startMultiSelection = false; - var fileNameToSelect = file.FileName; - var savedLastSelectedFileName = string.Empty; - - foreach (var f in this.filteredFiles) + else { - // select top-to-bottom - if (f.FileName == this.lastSelectedFileName) - { // start (the previously selected one) - startMultiSelection = true; - this.AddFileNameInSelection(this.lastSelectedFileName, false); - } - else if (startMultiSelection) + if (this.selectedFileNames.Count < this.selectionCountMax) { - if (this.selectionCountMax == 0) + if (!this.selectedFileNames.Contains(file.FileName)) { - this.AddFileNameInSelection(f.FileName, false); + this.AddFileNameInSelection(file.FileName, true); } else { - if (this.selectedFileNames.Count < this.selectionCountMax) + this.RemoveFileNameInSelection(file.FileName); + } + } + } + } + else if (ImGui.GetIO().KeyShift) + { + if (this.selectionCountMax != 1) + { // can select a block + this.selectedFileNames.Clear(); + + var startMultiSelection = false; + var fileNameToSelect = file.FileName; + var savedLastSelectedFileName = string.Empty; + + foreach (var f in this.filteredFiles) + { + // select top-to-bottom + if (f.FileName == this.lastSelectedFileName) + { // start (the previously selected one) + startMultiSelection = true; + this.AddFileNameInSelection(this.lastSelectedFileName, false); + } + else if (startMultiSelection) + { + if (this.selectionCountMax == 0) { this.AddFileNameInSelection(f.FileName, false); } else + { + if (this.selectedFileNames.Count < this.selectionCountMax) + { + this.AddFileNameInSelection(f.FileName, false); + } + else + { + startMultiSelection = false; + if (!string.IsNullOrEmpty(savedLastSelectedFileName)) + { + this.lastSelectedFileName = savedLastSelectedFileName; + } + + break; + } + } + } + + // select bottom-to-top + if (f.FileName == fileNameToSelect) + { + if (!startMultiSelection) + { + savedLastSelectedFileName = this.lastSelectedFileName; + this.lastSelectedFileName = fileNameToSelect; + fileNameToSelect = savedLastSelectedFileName; + startMultiSelection = true; + this.AddFileNameInSelection(this.lastSelectedFileName, false); + } + else { startMultiSelection = false; if (!string.IsNullOrEmpty(savedLastSelectedFileName)) @@ -641,228 +665,205 @@ public partial class FileDialog } } } - - // select bottom-to-top - if (f.FileName == fileNameToSelect) - { - if (!startMultiSelection) - { - savedLastSelectedFileName = this.lastSelectedFileName; - this.lastSelectedFileName = fileNameToSelect; - fileNameToSelect = savedLastSelectedFileName; - startMultiSelection = true; - this.AddFileNameInSelection(this.lastSelectedFileName, false); - } - else - { - startMultiSelection = false; - if (!string.IsNullOrEmpty(savedLastSelectedFileName)) - { - this.lastSelectedFileName = savedLastSelectedFileName; - } - - break; - } - } } } - } - else - { - this.selectedFileNames.Clear(); - this.fileNameBuffer = string.Empty; - this.AddFileNameInSelection(file.FileName, true); - } - } - - private void AddFileNameInSelection(string name, bool setLastSelection) - { - this.selectedFileNames.Add(name); - if (this.selectedFileNames.Count == 1) - { - this.fileNameBuffer = name; - } - else - { - this.fileNameBuffer = $"{this.selectedFileNames.Count} files Selected"; - } - - if (setLastSelection) - { - this.lastSelectedFileName = name; - } - } - - private void RemoveFileNameInSelection(string name) - { - this.selectedFileNames.Remove(name); - if (this.selectedFileNames.Count == 1) - { - this.fileNameBuffer = name; - } - else - { - this.fileNameBuffer = $"{this.selectedFileNames.Count} files Selected"; - } - } - - private bool DrawFooter() - { - var posY = ImGui.GetCursorPosY(); - - if (this.IsDirectoryMode()) - { - ImGui.Text("Directory Path :"); - } - else - { - ImGui.Text("File Name :"); - } - - ImGui.SameLine(); - - var width = ImGui.GetContentRegionAvail().X - 100; - if (this.filters.Count > 0) - { - width -= 150f; - } - - var selectOnly = this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly); - - ImGui.PushItemWidth(width); - if (selectOnly) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); - ImGui.InputText("##FileName", ref this.fileNameBuffer, 255, selectOnly ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None); - if (selectOnly) ImGui.PopStyleVar(); - ImGui.PopItemWidth(); - - if (this.filters.Count > 0) - { - ImGui.SameLine(); - var needToApplyNewFilter = false; - - ImGui.PushItemWidth(150f); - if (ImGui.BeginCombo("##Filters", this.selectedFilter.Filter, ImGuiComboFlags.None)) + else { - var idx = 0; - foreach (var filter in this.filters) - { - var selected = filter.Filter == this.selectedFilter.Filter; - ImGui.PushID(idx++); - if (ImGui.Selectable(filter.Filter, selected)) - { - this.selectedFilter = filter; - needToApplyNewFilter = true; - } + this.selectedFileNames.Clear(); + this.fileNameBuffer = string.Empty; + this.AddFileNameInSelection(file.FileName, true); + } + } - ImGui.PopID(); - } - - ImGui.EndCombo(); + private void AddFileNameInSelection(string name, bool setLastSelection) + { + this.selectedFileNames.Add(name); + if (this.selectedFileNames.Count == 1) + { + this.fileNameBuffer = name; + } + else + { + this.fileNameBuffer = $"{this.selectedFileNames.Count} files Selected"; } + if (setLastSelection) + { + this.lastSelectedFileName = name; + } + } + + private void RemoveFileNameInSelection(string name) + { + this.selectedFileNames.Remove(name); + if (this.selectedFileNames.Count == 1) + { + this.fileNameBuffer = name; + } + else + { + this.fileNameBuffer = $"{this.selectedFileNames.Count} files Selected"; + } + } + + private bool DrawFooter() + { + var posY = ImGui.GetCursorPosY(); + + if (this.IsDirectoryMode()) + { + ImGui.Text("Directory Path :"); + } + else + { + ImGui.Text("File Name :"); + } + + ImGui.SameLine(); + + var width = ImGui.GetContentRegionAvail().X - 100; + if (this.filters.Count > 0) + { + width -= 150f; + } + + var selectOnly = this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly); + + ImGui.PushItemWidth(width); + if (selectOnly) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); + ImGui.InputText("##FileName", ref this.fileNameBuffer, 255, selectOnly ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None); + if (selectOnly) ImGui.PopStyleVar(); ImGui.PopItemWidth(); - if (needToApplyNewFilter) + if (this.filters.Count > 0) { - this.SetPath(this.currentPath); - } - } - - var res = false; - - ImGui.SameLine(); - - var disableOk = string.IsNullOrEmpty(this.fileNameBuffer) || (selectOnly && !this.IsItemSelected()); - if (disableOk) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); - - if (ImGui.Button("Ok") && !disableOk) - { - this.isOk = true; - res = true; - } - - if (disableOk) ImGui.PopStyleVar(); - - ImGui.SameLine(); - - if (ImGui.Button("Cancel")) - { - this.isOk = false; - res = true; - } - - this.footerHeight = ImGui.GetCursorPosY() - posY; - - if (this.wantsToQuit && this.isOk) - { - res = true; - } - - return res; - } - - private bool IsItemSelected() - { - if (this.selectedFileNames.Count > 0) return true; - if (this.IsDirectoryMode()) return true; // current directory - return false; - } - - private bool ConfirmOrOpenOverWriteFileDialogIfNeeded(bool lastAction) - { - if (this.IsDirectoryMode()) return lastAction; - if (!this.isOk && lastAction) return true; // no need to confirm anything, since it was cancelled - - var confirmOverwrite = this.flags.HasFlag(ImGuiFileDialogFlags.ConfirmOverwrite); - - if (this.isOk && lastAction && !confirmOverwrite) return true; - - if (this.okResultToConfirm || (this.isOk && lastAction && confirmOverwrite)) - { // if waiting on a confirmation, or need to start one - if (this.isOk) - { - if (!File.Exists(this.GetFilePathName())) - { // quit dialog, it doesn't exist anyway - return true; - } - else - { // already exists, open dialog to confirm overwrite - this.isOk = false; - this.okResultToConfirm = true; - } - } - - var name = $"The file Already Exists !##{this.title}{this.id}OverWriteDialog"; - var res = false; - var open = true; - - ImGui.OpenPopup(name); - if (ImGui.BeginPopupModal(name, ref open, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove)) - { - ImGui.Text("Would you like to Overwrite it ?"); - if (ImGui.Button("Confirm")) - { - this.okResultToConfirm = false; - this.isOk = true; - res = true; - ImGui.CloseCurrentPopup(); - } - ImGui.SameLine(); - if (ImGui.Button("Cancel")) + var needToApplyNewFilter = false; + + ImGui.PushItemWidth(150f); + if (ImGui.BeginCombo("##Filters", this.selectedFilter.Filter, ImGuiComboFlags.None)) { - this.okResultToConfirm = false; - this.isOk = false; - res = false; - ImGui.CloseCurrentPopup(); + var idx = 0; + foreach (var filter in this.filters) + { + var selected = filter.Filter == this.selectedFilter.Filter; + ImGui.PushID(idx++); + if (ImGui.Selectable(filter.Filter, selected)) + { + this.selectedFilter = filter; + needToApplyNewFilter = true; + } + + ImGui.PopID(); + } + + ImGui.EndCombo(); } - ImGui.EndPopup(); + ImGui.PopItemWidth(); + + if (needToApplyNewFilter) + { + this.SetPath(this.currentPath); + } + } + + var res = false; + + ImGui.SameLine(); + + var disableOk = string.IsNullOrEmpty(this.fileNameBuffer) || (selectOnly && !this.IsItemSelected()); + if (disableOk) ImGui.PushStyleVar(ImGuiStyleVar.Alpha, 0.5f); + + if (ImGui.Button("Ok") && !disableOk) + { + this.isOk = true; + res = true; + } + + if (disableOk) ImGui.PopStyleVar(); + + ImGui.SameLine(); + + if (ImGui.Button("Cancel")) + { + this.isOk = false; + res = true; + } + + this.footerHeight = ImGui.GetCursorPosY() - posY; + + if (this.wantsToQuit && this.isOk) + { + res = true; } return res; } - return false; + private bool IsItemSelected() + { + if (this.selectedFileNames.Count > 0) return true; + if (this.IsDirectoryMode()) return true; // current directory + return false; + } + + private bool ConfirmOrOpenOverWriteFileDialogIfNeeded(bool lastAction) + { + if (this.IsDirectoryMode()) return lastAction; + if (!this.isOk && lastAction) return true; // no need to confirm anything, since it was cancelled + + var confirmOverwrite = this.flags.HasFlag(ImGuiFileDialogFlags.ConfirmOverwrite); + + if (this.isOk && lastAction && !confirmOverwrite) return true; + + if (this.okResultToConfirm || (this.isOk && lastAction && confirmOverwrite)) + { // if waiting on a confirmation, or need to start one + if (this.isOk) + { + if (!File.Exists(this.GetFilePathName())) + { // quit dialog, it doesn't exist anyway + return true; + } + else + { // already exists, open dialog to confirm overwrite + this.isOk = false; + this.okResultToConfirm = true; + } + } + + var name = $"The file Already Exists !##{this.title}{this.id}OverWriteDialog"; + var res = false; + var open = true; + + ImGui.OpenPopup(name); + if (ImGui.BeginPopupModal(name, ref open, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove)) + { + ImGui.Text("Would you like to Overwrite it ?"); + if (ImGui.Button("Confirm")) + { + this.okResultToConfirm = false; + this.isOk = true; + res = true; + ImGui.CloseCurrentPopup(); + } + + ImGui.SameLine(); + if (ImGui.Button("Cancel")) + { + this.okResultToConfirm = false; + this.isOk = false; + res = false; + ImGui.CloseCurrentPopup(); + } + + ImGui.EndPopup(); + } + + return res; + } + + return false; + } } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs index dad1a8a50..9e2a77f0d 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialog.cs @@ -3,237 +3,238 @@ using System.Collections.Generic; using System.IO; using System.Linq; -namespace Dalamud.Interface.ImGuiFileDialog; - -/// -/// A file or folder picker. -/// -public partial class FileDialog +namespace Dalamud.Interface.ImGuiFileDialog { - private readonly string title; - private readonly int selectionCountMax; - private readonly ImGuiFileDialogFlags flags; - private readonly string id; - private readonly string defaultExtension; - private readonly string defaultFileName; - - private bool visible; - - private string currentPath; - private string fileNameBuffer = string.Empty; - - private List pathDecomposition = new(); - private bool pathClicked = true; - private bool pathInputActivated = false; - private string pathInputBuffer = string.Empty; - - private bool isModal = false; - private bool okResultToConfirm = false; - private bool isOk; - private bool wantsToQuit; - - private bool createDirectoryMode = false; - private string createDirectoryBuffer = string.Empty; - - private string searchBuffer = string.Empty; - - private string lastSelectedFileName = string.Empty; - private List selectedFileNames = new(); - - private float footerHeight = 0; - - private string selectedSideBar = string.Empty; - private List drives = new(); - private List quickAccess = new(); - /// - /// Initializes a new instance of the class. + /// A file or folder picker. /// - /// A unique id for the dialog. - /// The text which is shown at the top of the dialog. - /// Which file extension filters to apply. This should be left blank to select directories. - /// The directory which the dialog should start inside of. - /// The default file or directory name. - /// The default extension when creating new files. - /// The maximum amount of files or directories which can be selected. Set to 0 for an infinite number. - /// Whether the dialog should be a modal popup. - /// Settings flags for the dialog, see . - public FileDialog( - string id, - string title, - string filters, - string path, - string defaultFileName, - string defaultExtension, - int selectionCountMax, - bool isModal, - ImGuiFileDialogFlags flags) + public partial class FileDialog { - this.id = id; - this.title = title; - this.flags = flags; - this.selectionCountMax = selectionCountMax; - this.isModal = isModal; + private readonly string title; + private readonly int selectionCountMax; + private readonly ImGuiFileDialogFlags flags; + private readonly string id; + private readonly string defaultExtension; + private readonly string defaultFileName; - this.currentPath = path; - this.defaultExtension = defaultExtension; - this.defaultFileName = defaultFileName; + private bool visible; - this.ParseFilters(filters); - this.SetSelectedFilterWithExt(this.defaultExtension); - this.SetDefaultFileName(); - this.SetPath(this.currentPath); + private string currentPath; + private string fileNameBuffer = string.Empty; - this.SetupSideBar(); - } + private List pathDecomposition = new(); + private bool pathClicked = true; + private bool pathInputActivated = false; + private string pathInputBuffer = string.Empty; - /// - /// Shows the dialog. - /// - public void Show() - { - this.visible = true; - } + private bool isModal = false; + private bool okResultToConfirm = false; + private bool isOk; + private bool wantsToQuit; - /// - /// Hides the dialog. - /// - public void Hide() - { - this.visible = false; - } + private bool createDirectoryMode = false; + private string createDirectoryBuffer = string.Empty; - /// - /// Gets whether a file or folder was successfully selected. - /// - /// The success state. Will be false if the selection was canceled or was otherwise unsuccessful. - public bool GetIsOk() - { - return this.isOk; - } + private string searchBuffer = string.Empty; - /// - /// Gets the result of the selection. - /// - /// The result of the selection (file or folder path). If multiple entries were selected, they are separated with commas. - public string GetResult() - { - if (!this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly)) + private string lastSelectedFileName = string.Empty; + private List selectedFileNames = new(); + + private float footerHeight = 0; + + private string selectedSideBar = string.Empty; + private List drives = new(); + private List quickAccess = new(); + + /// + /// Initializes a new instance of the class. + /// + /// A unique id for the dialog. + /// The text which is shown at the top of the dialog. + /// Which file extension filters to apply. This should be left blank to select directories. + /// The directory which the dialog should start inside of. + /// The default file or directory name. + /// The default extension when creating new files. + /// The maximum amount of files or directories which can be selected. Set to 0 for an infinite number. + /// Whether the dialog should be a modal popup. + /// Settings flags for the dialog, see . + public FileDialog( + string id, + string title, + string filters, + string path, + string defaultFileName, + string defaultExtension, + int selectionCountMax, + bool isModal, + ImGuiFileDialogFlags flags) { - return this.GetFilePathName(); + this.id = id; + this.title = title; + this.flags = flags; + this.selectionCountMax = selectionCountMax; + this.isModal = isModal; + + this.currentPath = path; + this.defaultExtension = defaultExtension; + this.defaultFileName = defaultFileName; + + this.ParseFilters(filters); + this.SetSelectedFilterWithExt(this.defaultExtension); + this.SetDefaultFileName(); + this.SetPath(this.currentPath); + + this.SetupSideBar(); } - if (this.IsDirectoryMode() && this.selectedFileNames.Count == 0) + /// + /// Shows the dialog. + /// + public void Show() { - return this.GetFilePathName(); // current directory + this.visible = true; } - var fullPaths = this.selectedFileNames.Where(x => !string.IsNullOrEmpty(x)).Select(x => Path.Combine(this.currentPath, x)); - return string.Join(",", fullPaths.ToArray()); - } - - /// - /// Gets the current path of the dialog. - /// - /// The path of the directory which the dialog is current viewing. - public string GetCurrentPath() - { - if (this.IsDirectoryMode()) + /// + /// Hides the dialog. + /// + public void Hide() { - // combine path file with directory input - var selectedDirectory = this.fileNameBuffer; - if (!string.IsNullOrEmpty(selectedDirectory) && selectedDirectory != ".") + this.visible = false; + } + + /// + /// Gets whether a file or folder was successfully selected. + /// + /// The success state. Will be false if the selection was canceled or was otherwise unsuccessful. + public bool GetIsOk() + { + return this.isOk; + } + + /// + /// Gets the result of the selection. + /// + /// The result of the selection (file or folder path). If multiple entries were selected, they are separated with commas. + public string GetResult() + { + if (!this.flags.HasFlag(ImGuiFileDialogFlags.SelectOnly)) { - return string.IsNullOrEmpty(this.currentPath) ? selectedDirectory : Path.Combine(this.currentPath, selectedDirectory); + return this.GetFilePathName(); } + + if (this.IsDirectoryMode() && this.selectedFileNames.Count == 0) + { + return this.GetFilePathName(); // current directory + } + + var fullPaths = this.selectedFileNames.Where(x => !string.IsNullOrEmpty(x)).Select(x => Path.Combine(this.currentPath, x)); + return string.Join(",", fullPaths.ToArray()); } - return this.currentPath; - } - - private string GetFilePathName() - { - var path = this.GetCurrentPath(); - var fileName = this.GetCurrentFileName(); - - if (!string.IsNullOrEmpty(fileName)) + /// + /// Gets the current path of the dialog. + /// + /// The path of the directory which the dialog is current viewing. + public string GetCurrentPath() { - return Path.Combine(path, fileName); + if (this.IsDirectoryMode()) + { + // combine path file with directory input + var selectedDirectory = this.fileNameBuffer; + if (!string.IsNullOrEmpty(selectedDirectory) && selectedDirectory != ".") + { + return string.IsNullOrEmpty(this.currentPath) ? selectedDirectory : Path.Combine(this.currentPath, selectedDirectory); + } + } + + return this.currentPath; } - return path; - } - - private string GetCurrentFileName() - { - if (this.IsDirectoryMode()) + private string GetFilePathName() { - return string.Empty; + var path = this.GetCurrentPath(); + var fileName = this.GetCurrentFileName(); + + if (!string.IsNullOrEmpty(fileName)) + { + return Path.Combine(path, fileName); + } + + return path; } - var result = this.fileNameBuffer; - - // a collection like {.cpp, .h}, so can't decide on an extension - if (this.selectedFilter.CollectionFilters != null && this.selectedFilter.CollectionFilters.Count > 0) + private string GetCurrentFileName() { + if (this.IsDirectoryMode()) + { + return string.Empty; + } + + var result = this.fileNameBuffer; + + // a collection like {.cpp, .h}, so can't decide on an extension + if (this.selectedFilter.CollectionFilters != null && this.selectedFilter.CollectionFilters.Count > 0) + { + return result; + } + + // a single one, like .cpp + if (!this.selectedFilter.Filter.Contains('*') && result != this.selectedFilter.Filter) + { + var lastPoint = result.LastIndexOf('.'); + if (lastPoint != -1) + { + result = result.Substring(0, lastPoint); + } + + result += this.selectedFilter.Filter; + } + return result; } - // a single one, like .cpp - if (!this.selectedFilter.Filter.Contains('*') && result != this.selectedFilter.Filter) + private void SetDefaultFileName() { - var lastPoint = result.LastIndexOf('.'); - if (lastPoint != -1) + this.fileNameBuffer = this.defaultFileName; + } + + private void SetPath(string path) + { + this.selectedSideBar = string.Empty; + this.currentPath = path; + this.files.Clear(); + this.pathDecomposition.Clear(); + this.selectedFileNames.Clear(); + if (this.IsDirectoryMode()) { - result = result.Substring(0, lastPoint); + this.SetDefaultFileName(); } - result += this.selectedFilter.Filter; + this.ScanDir(this.currentPath); } - return result; - } - - private void SetDefaultFileName() - { - this.fileNameBuffer = this.defaultFileName; - } - - private void SetPath(string path) - { - this.selectedSideBar = string.Empty; - this.currentPath = path; - this.files.Clear(); - this.pathDecomposition.Clear(); - this.selectedFileNames.Clear(); - if (this.IsDirectoryMode()) + private void SetCurrentDir(string path) { - this.SetDefaultFileName(); + var dir = new DirectoryInfo(path); + this.currentPath = dir.FullName; + if (this.currentPath[^1] == Path.DirectorySeparatorChar) + { // handle selecting a drive, like C: -> C:\ + this.currentPath = this.currentPath[0..^1]; + } + + this.pathInputBuffer = this.currentPath; + this.pathDecomposition = new List(this.currentPath.Split(Path.DirectorySeparatorChar)); } - this.ScanDir(this.currentPath); - } - - private void SetCurrentDir(string path) - { - var dir = new DirectoryInfo(path); - this.currentPath = dir.FullName; - if (this.currentPath[^1] == Path.DirectorySeparatorChar) - { // handle selecting a drive, like C: -> C:\ - this.currentPath = this.currentPath[0..^1]; + private bool IsDirectoryMode() + { + return this.filters.Count == 0; } - this.pathInputBuffer = this.currentPath; - this.pathDecomposition = new List(this.currentPath.Split(Path.DirectorySeparatorChar)); - } - - private bool IsDirectoryMode() - { - return this.filters.Count == 0; - } - - private void ResetEvents() - { - this.pathClicked = false; + private void ResetEvents() + { + this.pathClicked = false; + } } } diff --git a/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs b/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs index 9f1ddcc98..18bd9dc14 100644 --- a/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs +++ b/Dalamud/Interface/ImGuiFileDialog/FileDialogManager.cs @@ -1,100 +1,101 @@ using System; -namespace Dalamud.Interface.ImGuiFileDialog; - -/// -/// A manager for the class. -/// -public class FileDialogManager +namespace Dalamud.Interface.ImGuiFileDialog { - private FileDialog dialog; - private string savedPath = "."; - private Action callback; - /// - /// Create a dialog which selects an already existing folder. + /// A manager for the class. /// - /// The header title of the dialog. - /// The action to execute when the dialog is finished. - public void OpenFolderDialog(string title, Action callback) + public class FileDialogManager { - this.SetDialog("OpenFolderDialog", title, string.Empty, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback); - } + private FileDialog dialog; + private string savedPath = "."; + private Action callback; - /// - /// Create a dialog which selects an already existing folder or new folder. - /// - /// The header title of the dialog. - /// The default name to use when creating a new folder. - /// The action to execute when the dialog is finished. - public void SaveFolderDialog(string title, string defaultFolderName, Action callback) - { - this.SetDialog("SaveFolderDialog", title, string.Empty, this.savedPath, defaultFolderName, string.Empty, 1, false, ImGuiFileDialogFlags.None, callback); - } - - /// - /// Create a dialog which selects an already existing file. - /// - /// The header title of the dialog. - /// Which files to show in the dialog. - /// The action to execute when the dialog is finished. - public void OpenFileDialog(string title, string filters, Action callback) - { - this.SetDialog("OpenFileDialog", title, filters, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback); - } - - /// - /// Create a dialog which selects an already existing folder or new file. - /// - /// The header title of the dialog. - /// Which files to show in the dialog. - /// The default name to use when creating a new file. - /// The extension to use when creating a new file. - /// The action to execute when the dialog is finished. - public void SaveFileDialog(string title, string filters, string defaultFileName, string defaultExtension, Action callback) - { - this.SetDialog("SaveFileDialog", title, filters, this.savedPath, defaultFileName, defaultExtension, 1, false, ImGuiFileDialogFlags.None, callback); - } - - /// - /// Draws the current dialog, if any, and executes the callback if it is finished. - /// - public void Draw() - { - if (this.dialog == null) return; - if (this.dialog.Draw()) + /// + /// Create a dialog which selects an already existing folder. + /// + /// The header title of the dialog. + /// The action to execute when the dialog is finished. + public void OpenFolderDialog(string title, Action callback) + { + this.SetDialog("OpenFolderDialog", title, string.Empty, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback); + } + + /// + /// Create a dialog which selects an already existing folder or new folder. + /// + /// The header title of the dialog. + /// The default name to use when creating a new folder. + /// The action to execute when the dialog is finished. + public void SaveFolderDialog(string title, string defaultFolderName, Action callback) + { + this.SetDialog("SaveFolderDialog", title, string.Empty, this.savedPath, defaultFolderName, string.Empty, 1, false, ImGuiFileDialogFlags.None, callback); + } + + /// + /// Create a dialog which selects an already existing file. + /// + /// The header title of the dialog. + /// Which files to show in the dialog. + /// The action to execute when the dialog is finished. + public void OpenFileDialog(string title, string filters, Action callback) + { + this.SetDialog("OpenFileDialog", title, filters, this.savedPath, ".", string.Empty, 1, false, ImGuiFileDialogFlags.SelectOnly, callback); + } + + /// + /// Create a dialog which selects an already existing folder or new file. + /// + /// The header title of the dialog. + /// Which files to show in the dialog. + /// The default name to use when creating a new file. + /// The extension to use when creating a new file. + /// The action to execute when the dialog is finished. + public void SaveFileDialog(string title, string filters, string defaultFileName, string defaultExtension, Action callback) + { + this.SetDialog("SaveFileDialog", title, filters, this.savedPath, defaultFileName, defaultExtension, 1, false, ImGuiFileDialogFlags.None, callback); + } + + /// + /// Draws the current dialog, if any, and executes the callback if it is finished. + /// + public void Draw() + { + if (this.dialog == null) return; + if (this.dialog.Draw()) + { + this.callback(this.dialog.GetIsOk(), this.dialog.GetResult()); + this.savedPath = this.dialog.GetCurrentPath(); + this.Reset(); + } + } + + /// + /// Removes the current dialog, if any. + /// + public void Reset() + { + this.dialog?.Hide(); + this.dialog = null; + this.callback = null; + } + + private void SetDialog( + string id, + string title, + string filters, + string path, + string defaultFileName, + string defaultExtension, + int selectionCountMax, + bool isModal, + ImGuiFileDialogFlags flags, + Action callback) { - this.callback(this.dialog.GetIsOk(), this.dialog.GetResult()); - this.savedPath = this.dialog.GetCurrentPath(); this.Reset(); + this.callback = callback; + this.dialog = new FileDialog(id, title, filters, path, defaultFileName, defaultExtension, selectionCountMax, isModal, flags); + this.dialog.Show(); } } - - /// - /// Removes the current dialog, if any. - /// - public void Reset() - { - this.dialog?.Hide(); - this.dialog = null; - this.callback = null; - } - - private void SetDialog( - string id, - string title, - string filters, - string path, - string defaultFileName, - string defaultExtension, - int selectionCountMax, - bool isModal, - ImGuiFileDialogFlags flags, - Action callback) - { - this.Reset(); - this.callback = callback; - this.dialog = new FileDialog(id, title, filters, path, defaultFileName, defaultExtension, selectionCountMax, isModal, flags); - this.dialog.Show(); - } } diff --git a/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs b/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs index f62f5f721..fe189c77c 100644 --- a/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs +++ b/Dalamud/Interface/ImGuiFileDialog/ImGuiFileDialogFlags.cs @@ -4,56 +4,57 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Dalamud.Interface.ImGuiFileDialog; - -/// -/// Settings flags for the class. -/// -[Flags] -public enum ImGuiFileDialogFlags +namespace Dalamud.Interface.ImGuiFileDialog { /// - /// None. + /// Settings flags for the class. /// - None = 0, + [Flags] + public enum ImGuiFileDialogFlags + { + /// + /// None. + /// + None = 0, - /// - /// Confirm the selection when choosing a file which already exists. - /// - ConfirmOverwrite = 1, + /// + /// Confirm the selection when choosing a file which already exists. + /// + ConfirmOverwrite = 1, - /// - /// Only allow selection of files or folders which currently exist. - /// - SelectOnly = 2, + /// + /// Only allow selection of files or folders which currently exist. + /// + SelectOnly = 2, - /// - /// Hide files or folders which start with a period. - /// - DontShowHiddenFiles = 3, + /// + /// Hide files or folders which start with a period. + /// + DontShowHiddenFiles = 3, - /// - /// Disable the creation of new folders within the dialog. - /// - DisableCreateDirectoryButton = 4, + /// + /// Disable the creation of new folders within the dialog. + /// + DisableCreateDirectoryButton = 4, - /// - /// Hide the type column. - /// - HideColumnType = 5, + /// + /// Hide the type column. + /// + HideColumnType = 5, - /// - /// Hide the file size column. - /// - HideColumnSize = 6, + /// + /// Hide the file size column. + /// + HideColumnSize = 6, - /// - /// Hide the last modified date column. - /// - HideColumnDate = 7, + /// + /// Hide the last modified date column. + /// + HideColumnDate = 7, - /// - /// Hide the quick access sidebar. - /// - HideSideBar = 8, + /// + /// Hide the quick access sidebar. + /// + HideSideBar = 8, + } } diff --git a/Dalamud/Interface/ImGuiHelpers.cs b/Dalamud/Interface/ImGuiHelpers.cs index 17ce759fc..f138cdf40 100644 --- a/Dalamud/Interface/ImGuiHelpers.cs +++ b/Dalamud/Interface/ImGuiHelpers.cs @@ -3,120 +3,121 @@ using System.Numerics; using ImGuiNET; -namespace Dalamud.Interface; - -/// -/// Class containing various helper methods for use with ImGui inside Dalamud. -/// -public static class ImGuiHelpers +namespace Dalamud.Interface { /// - /// Gets the main viewport. + /// Class containing various helper methods for use with ImGui inside Dalamud. /// - public static ImGuiViewportPtr MainViewport { get; internal set; } - - /// - /// Gets the global Dalamud scale. - /// - public static float GlobalScale { get; private set; } - - /// - /// Gets a that is pre-scaled with the multiplier. - /// - /// Vector2 X parameter. - /// Vector2 Y parameter. - /// A scaled Vector2. - public static Vector2 ScaledVector2(float x, float y) => new Vector2(x, y) * GlobalScale; - - /// - /// Gets a that is pre-scaled with the multiplier. - /// - /// Vector4 X parameter. - /// Vector4 Y parameter. - /// Vector4 Z parameter. - /// Vector4 W parameter. - /// A scaled Vector2. - public static Vector4 ScaledVector4(float x, float y, float z, float w) => new Vector4(x, y, z, w) * GlobalScale; - - /// - /// Force the next ImGui window to stay inside the main game window. - /// - public static void ForceNextWindowMainViewport() => ImGui.SetNextWindowViewport(MainViewport.ID); - - /// - /// Create a dummy scaled by the global Dalamud scale. - /// - /// The size of the dummy. - public static void ScaledDummy(float size) => ScaledDummy(size, size); - - /// - /// Create a dummy scaled by the global Dalamud scale. - /// - /// Vector2 X parameter. - /// Vector2 Y parameter. - public static void ScaledDummy(float x, float y) => ScaledDummy(new Vector2(x, y)); - - /// - /// Create a dummy scaled by the global Dalamud scale. - /// - /// The size of the dummy. - public static void ScaledDummy(Vector2 size) => ImGui.Dummy(size * GlobalScale); - - /// - /// Use a relative ImGui.SameLine() from your current cursor position, scaled by the Dalamud global scale. - /// - /// The offset from your current cursor position. - /// The spacing to use. - public static void ScaledRelativeSameLine(float offset, float spacing = -1.0f) - => ImGui.SameLine(ImGui.GetCursorPosX() + (offset * GlobalScale), spacing); - - /// - /// Set the position of the next window relative to the main viewport. - /// - /// The position of the next window. - /// When to set the position. - /// The pivot to set the position around. - public static void SetNextWindowPosRelativeMainViewport(Vector2 position, ImGuiCond condition = ImGuiCond.None, Vector2 pivot = default) - => ImGui.SetNextWindowPos(position + MainViewport.Pos, condition, pivot); - - /// - /// Set the position of a window relative to the main viewport. - /// - /// The name/ID of the window. - /// The position of the window. - /// When to set the position. - public static void SetWindowPosRelativeMainViewport(string name, Vector2 position, ImGuiCond condition = ImGuiCond.None) - => ImGui.SetWindowPos(name, position + MainViewport.Pos, condition); - - /// - /// Creates default color palette for use with color pickers. - /// - /// The total number of swatches to use. - /// Default color palette. - public static List DefaultColorPalette(int swatchCount = 32) + public static class ImGuiHelpers { - var colorPalette = new List(); - for (var i = 0; i < swatchCount; i++) + /// + /// Gets the main viewport. + /// + public static ImGuiViewportPtr MainViewport { get; internal set; } + + /// + /// Gets the global Dalamud scale. + /// + public static float GlobalScale { get; private set; } + + /// + /// Gets a that is pre-scaled with the multiplier. + /// + /// Vector2 X parameter. + /// Vector2 Y parameter. + /// A scaled Vector2. + public static Vector2 ScaledVector2(float x, float y) => new Vector2(x, y) * GlobalScale; + + /// + /// Gets a that is pre-scaled with the multiplier. + /// + /// Vector4 X parameter. + /// Vector4 Y parameter. + /// Vector4 Z parameter. + /// Vector4 W parameter. + /// A scaled Vector2. + public static Vector4 ScaledVector4(float x, float y, float z, float w) => new Vector4(x, y, z, w) * GlobalScale; + + /// + /// Force the next ImGui window to stay inside the main game window. + /// + public static void ForceNextWindowMainViewport() => ImGui.SetNextWindowViewport(MainViewport.ID); + + /// + /// Create a dummy scaled by the global Dalamud scale. + /// + /// The size of the dummy. + public static void ScaledDummy(float size) => ScaledDummy(size, size); + + /// + /// Create a dummy scaled by the global Dalamud scale. + /// + /// Vector2 X parameter. + /// Vector2 Y parameter. + public static void ScaledDummy(float x, float y) => ScaledDummy(new Vector2(x, y)); + + /// + /// Create a dummy scaled by the global Dalamud scale. + /// + /// The size of the dummy. + public static void ScaledDummy(Vector2 size) => ImGui.Dummy(size * GlobalScale); + + /// + /// Use a relative ImGui.SameLine() from your current cursor position, scaled by the Dalamud global scale. + /// + /// The offset from your current cursor position. + /// The spacing to use. + public static void ScaledRelativeSameLine(float offset, float spacing = -1.0f) + => ImGui.SameLine(ImGui.GetCursorPosX() + (offset * GlobalScale), spacing); + + /// + /// Set the position of the next window relative to the main viewport. + /// + /// The position of the next window. + /// When to set the position. + /// The pivot to set the position around. + public static void SetNextWindowPosRelativeMainViewport(Vector2 position, ImGuiCond condition = ImGuiCond.None, Vector2 pivot = default) + => ImGui.SetNextWindowPos(position + MainViewport.Pos, condition, pivot); + + /// + /// Set the position of a window relative to the main viewport. + /// + /// The name/ID of the window. + /// The position of the window. + /// When to set the position. + public static void SetWindowPosRelativeMainViewport(string name, Vector2 position, ImGuiCond condition = ImGuiCond.None) + => ImGui.SetWindowPos(name, position + MainViewport.Pos, condition); + + /// + /// Creates default color palette for use with color pickers. + /// + /// The total number of swatches to use. + /// Default color palette. + public static List DefaultColorPalette(int swatchCount = 32) { - ImGui.ColorConvertHSVtoRGB(i / 31.0f, 0.7f, 0.8f, out var r, out var g, out var b); - colorPalette.Add(new Vector4(r, g, b, 1.0f)); + var colorPalette = new List(); + for (var i = 0; i < swatchCount; i++) + { + ImGui.ColorConvertHSVtoRGB(i / 31.0f, 0.7f, 0.8f, out var r, out var g, out var b); + colorPalette.Add(new Vector4(r, g, b, 1.0f)); + } + + return colorPalette; } - return colorPalette; - } + /// + /// Get the size of a button considering the default frame padding. + /// + /// Text in the button. + /// with the size of the button. + public static Vector2 GetButtonSize(string text) => ImGui.CalcTextSize(text) + (ImGui.GetStyle().FramePadding * 2); - /// - /// Get the size of a button considering the default frame padding. - /// - /// Text in the button. - /// with the size of the button. - public static Vector2 GetButtonSize(string text) => ImGui.CalcTextSize(text) + (ImGui.GetStyle().FramePadding * 2); - - /// - /// Get data needed for each new frame. - /// - internal static void NewFrame() - { - GlobalScale = ImGui.GetIO().FontGlobalScale; + /// + /// Get data needed for each new frame. + /// + internal static void NewFrame() + { + GlobalScale = ImGui.GetIO().FontGlobalScale; + } } } diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index aa18d25b9..0300885bb 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -11,337 +11,338 @@ using Dalamud.Game.Gui; using Dalamud.Plugin.Internal; using Serilog; -namespace Dalamud.Interface.Internal; - -/// -/// Class handling Dalamud core commands. -/// -internal class DalamudCommands +namespace Dalamud.Interface.Internal { /// - /// Initializes a new instance of the class. + /// Class handling Dalamud core commands. /// - public DalamudCommands() + internal class DalamudCommands { - } - - /// - /// Register all command handlers with the Dalamud instance. - /// - public void SetupCommands() - { - var commandManager = Service.Get(); - - commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand) + /// + /// Initializes a new instance of the class. + /// + public DalamudCommands() { - HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xldreloadplugins", new CommandInfo(this.OnPluginReloadCommand) - { - HelpMessage = Loc.Localize("DalamudPluginReloadHelp", "Reloads all plugins."), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xlhelp", new CommandInfo(this.OnHelpCommand) - { - HelpMessage = Loc.Localize("DalamudCmdInfoHelp", "Shows list of commands available."), - }); - - commandManager.AddHandler("/xlmute", new CommandInfo(this.OnBadWordsAddCommand) - { - HelpMessage = Loc.Localize("DalamudMuteHelp", "Mute a word or sentence from appearing in chat. Usage: /xlmute "), - }); - - commandManager.AddHandler("/xlmutelist", new CommandInfo(this.OnBadWordsListCommand) - { - HelpMessage = Loc.Localize("DalamudMuteListHelp", "List muted words or sentences."), - }); - - commandManager.AddHandler("/xlunmute", new CommandInfo(this.OnBadWordsRemoveCommand) - { - HelpMessage = Loc.Localize("DalamudUnmuteHelp", "Unmute a word or sentence. Usage: /xlunmute "), - }); - - commandManager.AddHandler("/ll", new CommandInfo(this.OnLastLinkCommand) - { - HelpMessage = Loc.Localize("DalamudLastLinkHelp", "Open the last posted link in your default browser."), - }); - - commandManager.AddHandler("/xlbgmset", new CommandInfo(this.OnBgmSetCommand) - { - HelpMessage = Loc.Localize("DalamudBgmSetHelp", "Set the Game background music. Usage: /xlbgmset "), - }); - - commandManager.AddHandler("/xldev", new CommandInfo(this.OnDebugDrawDevMenu) - { - HelpMessage = Loc.Localize("DalamudDevMenuHelp", "Draw dev menu DEBUG"), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xldata", new CommandInfo(this.OnDebugDrawDataMenu) - { - HelpMessage = Loc.Localize("DalamudDevDataMenuHelp", "Draw dev data menu DEBUG. Usage: /xldata [Data Dropdown Type]"), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xlime", new CommandInfo(this.OnDebugDrawIMEPanel) - { - HelpMessage = Loc.Localize("DalamudIMEPanelHelp", "Draw IME panel"), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog) - { - HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"), - ShowInHelp = false, - }); - - commandManager.AddHandler("/xlplugins", new CommandInfo(this.OnOpenInstallerCommand) - { - HelpMessage = Loc.Localize("DalamudInstallerHelp", "Open the plugin installer"), - }); - - commandManager.AddHandler("/xlcredits", new CommandInfo(this.OnOpenCreditsCommand) - { - HelpMessage = Loc.Localize("DalamudCreditsHelp", "Opens the credits for dalamud."), - }); - - commandManager.AddHandler("/xllanguage", new CommandInfo(this.OnSetLanguageCommand) - { - HelpMessage = - Loc.Localize( - "DalamudLanguageHelp", - "Set the language for the in-game addon and plugins that support it. Available languages: ") + - Localization.ApplicableLangCodes.Aggregate("en", (current, code) => current + ", " + code), - }); - - commandManager.AddHandler("/xlsettings", new CommandInfo(this.OnOpenSettingsCommand) - { - HelpMessage = Loc.Localize( - "DalamudSettingsHelp", - "Change various In-Game-Addon settings like chat channels and the discord bot setup."), - }); - - commandManager.AddHandler("/imdebug", new CommandInfo(this.OnDebugImInfoCommand) - { - HelpMessage = "ImGui DEBUG", - ShowInHelp = false, - }); - } - - private void OnUnloadCommand(string command, string arguments) - { - Service.Get().Print("Unloading..."); - Service.Get().Unload(); - } - - private void OnHelpCommand(string command, string arguments) - { - var chatGui = Service.Get(); - var commandManager = Service.Get(); - - var showDebug = arguments.Contains("debug"); - - chatGui.Print(Loc.Localize("DalamudCmdHelpAvailable", "Available commands:")); - foreach (var cmd in commandManager.Commands) - { - if (!cmd.Value.ShowInHelp && !showDebug) - continue; - - chatGui.Print($"{cmd.Key}: {cmd.Value.HelpMessage}"); - } - } - - private void OnPluginReloadCommand(string command, string arguments) - { - var chatGui = Service.Get(); - - chatGui.Print("Reloading..."); - - try - { - Service.Get().ReloadAllPlugins(); - chatGui.Print("OK"); - } - catch (Exception ex) - { - Log.Error(ex, "Plugin reload failed."); - chatGui.PrintError("Reload failed."); - } - } - - private void OnBadWordsAddCommand(string command, string arguments) - { - var chatGui = Service.Get(); - var configuration = Service.Get(); - - configuration.BadWords ??= new List(); - - if (string.IsNullOrEmpty(arguments)) - { - chatGui.Print(Loc.Localize("DalamudMuteNoArgs", "Please provide a word to mute.")); - return; } - configuration.BadWords.Add(arguments); - - configuration.Save(); - - chatGui.Print(string.Format(Loc.Localize("DalamudMuted", "Muted \"{0}\"."), arguments)); - } - - private void OnBadWordsListCommand(string command, string arguments) - { - var chatGui = Service.Get(); - var configuration = Service.Get(); - - configuration.BadWords ??= new List(); - - if (configuration.BadWords.Count == 0) + /// + /// Register all command handlers with the Dalamud instance. + /// + public void SetupCommands() { - chatGui.Print(Loc.Localize("DalamudNoneMuted", "No muted words or sentences.")); - return; + var commandManager = Service.Get(); + + commandManager.AddHandler("/xldclose", new CommandInfo(this.OnUnloadCommand) + { + HelpMessage = Loc.Localize("DalamudUnloadHelp", "Unloads XIVLauncher in-game addon."), + ShowInHelp = false, + }); + + commandManager.AddHandler("/xldreloadplugins", new CommandInfo(this.OnPluginReloadCommand) + { + HelpMessage = Loc.Localize("DalamudPluginReloadHelp", "Reloads all plugins."), + ShowInHelp = false, + }); + + commandManager.AddHandler("/xlhelp", new CommandInfo(this.OnHelpCommand) + { + HelpMessage = Loc.Localize("DalamudCmdInfoHelp", "Shows list of commands available."), + }); + + commandManager.AddHandler("/xlmute", new CommandInfo(this.OnBadWordsAddCommand) + { + HelpMessage = Loc.Localize("DalamudMuteHelp", "Mute a word or sentence from appearing in chat. Usage: /xlmute "), + }); + + commandManager.AddHandler("/xlmutelist", new CommandInfo(this.OnBadWordsListCommand) + { + HelpMessage = Loc.Localize("DalamudMuteListHelp", "List muted words or sentences."), + }); + + commandManager.AddHandler("/xlunmute", new CommandInfo(this.OnBadWordsRemoveCommand) + { + HelpMessage = Loc.Localize("DalamudUnmuteHelp", "Unmute a word or sentence. Usage: /xlunmute "), + }); + + commandManager.AddHandler("/ll", new CommandInfo(this.OnLastLinkCommand) + { + HelpMessage = Loc.Localize("DalamudLastLinkHelp", "Open the last posted link in your default browser."), + }); + + commandManager.AddHandler("/xlbgmset", new CommandInfo(this.OnBgmSetCommand) + { + HelpMessage = Loc.Localize("DalamudBgmSetHelp", "Set the Game background music. Usage: /xlbgmset "), + }); + + commandManager.AddHandler("/xldev", new CommandInfo(this.OnDebugDrawDevMenu) + { + HelpMessage = Loc.Localize("DalamudDevMenuHelp", "Draw dev menu DEBUG"), + ShowInHelp = false, + }); + + commandManager.AddHandler("/xldata", new CommandInfo(this.OnDebugDrawDataMenu) + { + HelpMessage = Loc.Localize("DalamudDevDataMenuHelp", "Draw dev data menu DEBUG. Usage: /xldata [Data Dropdown Type]"), + ShowInHelp = false, + }); + + commandManager.AddHandler("/xlime", new CommandInfo(this.OnDebugDrawIMEPanel) + { + HelpMessage = Loc.Localize("DalamudIMEPanelHelp", "Draw IME panel"), + ShowInHelp = false, + }); + + commandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog) + { + HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"), + ShowInHelp = false, + }); + + commandManager.AddHandler("/xlplugins", new CommandInfo(this.OnOpenInstallerCommand) + { + HelpMessage = Loc.Localize("DalamudInstallerHelp", "Open the plugin installer"), + }); + + commandManager.AddHandler("/xlcredits", new CommandInfo(this.OnOpenCreditsCommand) + { + HelpMessage = Loc.Localize("DalamudCreditsHelp", "Opens the credits for dalamud."), + }); + + commandManager.AddHandler("/xllanguage", new CommandInfo(this.OnSetLanguageCommand) + { + HelpMessage = + Loc.Localize( + "DalamudLanguageHelp", + "Set the language for the in-game addon and plugins that support it. Available languages: ") + + Localization.ApplicableLangCodes.Aggregate("en", (current, code) => current + ", " + code), + }); + + commandManager.AddHandler("/xlsettings", new CommandInfo(this.OnOpenSettingsCommand) + { + HelpMessage = Loc.Localize( + "DalamudSettingsHelp", + "Change various In-Game-Addon settings like chat channels and the discord bot setup."), + }); + + commandManager.AddHandler("/imdebug", new CommandInfo(this.OnDebugImInfoCommand) + { + HelpMessage = "ImGui DEBUG", + ShowInHelp = false, + }); } - configuration.Save(); - - foreach (var word in configuration.BadWords) - chatGui.Print($"\"{word}\""); - } - - private void OnBadWordsRemoveCommand(string command, string arguments) - { - var chatGui = Service.Get(); - var configuration = Service.Get(); - - configuration.BadWords ??= new List(); - - configuration.BadWords.RemoveAll(x => x == arguments); - - configuration.Save(); - - chatGui.Print(string.Format(Loc.Localize("DalamudUnmuted", "Unmuted \"{0}\"."), arguments)); - } - - private void OnLastLinkCommand(string command, string arguments) - { - var chatHandlers = Service.Get(); - var chatGui = Service.Get(); - - if (string.IsNullOrEmpty(chatHandlers.LastLink)) + private void OnUnloadCommand(string command, string arguments) { - chatGui.Print(Loc.Localize("DalamudNoLastLink", "No last link...")); - return; + Service.Get().Print("Unloading..."); + Service.Get().Unload(); } - chatGui.Print(string.Format(Loc.Localize("DalamudOpeningLink", "Opening {0}"), chatHandlers.LastLink)); - Process.Start(new ProcessStartInfo(chatHandlers.LastLink) + private void OnHelpCommand(string command, string arguments) { - UseShellExecute = true, - }); - } + var chatGui = Service.Get(); + var commandManager = Service.Get(); - private void OnBgmSetCommand(string command, string arguments) - { - var gameGui = Service.Get(); + var showDebug = arguments.Contains("debug"); - if (ushort.TryParse(arguments, out var value)) - { - gameGui.SetBgm(value); - } - else - { - // Revert to the original BGM by specifying an invalid one - gameGui.SetBgm(9999); - } - } + chatGui.Print(Loc.Localize("DalamudCmdHelpAvailable", "Available commands:")); + foreach (var cmd in commandManager.Commands) + { + if (!cmd.Value.ShowInHelp && !showDebug) + continue; - private void OnDebugDrawDevMenu(string command, string arguments) - { - Service.Get().ToggleDevMenu(); - } - - private void OnDebugDrawDataMenu(string command, string arguments) - { - var dalamudInterface = Service.Get(); - - if (string.IsNullOrEmpty(arguments)) - dalamudInterface.ToggleDataWindow(); - else - dalamudInterface.ToggleDataWindow(arguments); - } - - private void OnDebugDrawIMEPanel(string command, string arguments) - { - Service.Get().OpenIMEWindow(); - } - - private void OnOpenLog(string command, string arguments) - { - Service.Get().ToggleLogWindow(); - } - - private void OnDebugImInfoCommand(string command, string arguments) - { - var io = Service.Get().LastImGuiIoPtr; - var info = $"WantCaptureKeyboard: {io.WantCaptureKeyboard}\n"; - info += $"WantCaptureMouse: {io.WantCaptureMouse}\n"; - info += $"WantSetMousePos: {io.WantSetMousePos}\n"; - info += $"WantTextInput: {io.WantTextInput}\n"; - info += $"WantSaveIniSettings: {io.WantSaveIniSettings}\n"; - info += $"BackendFlags: {(int)io.BackendFlags}\n"; - info += $"DeltaTime: {io.DeltaTime}\n"; - info += $"DisplaySize: {io.DisplaySize.X} {io.DisplaySize.Y}\n"; - info += $"Framerate: {io.Framerate}\n"; - info += $"MetricsActiveWindows: {io.MetricsActiveWindows}\n"; - info += $"MetricsRenderWindows: {io.MetricsRenderWindows}\n"; - info += $"MousePos: {io.MousePos.X} {io.MousePos.Y}\n"; - info += $"MouseClicked: {io.MouseClicked}\n"; - info += $"MouseDown: {io.MouseDown}\n"; - info += $"NavActive: {io.NavActive}\n"; - info += $"NavVisible: {io.NavVisible}\n"; - - Log.Information(info); - } - - private void OnOpenInstallerCommand(string command, string arguments) - { - Service.Get().TogglePluginInstallerWindow(); - } - - private void OnOpenCreditsCommand(string command, string arguments) - { - Service.Get().ToggleCreditsWindow(); - } - - private void OnSetLanguageCommand(string command, string arguments) - { - var chatGui = Service.Get(); - var configuration = Service.Get(); - var localization = Service.Get(); - - if (Localization.ApplicableLangCodes.Contains(arguments.ToLower()) || arguments.ToLower() == "en") - { - localization.SetupWithLangCode(arguments.ToLower()); - configuration.LanguageOverride = arguments.ToLower(); - - chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), arguments)); - } - else - { - localization.SetupWithUiCulture(); - configuration.LanguageOverride = null; - - chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), "default")); + chatGui.Print($"{cmd.Key}: {cmd.Value.HelpMessage}"); + } } - configuration.Save(); - } + private void OnPluginReloadCommand(string command, string arguments) + { + var chatGui = Service.Get(); - private void OnOpenSettingsCommand(string command, string arguments) - { - Service.Get().ToggleSettingsWindow(); + chatGui.Print("Reloading..."); + + try + { + Service.Get().ReloadAllPlugins(); + chatGui.Print("OK"); + } + catch (Exception ex) + { + Log.Error(ex, "Plugin reload failed."); + chatGui.PrintError("Reload failed."); + } + } + + private void OnBadWordsAddCommand(string command, string arguments) + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + + configuration.BadWords ??= new List(); + + if (string.IsNullOrEmpty(arguments)) + { + chatGui.Print(Loc.Localize("DalamudMuteNoArgs", "Please provide a word to mute.")); + return; + } + + configuration.BadWords.Add(arguments); + + configuration.Save(); + + chatGui.Print(string.Format(Loc.Localize("DalamudMuted", "Muted \"{0}\"."), arguments)); + } + + private void OnBadWordsListCommand(string command, string arguments) + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + + configuration.BadWords ??= new List(); + + if (configuration.BadWords.Count == 0) + { + chatGui.Print(Loc.Localize("DalamudNoneMuted", "No muted words or sentences.")); + return; + } + + configuration.Save(); + + foreach (var word in configuration.BadWords) + chatGui.Print($"\"{word}\""); + } + + private void OnBadWordsRemoveCommand(string command, string arguments) + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + + configuration.BadWords ??= new List(); + + configuration.BadWords.RemoveAll(x => x == arguments); + + configuration.Save(); + + chatGui.Print(string.Format(Loc.Localize("DalamudUnmuted", "Unmuted \"{0}\"."), arguments)); + } + + private void OnLastLinkCommand(string command, string arguments) + { + var chatHandlers = Service.Get(); + var chatGui = Service.Get(); + + if (string.IsNullOrEmpty(chatHandlers.LastLink)) + { + chatGui.Print(Loc.Localize("DalamudNoLastLink", "No last link...")); + return; + } + + chatGui.Print(string.Format(Loc.Localize("DalamudOpeningLink", "Opening {0}"), chatHandlers.LastLink)); + Process.Start(new ProcessStartInfo(chatHandlers.LastLink) + { + UseShellExecute = true, + }); + } + + private void OnBgmSetCommand(string command, string arguments) + { + var gameGui = Service.Get(); + + if (ushort.TryParse(arguments, out var value)) + { + gameGui.SetBgm(value); + } + else + { + // Revert to the original BGM by specifying an invalid one + gameGui.SetBgm(9999); + } + } + + private void OnDebugDrawDevMenu(string command, string arguments) + { + Service.Get().ToggleDevMenu(); + } + + private void OnDebugDrawDataMenu(string command, string arguments) + { + var dalamudInterface = Service.Get(); + + if (string.IsNullOrEmpty(arguments)) + dalamudInterface.ToggleDataWindow(); + else + dalamudInterface.ToggleDataWindow(arguments); + } + + private void OnDebugDrawIMEPanel(string command, string arguments) + { + Service.Get().OpenIMEWindow(); + } + + private void OnOpenLog(string command, string arguments) + { + Service.Get().ToggleLogWindow(); + } + + private void OnDebugImInfoCommand(string command, string arguments) + { + var io = Service.Get().LastImGuiIoPtr; + var info = $"WantCaptureKeyboard: {io.WantCaptureKeyboard}\n"; + info += $"WantCaptureMouse: {io.WantCaptureMouse}\n"; + info += $"WantSetMousePos: {io.WantSetMousePos}\n"; + info += $"WantTextInput: {io.WantTextInput}\n"; + info += $"WantSaveIniSettings: {io.WantSaveIniSettings}\n"; + info += $"BackendFlags: {(int)io.BackendFlags}\n"; + info += $"DeltaTime: {io.DeltaTime}\n"; + info += $"DisplaySize: {io.DisplaySize.X} {io.DisplaySize.Y}\n"; + info += $"Framerate: {io.Framerate}\n"; + info += $"MetricsActiveWindows: {io.MetricsActiveWindows}\n"; + info += $"MetricsRenderWindows: {io.MetricsRenderWindows}\n"; + info += $"MousePos: {io.MousePos.X} {io.MousePos.Y}\n"; + info += $"MouseClicked: {io.MouseClicked}\n"; + info += $"MouseDown: {io.MouseDown}\n"; + info += $"NavActive: {io.NavActive}\n"; + info += $"NavVisible: {io.NavVisible}\n"; + + Log.Information(info); + } + + private void OnOpenInstallerCommand(string command, string arguments) + { + Service.Get().TogglePluginInstallerWindow(); + } + + private void OnOpenCreditsCommand(string command, string arguments) + { + Service.Get().ToggleCreditsWindow(); + } + + private void OnSetLanguageCommand(string command, string arguments) + { + var chatGui = Service.Get(); + var configuration = Service.Get(); + var localization = Service.Get(); + + if (Localization.ApplicableLangCodes.Contains(arguments.ToLower()) || arguments.ToLower() == "en") + { + localization.SetupWithLangCode(arguments.ToLower()); + configuration.LanguageOverride = arguments.ToLower(); + + chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), arguments)); + } + else + { + localization.SetupWithUiCulture(); + configuration.LanguageOverride = null; + + chatGui.Print(string.Format(Loc.Localize("DalamudLanguageSetTo", "Language set to {0}"), "default")); + } + + configuration.Save(); + } + + private void OnOpenSettingsCommand(string command, string arguments) + { + Service.Get().ToggleSettingsWindow(); + } } } diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 5320a8c41..ece1b4cc6 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -26,659 +26,660 @@ using ImGuiNET; using PInvoke; using Serilog.Events; -namespace Dalamud.Interface.Internal; - -/// -/// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping. -/// -internal class DalamudInterface : IDisposable +namespace Dalamud.Interface.Internal { - private static readonly ModuleLog Log = new("DUI"); + /// + /// This plugin implements all of the Dalamud interface separately, to allow for reloading of the interface and rapid prototyping. + /// + internal class DalamudInterface : IDisposable + { + private static readonly ModuleLog Log = new("DUI"); - private readonly ChangelogWindow changelogWindow; - private readonly ColorDemoWindow colorDemoWindow; - private readonly ComponentDemoWindow componentDemoWindow; - private readonly CreditsWindow creditsWindow; - private readonly DataWindow dataWindow; - private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow; - private readonly IMEWindow imeWindow; - private readonly ConsoleWindow consoleWindow; - private readonly PluginStatWindow pluginStatWindow; - private readonly PluginInstallerWindow pluginWindow; - private readonly SettingsWindow settingsWindow; - private readonly SelfTestWindow selfTestWindow; - private readonly StyleEditorWindow styleEditorWindow; + private readonly ChangelogWindow changelogWindow; + private readonly ColorDemoWindow colorDemoWindow; + private readonly ComponentDemoWindow componentDemoWindow; + private readonly CreditsWindow creditsWindow; + private readonly DataWindow dataWindow; + private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow; + private readonly IMEWindow imeWindow; + private readonly ConsoleWindow consoleWindow; + private readonly PluginStatWindow pluginStatWindow; + private readonly PluginInstallerWindow pluginWindow; + private readonly SettingsWindow settingsWindow; + private readonly SelfTestWindow selfTestWindow; + private readonly StyleEditorWindow styleEditorWindow; - private ulong frameCount = 0; + private ulong frameCount = 0; #if DEBUG - private bool isImGuiDrawDevMenu = true; + private bool isImGuiDrawDevMenu = true; #else private bool isImGuiDrawDevMenu = false; #endif - private bool isImGuiDrawDemoWindow = false; - private bool isImGuiDrawMetricsWindow = false; + private bool isImGuiDrawDemoWindow = false; + private bool isImGuiDrawMetricsWindow = false; - /// - /// Initializes a new instance of the class. - /// - public DalamudInterface() - { - var configuration = Service.Get(); - - this.WindowSystem = new WindowSystem("DalamudCore"); - - this.changelogWindow = new ChangelogWindow() { IsOpen = false }; - this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false }; - this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false }; - this.creditsWindow = new CreditsWindow() { IsOpen = false }; - this.dataWindow = new DataWindow() { IsOpen = false }; - this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false }; - this.imeWindow = new IMEWindow() { IsOpen = false }; - this.consoleWindow = new ConsoleWindow() { IsOpen = configuration.LogOpenAtStartup }; - this.pluginStatWindow = new PluginStatWindow() { IsOpen = false }; - this.pluginWindow = new PluginInstallerWindow() { IsOpen = false }; - this.settingsWindow = new SettingsWindow() { IsOpen = false }; - this.selfTestWindow = new SelfTestWindow() { IsOpen = false }; - this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false }; - - this.WindowSystem.AddWindow(this.changelogWindow); - this.WindowSystem.AddWindow(this.colorDemoWindow); - this.WindowSystem.AddWindow(this.componentDemoWindow); - this.WindowSystem.AddWindow(this.creditsWindow); - this.WindowSystem.AddWindow(this.dataWindow); - this.WindowSystem.AddWindow(this.gamepadModeNotifierWindow); - this.WindowSystem.AddWindow(this.imeWindow); - this.WindowSystem.AddWindow(this.consoleWindow); - this.WindowSystem.AddWindow(this.pluginStatWindow); - this.WindowSystem.AddWindow(this.pluginWindow); - this.WindowSystem.AddWindow(this.settingsWindow); - this.WindowSystem.AddWindow(this.selfTestWindow); - this.WindowSystem.AddWindow(this.styleEditorWindow); - - ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; - - Service.Get().Draw += this.OnDraw; - } - - /// - /// Gets the controlling all Dalamud-internal windows. - /// - public WindowSystem WindowSystem { get; init; } - - /// - /// Gets or sets a value indicating whether the /xldev menu is open. - /// - public bool IsDevMenuOpen - { - get => this.isImGuiDrawDevMenu; - set => this.isImGuiDrawDevMenu = value; - } - - /// - public void Dispose() - { - Service.Get().Draw -= this.OnDraw; - - this.WindowSystem.RemoveAllWindows(); - - this.creditsWindow.Dispose(); - this.consoleWindow.Dispose(); - this.pluginWindow.Dispose(); - } - - #region Open - - /// - /// Opens the . - /// - public void OpenChangelogWindow() => this.changelogWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenColorsDemoWindow() => this.colorDemoWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenComponentDemoWindow() => this.componentDemoWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenCreditsWindow() => this.creditsWindow.IsOpen = true; - - /// - /// Opens the . - /// - /// The data kind to switch to after opening. - public void OpenDataWindow(string dataKind = null) - { - this.dataWindow.IsOpen = true; - if (dataKind != null && this.dataWindow.IsOpen) + /// + /// Initializes a new instance of the class. + /// + public DalamudInterface() { - this.dataWindow.SetDataKind(dataKind); + var configuration = Service.Get(); + + this.WindowSystem = new WindowSystem("DalamudCore"); + + this.changelogWindow = new ChangelogWindow() { IsOpen = false }; + this.colorDemoWindow = new ColorDemoWindow() { IsOpen = false }; + this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false }; + this.creditsWindow = new CreditsWindow() { IsOpen = false }; + this.dataWindow = new DataWindow() { IsOpen = false }; + this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false }; + this.imeWindow = new IMEWindow() { IsOpen = false }; + this.consoleWindow = new ConsoleWindow() { IsOpen = configuration.LogOpenAtStartup }; + this.pluginStatWindow = new PluginStatWindow() { IsOpen = false }; + this.pluginWindow = new PluginInstallerWindow() { IsOpen = false }; + this.settingsWindow = new SettingsWindow() { IsOpen = false }; + this.selfTestWindow = new SelfTestWindow() { IsOpen = false }; + this.styleEditorWindow = new StyleEditorWindow() { IsOpen = false }; + + this.WindowSystem.AddWindow(this.changelogWindow); + this.WindowSystem.AddWindow(this.colorDemoWindow); + this.WindowSystem.AddWindow(this.componentDemoWindow); + this.WindowSystem.AddWindow(this.creditsWindow); + this.WindowSystem.AddWindow(this.dataWindow); + this.WindowSystem.AddWindow(this.gamepadModeNotifierWindow); + this.WindowSystem.AddWindow(this.imeWindow); + this.WindowSystem.AddWindow(this.consoleWindow); + this.WindowSystem.AddWindow(this.pluginStatWindow); + this.WindowSystem.AddWindow(this.pluginWindow); + this.WindowSystem.AddWindow(this.settingsWindow); + this.WindowSystem.AddWindow(this.selfTestWindow); + this.WindowSystem.AddWindow(this.styleEditorWindow); + + ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup; + + Service.Get().Draw += this.OnDraw; } - } - /// - /// Opens the dev menu bar. - /// - public void OpenDevMenu() => this.isImGuiDrawDevMenu = true; + /// + /// Gets the controlling all Dalamud-internal windows. + /// + public WindowSystem WindowSystem { get; init; } - /// - /// Opens the . - /// - public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenIMEWindow() => this.imeWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenLogWindow() => this.consoleWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenPluginStats() => this.pluginStatWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenPluginInstaller() => this.pluginWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenSettings() => this.settingsWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenSelfTest() => this.selfTestWindow.IsOpen = true; - - /// - /// Opens the . - /// - public void OpenStyleEditor() => this.styleEditorWindow.IsOpen = true; - - #endregion - - #region Close - - /// - /// Closes the . - /// - public void CloseIMEWindow() => this.imeWindow.IsOpen = false; - - #endregion - - #region Toggle - - /// - /// Toggles the . - /// - public void ToggleChangelogWindow() => this.changelogWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleColorsDemoWindow() => this.colorDemoWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleComponentDemoWindow() => this.componentDemoWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleCreditsWindow() => this.creditsWindow.Toggle(); - - /// - /// Toggles the . - /// - /// The data kind to switch to after opening. - public void ToggleDataWindow(string dataKind = null) - { - this.dataWindow.Toggle(); - if (dataKind != null && this.dataWindow.IsOpen) + /// + /// Gets or sets a value indicating whether the /xldev menu is open. + /// + public bool IsDevMenuOpen { - this.dataWindow.SetDataKind(dataKind); + get => this.isImGuiDrawDevMenu; + set => this.isImGuiDrawDevMenu = value; } - } - /// - /// Toggles the dev menu bar. - /// - public void ToggleDevMenu() => this.isImGuiDrawDevMenu ^= true; - - /// - /// Toggles the . - /// - public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleIMEWindow() => this.imeWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleLogWindow() => this.consoleWindow.Toggle(); - - /// - /// Toggles the . - /// - public void TogglePluginStatsWindow() => this.pluginStatWindow.Toggle(); - - /// - /// Toggles the . - /// - public void TogglePluginInstallerWindow() => this.pluginWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleSettingsWindow() => this.settingsWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleSelfTestWindow() => this.selfTestWindow.Toggle(); - - /// - /// Toggles the . - /// - public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle(); - - #endregion - - private void OnDraw() - { - this.frameCount++; - - try + /// + public void Dispose() { - this.DrawHiddenDevMenuOpener(); - this.DrawDevMenu(); + Service.Get().Draw -= this.OnDraw; - if (Service.Get().GameUiHidden) - return; + this.WindowSystem.RemoveAllWindows(); - this.WindowSystem.Draw(); + this.creditsWindow.Dispose(); + this.consoleWindow.Dispose(); + this.pluginWindow.Dispose(); + } - if (this.isImGuiDrawDemoWindow) - ImGui.ShowDemoWindow(ref this.isImGuiDrawDemoWindow); + #region Open - if (this.isImGuiDrawMetricsWindow) - ImGui.ShowMetricsWindow(ref this.isImGuiDrawMetricsWindow); + /// + /// Opens the . + /// + public void OpenChangelogWindow() => this.changelogWindow.IsOpen = true; - // Release focus of any ImGui window if we click into the game. - var io = ImGui.GetIO(); - if (!io.WantCaptureMouse && (User32.GetKeyState((int)User32.VirtualKey.VK_LBUTTON) & 0x8000) != 0) + /// + /// Opens the . + /// + public void OpenColorsDemoWindow() => this.colorDemoWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenComponentDemoWindow() => this.componentDemoWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenCreditsWindow() => this.creditsWindow.IsOpen = true; + + /// + /// Opens the . + /// + /// The data kind to switch to after opening. + public void OpenDataWindow(string dataKind = null) + { + this.dataWindow.IsOpen = true; + if (dataKind != null && this.dataWindow.IsOpen) { - ImGui.SetWindowFocus(null); + this.dataWindow.SetDataKind(dataKind); } } - catch (Exception ex) + + /// + /// Opens the dev menu bar. + /// + public void OpenDevMenu() => this.isImGuiDrawDevMenu = true; + + /// + /// Opens the . + /// + public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenIMEWindow() => this.imeWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenLogWindow() => this.consoleWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenPluginStats() => this.pluginStatWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenPluginInstaller() => this.pluginWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenSettings() => this.settingsWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenSelfTest() => this.selfTestWindow.IsOpen = true; + + /// + /// Opens the . + /// + public void OpenStyleEditor() => this.styleEditorWindow.IsOpen = true; + + #endregion + + #region Close + + /// + /// Closes the . + /// + public void CloseIMEWindow() => this.imeWindow.IsOpen = false; + + #endregion + + #region Toggle + + /// + /// Toggles the . + /// + public void ToggleChangelogWindow() => this.changelogWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleColorsDemoWindow() => this.colorDemoWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleComponentDemoWindow() => this.componentDemoWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleCreditsWindow() => this.creditsWindow.Toggle(); + + /// + /// Toggles the . + /// + /// The data kind to switch to after opening. + public void ToggleDataWindow(string dataKind = null) { - PluginLog.Error(ex, "Error during OnDraw"); - } - } - - private void DrawHiddenDevMenuOpener() - { - var condition = Service.Get(); - - if (!this.isImGuiDrawDevMenu && !condition.Any()) - { - ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1)); - ImGui.PushStyleColor(ImGuiCol.TextSelectedBg, new Vector4(0, 0, 0, 1)); - ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(0, 0, 0, 1)); - ImGui.PushStyleColor(ImGuiCol.BorderShadow, new Vector4(0, 0, 0, 1)); - ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 1)); - - var mainViewportPos = ImGui.GetMainViewport().Pos; - ImGui.SetNextWindowPos(new Vector2(mainViewportPos.X, mainViewportPos.Y), ImGuiCond.Always); - ImGui.SetNextWindowBgAlpha(1); - - if (ImGui.Begin("DevMenu Opener", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) + this.dataWindow.Toggle(); + if (dataKind != null && this.dataWindow.IsOpen) { - if (ImGui.Button("###devMenuOpener", new Vector2(40, 25))) - this.isImGuiDrawDevMenu = true; - - ImGui.End(); + this.dataWindow.SetDataKind(dataKind); } - - ImGui.PopStyleColor(8); } - } - private void DrawDevMenu() - { - if (this.isImGuiDrawDevMenu) + /// + /// Toggles the dev menu bar. + /// + public void ToggleDevMenu() => this.isImGuiDrawDevMenu ^= true; + + /// + /// Toggles the . + /// + public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleIMEWindow() => this.imeWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleLogWindow() => this.consoleWindow.Toggle(); + + /// + /// Toggles the . + /// + public void TogglePluginStatsWindow() => this.pluginStatWindow.Toggle(); + + /// + /// Toggles the . + /// + public void TogglePluginInstallerWindow() => this.pluginWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleSettingsWindow() => this.settingsWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleSelfTestWindow() => this.selfTestWindow.Toggle(); + + /// + /// Toggles the . + /// + public void ToggleStyleEditorWindow() => this.selfTestWindow.Toggle(); + + #endregion + + private void OnDraw() { - if (ImGui.BeginMainMenuBar()) + this.frameCount++; + + try { - var dalamud = Service.Get(); - var configuration = Service.Get(); - var pluginManager = Service.Get(); - - if (ImGui.BeginMenu("Dalamud")) - { - ImGui.MenuItem("Draw Dalamud dev menu", string.Empty, ref this.isImGuiDrawDevMenu); - - ImGui.Separator(); - - if (ImGui.MenuItem("Open Log window")) - { - this.OpenLogWindow(); - } - - if (ImGui.BeginMenu("Set log level...")) - { - foreach (var logLevel in Enum.GetValues(typeof(LogEventLevel)).Cast()) - { - if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, dalamud.LogLevelSwitch.MinimumLevel == logLevel)) - { - dalamud.LogLevelSwitch.MinimumLevel = logLevel; - configuration.LogLevel = logLevel; - configuration.Save(); - } - } - - ImGui.EndMenu(); - } - - var antiDebug = Service.Get(); - if (ImGui.MenuItem("Enable AntiDebug", null, antiDebug.IsEnabled)) - { - var newEnabled = !antiDebug.IsEnabled; - if (newEnabled) - antiDebug.Enable(); - else - antiDebug.Disable(); - - configuration.IsAntiAntiDebugEnabled = newEnabled; - configuration.Save(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Open Data window")) - { - this.OpenDataWindow(); - } - - if (ImGui.MenuItem("Open Credits window")) - { - this.OpenCreditsWindow(); - } - - if (ImGui.MenuItem("Open Settings window")) - { - this.OpenSettings(); - } - - if (ImGui.MenuItem("Open Changelog window")) - { - this.OpenChangelogWindow(); - } - - if (ImGui.MenuItem("Open Components Demo")) - { - this.OpenComponentDemoWindow(); - } - - if (ImGui.MenuItem("Open Colors Demo")) - { - this.OpenColorsDemoWindow(); - } - - if (ImGui.MenuItem("Open Self-Test")) - { - this.OpenSelfTest(); - } - - if (ImGui.MenuItem("Open Style Editor")) - { - this.OpenStyleEditor(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Unload Dalamud")) - { - Service.Get().Unload(); - } - - if (ImGui.MenuItem("Kill game")) - { - Process.GetCurrentProcess().Kill(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Access Violation")) - { - Marshal.ReadByte(IntPtr.Zero); - } - - if (ImGui.MenuItem("Crash game")) - { - unsafe - { - var framework = Framework.Instance(); - framework->UIModule = (UIModule*)0; - } - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Enable Dalamud testing", string.Empty, configuration.DoDalamudTest)) - { - configuration.DoDalamudTest ^= true; - configuration.Save(); - } - - var startInfo = Service.Get(); - ImGui.MenuItem(Util.AssemblyVersion, false); - ImGui.MenuItem(startInfo.GameVersion.ToString(), false); - - ImGui.EndMenu(); - } - - if (ImGui.BeginMenu("GUI")) - { - ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow); - - ImGui.MenuItem("Draw metrics", string.Empty, ref this.isImGuiDrawMetricsWindow); - - ImGui.Separator(); - - var val = ImGuiManagedAsserts.AssertsEnabled; - if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val)) - { - ImGuiManagedAsserts.AssertsEnabled = val; - } - - if (ImGui.MenuItem("Enable asserts at startup", null, configuration.AssertsEnabledAtStartup)) - { - configuration.AssertsEnabledAtStartup = !configuration.AssertsEnabledAtStartup; - configuration.Save(); - } - - if (ImGui.MenuItem("Clear focus")) - { - ImGui.SetWindowFocus(null); - } - - if (ImGui.MenuItem("Dump style")) - { - var info = string.Empty; - var style = StyleModelV1.Get(); - var enCulture = new CultureInfo("en-US"); - - foreach (var propertyInfo in typeof(StyleModel).GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - if (propertyInfo.PropertyType == typeof(Vector2)) - { - var vec2 = (Vector2)propertyInfo.GetValue(style); - info += $"{propertyInfo.Name} = new Vector2({vec2.X.ToString(enCulture)}f, {vec2.Y.ToString(enCulture)}f),\n"; - } - else - { - info += $"{propertyInfo.Name} = {propertyInfo.GetValue(style)},\n"; - } - } - - info += "Colors = new Dictionary()\n"; - info += "{\n"; - - foreach (var color in style.Colors) - { - info += - $"{{\"{color.Key}\", new Vector4({color.Value.X.ToString(enCulture)}f, {color.Value.Y.ToString(enCulture)}f, {color.Value.Z.ToString(enCulture)}f, {color.Value.W.ToString(enCulture)}f)}},\n"; - } - - info += "},"; - - Log.Information(info); - } - - ImGui.EndMenu(); - } - - if (ImGui.BeginMenu("Game")) - { - if (ImGui.MenuItem("Replace ExceptionHandler")) - { - dalamud.ReplaceExceptionHandler(); - } - - ImGui.EndMenu(); - } - - if (ImGui.BeginMenu("Plugins")) - { - if (ImGui.MenuItem("Open Plugin installer")) - { - this.OpenPluginInstaller(); - } - - if (ImGui.MenuItem("Clear cached images/icons")) - { - this.pluginWindow?.ClearIconCache(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Open Plugin Stats")) - { - this.OpenPluginStats(); - } - - if (ImGui.MenuItem("Print plugin info")) - { - foreach (var plugin in pluginManager.InstalledPlugins) - { - // TODO: some more here, state maybe? - PluginLog.Information($"{plugin.Name}"); - } - } - - if (ImGui.MenuItem("Reload plugins")) - { - try - { - pluginManager.ReloadAllPlugins(); - } - catch (Exception ex) - { - Service.Get().PrintError("Reload failed."); - PluginLog.Error(ex, "Plugin reload failed."); - } - } - - if (ImGui.MenuItem("Scan dev plugins")) - { - pluginManager.ScanDevPlugins(); - } - - ImGui.Separator(); - - if (ImGui.MenuItem("Load all API levels", null, configuration.LoadAllApiLevels)) - { - configuration.LoadAllApiLevels = !configuration.LoadAllApiLevels; - configuration.Save(); - } - - if (ImGui.MenuItem("Load banned plugins", null, configuration.LoadBannedPlugins)) - { - configuration.LoadBannedPlugins = !configuration.LoadBannedPlugins; - configuration.Save(); - } - - ImGui.Separator(); - ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false); - ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count, false); - ImGui.EndMenu(); - } - - if (ImGui.BeginMenu("Localization")) - { - var localization = Service.Get(); - - if (ImGui.MenuItem("Export localizable")) - { - localization.ExportLocalizable(); - } - - if (ImGui.BeginMenu("Load language...")) - { - if (ImGui.MenuItem("From Fallbacks")) - { - localization.SetupWithFallbacks(); - } - - if (ImGui.MenuItem("From UICulture")) - { - localization.SetupWithUiCulture(); - } - - foreach (var applicableLangCode in Localization.ApplicableLangCodes) - { - if (ImGui.MenuItem($"Applicable: {applicableLangCode}")) - { - localization.SetupWithLangCode(applicableLangCode); - } - } - - ImGui.EndMenu(); - } - - ImGui.EndMenu(); - } + this.DrawHiddenDevMenuOpener(); + this.DrawDevMenu(); if (Service.Get().GameUiHidden) - ImGui.BeginMenu("UI is hidden...", false); + return; - ImGui.BeginMenu(Util.GetGitHash(), false); - ImGui.BeginMenu(this.frameCount.ToString(), false); - ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("F2"), false); + this.WindowSystem.Draw(); - ImGui.EndMainMenuBar(); + if (this.isImGuiDrawDemoWindow) + ImGui.ShowDemoWindow(ref this.isImGuiDrawDemoWindow); + + if (this.isImGuiDrawMetricsWindow) + ImGui.ShowMetricsWindow(ref this.isImGuiDrawMetricsWindow); + + // Release focus of any ImGui window if we click into the game. + var io = ImGui.GetIO(); + if (!io.WantCaptureMouse && (User32.GetKeyState((int)User32.VirtualKey.VK_LBUTTON) & 0x8000) != 0) + { + ImGui.SetWindowFocus(null); + } + } + catch (Exception ex) + { + PluginLog.Error(ex, "Error during OnDraw"); + } + } + + private void DrawHiddenDevMenuOpener() + { + var condition = Service.Get(); + + if (!this.isImGuiDrawDevMenu && !condition.Any()) + { + ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, Vector4.Zero); + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1)); + ImGui.PushStyleColor(ImGuiCol.TextSelectedBg, new Vector4(0, 0, 0, 1)); + ImGui.PushStyleColor(ImGuiCol.Border, new Vector4(0, 0, 0, 1)); + ImGui.PushStyleColor(ImGuiCol.BorderShadow, new Vector4(0, 0, 0, 1)); + ImGui.PushStyleColor(ImGuiCol.WindowBg, new Vector4(0, 0, 0, 1)); + + var mainViewportPos = ImGui.GetMainViewport().Pos; + ImGui.SetNextWindowPos(new Vector2(mainViewportPos.X, mainViewportPos.Y), ImGuiCond.Always); + ImGui.SetNextWindowBgAlpha(1); + + if (ImGui.Begin("DevMenu Opener", ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoSavedSettings)) + { + if (ImGui.Button("###devMenuOpener", new Vector2(40, 25))) + this.isImGuiDrawDevMenu = true; + + ImGui.End(); + } + + ImGui.PopStyleColor(8); + } + } + + private void DrawDevMenu() + { + if (this.isImGuiDrawDevMenu) + { + if (ImGui.BeginMainMenuBar()) + { + var dalamud = Service.Get(); + var configuration = Service.Get(); + var pluginManager = Service.Get(); + + if (ImGui.BeginMenu("Dalamud")) + { + ImGui.MenuItem("Draw Dalamud dev menu", string.Empty, ref this.isImGuiDrawDevMenu); + + ImGui.Separator(); + + if (ImGui.MenuItem("Open Log window")) + { + this.OpenLogWindow(); + } + + if (ImGui.BeginMenu("Set log level...")) + { + foreach (var logLevel in Enum.GetValues(typeof(LogEventLevel)).Cast()) + { + if (ImGui.MenuItem(logLevel + "##logLevelSwitch", string.Empty, dalamud.LogLevelSwitch.MinimumLevel == logLevel)) + { + dalamud.LogLevelSwitch.MinimumLevel = logLevel; + configuration.LogLevel = logLevel; + configuration.Save(); + } + } + + ImGui.EndMenu(); + } + + var antiDebug = Service.Get(); + if (ImGui.MenuItem("Enable AntiDebug", null, antiDebug.IsEnabled)) + { + var newEnabled = !antiDebug.IsEnabled; + if (newEnabled) + antiDebug.Enable(); + else + antiDebug.Disable(); + + configuration.IsAntiAntiDebugEnabled = newEnabled; + configuration.Save(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Open Data window")) + { + this.OpenDataWindow(); + } + + if (ImGui.MenuItem("Open Credits window")) + { + this.OpenCreditsWindow(); + } + + if (ImGui.MenuItem("Open Settings window")) + { + this.OpenSettings(); + } + + if (ImGui.MenuItem("Open Changelog window")) + { + this.OpenChangelogWindow(); + } + + if (ImGui.MenuItem("Open Components Demo")) + { + this.OpenComponentDemoWindow(); + } + + if (ImGui.MenuItem("Open Colors Demo")) + { + this.OpenColorsDemoWindow(); + } + + if (ImGui.MenuItem("Open Self-Test")) + { + this.OpenSelfTest(); + } + + if (ImGui.MenuItem("Open Style Editor")) + { + this.OpenStyleEditor(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Unload Dalamud")) + { + Service.Get().Unload(); + } + + if (ImGui.MenuItem("Kill game")) + { + Process.GetCurrentProcess().Kill(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Access Violation")) + { + Marshal.ReadByte(IntPtr.Zero); + } + + if (ImGui.MenuItem("Crash game")) + { + unsafe + { + var framework = Framework.Instance(); + framework->UIModule = (UIModule*)0; + } + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Enable Dalamud testing", string.Empty, configuration.DoDalamudTest)) + { + configuration.DoDalamudTest ^= true; + configuration.Save(); + } + + var startInfo = Service.Get(); + ImGui.MenuItem(Util.AssemblyVersion, false); + ImGui.MenuItem(startInfo.GameVersion.ToString(), false); + + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("GUI")) + { + ImGui.MenuItem("Draw ImGui demo", string.Empty, ref this.isImGuiDrawDemoWindow); + + ImGui.MenuItem("Draw metrics", string.Empty, ref this.isImGuiDrawMetricsWindow); + + ImGui.Separator(); + + var val = ImGuiManagedAsserts.AssertsEnabled; + if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val)) + { + ImGuiManagedAsserts.AssertsEnabled = val; + } + + if (ImGui.MenuItem("Enable asserts at startup", null, configuration.AssertsEnabledAtStartup)) + { + configuration.AssertsEnabledAtStartup = !configuration.AssertsEnabledAtStartup; + configuration.Save(); + } + + if (ImGui.MenuItem("Clear focus")) + { + ImGui.SetWindowFocus(null); + } + + if (ImGui.MenuItem("Dump style")) + { + var info = string.Empty; + var style = StyleModelV1.Get(); + var enCulture = new CultureInfo("en-US"); + + foreach (var propertyInfo in typeof(StyleModel).GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if (propertyInfo.PropertyType == typeof(Vector2)) + { + var vec2 = (Vector2)propertyInfo.GetValue(style); + info += $"{propertyInfo.Name} = new Vector2({vec2.X.ToString(enCulture)}f, {vec2.Y.ToString(enCulture)}f),\n"; + } + else + { + info += $"{propertyInfo.Name} = {propertyInfo.GetValue(style)},\n"; + } + } + + info += "Colors = new Dictionary()\n"; + info += "{\n"; + + foreach (var color in style.Colors) + { + info += + $"{{\"{color.Key}\", new Vector4({color.Value.X.ToString(enCulture)}f, {color.Value.Y.ToString(enCulture)}f, {color.Value.Z.ToString(enCulture)}f, {color.Value.W.ToString(enCulture)}f)}},\n"; + } + + info += "},"; + + Log.Information(info); + } + + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Game")) + { + if (ImGui.MenuItem("Replace ExceptionHandler")) + { + dalamud.ReplaceExceptionHandler(); + } + + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Plugins")) + { + if (ImGui.MenuItem("Open Plugin installer")) + { + this.OpenPluginInstaller(); + } + + if (ImGui.MenuItem("Clear cached images/icons")) + { + this.pluginWindow?.ClearIconCache(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Open Plugin Stats")) + { + this.OpenPluginStats(); + } + + if (ImGui.MenuItem("Print plugin info")) + { + foreach (var plugin in pluginManager.InstalledPlugins) + { + // TODO: some more here, state maybe? + PluginLog.Information($"{plugin.Name}"); + } + } + + if (ImGui.MenuItem("Reload plugins")) + { + try + { + pluginManager.ReloadAllPlugins(); + } + catch (Exception ex) + { + Service.Get().PrintError("Reload failed."); + PluginLog.Error(ex, "Plugin reload failed."); + } + } + + if (ImGui.MenuItem("Scan dev plugins")) + { + pluginManager.ScanDevPlugins(); + } + + ImGui.Separator(); + + if (ImGui.MenuItem("Load all API levels", null, configuration.LoadAllApiLevels)) + { + configuration.LoadAllApiLevels = !configuration.LoadAllApiLevels; + configuration.Save(); + } + + if (ImGui.MenuItem("Load banned plugins", null, configuration.LoadBannedPlugins)) + { + configuration.LoadBannedPlugins = !configuration.LoadBannedPlugins; + configuration.Save(); + } + + ImGui.Separator(); + ImGui.MenuItem("API Level:" + PluginManager.DalamudApiLevel, false); + ImGui.MenuItem("Loaded plugins:" + pluginManager.InstalledPlugins.Count, false); + ImGui.EndMenu(); + } + + if (ImGui.BeginMenu("Localization")) + { + var localization = Service.Get(); + + if (ImGui.MenuItem("Export localizable")) + { + localization.ExportLocalizable(); + } + + if (ImGui.BeginMenu("Load language...")) + { + if (ImGui.MenuItem("From Fallbacks")) + { + localization.SetupWithFallbacks(); + } + + if (ImGui.MenuItem("From UICulture")) + { + localization.SetupWithUiCulture(); + } + + foreach (var applicableLangCode in Localization.ApplicableLangCodes) + { + if (ImGui.MenuItem($"Applicable: {applicableLangCode}")) + { + localization.SetupWithLangCode(applicableLangCode); + } + } + + ImGui.EndMenu(); + } + + ImGui.EndMenu(); + } + + if (Service.Get().GameUiHidden) + ImGui.BeginMenu("UI is hidden...", false); + + ImGui.BeginMenu(Util.GetGitHash(), false); + ImGui.BeginMenu(this.frameCount.ToString(), false); + ImGui.BeginMenu(ImGui.GetIO().Framerate.ToString("F2"), false); + + ImGui.EndMainMenuBar(); + } } } } diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 24bd612e7..33806fd73 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -39,666 +39,667 @@ using SharpDX.Direct3D11; * - Might eventually want to render to a separate target and composite, especially with reshade etc in the mix. */ -namespace Dalamud.Interface.Internal; - -/// -/// This class manages interaction with the ImGui interface. -/// -internal class InterfaceManager : IDisposable +namespace Dalamud.Interface.Internal { - private readonly string rtssPath; - - private readonly Hook presentHook; - private readonly Hook resizeBuffersHook; - private readonly Hook setCursorHook; - - private readonly ManualResetEvent fontBuildSignal; - private readonly SwapChainVtableResolver address; - private RawDX11Scene? scene; - - // can't access imgui IO before first present call - private bool lastWantCapture = false; - private bool isRebuildingFonts = false; - /// - /// Initializes a new instance of the class. + /// This class manages interaction with the ImGui interface. /// - public InterfaceManager() + internal class InterfaceManager : IDisposable { - Service.Set(); + private readonly string rtssPath; - var scanner = Service.Get(); + private readonly Hook presentHook; + private readonly Hook resizeBuffersHook; + private readonly Hook setCursorHook; - this.fontBuildSignal = new ManualResetEvent(false); + private readonly ManualResetEvent fontBuildSignal; + private readonly SwapChainVtableResolver address; + private RawDX11Scene? scene; - this.address = new SwapChainVtableResolver(); - this.address.Setup(scanner); + // can't access imgui IO before first present call + private bool lastWantCapture = false; + private bool isRebuildingFonts = false; - try + /// + /// Initializes a new instance of the class. + /// + public InterfaceManager() { - var rtss = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); + Service.Set(); - if (rtss != IntPtr.Zero) - { - var fileName = new StringBuilder(255); - _ = NativeFunctions.GetModuleFileNameW(rtss, fileName, fileName.Capacity); - this.rtssPath = fileName.ToString(); - Log.Verbose($"RTSS at {this.rtssPath}"); + var scanner = Service.Get(); - if (!NativeFunctions.FreeLibrary(rtss)) - throw new Win32Exception(); - } - } - catch (Exception e) - { - Log.Error(e, "RTSS Free failed"); - } + this.fontBuildSignal = new ManualResetEvent(false); - this.setCursorHook = Hook.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true); - this.presentHook = new Hook(this.address.Present, this.PresentDetour); - this.resizeBuffersHook = new Hook(this.address.ResizeBuffers, this.ResizeBuffersDetour); + this.address = new SwapChainVtableResolver(); + this.address.Setup(scanner); - var setCursorAddress = this.setCursorHook?.Address ?? IntPtr.Zero; - - Log.Verbose("===== S W A P C H A I N ====="); - Log.Verbose($"SetCursor address 0x{setCursorAddress.ToInt64():X}"); - Log.Verbose($"Present address 0x{this.presentHook.Address.ToInt64():X}"); - Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook.Address.ToInt64():X}"); - } - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr PresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate IntPtr SetCursorDelegate(IntPtr hCursor); - - private delegate void InstallRTSSHook(); - - /// - /// This event gets called each frame to facilitate ImGui drawing. - /// - public event RawDX11Scene.BuildUIDelegate Draw; - - /// - /// This event gets called when ResizeBuffers is called. - /// - public event Action ResizeBuffers; - - /// - /// Gets or sets an action that is executed when fonts are rebuilt. - /// - public event Action BuildFonts; - - /// - /// Gets the default ImGui font. - /// - public static ImFontPtr DefaultFont { get; private set; } - - /// - /// Gets an included FontAwesome icon font. - /// - public static ImFontPtr IconFont { get; private set; } - - /// - /// Gets an included monospaced font. - /// - public static ImFontPtr MonoFont { get; private set; } - - /// - /// Gets or sets the pointer to ImGui.IO(), when it was last used. - /// - public ImGuiIOPtr LastImGuiIoPtr { get; set; } - - /// - /// Gets the D3D11 device instance. - /// - public Device? Device => this.scene?.Device; - - /// - /// Gets the address handle to the main process window. - /// - public IntPtr WindowHandlePtr => this.scene.WindowHandlePtr; - - /// - /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. - /// - public bool OverrideGameCursor - { - get => this.scene.UpdateCursor; - set => this.scene.UpdateCursor = value; - } - - /// - /// Gets or sets a value indicating whether the fonts are built and ready to use. - /// - public bool FontsReady { get; set; } = false; - - /// - /// Gets a value indicating whether the Dalamud interface ready to use. - /// - public bool IsReady => this.scene != null; - - /// - /// Enable this module. - /// - public void Enable() - { - this.setCursorHook?.Enable(); - this.presentHook.Enable(); - this.resizeBuffersHook.Enable(); - - try - { - if (!string.IsNullOrEmpty(this.rtssPath)) - { - NativeFunctions.LoadLibraryW(this.rtssPath); - var rtssModule = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); - var installAddr = NativeFunctions.GetProcAddress(rtssModule, "InstallRTSSHook"); - - Marshal.GetDelegateForFunctionPointer(installAddr).Invoke(); - } - } - catch (Exception ex) - { - Log.Error(ex, "Could not reload RTSS"); - } - } - - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - // HACK: this is usually called on a separate thread from PresentDetour (likely on a dedicated render thread) - // and if we aren't already disabled, disposing of the scene and hook can frequently crash due to the hook - // being disposed of in this thread while it is actively in use in the render thread. - // This is a terrible way to prevent issues, but should basically always work to ensure that all outstanding - // calls to PresentDetour have finished (and Disable means no new ones will start), before we try to cleanup - // So... not great, but much better than constantly crashing on unload - this.Disable(); - Thread.Sleep(500); - - this.scene?.Dispose(); - this.setCursorHook?.Dispose(); - this.presentHook.Dispose(); - this.resizeBuffersHook.Dispose(); - } - -#nullable enable - - /// - /// Load an image from disk. - /// - /// The filepath to load. - /// A texture, ready to use in ImGui. - public TextureWrap? LoadImage(string filePath) - { - try - { - return this.scene?.LoadImage(filePath) ?? null; - } - catch (Exception ex) - { - Log.Error(ex, $"Failed to load image from {filePath}"); - } - - return null; - } - - /// - /// Load an image from an array of bytes. - /// - /// The data to load. - /// A texture, ready to use in ImGui. - public TextureWrap? LoadImage(byte[] imageData) - { - try - { - return this.scene?.LoadImage(imageData) ?? null; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to load image from memory"); - } - - return null; - } - - /// - /// Load an image from an array of bytes. - /// - /// The data to load. - /// The width in pixels. - /// The height in pixels. - /// The number of channels. - /// A texture, ready to use in ImGui. - public TextureWrap? LoadImageRaw(byte[] imageData, int width, int height, int numChannels) - { - try - { - return this.scene?.LoadImageRaw(imageData, width, height, numChannels) ?? null; - } - catch (Exception ex) - { - Log.Error(ex, "Failed to load image from raw data"); - } - - return null; - } - -#nullable restore - - /// - /// Sets up a deferred invocation of font rebuilding, before the next render frame. - /// - public void RebuildFonts() - { - Log.Verbose("[FONT] RebuildFonts() called"); - - // don't invoke this multiple times per frame, in case multiple plugins call it - if (!this.isRebuildingFonts) - { - Log.Verbose("[FONT] RebuildFonts() trigger"); - - this.isRebuildingFonts = true; - this.scene.OnNewRenderFrame += this.RebuildFontsInternal; - } - } - - /// - /// Wait for the rebuilding fonts to complete. - /// - public void WaitForFontRebuild() - { - this.fontBuildSignal.WaitOne(); - } - - private static void ShowFontError(string path) - { - Util.Fatal($"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", "Error"); - } - - /* - * NOTE(goat): When hooking ReShade DXGISwapChain::runtime_present, this is missing the syncInterval arg. - * Seems to work fine regardless, I guess, so whatever. - */ - private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) - { - if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer) - return this.presentHook.Original(swapChain, syncInterval, presentFlags); - - if (this.scene == null) - { try { - this.scene = new RawDX11Scene(swapChain); - } - catch (DllNotFoundException ex) - { - Log.Error(ex, "Could not load ImGui dependencies."); + var rtss = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); - var res = PInvoke.User32.MessageBox( - IntPtr.Zero, - "Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?", - "Dalamud Error", - User32.MessageBoxOptions.MB_YESNO | User32.MessageBoxOptions.MB_TOPMOST | User32.MessageBoxOptions.MB_ICONERROR); - - if (res == User32.MessageBoxResult.IDYES) + if (rtss != IntPtr.Zero) { - var psi = new ProcessStartInfo - { - FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe", - UseShellExecute = true, - }; - Process.Start(psi); + var fileName = new StringBuilder(255); + _ = NativeFunctions.GetModuleFileNameW(rtss, fileName, fileName.Capacity); + this.rtssPath = fileName.ToString(); + Log.Verbose($"RTSS at {this.rtssPath}"); + + if (!NativeFunctions.FreeLibrary(rtss)) + throw new Win32Exception(); } - - Environment.Exit(-1); + } + catch (Exception e) + { + Log.Error(e, "RTSS Free failed"); } - var startInfo = Service.Get(); - var configuration = Service.Get(); + this.setCursorHook = Hook.FromSymbol("user32.dll", "SetCursor", this.SetCursorDetour, true); + this.presentHook = new Hook(this.address.Present, this.PresentDetour); + this.resizeBuffersHook = new Hook(this.address.ResizeBuffers, this.ResizeBuffersDetour); - var iniFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath), "dalamudUI.ini")); + var setCursorAddress = this.setCursorHook?.Address ?? IntPtr.Zero; + + Log.Verbose("===== S W A P C H A I N ====="); + Log.Verbose($"SetCursor address 0x{setCursorAddress.ToInt64():X}"); + Log.Verbose($"Present address 0x{this.presentHook.Address.ToInt64():X}"); + Log.Verbose($"ResizeBuffers address 0x{this.resizeBuffersHook.Address.ToInt64():X}"); + } + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr PresentDelegate(IntPtr swapChain, uint syncInterval, uint presentFlags); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr ResizeBuffersDelegate(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags); + + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] + private delegate IntPtr SetCursorDelegate(IntPtr hCursor); + + private delegate void InstallRTSSHook(); + + /// + /// This event gets called each frame to facilitate ImGui drawing. + /// + public event RawDX11Scene.BuildUIDelegate Draw; + + /// + /// This event gets called when ResizeBuffers is called. + /// + public event Action ResizeBuffers; + + /// + /// Gets or sets an action that is executed when fonts are rebuilt. + /// + public event Action BuildFonts; + + /// + /// Gets the default ImGui font. + /// + public static ImFontPtr DefaultFont { get; private set; } + + /// + /// Gets an included FontAwesome icon font. + /// + public static ImFontPtr IconFont { get; private set; } + + /// + /// Gets an included monospaced font. + /// + public static ImFontPtr MonoFont { get; private set; } + + /// + /// Gets or sets the pointer to ImGui.IO(), when it was last used. + /// + public ImGuiIOPtr LastImGuiIoPtr { get; set; } + + /// + /// Gets the D3D11 device instance. + /// + public Device? Device => this.scene?.Device; + + /// + /// Gets the address handle to the main process window. + /// + public IntPtr WindowHandlePtr => this.scene.WindowHandlePtr; + + /// + /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. + /// + public bool OverrideGameCursor + { + get => this.scene.UpdateCursor; + set => this.scene.UpdateCursor = value; + } + + /// + /// Gets or sets a value indicating whether the fonts are built and ready to use. + /// + public bool FontsReady { get; set; } = false; + + /// + /// Gets a value indicating whether the Dalamud interface ready to use. + /// + public bool IsReady => this.scene != null; + + /// + /// Enable this module. + /// + public void Enable() + { + this.setCursorHook?.Enable(); + this.presentHook.Enable(); + this.resizeBuffersHook.Enable(); try { - if (iniFileInfo.Length > 1200000) + if (!string.IsNullOrEmpty(this.rtssPath)) { - Log.Warning("dalamudUI.ini was over 1mb, deleting"); - iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); - iniFileInfo.Delete(); + NativeFunctions.LoadLibraryW(this.rtssPath); + var rtssModule = NativeFunctions.GetModuleHandleW("RTSSHooks64.dll"); + var installAddr = NativeFunctions.GetProcAddress(rtssModule, "InstallRTSSHook"); + + Marshal.GetDelegateForFunctionPointer(installAddr).Invoke(); } } catch (Exception ex) { - Log.Error(ex, "Could not delete dalamudUI.ini"); + Log.Error(ex, "Could not reload RTSS"); } - - this.scene.ImGuiIniPath = iniFileInfo.FullName; - this.scene.OnBuildUI += this.Display; - this.scene.OnNewInputFrame += this.OnNewInputFrame; - - this.SetupFonts(); - - StyleModel.TransferOldModels(); - - if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name)) - { - configuration.SavedStyles = new List { StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic }; - configuration.ChosenStyle = StyleModelV1.DalamudStandard.Name; - } - else if (configuration.SavedStyles.Count == 1) - { - configuration.SavedStyles.Add(StyleModelV1.DalamudClassic); - } - else if (configuration.SavedStyles[1].Name != StyleModelV1.DalamudClassic.Name) - { - configuration.SavedStyles.Insert(1, StyleModelV1.DalamudClassic); - } - - configuration.SavedStyles[0] = StyleModelV1.DalamudStandard; - configuration.SavedStyles[1] = StyleModelV1.DalamudClassic; - - var style = configuration.SavedStyles.FirstOrDefault(x => x.Name == configuration.ChosenStyle); - if (style == null) - { - style = StyleModelV1.DalamudStandard; - configuration.ChosenStyle = style.Name; - configuration.Save(); - } - - style.Apply(); - - ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; - - if (!configuration.IsDocking) - { - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable; - } - else - { - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; - } - - // NOTE (Chiv) Toggle gamepad navigation via setting - if (!configuration.IsGamepadNavigationEnabled) - { - ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; - } - else - { - ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; - } - - // NOTE (Chiv) Explicitly deactivate on dalamud boot - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; - - ImGuiHelpers.MainViewport = ImGui.GetMainViewport(); - - Log.Information("[IM] Scene & ImGui setup OK!"); - - Service.Get().Enable(); } - if (this.address.IsReshade) + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() { - var pRes = this.presentHook.Original(swapChain, syncInterval, presentFlags); + // HACK: this is usually called on a separate thread from PresentDetour (likely on a dedicated render thread) + // and if we aren't already disabled, disposing of the scene and hook can frequently crash due to the hook + // being disposed of in this thread while it is actively in use in the render thread. + // This is a terrible way to prevent issues, but should basically always work to ensure that all outstanding + // calls to PresentDetour have finished (and Disable means no new ones will start), before we try to cleanup + // So... not great, but much better than constantly crashing on unload + this.Disable(); + Thread.Sleep(500); + + this.scene?.Dispose(); + this.setCursorHook?.Dispose(); + this.presentHook.Dispose(); + this.resizeBuffersHook.Dispose(); + } + +#nullable enable + + /// + /// Load an image from disk. + /// + /// The filepath to load. + /// A texture, ready to use in ImGui. + public TextureWrap? LoadImage(string filePath) + { + try + { + return this.scene?.LoadImage(filePath) ?? null; + } + catch (Exception ex) + { + Log.Error(ex, $"Failed to load image from {filePath}"); + } + + return null; + } + + /// + /// Load an image from an array of bytes. + /// + /// The data to load. + /// A texture, ready to use in ImGui. + public TextureWrap? LoadImage(byte[] imageData) + { + try + { + return this.scene?.LoadImage(imageData) ?? null; + } + catch (Exception ex) + { + Log.Error(ex, "Failed to load image from memory"); + } + + return null; + } + + /// + /// Load an image from an array of bytes. + /// + /// The data to load. + /// The width in pixels. + /// The height in pixels. + /// The number of channels. + /// A texture, ready to use in ImGui. + public TextureWrap? LoadImageRaw(byte[] imageData, int width, int height, int numChannels) + { + try + { + return this.scene?.LoadImageRaw(imageData, width, height, numChannels) ?? null; + } + catch (Exception ex) + { + Log.Error(ex, "Failed to load image from raw data"); + } + + return null; + } + +#nullable restore + + /// + /// Sets up a deferred invocation of font rebuilding, before the next render frame. + /// + public void RebuildFonts() + { + Log.Verbose("[FONT] RebuildFonts() called"); + + // don't invoke this multiple times per frame, in case multiple plugins call it + if (!this.isRebuildingFonts) + { + Log.Verbose("[FONT] RebuildFonts() trigger"); + + this.isRebuildingFonts = true; + this.scene.OnNewRenderFrame += this.RebuildFontsInternal; + } + } + + /// + /// Wait for the rebuilding fonts to complete. + /// + public void WaitForFontRebuild() + { + this.fontBuildSignal.WaitOne(); + } + + private static void ShowFontError(string path) + { + Util.Fatal($"One or more files required by XIVLauncher were not found.\nPlease restart and report this error if it occurs again.\n\n{path}", "Error"); + } + + /* + * NOTE(goat): When hooking ReShade DXGISwapChain::runtime_present, this is missing the syncInterval arg. + * Seems to work fine regardless, I guess, so whatever. + */ + private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags) + { + if (this.scene != null && swapChain != this.scene.SwapChain.NativePointer) + return this.presentHook.Original(swapChain, syncInterval, presentFlags); + + if (this.scene == null) + { + try + { + this.scene = new RawDX11Scene(swapChain); + } + catch (DllNotFoundException ex) + { + Log.Error(ex, "Could not load ImGui dependencies."); + + var res = PInvoke.User32.MessageBox( + IntPtr.Zero, + "Dalamud plugins require the Microsoft Visual C++ Redistributable to be installed.\nPlease install the runtime from the official Microsoft website or disable Dalamud.\n\nDo you want to download the redistributable now?", + "Dalamud Error", + User32.MessageBoxOptions.MB_YESNO | User32.MessageBoxOptions.MB_TOPMOST | User32.MessageBoxOptions.MB_ICONERROR); + + if (res == User32.MessageBoxResult.IDYES) + { + var psi = new ProcessStartInfo + { + FileName = "https://aka.ms/vs/16/release/vc_redist.x64.exe", + UseShellExecute = true, + }; + Process.Start(psi); + } + + Environment.Exit(-1); + } + + var startInfo = Service.Get(); + var configuration = Service.Get(); + + var iniFileInfo = new FileInfo(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath), "dalamudUI.ini")); + + try + { + if (iniFileInfo.Length > 1200000) + { + Log.Warning("dalamudUI.ini was over 1mb, deleting"); + iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini")); + iniFileInfo.Delete(); + } + } + catch (Exception ex) + { + Log.Error(ex, "Could not delete dalamudUI.ini"); + } + + this.scene.ImGuiIniPath = iniFileInfo.FullName; + this.scene.OnBuildUI += this.Display; + this.scene.OnNewInputFrame += this.OnNewInputFrame; + + this.SetupFonts(); + + StyleModel.TransferOldModels(); + + if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModelV1.DalamudStandard.Name)) + { + configuration.SavedStyles = new List { StyleModelV1.DalamudStandard, StyleModelV1.DalamudClassic }; + configuration.ChosenStyle = StyleModelV1.DalamudStandard.Name; + } + else if (configuration.SavedStyles.Count == 1) + { + configuration.SavedStyles.Add(StyleModelV1.DalamudClassic); + } + else if (configuration.SavedStyles[1].Name != StyleModelV1.DalamudClassic.Name) + { + configuration.SavedStyles.Insert(1, StyleModelV1.DalamudClassic); + } + + configuration.SavedStyles[0] = StyleModelV1.DalamudStandard; + configuration.SavedStyles[1] = StyleModelV1.DalamudClassic; + + var style = configuration.SavedStyles.FirstOrDefault(x => x.Name == configuration.ChosenStyle); + if (style == null) + { + style = StyleModelV1.DalamudStandard; + configuration.ChosenStyle = style.Name; + configuration.Save(); + } + + style.Apply(); + + ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; + + if (!configuration.IsDocking) + { + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable; + } + else + { + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; + } + + // NOTE (Chiv) Toggle gamepad navigation via setting + if (!configuration.IsGamepadNavigationEnabled) + { + ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; + } + else + { + ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; + } + + // NOTE (Chiv) Explicitly deactivate on dalamud boot + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableGamepad; + + ImGuiHelpers.MainViewport = ImGui.GetMainViewport(); + + Log.Information("[IM] Scene & ImGui setup OK!"); + + Service.Get().Enable(); + } + + if (this.address.IsReshade) + { + var pRes = this.presentHook.Original(swapChain, syncInterval, presentFlags); + + this.RenderImGui(); + + return pRes; + } this.RenderImGui(); - return pRes; + return this.presentHook.Original(swapChain, syncInterval, presentFlags); } - this.RenderImGui(); - - return this.presentHook.Original(swapChain, syncInterval, presentFlags); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void RenderImGui() - { - // Process information needed by ImGuiHelpers each frame. - ImGuiHelpers.NewFrame(); - - // Check if we can still enable viewports without any issues. - this.CheckViewportState(); - - this.scene.Render(); - } - - private void CheckViewportState() - { - var configuration = Service.Get(); - - if (configuration.IsDisableViewport || this.scene.SwapChain.IsFullScreen || ImGui.GetPlatformIO().Monitors.Size == 1) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RenderImGui() { - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable; - return; + // Process information needed by ImGuiHelpers each frame. + ImGuiHelpers.NewFrame(); + + // Check if we can still enable viewports without any issues. + this.CheckViewportState(); + + this.scene.Render(); } - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; - } + private void CheckViewportState() + { + var configuration = Service.Get(); - private unsafe void SetupFonts() - { - var dalamud = Service.Get(); - - this.fontBuildSignal.Reset(); - - ImGui.GetIO().Fonts.Clear(); - - ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); - fontConfig.MergeMode = true; - fontConfig.PixelSnapH = true; - - var fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf"); - - if (!File.Exists(fontPathJp)) - ShowFontError(fontPathJp); - - var japaneseRangeHandle = GCHandle.Alloc(GlyphRangesJapanese.GlyphRanges, GCHandleType.Pinned); - - DefaultFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathJp, 17.0f, null, japaneseRangeHandle.AddrOfPinnedObject()); - - var fontPathGame = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "gamesym.ttf"); - - if (!File.Exists(fontPathGame)) - ShowFontError(fontPathGame); - - var gameRangeHandle = GCHandle.Alloc( - new ushort[] + if (configuration.IsDisableViewport || this.scene.SwapChain.IsFullScreen || ImGui.GetPlatformIO().Monitors.Size == 1) { + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.ViewportsEnable; + return; + } + + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; + } + + private unsafe void SetupFonts() + { + var dalamud = Service.Get(); + + this.fontBuildSignal.Reset(); + + ImGui.GetIO().Fonts.Clear(); + + ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); + fontConfig.MergeMode = true; + fontConfig.PixelSnapH = true; + + var fontPathJp = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf"); + + if (!File.Exists(fontPathJp)) + ShowFontError(fontPathJp); + + var japaneseRangeHandle = GCHandle.Alloc(GlyphRangesJapanese.GlyphRanges, GCHandleType.Pinned); + + DefaultFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathJp, 17.0f, null, japaneseRangeHandle.AddrOfPinnedObject()); + + var fontPathGame = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "gamesym.ttf"); + + if (!File.Exists(fontPathGame)) + ShowFontError(fontPathGame); + + var gameRangeHandle = GCHandle.Alloc( + new ushort[] + { 0xE020, 0xE0DB, 0, - }, - GCHandleType.Pinned); + }, + GCHandleType.Pinned); - ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathGame, 17.0f, fontConfig, gameRangeHandle.AddrOfPinnedObject()); + ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathGame, 17.0f, fontConfig, gameRangeHandle.AddrOfPinnedObject()); - var fontPathIcon = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "FontAwesome5FreeSolid.otf"); + var fontPathIcon = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "FontAwesome5FreeSolid.otf"); - if (!File.Exists(fontPathIcon)) - ShowFontError(fontPathIcon); + if (!File.Exists(fontPathIcon)) + ShowFontError(fontPathIcon); - var iconRangeHandle = GCHandle.Alloc( - new ushort[] - { + var iconRangeHandle = GCHandle.Alloc( + new ushort[] + { 0xE000, 0xF8FF, 0, - }, - GCHandleType.Pinned); - IconFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathIcon, 17.0f, null, iconRangeHandle.AddrOfPinnedObject()); + }, + GCHandleType.Pinned); + IconFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathIcon, 17.0f, null, iconRangeHandle.AddrOfPinnedObject()); - var fontPathMono = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "Inconsolata-Regular.ttf"); + var fontPathMono = Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "Inconsolata-Regular.ttf"); - if (!File.Exists(fontPathMono)) - ShowFontError(fontPathMono); + if (!File.Exists(fontPathMono)) + ShowFontError(fontPathMono); - MonoFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathMono, 16.0f); + MonoFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathMono, 16.0f); - Log.Verbose("[FONT] Invoke OnBuildFonts"); - this.BuildFonts?.Invoke(); - Log.Verbose("[FONT] OnBuildFonts OK!"); + Log.Verbose("[FONT] Invoke OnBuildFonts"); + this.BuildFonts?.Invoke(); + Log.Verbose("[FONT] OnBuildFonts OK!"); - for (var i = 0; i < ImGui.GetIO().Fonts.Fonts.Size; i++) - { - Log.Verbose("{0} - {1}", i, ImGui.GetIO().Fonts.Fonts[i].GetDebugName()); + for (var i = 0; i < ImGui.GetIO().Fonts.Fonts.Size; i++) + { + Log.Verbose("{0} - {1}", i, ImGui.GetIO().Fonts.Fonts[i].GetDebugName()); + } + + ImGui.GetIO().Fonts.Build(); + + Log.Verbose("[FONT] Fonts built!"); + + this.fontBuildSignal.Set(); + + fontConfig.Destroy(); + japaneseRangeHandle.Free(); + gameRangeHandle.Free(); + iconRangeHandle.Free(); + + this.FontsReady = true; } - ImGui.GetIO().Fonts.Build(); + private void Disable() + { + this.setCursorHook?.Disable(); + this.presentHook.Disable(); + this.resizeBuffersHook.Disable(); + } - Log.Verbose("[FONT] Fonts built!"); + // This is intended to only be called as a handler attached to scene.OnNewRenderFrame + private void RebuildFontsInternal() + { + Log.Verbose("[FONT] RebuildFontsInternal() called"); + this.SetupFonts(); - this.fontBuildSignal.Set(); + Log.Verbose("[FONT] RebuildFontsInternal() detaching"); + this.scene.OnNewRenderFrame -= this.RebuildFontsInternal; + this.scene.InvalidateFonts(); - fontConfig.Destroy(); - japaneseRangeHandle.Free(); - gameRangeHandle.Free(); - iconRangeHandle.Free(); + Log.Verbose("[FONT] Font Rebuild OK!"); - this.FontsReady = true; - } + this.isRebuildingFonts = false; + } - private void Disable() - { - this.setCursorHook?.Disable(); - this.presentHook.Disable(); - this.resizeBuffersHook.Disable(); - } - - // This is intended to only be called as a handler attached to scene.OnNewRenderFrame - private void RebuildFontsInternal() - { - Log.Verbose("[FONT] RebuildFontsInternal() called"); - this.SetupFonts(); - - Log.Verbose("[FONT] RebuildFontsInternal() detaching"); - this.scene.OnNewRenderFrame -= this.RebuildFontsInternal; - this.scene.InvalidateFonts(); - - Log.Verbose("[FONT] Font Rebuild OK!"); - - this.isRebuildingFonts = false; - } - - private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags) - { + private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags) + { #if DEBUG - Log.Verbose($"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}"); + Log.Verbose($"Calling resizebuffers swap@{swapChain.ToInt64():X}{bufferCount} {width} {height} {newFormat} {swapChainFlags}"); #endif - this.ResizeBuffers?.Invoke(); + this.ResizeBuffers?.Invoke(); - // We have to ensure we're working with the main swapchain, - // as viewports might be resizing as well - if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer) - return this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); + // We have to ensure we're working with the main swapchain, + // as viewports might be resizing as well + if (this.scene == null || swapChain != this.scene.SwapChain.NativePointer) + return this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); - this.scene?.OnPreResize(); + this.scene?.OnPreResize(); - var ret = this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); - if (ret.ToInt64() == 0x887A0001) - { - Log.Error("invalid call to resizeBuffers"); - } - - this.scene?.OnPostResize((int)width, (int)height); - - return ret; - } - - private IntPtr SetCursorDetour(IntPtr hCursor) - { - if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) - return IntPtr.Zero; - - return this.setCursorHook.Original(hCursor); - } - - private void OnNewInputFrame() - { - var dalamudInterface = Service.GetNullable(); - var gamepadState = Service.GetNullable(); - var keyState = Service.GetNullable(); - - // fix for keys in game getting stuck, if you were holding a game key (like run) - // and then clicked on an imgui textbox - imgui would swallow the keyup event, - // so the game would think the key remained pressed continuously until you left - // imgui and pressed and released the key again - if (ImGui.GetIO().WantTextInput) - { - keyState.ClearAll(); - } - - // TODO: mouse state? - - var gamepadEnabled = (ImGui.GetIO().BackendFlags & ImGuiBackendFlags.HasGamepad) > 0; - - // NOTE (Chiv) Activate ImGui navigation via L1+L3 press - // (mimicking how mouse navigation is activated via L1+R3 press in game). - if (gamepadEnabled - && gamepadState.Raw(GamepadButtons.L1) > 0 - && gamepadState.Pressed(GamepadButtons.L3) > 0) - { - ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad; - gamepadState.NavEnableGamepad ^= true; - dalamudInterface.ToggleGamepadModeNotifierWindow(); - } - - if (gamepadEnabled && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0) - { - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = gamepadState.Raw(GamepadButtons.South); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = gamepadState.Raw(GamepadButtons.East); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = gamepadState.Raw(GamepadButtons.North); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = gamepadState.Raw(GamepadButtons.West); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = gamepadState.Raw(GamepadButtons.DpadLeft); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = gamepadState.Raw(GamepadButtons.DpadRight); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = gamepadState.Raw(GamepadButtons.DpadUp); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = gamepadState.Raw(GamepadButtons.DpadDown); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = gamepadState.LeftStickLeft; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = gamepadState.LeftStickRight; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = gamepadState.LeftStickUp; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = gamepadState.LeftStickDown; - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = gamepadState.Raw(GamepadButtons.L1); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = gamepadState.Raw(GamepadButtons.R1); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = gamepadState.Raw(GamepadButtons.L2); - ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = gamepadState.Raw(GamepadButtons.R2); - - if (gamepadState.Pressed(GamepadButtons.R3) > 0) + var ret = this.resizeBuffersHook.Original(swapChain, bufferCount, width, height, newFormat, swapChainFlags); + if (ret.ToInt64() == 0x887A0001) { - dalamudInterface.TogglePluginInstallerWindow(); + Log.Error("invalid call to resizeBuffers"); + } + + this.scene?.OnPostResize((int)width, (int)height); + + return ret; + } + + private IntPtr SetCursorDetour(IntPtr hCursor) + { + if (this.lastWantCapture == true && (!this.scene?.IsImGuiCursor(hCursor) ?? false) && this.OverrideGameCursor) + return IntPtr.Zero; + + return this.setCursorHook.Original(hCursor); + } + + private void OnNewInputFrame() + { + var dalamudInterface = Service.GetNullable(); + var gamepadState = Service.GetNullable(); + var keyState = Service.GetNullable(); + + // fix for keys in game getting stuck, if you were holding a game key (like run) + // and then clicked on an imgui textbox - imgui would swallow the keyup event, + // so the game would think the key remained pressed continuously until you left + // imgui and pressed and released the key again + if (ImGui.GetIO().WantTextInput) + { + keyState.ClearAll(); + } + + // TODO: mouse state? + + var gamepadEnabled = (ImGui.GetIO().BackendFlags & ImGuiBackendFlags.HasGamepad) > 0; + + // NOTE (Chiv) Activate ImGui navigation via L1+L3 press + // (mimicking how mouse navigation is activated via L1+R3 press in game). + if (gamepadEnabled + && gamepadState.Raw(GamepadButtons.L1) > 0 + && gamepadState.Pressed(GamepadButtons.L3) > 0) + { + ImGui.GetIO().ConfigFlags ^= ImGuiConfigFlags.NavEnableGamepad; + gamepadState.NavEnableGamepad ^= true; + dalamudInterface.ToggleGamepadModeNotifierWindow(); + } + + if (gamepadEnabled && (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0) + { + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Activate] = gamepadState.Raw(GamepadButtons.South); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Cancel] = gamepadState.Raw(GamepadButtons.East); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Input] = gamepadState.Raw(GamepadButtons.North); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.Menu] = gamepadState.Raw(GamepadButtons.West); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadLeft] = gamepadState.Raw(GamepadButtons.DpadLeft); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadRight] = gamepadState.Raw(GamepadButtons.DpadRight); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadUp] = gamepadState.Raw(GamepadButtons.DpadUp); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.DpadDown] = gamepadState.Raw(GamepadButtons.DpadDown); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickLeft] = gamepadState.LeftStickLeft; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickRight] = gamepadState.LeftStickRight; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickUp] = gamepadState.LeftStickUp; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.LStickDown] = gamepadState.LeftStickDown; + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusPrev] = gamepadState.Raw(GamepadButtons.L1); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.FocusNext] = gamepadState.Raw(GamepadButtons.R1); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakSlow] = gamepadState.Raw(GamepadButtons.L2); + ImGui.GetIO().NavInputs[(int)ImGuiNavInput.TweakFast] = gamepadState.Raw(GamepadButtons.R2); + + if (gamepadState.Pressed(GamepadButtons.R3) > 0) + { + dalamudInterface.TogglePluginInstallerWindow(); + } } } - } - private void Display() - { - // this is more or less part of what reshade/etc do to avoid having to manually - // set the cursor inside the ui - // This will just tell ImGui to draw its own software cursor instead of using the hardware cursor - // The scene internally will handle hiding and showing the hardware (game) cursor - // If the player has the game software cursor enabled, we can't really do anything about that and - // they will see both cursors. - // Doing this here because it's somewhat application-specific behavior - // ImGui.GetIO().MouseDrawCursor = ImGui.GetIO().WantCaptureMouse; - this.LastImGuiIoPtr = ImGui.GetIO(); - this.lastWantCapture = this.LastImGuiIoPtr.WantCaptureMouse; + private void Display() + { + // this is more or less part of what reshade/etc do to avoid having to manually + // set the cursor inside the ui + // This will just tell ImGui to draw its own software cursor instead of using the hardware cursor + // The scene internally will handle hiding and showing the hardware (game) cursor + // If the player has the game software cursor enabled, we can't really do anything about that and + // they will see both cursors. + // Doing this here because it's somewhat application-specific behavior + // ImGui.GetIO().MouseDrawCursor = ImGui.GetIO().WantCaptureMouse; + this.LastImGuiIoPtr = ImGui.GetIO(); + this.lastWantCapture = this.LastImGuiIoPtr.WantCaptureMouse; - WindowSystem.HasAnyWindowSystemFocus = false; - WindowSystem.FocusedWindowSystemNamespace = string.Empty; + WindowSystem.HasAnyWindowSystemFocus = false; + WindowSystem.FocusedWindowSystemNamespace = string.Empty; - var snap = ImGuiManagedAsserts.GetSnapshot(); - this.Draw?.Invoke(); - ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap); + var snap = ImGuiManagedAsserts.GetSnapshot(); + this.Draw?.Invoke(); + ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap); - Service.Get().Draw(); + Service.Get().Draw(); + } } } diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs index fd203192f..5c76854d2 100644 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs +++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs @@ -1,21 +1,22 @@ -namespace Dalamud.Interface.Internal.ManagedAsserts; - -/// -/// Offsets to various data in ImGui context. -/// -/// -/// Last updated for ImGui 1.83. -/// -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")] -internal static class ImGuiContextOffsets +namespace Dalamud.Interface.Internal.ManagedAsserts { - public const int CurrentWindowStackOffset = 0x73A; + /// + /// Offsets to various data in ImGui context. + /// + /// + /// Last updated for ImGui 1.83. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")] + internal static class ImGuiContextOffsets + { + public const int CurrentWindowStackOffset = 0x73A; - public const int ColorStackOffset = 0x79C; + public const int ColorStackOffset = 0x79C; - public const int StyleVarStackOffset = 0x7A0; + public const int StyleVarStackOffset = 0x7A0; - public const int FontStackOffset = 0x7A4; + public const int FontStackOffset = 0x7A4; - public const int BeginPopupStackOffset = 0x7B8; + public const int BeginPopupStackOffset = 0x7B8; + } } diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs index 91b7deaf0..ae5a0c3ff 100644 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs +++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiManagedAsserts.cs @@ -4,132 +4,133 @@ using ImGuiNET; using static Dalamud.NativeFunctions; -namespace Dalamud.Interface.Internal.ManagedAsserts; - -/// -/// Report ImGui problems with a MessageBox dialog. -/// -internal static class ImGuiManagedAsserts +namespace Dalamud.Interface.Internal.ManagedAsserts { /// - /// Gets or sets a value indicating whether asserts are enabled for ImGui. + /// Report ImGui problems with a MessageBox dialog. /// - public static bool AssertsEnabled { get; set; } - - /// - /// Create a snapshot of the current ImGui context. - /// Should be called before rendering an ImGui frame. - /// - /// A snapshot of the current context. - public static unsafe ImGuiContextSnapshot GetSnapshot() + internal static class ImGuiManagedAsserts { - var contextPtr = ImGui.GetCurrentContext(); + /// + /// Gets or sets a value indicating whether asserts are enabled for ImGui. + /// + public static bool AssertsEnabled { get; set; } - var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size - var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size - var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size - var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size - var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size - - return new ImGuiContextSnapshot + /// + /// Create a snapshot of the current ImGui context. + /// Should be called before rendering an ImGui frame. + /// + /// A snapshot of the current context. + public static unsafe ImGuiContextSnapshot GetSnapshot() { - StyleVarStackSize = styleVarStack, - ColorStackSize = colorStack, - FontStackSize = fontStack, - BeginPopupStackSize = popupStack, - WindowStackSize = windowStack, - }; - } + var contextPtr = ImGui.GetCurrentContext(); - /// - /// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog. - /// - /// The source of any problems, something to blame. - /// ImGui context snapshot. - public static void ReportProblems(string source, ImGuiContextSnapshot before) - { - if (!AssertsEnabled) - { - return; - } + var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size + var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size + var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size + var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size + var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size - var cSnap = GetSnapshot(); - - if (before.StyleVarStackSize != cSnap.StyleVarStackSize) - { - ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}"); - return; - } - - if (before.ColorStackSize != cSnap.ColorStackSize) - { - ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}"); - return; - } - - if (before.FontStackSize != cSnap.FontStackSize) - { - ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}"); - return; - } - - if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize) - { - ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}"); - return; - } - - if (cSnap.WindowStackSize != 1) - { - if (cSnap.WindowStackSize > 1) + return new ImGuiContextSnapshot { - ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); + StyleVarStackSize = styleVarStack, + ColorStackSize = colorStack, + FontStackSize = fontStack, + BeginPopupStackSize = popupStack, + WindowStackSize = windowStack, + }; + } + + /// + /// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog. + /// + /// The source of any problems, something to blame. + /// ImGui context snapshot. + public static void ReportProblems(string source, ImGuiContextSnapshot before) + { + if (!AssertsEnabled) + { + return; } - else + + var cSnap = GetSnapshot(); + + if (before.StyleVarStackSize != cSnap.StyleVarStackSize) { - ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); + ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}"); + return; + } + + if (before.ColorStackSize != cSnap.ColorStackSize) + { + ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}"); + return; + } + + if (before.FontStackSize != cSnap.FontStackSize) + { + ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}"); + return; + } + + if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize) + { + ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}"); + return; + } + + if (cSnap.WindowStackSize != 1) + { + if (cSnap.WindowStackSize > 1) + { + ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); + } + else + { + ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}"); + } } } - } - private static void ShowAssert(string source, string message) - { - var caption = $"You fucked up"; - message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them."; - var flags = MessageBoxType.Ok | MessageBoxType.IconError; + private static void ShowAssert(string source, string message) + { + var caption = $"You fucked up"; + message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them."; + var flags = MessageBoxType.Ok | MessageBoxType.IconError; - _ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); - AssertsEnabled = false; - } - - /// - /// A snapshot of various ImGui context properties. - /// - public class ImGuiContextSnapshot - { - /// - /// Gets the ImGui style var stack size. - /// - public int StyleVarStackSize { get; init; } + _ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); + AssertsEnabled = false; + } /// - /// Gets the ImGui color stack size. + /// A snapshot of various ImGui context properties. /// - public int ColorStackSize { get; init; } + public class ImGuiContextSnapshot + { + /// + /// Gets the ImGui style var stack size. + /// + public int StyleVarStackSize { get; init; } - /// - /// Gets the ImGui font stack size. - /// - public int FontStackSize { get; init; } + /// + /// Gets the ImGui color stack size. + /// + public int ColorStackSize { get; init; } - /// - /// Gets the ImGui begin popup stack size. - /// - public int BeginPopupStackSize { get; init; } + /// + /// Gets the ImGui font stack size. + /// + public int FontStackSize { get; init; } - /// - /// Gets the ImGui window stack size. - /// - public int WindowStackSize { get; init; } + /// + /// Gets the ImGui begin popup stack size. + /// + public int BeginPopupStackSize { get; init; } + + /// + /// Gets the ImGui window stack size. + /// + public int WindowStackSize { get; init; } + } } } diff --git a/Dalamud/Interface/Internal/Notifications/NotificationManager.cs b/Dalamud/Interface/Internal/Notifications/NotificationManager.cs index f08193cd8..aeef3c934 100644 --- a/Dalamud/Interface/Internal/Notifications/NotificationManager.cs +++ b/Dalamud/Interface/Internal/Notifications/NotificationManager.cs @@ -7,305 +7,306 @@ using Dalamud.Interface.Colors; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Notifications; - -/// -/// Class handling notifications/toasts in ImGui. -/// Ported from https://github.com/patrickcjk/imgui-notify. -/// -internal class NotificationManager +namespace Dalamud.Interface.Internal.Notifications { /// - /// Value indicating the bottom-left X padding. + /// Class handling notifications/toasts in ImGui. + /// Ported from https://github.com/patrickcjk/imgui-notify. /// - internal const float NotifyPaddingX = 20.0f; - - /// - /// Value indicating the bottom-left Y padding. - /// - internal const float NotifyPaddingY = 20.0f; - - /// - /// Value indicating the Y padding between each message. - /// - internal const float NotifyPaddingMessageY = 10.0f; - - /// - /// Value indicating the fade-in and out duration. - /// - internal const int NotifyFadeInOutTime = 500; - - /// - /// Value indicating the default time until the notification is dismissed. - /// - internal const int NotifyDefaultDismiss = 3000; - - /// - /// Value indicating the maximum opacity. - /// - internal const float NotifyOpacity = 0.82f; - - /// - /// Value indicating default window flags for the notifications. - /// - internal const ImGuiWindowFlags NotifyToastFlags = - ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoInputs | - ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoFocusOnAppearing; - - private readonly List notifications = new(); - - /// - /// Add a notification to the notification queue. - /// - /// The content of the notification. - /// The title of the notification. - /// The type of the notification. - /// The time the notification should be displayed for. - public void AddNotification(string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = NotifyDefaultDismiss) + internal class NotificationManager { - this.notifications.Add(new Notification + /// + /// Value indicating the bottom-left X padding. + /// + internal const float NotifyPaddingX = 20.0f; + + /// + /// Value indicating the bottom-left Y padding. + /// + internal const float NotifyPaddingY = 20.0f; + + /// + /// Value indicating the Y padding between each message. + /// + internal const float NotifyPaddingMessageY = 10.0f; + + /// + /// Value indicating the fade-in and out duration. + /// + internal const int NotifyFadeInOutTime = 500; + + /// + /// Value indicating the default time until the notification is dismissed. + /// + internal const int NotifyDefaultDismiss = 3000; + + /// + /// Value indicating the maximum opacity. + /// + internal const float NotifyOpacity = 0.82f; + + /// + /// Value indicating default window flags for the notifications. + /// + internal const ImGuiWindowFlags NotifyToastFlags = + ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoInputs | + ImGuiWindowFlags.NoNav | ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoFocusOnAppearing; + + private readonly List notifications = new(); + + /// + /// Add a notification to the notification queue. + /// + /// The content of the notification. + /// The title of the notification. + /// The type of the notification. + /// The time the notification should be displayed for. + public void AddNotification(string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = NotifyDefaultDismiss) { - Content = content, - Title = title, - NotificationType = type, - DurationMs = msDelay, - }); - } + this.notifications.Add(new Notification + { + Content = content, + Title = title, + NotificationType = type, + DurationMs = msDelay, + }); + } - /// - /// Draw all currently queued notifications. - /// - public void Draw() - { - var viewportSize = ImGuiHelpers.MainViewport.Size; - var height = 0f; - - for (var i = 0; i < this.notifications.Count; i++) + /// + /// Draw all currently queued notifications. + /// + public void Draw() { - var tn = this.notifications.ElementAt(i); + var viewportSize = ImGuiHelpers.MainViewport.Size; + var height = 0f; - if (tn.GetPhase() == Notification.Phase.Expired) + for (var i = 0; i < this.notifications.Count; i++) { - this.notifications.RemoveAt(i); - continue; - } + var tn = this.notifications.ElementAt(i); - var opacity = tn.GetFadePercent(); + if (tn.GetPhase() == Notification.Phase.Expired) + { + this.notifications.RemoveAt(i); + continue; + } - var iconColor = tn.Color; - iconColor.W = opacity; + var opacity = tn.GetFadePercent(); - var windowName = $"##NOTIFY{i}"; + var iconColor = tn.Color; + iconColor.W = opacity; - ImGuiHelpers.ForceNextWindowMainViewport(); - ImGui.SetNextWindowBgAlpha(opacity); - ImGui.SetNextWindowPos(new Vector2(viewportSize.X - NotifyPaddingX, viewportSize.Y - NotifyPaddingY - height), ImGuiCond.Always, Vector2.One); - ImGui.Begin(windowName, NotifyToastFlags); + var windowName = $"##NOTIFY{i}"; - ImGui.PushTextWrapPos(viewportSize.X / 3.0f); + ImGuiHelpers.ForceNextWindowMainViewport(); + ImGui.SetNextWindowBgAlpha(opacity); + ImGui.SetNextWindowPos(new Vector2(viewportSize.X - NotifyPaddingX, viewportSize.Y - NotifyPaddingY - height), ImGuiCond.Always, Vector2.One); + ImGui.Begin(windowName, NotifyToastFlags); - var wasTitleRendered = false; + ImGui.PushTextWrapPos(viewportSize.X / 3.0f); - if (!tn.Icon.IsNullOrEmpty()) - { - wasTitleRendered = true; - ImGui.PushFont(InterfaceManager.IconFont); - ImGui.TextColored(iconColor, tn.Icon); - ImGui.PopFont(); - } + var wasTitleRendered = false; - var textColor = ImGuiColors.DalamudWhite; - textColor.W = opacity; - - ImGui.PushStyleColor(ImGuiCol.Text, textColor); - - if (!tn.Title.IsNullOrEmpty()) - { if (!tn.Icon.IsNullOrEmpty()) { - ImGui.SameLine(); + wasTitleRendered = true; + ImGui.PushFont(InterfaceManager.IconFont); + ImGui.TextColored(iconColor, tn.Icon); + ImGui.PopFont(); } - ImGui.TextUnformatted(tn.Title); - wasTitleRendered = true; - } - else if (!tn.DefaultTitle.IsNullOrEmpty()) - { - if (!tn.Icon.IsNullOrEmpty()) + var textColor = ImGuiColors.DalamudWhite; + textColor.W = opacity; + + ImGui.PushStyleColor(ImGuiCol.Text, textColor); + + if (!tn.Title.IsNullOrEmpty()) { - ImGui.SameLine(); + if (!tn.Icon.IsNullOrEmpty()) + { + ImGui.SameLine(); + } + + ImGui.TextUnformatted(tn.Title); + wasTitleRendered = true; } - - ImGui.TextUnformatted(tn.DefaultTitle); - wasTitleRendered = true; - } - - if (wasTitleRendered && !tn.Content.IsNullOrEmpty()) - { - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5.0f); - } - - if (!tn.Content.IsNullOrEmpty()) - { - if (wasTitleRendered) + else if (!tn.DefaultTitle.IsNullOrEmpty()) { - ImGui.Separator(); + if (!tn.Icon.IsNullOrEmpty()) + { + ImGui.SameLine(); + } + + ImGui.TextUnformatted(tn.DefaultTitle); + wasTitleRendered = true; } - ImGui.TextUnformatted(tn.Content); + if (wasTitleRendered && !tn.Content.IsNullOrEmpty()) + { + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 5.0f); + } + + if (!tn.Content.IsNullOrEmpty()) + { + if (wasTitleRendered) + { + ImGui.Separator(); + } + + ImGui.TextUnformatted(tn.Content); + } + + ImGui.PopStyleColor(); + + ImGui.PopTextWrapPos(); + + height += ImGui.GetWindowHeight() + NotifyPaddingMessageY; + + ImGui.End(); } - - ImGui.PopStyleColor(); - - ImGui.PopTextWrapPos(); - - height += ImGui.GetWindowHeight() + NotifyPaddingMessageY; - - ImGui.End(); - } - } - - /// - /// Container class for notifications. - /// - internal class Notification - { - /// - /// Possible notification phases. - /// - internal enum Phase - { - /// - /// Phase indicating fade-in. - /// - FadeIn, - - /// - /// Phase indicating waiting until fade-out. - /// - Wait, - - /// - /// Phase indicating fade-out. - /// - FadeOut, - - /// - /// Phase indicating that the notification has expired. - /// - Expired, } /// - /// Gets the type of the notification. + /// Container class for notifications. /// - internal NotificationType NotificationType { get; init; } - - /// - /// Gets the title of the notification. - /// - internal string? Title { get; init; } - - /// - /// Gets the content of the notification. - /// - internal string Content { get; init; } - - /// - /// Gets the duration of the notification in milliseconds. - /// - internal uint DurationMs { get; init; } - - /// - /// Gets the creation time of the notification. - /// - internal DateTime CreationTime { get; init; } = DateTime.Now; - - /// - /// Gets the default color of the notification. - /// - /// Thrown when is set to an out-of-range value. - internal Vector4 Color => this.NotificationType switch + internal class Notification { - NotificationType.None => ImGuiColors.DalamudWhite, - NotificationType.Success => ImGuiColors.HealerGreen, - NotificationType.Warning => ImGuiColors.DalamudOrange, - NotificationType.Error => ImGuiColors.DalamudRed, - NotificationType.Info => ImGuiColors.TankBlue, - _ => throw new ArgumentOutOfRangeException(), - }; - - /// - /// Gets the icon of the notification. - /// - /// Thrown when is set to an out-of-range value. - internal string? Icon => this.NotificationType switch - { - NotificationType.None => null, - NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconString(), - NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconString(), - NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconString(), - NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconString(), - _ => throw new ArgumentOutOfRangeException(), - }; - - /// - /// Gets the default title of the notification. - /// - /// Thrown when is set to an out-of-range value. - internal string? DefaultTitle => this.NotificationType switch - { - NotificationType.None => null, - NotificationType.Success => NotificationType.Success.ToString(), - NotificationType.Warning => NotificationType.Warning.ToString(), - NotificationType.Error => NotificationType.Error.ToString(), - NotificationType.Info => NotificationType.Info.ToString(), - _ => throw new ArgumentOutOfRangeException(), - }; - - /// - /// Gets the elapsed time since creating the notification. - /// - internal TimeSpan ElapsedTime => DateTime.Now - this.CreationTime; - - /// - /// Gets the phase of the notification. - /// - /// The phase of the notification. - internal Phase GetPhase() - { - var elapsed = (int)this.ElapsedTime.TotalMilliseconds; - - if (elapsed > NotifyFadeInOutTime + this.DurationMs + NotifyFadeInOutTime) - return Phase.Expired; - else if (elapsed > NotifyFadeInOutTime + this.DurationMs) - return Phase.FadeOut; - else if (elapsed > NotifyFadeInOutTime) - return Phase.Wait; - else - return Phase.FadeIn; - } - - /// - /// Gets the opacity of the notification. - /// - /// The opacity, in a range from 0 to 1. - internal float GetFadePercent() - { - var phase = this.GetPhase(); - var elapsed = this.ElapsedTime.TotalMilliseconds; - - if (phase == Phase.FadeIn) + /// + /// Possible notification phases. + /// + internal enum Phase { - return (float)elapsed / NotifyFadeInOutTime * NotifyOpacity; - } - else if (phase == Phase.FadeOut) - { - return (1.0f - (((float)elapsed - NotifyFadeInOutTime - this.DurationMs) / - NotifyFadeInOutTime)) * NotifyOpacity; + /// + /// Phase indicating fade-in. + /// + FadeIn, + + /// + /// Phase indicating waiting until fade-out. + /// + Wait, + + /// + /// Phase indicating fade-out. + /// + FadeOut, + + /// + /// Phase indicating that the notification has expired. + /// + Expired, } - return 1.0f * NotifyOpacity; + /// + /// Gets the type of the notification. + /// + internal NotificationType NotificationType { get; init; } + + /// + /// Gets the title of the notification. + /// + internal string? Title { get; init; } + + /// + /// Gets the content of the notification. + /// + internal string Content { get; init; } + + /// + /// Gets the duration of the notification in milliseconds. + /// + internal uint DurationMs { get; init; } + + /// + /// Gets the creation time of the notification. + /// + internal DateTime CreationTime { get; init; } = DateTime.Now; + + /// + /// Gets the default color of the notification. + /// + /// Thrown when is set to an out-of-range value. + internal Vector4 Color => this.NotificationType switch + { + NotificationType.None => ImGuiColors.DalamudWhite, + NotificationType.Success => ImGuiColors.HealerGreen, + NotificationType.Warning => ImGuiColors.DalamudOrange, + NotificationType.Error => ImGuiColors.DalamudRed, + NotificationType.Info => ImGuiColors.TankBlue, + _ => throw new ArgumentOutOfRangeException(), + }; + + /// + /// Gets the icon of the notification. + /// + /// Thrown when is set to an out-of-range value. + internal string? Icon => this.NotificationType switch + { + NotificationType.None => null, + NotificationType.Success => FontAwesomeIcon.CheckCircle.ToIconString(), + NotificationType.Warning => FontAwesomeIcon.ExclamationCircle.ToIconString(), + NotificationType.Error => FontAwesomeIcon.TimesCircle.ToIconString(), + NotificationType.Info => FontAwesomeIcon.InfoCircle.ToIconString(), + _ => throw new ArgumentOutOfRangeException(), + }; + + /// + /// Gets the default title of the notification. + /// + /// Thrown when is set to an out-of-range value. + internal string? DefaultTitle => this.NotificationType switch + { + NotificationType.None => null, + NotificationType.Success => NotificationType.Success.ToString(), + NotificationType.Warning => NotificationType.Warning.ToString(), + NotificationType.Error => NotificationType.Error.ToString(), + NotificationType.Info => NotificationType.Info.ToString(), + _ => throw new ArgumentOutOfRangeException(), + }; + + /// + /// Gets the elapsed time since creating the notification. + /// + internal TimeSpan ElapsedTime => DateTime.Now - this.CreationTime; + + /// + /// Gets the phase of the notification. + /// + /// The phase of the notification. + internal Phase GetPhase() + { + var elapsed = (int)this.ElapsedTime.TotalMilliseconds; + + if (elapsed > NotifyFadeInOutTime + this.DurationMs + NotifyFadeInOutTime) + return Phase.Expired; + else if (elapsed > NotifyFadeInOutTime + this.DurationMs) + return Phase.FadeOut; + else if (elapsed > NotifyFadeInOutTime) + return Phase.Wait; + else + return Phase.FadeIn; + } + + /// + /// Gets the opacity of the notification. + /// + /// The opacity, in a range from 0 to 1. + internal float GetFadePercent() + { + var phase = this.GetPhase(); + var elapsed = this.ElapsedTime.TotalMilliseconds; + + if (phase == Phase.FadeIn) + { + return (float)elapsed / NotifyFadeInOutTime * NotifyOpacity; + } + else if (phase == Phase.FadeOut) + { + return (1.0f - (((float)elapsed - NotifyFadeInOutTime - this.DurationMs) / + NotifyFadeInOutTime)) * NotifyOpacity; + } + + return 1.0f * NotifyOpacity; + } } } } diff --git a/Dalamud/Interface/Internal/Notifications/NotificationType.cs b/Dalamud/Interface/Internal/Notifications/NotificationType.cs index 331621df4..d2cb56686 100644 --- a/Dalamud/Interface/Internal/Notifications/NotificationType.cs +++ b/Dalamud/Interface/Internal/Notifications/NotificationType.cs @@ -1,32 +1,33 @@ -namespace Dalamud.Interface.Internal.Notifications; - -/// -/// Possible notification types. -/// -public enum NotificationType +namespace Dalamud.Interface.Internal.Notifications { /// - /// No special type. + /// Possible notification types. /// - None, + public enum NotificationType + { + /// + /// No special type. + /// + None, - /// - /// Type indicating success. - /// - Success, + /// + /// Type indicating success. + /// + Success, - /// - /// Type indicating a warning. - /// - Warning, + /// + /// Type indicating a warning. + /// + Warning, - /// - /// Type indicating an error. - /// - Error, + /// + /// Type indicating an error. + /// + Error, - /// - /// Type indicating generic information. - /// - Info, + /// + /// Type indicating generic information. + /// + Info, + } } diff --git a/Dalamud/Interface/Internal/PluginCategoryManager.cs b/Dalamud/Interface/Internal/PluginCategoryManager.cs index f2c6a2769..d1c7fdc50 100644 --- a/Dalamud/Interface/Internal/PluginCategoryManager.cs +++ b/Dalamud/Interface/Internal/PluginCategoryManager.cs @@ -5,401 +5,402 @@ using System.Linq; using CheapLoc; using Dalamud.Plugin.Internal.Types; -namespace Dalamud.Interface.Internal; - -/// -/// Manage category filters for PluginInstallerWindow. -/// -internal class PluginCategoryManager +namespace Dalamud.Interface.Internal { /// - /// First categoryId for tag based categories. + /// Manage category filters for PluginInstallerWindow. /// - public const int FirstTagBasedCategoryId = 100; - - private readonly CategoryInfo[] categoryList = + internal class PluginCategoryManager { - new(0, "special.all", () => Locs.Category_All), - new(10, "special.devInstalled", () => Locs.Category_DevInstalled), - new(11, "special.devIconTester", () => Locs.Category_IconTester), - new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other), - new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs), - new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI), - new(FirstTagBasedCategoryId + 3, "minigames", () => Locs.Category_MiniGames), - new(FirstTagBasedCategoryId + 4, "inventory", () => Locs.Category_Inventory), - new(FirstTagBasedCategoryId + 5, "sound", () => Locs.Category_Sound), - new(FirstTagBasedCategoryId + 6, "social", () => Locs.Category_Social), - new(FirstTagBasedCategoryId + 7, "utility", () => Locs.Category_Utility), + /// + /// First categoryId for tag based categories. + /// + public const int FirstTagBasedCategoryId = 100; + + private readonly CategoryInfo[] categoryList = + { + new(0, "special.all", () => Locs.Category_All), + new(10, "special.devInstalled", () => Locs.Category_DevInstalled), + new(11, "special.devIconTester", () => Locs.Category_IconTester), + new(FirstTagBasedCategoryId + 0, "other", () => Locs.Category_Other), + new(FirstTagBasedCategoryId + 1, "jobs", () => Locs.Category_Jobs), + new(FirstTagBasedCategoryId + 2, "ui", () => Locs.Category_UI), + new(FirstTagBasedCategoryId + 3, "minigames", () => Locs.Category_MiniGames), + new(FirstTagBasedCategoryId + 4, "inventory", () => Locs.Category_Inventory), + new(FirstTagBasedCategoryId + 5, "sound", () => Locs.Category_Sound), + new(FirstTagBasedCategoryId + 6, "social", () => Locs.Category_Social), + new(FirstTagBasedCategoryId + 7, "utility", () => Locs.Category_Utility), // order doesn't matter, all tag driven categories should have Id >= FirstTagBasedCategoryId - }; + }; - private GroupInfo[] groupList = - { - new(GroupKind.DevTools, () => Locs.Group_DevTools, 10, 11), - new(GroupKind.Installed, () => Locs.Group_Installed, 0), - new(GroupKind.Available, () => Locs.Group_Available, 0), + private GroupInfo[] groupList = + { + new(GroupKind.DevTools, () => Locs.Group_DevTools, 10, 11), + new(GroupKind.Installed, () => Locs.Group_Installed, 0), + new(GroupKind.Available, () => Locs.Group_Available, 0), // order important, used for drawing, keep in sync with defaults for currentGroupIdx - }; + }; - private int currentGroupIdx = 2; - private int currentCategoryIdx = 0; - private bool isContentDirty; + private int currentGroupIdx = 2; + private int currentCategoryIdx = 0; + private bool isContentDirty; - private Dictionary mapPluginCategories = new(); - private List highlightedCategoryIds = new(); - - /// - /// Type of category group. - /// - public enum GroupKind - { - /// - /// UI group: dev mode only. - /// - DevTools, + private Dictionary mapPluginCategories = new(); + private List highlightedCategoryIds = new(); /// - /// UI group: installed plugins. + /// Type of category group. /// - Installed, - - /// - /// UI group: plugins that can be installed. - /// - Available, - } - - /// - /// Gets the list of all known categories. - /// - public CategoryInfo[] CategoryList => this.categoryList; - - /// - /// Gets the list of all known UI groups. - /// - public GroupInfo[] GroupList => this.groupList; - - /// - /// Gets or sets current group. - /// - public int CurrentGroupIdx - { - get => this.currentGroupIdx; - set + public enum GroupKind { - if (this.currentGroupIdx != value) - { - this.currentGroupIdx = value; - this.currentCategoryIdx = 0; - this.isContentDirty = true; - } + /// + /// UI group: dev mode only. + /// + DevTools, + + /// + /// UI group: installed plugins. + /// + Installed, + + /// + /// UI group: plugins that can be installed. + /// + Available, } - } - /// - /// Gets or sets current category, index in current Group.Categories array. - /// - public int CurrentCategoryIdx - { - get => this.currentCategoryIdx; - set + /// + /// Gets the list of all known categories. + /// + public CategoryInfo[] CategoryList => this.categoryList; + + /// + /// Gets the list of all known UI groups. + /// + public GroupInfo[] GroupList => this.groupList; + + /// + /// Gets or sets current group. + /// + public int CurrentGroupIdx { - if (this.currentCategoryIdx != value) + get => this.currentGroupIdx; + set { - this.currentCategoryIdx = value; - this.isContentDirty = true; - } - } - } - - /// - /// Gets a value indicating whether current group + category selection changed recently. - /// Changes in Available group should be followed with , everythine else can use . - /// - public bool IsContentDirty => this.isContentDirty; - - /// - /// Gets a value indicating whether and are valid. - /// - public bool IsSelectionValid => - (this.currentGroupIdx >= 0) && - (this.currentGroupIdx < this.groupList.Length) && - (this.currentCategoryIdx >= 0) && - (this.currentCategoryIdx < this.groupList[this.currentGroupIdx].Categories.Count); - - /// - /// Rebuild available categories based on currently available plugins. - /// - /// list of all available plugin manifests to install. - public void BuildCategories(IEnumerable availablePlugins) - { - // rebuild map plugin name -> categoryIds - this.mapPluginCategories.Clear(); - - var groupAvail = Array.Find(this.groupList, x => x.GroupKind == GroupKind.Available); - var prevCategoryIds = new List(); - prevCategoryIds.AddRange(groupAvail.Categories); - - var categoryList = new List(); - var allCategoryIndices = new List(); - - foreach (var plugin in availablePlugins) - { - categoryList.Clear(); - - var pluginCategoryTags = this.GetCategoryTagsForManifest(plugin); - if (pluginCategoryTags != null) - { - foreach (var tag in pluginCategoryTags) + if (this.currentGroupIdx != value) { - // only tags from whitelist can be accepted - var matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase)); - if (matchIdx >= 0) - { - var categoryId = this.CategoryList[matchIdx].CategoryId; - if (categoryId >= FirstTagBasedCategoryId) - { - categoryList.Add(categoryId); + this.currentGroupIdx = value; + this.currentCategoryIdx = 0; + this.isContentDirty = true; + } + } + } - if (!allCategoryIndices.Contains(matchIdx)) + /// + /// Gets or sets current category, index in current Group.Categories array. + /// + public int CurrentCategoryIdx + { + get => this.currentCategoryIdx; + set + { + if (this.currentCategoryIdx != value) + { + this.currentCategoryIdx = value; + this.isContentDirty = true; + } + } + } + + /// + /// Gets a value indicating whether current group + category selection changed recently. + /// Changes in Available group should be followed with , everythine else can use . + /// + public bool IsContentDirty => this.isContentDirty; + + /// + /// Gets a value indicating whether and are valid. + /// + public bool IsSelectionValid => + (this.currentGroupIdx >= 0) && + (this.currentGroupIdx < this.groupList.Length) && + (this.currentCategoryIdx >= 0) && + (this.currentCategoryIdx < this.groupList[this.currentGroupIdx].Categories.Count); + + /// + /// Rebuild available categories based on currently available plugins. + /// + /// list of all available plugin manifests to install. + public void BuildCategories(IEnumerable availablePlugins) + { + // rebuild map plugin name -> categoryIds + this.mapPluginCategories.Clear(); + + var groupAvail = Array.Find(this.groupList, x => x.GroupKind == GroupKind.Available); + var prevCategoryIds = new List(); + prevCategoryIds.AddRange(groupAvail.Categories); + + var categoryList = new List(); + var allCategoryIndices = new List(); + + foreach (var plugin in availablePlugins) + { + categoryList.Clear(); + + var pluginCategoryTags = this.GetCategoryTagsForManifest(plugin); + if (pluginCategoryTags != null) + { + foreach (var tag in pluginCategoryTags) + { + // only tags from whitelist can be accepted + var matchIdx = Array.FindIndex(this.CategoryList, x => x.Tag.Equals(tag, StringComparison.InvariantCultureIgnoreCase)); + if (matchIdx >= 0) + { + var categoryId = this.CategoryList[matchIdx].CategoryId; + if (categoryId >= FirstTagBasedCategoryId) { - allCategoryIndices.Add(matchIdx); + categoryList.Add(categoryId); + + if (!allCategoryIndices.Contains(matchIdx)) + { + allCategoryIndices.Add(matchIdx); + } + } + } + } + } + + // always add, even if empty + this.mapPluginCategories.Add(plugin, categoryList.ToArray()); + } + + // sort all categories by their loc name + allCategoryIndices.Sort((idxX, idxY) => this.CategoryList[idxX].Name.CompareTo(this.CategoryList[idxY].Name)); + + // rebuild all categories in group, leaving first entry = All intact and always on top + if (groupAvail.Categories.Count > 1) + { + groupAvail.Categories.RemoveRange(1, groupAvail.Categories.Count - 1); + } + + foreach (var categoryIdx in allCategoryIndices) + { + groupAvail.Categories.Add(this.CategoryList[categoryIdx].CategoryId); + } + + // compare with prev state and mark as dirty if needed + var noCategoryChanges = Enumerable.SequenceEqual(prevCategoryIds, groupAvail.Categories); + if (!noCategoryChanges) + { + this.isContentDirty = true; + } + } + + /// + /// Filters list of available plugins based on currently selected category. + /// Resets . + /// + /// List of available plugins to install. + /// Filtered list of plugins. + public List GetCurrentCategoryContent(IEnumerable plugins) + { + var result = new List(); + + if (this.IsSelectionValid) + { + var groupInfo = this.groupList[this.currentGroupIdx]; + + var includeAll = (this.currentCategoryIdx == 0) || (groupInfo.GroupKind != GroupKind.Available); + if (includeAll) + { + result.AddRange(plugins); + } + else + { + var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryId == groupInfo.Categories[this.currentCategoryIdx]); + + foreach (var plugin in plugins) + { + if (this.mapPluginCategories.TryGetValue(plugin, out var pluginCategoryIds)) + { + var matchIdx = Array.IndexOf(pluginCategoryIds, selectedCategoryInfo.CategoryId); + if (matchIdx >= 0) + { + result.Add(plugin); } } } } } - // always add, even if empty - this.mapPluginCategories.Add(plugin, categoryList.ToArray()); + this.ResetContentDirty(); + return result; } - // sort all categories by their loc name - allCategoryIndices.Sort((idxX, idxY) => this.CategoryList[idxX].Name.CompareTo(this.CategoryList[idxY].Name)); - - // rebuild all categories in group, leaving first entry = All intact and always on top - if (groupAvail.Categories.Count > 1) + /// + /// Clears flag, indicating that all cached values about currently selected group + category have been updated. + /// + public void ResetContentDirty() { - groupAvail.Categories.RemoveRange(1, groupAvail.Categories.Count - 1); + this.isContentDirty = false; } - foreach (var categoryIdx in allCategoryIndices) + /// + /// Sets category highlight based on list of plugins. Used for searching. + /// + /// List of plugins whose categories should be highlighted. + public void SetCategoryHighlightsForPlugins(IEnumerable plugins) { - groupAvail.Categories.Add(this.CategoryList[categoryIdx].CategoryId); - } + this.highlightedCategoryIds.Clear(); - // compare with prev state and mark as dirty if needed - var noCategoryChanges = Enumerable.SequenceEqual(prevCategoryIds, groupAvail.Categories); - if (!noCategoryChanges) - { - this.isContentDirty = true; - } - } - - /// - /// Filters list of available plugins based on currently selected category. - /// Resets . - /// - /// List of available plugins to install. - /// Filtered list of plugins. - public List GetCurrentCategoryContent(IEnumerable plugins) - { - var result = new List(); - - if (this.IsSelectionValid) - { - var groupInfo = this.groupList[this.currentGroupIdx]; - - var includeAll = (this.currentCategoryIdx == 0) || (groupInfo.GroupKind != GroupKind.Available); - if (includeAll) + if (plugins != null) { - result.AddRange(plugins); - } - else - { - var selectedCategoryInfo = Array.Find(this.categoryList, x => x.CategoryId == groupInfo.Categories[this.currentCategoryIdx]); - - foreach (var plugin in plugins) + foreach (var entry in plugins) { - if (this.mapPluginCategories.TryGetValue(plugin, out var pluginCategoryIds)) + if (this.mapPluginCategories.TryGetValue(entry, out var pluginCategories)) { - var matchIdx = Array.IndexOf(pluginCategoryIds, selectedCategoryInfo.CategoryId); - if (matchIdx >= 0) + foreach (var categoryId in pluginCategories) { - result.Add(plugin); + if (!this.highlightedCategoryIds.Contains(categoryId)) + { + this.highlightedCategoryIds.Add(categoryId); + } } } } } } - this.ResetContentDirty(); - return result; - } + /// + /// Checks if category should be highlighted. + /// + /// CategoryId to check. + /// true if highlight is needed. + public bool IsCategoryHighlighted(int categoryId) => this.highlightedCategoryIds.Contains(categoryId); - /// - /// Clears flag, indicating that all cached values about currently selected group + category have been updated. - /// - public void ResetContentDirty() - { - this.isContentDirty = false; - } - - /// - /// Sets category highlight based on list of plugins. Used for searching. - /// - /// List of plugins whose categories should be highlighted. - public void SetCategoryHighlightsForPlugins(IEnumerable plugins) - { - this.highlightedCategoryIds.Clear(); - - if (plugins != null) + private IEnumerable GetCategoryTagsForManifest(PluginManifest pluginManifest) { - foreach (var entry in plugins) + if (pluginManifest.CategoryTags != null) { - if (this.mapPluginCategories.TryGetValue(entry, out var pluginCategories)) - { - foreach (var categoryId in pluginCategories) - { - if (!this.highlightedCategoryIds.Contains(categoryId)) - { - this.highlightedCategoryIds.Add(categoryId); - } - } - } + return pluginManifest.CategoryTags; } + + return null; } - } - /// - /// Checks if category should be highlighted. - /// - /// CategoryId to check. - /// true if highlight is needed. - public bool IsCategoryHighlighted(int categoryId) => this.highlightedCategoryIds.Contains(categoryId); - - private IEnumerable GetCategoryTagsForManifest(PluginManifest pluginManifest) - { - if (pluginManifest.CategoryTags != null) + /// + /// Plugin installer category info. + /// + public struct CategoryInfo { - return pluginManifest.CategoryTags; + /// + /// Unique Id number of category, tag match based should be greater of equal . + /// + public int CategoryId; + + /// + /// Tag from plugin manifest to match. + /// + public string Tag; + + private Func nameFunc; + + /// + /// Initializes a new instance of the struct. + /// + /// Unique id of category. + /// Tag to match. + /// Function returning localized name of category. + public CategoryInfo(int categoryId, string tag, Func nameFunc) + { + this.CategoryId = categoryId; + this.Tag = tag; + this.nameFunc = nameFunc; + } + + /// + /// Gets the name of category. + /// + public string Name => this.nameFunc(); } - return null; - } - - /// - /// Plugin installer category info. - /// - public struct CategoryInfo - { /// - /// Unique Id number of category, tag match based should be greater of equal . + /// Plugin installer UI group, a container for categories. /// - public int CategoryId; - - /// - /// Tag from plugin manifest to match. - /// - public string Tag; - - private Func nameFunc; - - /// - /// Initializes a new instance of the struct. - /// - /// Unique id of category. - /// Tag to match. - /// Function returning localized name of category. - public CategoryInfo(int categoryId, string tag, Func nameFunc) + public struct GroupInfo { - this.CategoryId = categoryId; - this.Tag = tag; - this.nameFunc = nameFunc; + /// + /// Type of group. + /// + public GroupKind GroupKind; + + /// + /// List of categories in container. + /// + public List Categories; + + private Func nameFunc; + + /// + /// Initializes a new instance of the struct. + /// + /// Type of group. + /// Function returning localized name of category. + /// List of category Ids to hardcode. + public GroupInfo(GroupKind groupKind, Func nameFunc, params int[] categories) + { + this.GroupKind = groupKind; + this.nameFunc = nameFunc; + + this.Categories = new(); + this.Categories.AddRange(categories); + } + + /// + /// Gets the name of UI group. + /// + public string Name => this.nameFunc(); } - /// - /// Gets the name of category. - /// - public string Name => this.nameFunc(); - } - - /// - /// Plugin installer UI group, a container for categories. - /// - public struct GroupInfo - { - /// - /// Type of group. - /// - public GroupKind GroupKind; - - /// - /// List of categories in container. - /// - public List Categories; - - private Func nameFunc; - - /// - /// Initializes a new instance of the struct. - /// - /// Type of group. - /// Function returning localized name of category. - /// List of category Ids to hardcode. - public GroupInfo(GroupKind groupKind, Func nameFunc, params int[] categories) + private static class Locs { - this.GroupKind = groupKind; - this.nameFunc = nameFunc; + #region UI groups - this.Categories = new(); - this.Categories.AddRange(categories); + public static string Group_DevTools => Loc.Localize("InstallerDevTools", "Dev Tools"); + + public static string Group_Installed => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins"); + + public static string Group_Available => Loc.Localize("InstallerAvailablePlugins", "Available Plugins"); + + #endregion + + #region Categories + + public static string Category_All => Loc.Localize("InstallerCategoryAll", "All"); + + public static string Category_DevInstalled => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins"); + + public static string Category_IconTester => "Image/Icon Tester"; + + public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other"); + + public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs"); + + public static string Category_UI => Loc.Localize("InstallerCategoryUI", "UI"); + + public static string Category_MiniGames => Loc.Localize("InstallerCategoryMiniGames", "Mini games"); + + public static string Category_Inventory => Loc.Localize("InstallerCategoryInventory", "Inventory"); + + public static string Category_Sound => Loc.Localize("InstallerCategorySound", "Sound"); + + public static string Category_Social => Loc.Localize("InstallerCategorySocial", "Social"); + + public static string Category_Utility => Loc.Localize("InstallerCategoryUtility", "Utility"); + + #endregion } - - /// - /// Gets the name of UI group. - /// - public string Name => this.nameFunc(); - } - - private static class Locs - { - #region UI groups - - public static string Group_DevTools => Loc.Localize("InstallerDevTools", "Dev Tools"); - - public static string Group_Installed => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins"); - - public static string Group_Available => Loc.Localize("InstallerAvailablePlugins", "Available Plugins"); - - #endregion - - #region Categories - - public static string Category_All => Loc.Localize("InstallerCategoryAll", "All"); - - public static string Category_DevInstalled => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins"); - - public static string Category_IconTester => "Image/Icon Tester"; - - public static string Category_Other => Loc.Localize("InstallerCategoryOther", "Other"); - - public static string Category_Jobs => Loc.Localize("InstallerCategoryJobs", "Jobs"); - - public static string Category_UI => Loc.Localize("InstallerCategoryUI", "UI"); - - public static string Category_MiniGames => Loc.Localize("InstallerCategoryMiniGames", "Mini games"); - - public static string Category_Inventory => Loc.Localize("InstallerCategoryInventory", "Inventory"); - - public static string Category_Sound => Loc.Localize("InstallerCategorySound", "Sound"); - - public static string Category_Social => Loc.Localize("InstallerCategorySocial", "Social"); - - public static string Category_Utility => Loc.Localize("InstallerCategoryUtility", "Utility"); - - #endregion } } diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index 28adf10b5..e64ec6bc2 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -13,19 +13,19 @@ using ImGuiNET; // Customised version of https://github.com/aers/FFXIVUIDebug -namespace Dalamud.Interface.Internal; - -/// -/// This class displays a debug window to inspect native addons. -/// -internal unsafe class UIDebug +namespace Dalamud.Interface.Internal { - private const int UnitListCount = 18; - - private readonly GetAtkStageSingleton getAtkStageSingleton; - private readonly bool[] selectedInList = new bool[UnitListCount]; - private readonly string[] listNames = new string[UnitListCount] + /// + /// This class displays a debug window to inspect native addons. + /// + internal unsafe class UIDebug { + private const int UnitListCount = 18; + + private readonly GetAtkStageSingleton getAtkStageSingleton; + private readonly bool[] selectedInList = new bool[UnitListCount]; + private readonly string[] listNames = new string[UnitListCount] + { "Depth Layer 1", "Depth Layer 2", "Depth Layer 3", @@ -44,688 +44,689 @@ internal unsafe class UIDebug "Units 16", "Units 17", "Units 18", - }; + }; - private bool doingSearch; - private string searchInput = string.Empty; - private AtkUnitBase* selectedUnitBase = null; - private ulong beginModule; - private ulong endModule; + private bool doingSearch; + private string searchInput = string.Empty; + private AtkUnitBase* selectedUnitBase = null; + private ulong beginModule; + private ulong endModule; - /// - /// Initializes a new instance of the class. - /// - public UIDebug() - { - var sigScanner = Service.Get(); - var getSingletonAddr = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF"); - this.getAtkStageSingleton = Marshal.GetDelegateForFunctionPointer(getSingletonAddr); - } - - private delegate AtkStage* GetAtkStageSingleton(); - - /// - /// Renders this window. - /// - public void Draw() - { - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); - ImGui.BeginChild("st_uiDebug_unitBaseSelect", new Vector2(250, -1), true); - - ImGui.SetNextItemWidth(-1); - ImGui.InputTextWithHint("###atkUnitBaseSearch", "Search", ref this.searchInput, 0x20); - - this.DrawUnitBaseList(); - ImGui.EndChild(); - if (this.selectedUnitBase != null) + /// + /// Initializes a new instance of the class. + /// + public UIDebug() { - ImGui.SameLine(); - ImGui.BeginChild("st_uiDebug_selectedUnitBase", new Vector2(-1, -1), true); - this.DrawUnitBase(this.selectedUnitBase); + var sigScanner = Service.Get(); + var getSingletonAddr = sigScanner.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF"); + this.getAtkStageSingleton = Marshal.GetDelegateForFunctionPointer(getSingletonAddr); + } + + private delegate AtkStage* GetAtkStageSingleton(); + + /// + /// Renders this window. + /// + public void Draw() + { + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); + ImGui.BeginChild("st_uiDebug_unitBaseSelect", new Vector2(250, -1), true); + + ImGui.SetNextItemWidth(-1); + ImGui.InputTextWithHint("###atkUnitBaseSearch", "Search", ref this.searchInput, 0x20); + + this.DrawUnitBaseList(); ImGui.EndChild(); + if (this.selectedUnitBase != null) + { + ImGui.SameLine(); + ImGui.BeginChild("st_uiDebug_selectedUnitBase", new Vector2(-1, -1), true); + this.DrawUnitBase(this.selectedUnitBase); + ImGui.EndChild(); + } + + ImGui.PopStyleVar(); } - ImGui.PopStyleVar(); - } - - private static void ClickToCopyText(string text, string textCopy = null) - { - textCopy ??= text; - ImGui.Text($"{text}"); - if (ImGui.IsItemHovered()) + private static void ClickToCopyText(string text, string textCopy = null) { - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - if (textCopy != text) ImGui.SetTooltip(textCopy); + textCopy ??= text; + ImGui.Text($"{text}"); + if (ImGui.IsItemHovered()) + { + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + if (textCopy != text) ImGui.SetTooltip(textCopy); + } + + if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); } - if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}"); - } - - private void DrawUnitBase(AtkUnitBase* atkUnitBase) - { - var isVisible = (atkUnitBase->Flags & 0x20) == 0x20; - var addonName = Marshal.PtrToStringAnsi(new IntPtr(atkUnitBase->Name)); - var agent = Service.Get().FindAgentInterface(atkUnitBase); - - ImGui.Text($"{addonName}"); - ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, isVisible ? 0xFF00FF00 : 0xFF0000FF); - ImGui.Text(isVisible ? "Visible" : "Not Visible"); - ImGui.PopStyleColor(); - - ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - 25); - if (ImGui.SmallButton("V")) + private void DrawUnitBase(AtkUnitBase* atkUnitBase) { - atkUnitBase->Flags ^= 0x20; - } + var isVisible = (atkUnitBase->Flags & 0x20) == 0x20; + var addonName = Marshal.PtrToStringAnsi(new IntPtr(atkUnitBase->Name)); + var agent = Service.Get().FindAgentInterface(atkUnitBase); - ImGui.Separator(); - ClickToCopyText($"Address: {(ulong)atkUnitBase:X}", $"{(ulong)atkUnitBase:X}"); - ClickToCopyText($"Agent: {(ulong)agent:X}", $"{(ulong)agent:X}"); - ImGui.Separator(); + ImGui.Text($"{addonName}"); + ImGui.SameLine(); + ImGui.PushStyleColor(ImGuiCol.Text, isVisible ? 0xFF00FF00 : 0xFF0000FF); + ImGui.Text(isVisible ? "Visible" : "Not Visible"); + ImGui.PopStyleColor(); - ImGui.Text($"Position: [ {atkUnitBase->X} , {atkUnitBase->Y} ]"); - ImGui.Text($"Scale: {atkUnitBase->Scale * 100}%%"); - ImGui.Text($"Widget Count {atkUnitBase->UldManager.ObjectCount}"); + ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - 25); + if (ImGui.SmallButton("V")) + { + atkUnitBase->Flags ^= 0x20; + } - ImGui.Separator(); + ImGui.Separator(); + ClickToCopyText($"Address: {(ulong)atkUnitBase:X}", $"{(ulong)atkUnitBase:X}"); + ClickToCopyText($"Agent: {(ulong)agent:X}", $"{(ulong)agent:X}"); + ImGui.Separator(); - object addonObj = *atkUnitBase; + ImGui.Text($"Position: [ {atkUnitBase->X} , {atkUnitBase->Y} ]"); + ImGui.Text($"Scale: {atkUnitBase->Scale * 100}%%"); + ImGui.Text($"Widget Count {atkUnitBase->UldManager.ObjectCount}"); - this.PrintOutObject(addonObj, (ulong)atkUnitBase, new List()); + ImGui.Separator(); - ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale)); - ImGui.Separator(); - if (atkUnitBase->RootNode != null) - this.PrintNode(atkUnitBase->RootNode); + object addonObj = *atkUnitBase; + + this.PrintOutObject(addonObj, (ulong)atkUnitBase, new List()); - if (atkUnitBase->UldManager.NodeListCount > 0) - { ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale)); ImGui.Separator(); - ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); - if (ImGui.TreeNode($"Node List##{(ulong)atkUnitBase:X}")) - { - ImGui.PopStyleColor(); + if (atkUnitBase->RootNode != null) + this.PrintNode(atkUnitBase->RootNode); - for (var j = 0; j < atkUnitBase->UldManager.NodeListCount; j++) + if (atkUnitBase->UldManager.NodeListCount > 0) + { + ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale)); + ImGui.Separator(); + ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); + if (ImGui.TreeNode($"Node List##{(ulong)atkUnitBase:X}")) { - this.PrintNode(atkUnitBase->UldManager.NodeList[j], false, $"[{j}] "); - } + ImGui.PopStyleColor(); - ImGui.TreePop(); - } - else - { - ImGui.PopStyleColor(); - } - } - } - - private void PrintNode(AtkResNode* node, bool printSiblings = true, string treePrefix = "") - { - if (node == null) - return; - - if ((int)node->Type < 1000) - this.PrintSimpleNode(node, treePrefix); - else - this.PrintComponentNode(node, treePrefix); - - if (printSiblings) - { - var prevNode = node; - while ((prevNode = prevNode->PrevSiblingNode) != null) - this.PrintNode(prevNode, false, "prev "); - - var nextNode = node; - while ((nextNode = nextNode->NextSiblingNode) != null) - this.PrintNode(nextNode, false, "next "); - } - } - - private void PrintSimpleNode(AtkResNode* node, string treePrefix) - { - var popped = false; - var isVisible = (node->Flags & 0x10) == 0x10; - - if (isVisible) - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); - - if (ImGui.TreeNode($"{treePrefix}{node->Type} Node (ptr = {(long)node:X})###{(long)node}")) - { - if (ImGui.IsItemHovered()) - this.DrawOutline(node); - - if (isVisible) - { - ImGui.PopStyleColor(); - popped = true; - } - - ImGui.Text("Node: "); - ImGui.SameLine(); - ClickToCopyText($"{(ulong)node:X}"); - ImGui.SameLine(); - switch (node->Type) - { - case NodeType.Text: this.PrintOutObject(*(AtkTextNode*)node, (ulong)node, new List()); break; - case NodeType.Image: this.PrintOutObject(*(AtkImageNode*)node, (ulong)node, new List()); break; - case NodeType.Collision: this.PrintOutObject(*(AtkCollisionNode*)node, (ulong)node, new List()); break; - case NodeType.NineGrid: this.PrintOutObject(*(AtkNineGridNode*)node, (ulong)node, new List()); break; - case NodeType.Counter: this.PrintOutObject(*(AtkCounterNode*)node, (ulong)node, new List()); break; - default: this.PrintOutObject(*node, (ulong)node, new List()); break; - } - - this.PrintResNode(node); - - if (node->ChildNode != null) - this.PrintNode(node->ChildNode); - - switch (node->Type) - { - case NodeType.Text: - var textNode = (AtkTextNode*)node; - ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(textNode->NodeText.StringPtr))}"); - - ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize); - - ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}"); - int b = textNode->AlignmentFontType; - if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1)) + for (var j = 0; j < atkUnitBase->UldManager.NodeListCount; j++) { - while (b > byte.MaxValue) b -= byte.MaxValue; - while (b < byte.MinValue) b += byte.MaxValue; - textNode->AlignmentFontType = (byte)b; - textNode->AtkResNode.Flags_2 |= 0x1; + this.PrintNode(atkUnitBase->UldManager.NodeList[j], false, $"[{j}] "); } - ImGui.Text($"Color: #{textNode->TextColor.R:X2}{textNode->TextColor.G:X2}{textNode->TextColor.B:X2}{textNode->TextColor.A:X2}"); - ImGui.SameLine(); - ImGui.Text($"EdgeColor: #{textNode->EdgeColor.R:X2}{textNode->EdgeColor.G:X2}{textNode->EdgeColor.B:X2}{textNode->EdgeColor.A:X2}"); - ImGui.SameLine(); - ImGui.Text($"BGColor: #{textNode->BackgroundColor.R:X2}{textNode->BackgroundColor.G:X2}{textNode->BackgroundColor.B:X2}{textNode->BackgroundColor.A:X2}"); + ImGui.TreePop(); + } + else + { + ImGui.PopStyleColor(); + } + } + } - ImGui.Text($"TextFlags: {textNode->TextFlags}"); - ImGui.SameLine(); - ImGui.Text($"TextFlags2: {textNode->TextFlags2}"); + private void PrintNode(AtkResNode* node, bool printSiblings = true, string treePrefix = "") + { + if (node == null) + return; - break; - case NodeType.Counter: - var counterNode = (AtkCounterNode*)node; - ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(counterNode->NodeText.StringPtr))}"); - break; - case NodeType.Image: - var imageNode = (AtkImageNode*)node; - if (imageNode->PartsList != null) - { - if (imageNode->PartId > imageNode->PartsList->PartCount) + if ((int)node->Type < 1000) + this.PrintSimpleNode(node, treePrefix); + else + this.PrintComponentNode(node, treePrefix); + + if (printSiblings) + { + var prevNode = node; + while ((prevNode = prevNode->PrevSiblingNode) != null) + this.PrintNode(prevNode, false, "prev "); + + var nextNode = node; + while ((nextNode = nextNode->NextSiblingNode) != null) + this.PrintNode(nextNode, false, "next "); + } + } + + private void PrintSimpleNode(AtkResNode* node, string treePrefix) + { + var popped = false; + var isVisible = (node->Flags & 0x10) == 0x10; + + if (isVisible) + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); + + if (ImGui.TreeNode($"{treePrefix}{node->Type} Node (ptr = {(long)node:X})###{(long)node}")) + { + if (ImGui.IsItemHovered()) + this.DrawOutline(node); + + if (isVisible) + { + ImGui.PopStyleColor(); + popped = true; + } + + ImGui.Text("Node: "); + ImGui.SameLine(); + ClickToCopyText($"{(ulong)node:X}"); + ImGui.SameLine(); + switch (node->Type) + { + case NodeType.Text: this.PrintOutObject(*(AtkTextNode*)node, (ulong)node, new List()); break; + case NodeType.Image: this.PrintOutObject(*(AtkImageNode*)node, (ulong)node, new List()); break; + case NodeType.Collision: this.PrintOutObject(*(AtkCollisionNode*)node, (ulong)node, new List()); break; + case NodeType.NineGrid: this.PrintOutObject(*(AtkNineGridNode*)node, (ulong)node, new List()); break; + case NodeType.Counter: this.PrintOutObject(*(AtkCounterNode*)node, (ulong)node, new List()); break; + default: this.PrintOutObject(*node, (ulong)node, new List()); break; + } + + this.PrintResNode(node); + + if (node->ChildNode != null) + this.PrintNode(node->ChildNode); + + switch (node->Type) + { + case NodeType.Text: + var textNode = (AtkTextNode*)node; + ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(textNode->NodeText.StringPtr))}"); + + ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize); + + ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}"); + int b = textNode->AlignmentFontType; + if (ImGui.InputInt($"###setAlignment{(ulong)textNode:X}", ref b, 1)) { - ImGui.Text("part id > part count?"); + while (b > byte.MaxValue) b -= byte.MaxValue; + while (b < byte.MinValue) b += byte.MaxValue; + textNode->AlignmentFontType = (byte)b; + textNode->AtkResNode.Flags_2 |= 0x1; + } + + ImGui.Text($"Color: #{textNode->TextColor.R:X2}{textNode->TextColor.G:X2}{textNode->TextColor.B:X2}{textNode->TextColor.A:X2}"); + ImGui.SameLine(); + ImGui.Text($"EdgeColor: #{textNode->EdgeColor.R:X2}{textNode->EdgeColor.G:X2}{textNode->EdgeColor.B:X2}{textNode->EdgeColor.A:X2}"); + ImGui.SameLine(); + ImGui.Text($"BGColor: #{textNode->BackgroundColor.R:X2}{textNode->BackgroundColor.G:X2}{textNode->BackgroundColor.B:X2}{textNode->BackgroundColor.A:X2}"); + + ImGui.Text($"TextFlags: {textNode->TextFlags}"); + ImGui.SameLine(); + ImGui.Text($"TextFlags2: {textNode->TextFlags2}"); + + break; + case NodeType.Counter: + var counterNode = (AtkCounterNode*)node; + ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(counterNode->NodeText.StringPtr))}"); + break; + case NodeType.Image: + var imageNode = (AtkImageNode*)node; + if (imageNode->PartsList != null) + { + if (imageNode->PartId > imageNode->PartsList->PartCount) + { + ImGui.Text("part id > part count?"); + } + else + { + var textureInfo = imageNode->PartsList->Parts[imageNode->PartId].UldAsset; + var texType = textureInfo->AtkTexture.TextureType; + ImGui.Text($"texture type: {texType} part_id={imageNode->PartId} part_id_count={imageNode->PartsList->PartCount}"); + if (texType == TextureType.Resource) + { + var texFileNameStdString = &textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName; + var texString = texFileNameStdString->Length < 16 + ? Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->Buffer) + : Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->BufferPtr); + + ImGui.Text($"texture path: {texString}"); + var kernelTexture = textureInfo->AtkTexture.Resource->KernelTextureObject; + + if (ImGui.TreeNode($"Texture##{(ulong)kernelTexture->D3D11ShaderResourceView:X}")) + { + ImGui.Image(new IntPtr(kernelTexture->D3D11ShaderResourceView), new Vector2(kernelTexture->Width, kernelTexture->Height)); + ImGui.TreePop(); + } + } + else if (texType == TextureType.KernelTexture) + { + if (ImGui.TreeNode($"Texture##{(ulong)textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView:X}")) + { + ImGui.Image(new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2(textureInfo->AtkTexture.KernelTexture->Width, textureInfo->AtkTexture.KernelTexture->Height)); + ImGui.TreePop(); + } + } + } } else { - var textureInfo = imageNode->PartsList->Parts[imageNode->PartId].UldAsset; - var texType = textureInfo->AtkTexture.TextureType; - ImGui.Text($"texture type: {texType} part_id={imageNode->PartId} part_id_count={imageNode->PartsList->PartCount}"); - if (texType == TextureType.Resource) - { - var texFileNameStdString = &textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName; - var texString = texFileNameStdString->Length < 16 - ? Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->Buffer) - : Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->BufferPtr); - - ImGui.Text($"texture path: {texString}"); - var kernelTexture = textureInfo->AtkTexture.Resource->KernelTextureObject; - - if (ImGui.TreeNode($"Texture##{(ulong)kernelTexture->D3D11ShaderResourceView:X}")) - { - ImGui.Image(new IntPtr(kernelTexture->D3D11ShaderResourceView), new Vector2(kernelTexture->Width, kernelTexture->Height)); - ImGui.TreePop(); - } - } - else if (texType == TextureType.KernelTexture) - { - if (ImGui.TreeNode($"Texture##{(ulong)textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView:X}")) - { - ImGui.Image(new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2(textureInfo->AtkTexture.KernelTexture->Width, textureInfo->AtkTexture.KernelTexture->Height)); - ImGui.TreePop(); - } - } + ImGui.Text("no texture loaded"); } - } - else - { - ImGui.Text("no texture loaded"); - } - break; - } - - ImGui.TreePop(); - } - else if (ImGui.IsItemHovered()) - { - this.DrawOutline(node); - } - - if (isVisible && !popped) - ImGui.PopStyleColor(); - } - - private void PrintComponentNode(AtkResNode* node, string treePrefix) - { - var compNode = (AtkComponentNode*)node; - - var popped = false; - var isVisible = (node->Flags & 0x10) == 0x10; - - if (isVisible) - ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); - - var componentInfo = compNode->Component->UldManager; - - var childCount = componentInfo.NodeListCount; - - var objectInfo = (AtkUldComponentInfo*)componentInfo.Objects; - if (ImGui.TreeNode($"{treePrefix}{objectInfo->ComponentType} Component Node (ptr = {(long)node:X}, component ptr = {(long)compNode->Component:X}) child count = {childCount} ###{(long)node}")) - { - if (ImGui.IsItemHovered()) - this.DrawOutline(node); - - if (isVisible) - { - ImGui.PopStyleColor(); - popped = true; - } - - ImGui.Text("Node: "); - ImGui.SameLine(); - ClickToCopyText($"{(ulong)node:X}"); - ImGui.SameLine(); - this.PrintOutObject(*compNode, (ulong)compNode, new List()); - ImGui.Text("Component: "); - ImGui.SameLine(); - ClickToCopyText($"{(ulong)compNode->Component:X}"); - ImGui.SameLine(); - - switch (objectInfo->ComponentType) - { - case ComponentType.Button: this.PrintOutObject(*(AtkComponentButton*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.Slider: this.PrintOutObject(*(AtkComponentSlider*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.Window: this.PrintOutObject(*(AtkComponentWindow*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.CheckBox: this.PrintOutObject(*(AtkComponentCheckBox*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.GaugeBar: this.PrintOutObject(*(AtkComponentGaugeBar*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.RadioButton: this.PrintOutObject(*(AtkComponentRadioButton*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.TextInput: this.PrintOutObject(*(AtkComponentTextInput*)compNode->Component, (ulong)compNode->Component, new List()); break; - case ComponentType.Icon: this.PrintOutObject(*(AtkComponentIcon*)compNode->Component, (ulong)compNode->Component, new List()); break; - default: this.PrintOutObject(*compNode->Component, (ulong)compNode->Component, new List()); break; - } - - this.PrintResNode(node); - this.PrintNode(componentInfo.RootNode); - - switch (objectInfo->ComponentType) - { - case ComponentType.TextInput: - var textInputComponent = (AtkComponentTextInput*)compNode->Component; - ImGui.Text($"InputBase Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); - ImGui.Text($"InputBase Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); - ImGui.Text($"Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText1.StringPtr))}"); - ImGui.Text($"Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText2.StringPtr))}"); - ImGui.Text($"Text3: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText3.StringPtr))}"); - ImGui.Text($"Text4: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText4.StringPtr))}"); - ImGui.Text($"Text5: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText5.StringPtr))}"); - break; - } - - ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); - if (ImGui.TreeNode($"Node List##{(ulong)node:X}")) - { - ImGui.PopStyleColor(); - - for (var i = 0; i < compNode->Component->UldManager.NodeListCount; i++) - { - this.PrintNode(compNode->Component->UldManager.NodeList[i], false, $"[{i}] "); + break; } ImGui.TreePop(); } - else + else if (ImGui.IsItemHovered()) { + this.DrawOutline(node); + } + + if (isVisible && !popped) ImGui.PopStyleColor(); + } + + private void PrintComponentNode(AtkResNode* node, string treePrefix) + { + var compNode = (AtkComponentNode*)node; + + var popped = false; + var isVisible = (node->Flags & 0x10) == 0x10; + + if (isVisible) + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255)); + + var componentInfo = compNode->Component->UldManager; + + var childCount = componentInfo.NodeListCount; + + var objectInfo = (AtkUldComponentInfo*)componentInfo.Objects; + if (ImGui.TreeNode($"{treePrefix}{objectInfo->ComponentType} Component Node (ptr = {(long)node:X}, component ptr = {(long)compNode->Component:X}) child count = {childCount} ###{(long)node}")) + { + if (ImGui.IsItemHovered()) + this.DrawOutline(node); + + if (isVisible) + { + ImGui.PopStyleColor(); + popped = true; + } + + ImGui.Text("Node: "); + ImGui.SameLine(); + ClickToCopyText($"{(ulong)node:X}"); + ImGui.SameLine(); + this.PrintOutObject(*compNode, (ulong)compNode, new List()); + ImGui.Text("Component: "); + ImGui.SameLine(); + ClickToCopyText($"{(ulong)compNode->Component:X}"); + ImGui.SameLine(); + + switch (objectInfo->ComponentType) + { + case ComponentType.Button: this.PrintOutObject(*(AtkComponentButton*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.Slider: this.PrintOutObject(*(AtkComponentSlider*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.Window: this.PrintOutObject(*(AtkComponentWindow*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.CheckBox: this.PrintOutObject(*(AtkComponentCheckBox*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.GaugeBar: this.PrintOutObject(*(AtkComponentGaugeBar*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.RadioButton: this.PrintOutObject(*(AtkComponentRadioButton*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.TextInput: this.PrintOutObject(*(AtkComponentTextInput*)compNode->Component, (ulong)compNode->Component, new List()); break; + case ComponentType.Icon: this.PrintOutObject(*(AtkComponentIcon*)compNode->Component, (ulong)compNode->Component, new List()); break; + default: this.PrintOutObject(*compNode->Component, (ulong)compNode->Component, new List()); break; + } + + this.PrintResNode(node); + this.PrintNode(componentInfo.RootNode); + + switch (objectInfo->ComponentType) + { + case ComponentType.TextInput: + var textInputComponent = (AtkComponentTextInput*)compNode->Component; + ImGui.Text($"InputBase Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); + ImGui.Text($"InputBase Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); + ImGui.Text($"Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText1.StringPtr))}"); + ImGui.Text($"Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText2.StringPtr))}"); + ImGui.Text($"Text3: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText3.StringPtr))}"); + ImGui.Text($"Text4: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText4.StringPtr))}"); + ImGui.Text($"Text5: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText5.StringPtr))}"); + break; + } + + ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA); + if (ImGui.TreeNode($"Node List##{(ulong)node:X}")) + { + ImGui.PopStyleColor(); + + for (var i = 0; i < compNode->Component->UldManager.NodeListCount; i++) + { + this.PrintNode(compNode->Component->UldManager.NodeList[i], false, $"[{i}] "); + } + + ImGui.TreePop(); + } + else + { + ImGui.PopStyleColor(); + } + + ImGui.TreePop(); + } + else if (ImGui.IsItemHovered()) + { + this.DrawOutline(node); } - ImGui.TreePop(); - } - else if (ImGui.IsItemHovered()) - { - this.DrawOutline(node); + if (isVisible && !popped) + ImGui.PopStyleColor(); } - if (isVisible && !popped) + private void PrintResNode(AtkResNode* node) + { + ImGui.Text($"NodeID: {node->NodeID}"); + ImGui.SameLine(); + if (ImGui.SmallButton($"T:Visible##{(ulong)node:X}")) + { + node->Flags ^= 0x10; + } + + ImGui.SameLine(); + if (ImGui.SmallButton($"C:Ptr##{(ulong)node:X}")) + { + ImGui.SetClipboardText($"{(ulong)node:X}"); + } + + ImGui.Text( + $"X: {node->X} Y: {node->Y} " + + $"ScaleX: {node->ScaleX} ScaleY: {node->ScaleY} " + + $"Rotation: {node->Rotation} " + + $"Width: {node->Width} Height: {node->Height} " + + $"OriginX: {node->OriginX} OriginY: {node->OriginY}"); + ImGui.Text( + $"RGBA: 0x{node->Color.R:X2}{node->Color.G:X2}{node->Color.B:X2}{node->Color.A:X2} " + + $"AddRGB: {node->AddRed} {node->AddGreen} {node->AddBlue} " + + $"MultiplyRGB: {node->MultiplyRed} {node->MultiplyGreen} {node->MultiplyBlue}"); + } + + private bool DrawUnitListHeader(int index, uint count, ulong ptr, bool highlight) + { + ImGui.PushStyleColor(ImGuiCol.Text, highlight ? 0xFFAAAA00 : 0xFFFFFFFF); + if (!string.IsNullOrEmpty(this.searchInput) && !this.doingSearch) + { + ImGui.SetNextItemOpen(true, ImGuiCond.Always); + } + else if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) + { + ImGui.SetNextItemOpen(false, ImGuiCond.Always); + } + + var treeNode = ImGui.TreeNode($"{this.listNames[index]}##unitList_{index}"); ImGui.PopStyleColor(); - } - private void PrintResNode(AtkResNode* node) - { - ImGui.Text($"NodeID: {node->NodeID}"); - ImGui.SameLine(); - if (ImGui.SmallButton($"T:Visible##{(ulong)node:X}")) - { - node->Flags ^= 0x10; + ImGui.SameLine(); + ImGui.TextDisabled($"C:{count} {ptr:X}"); + return treeNode; } - ImGui.SameLine(); - if (ImGui.SmallButton($"C:Ptr##{(ulong)node:X}")) + private void DrawUnitBaseList() { - ImGui.SetClipboardText($"{(ulong)node:X}"); - } + var foundSelected = false; + var noResults = true; + var stage = this.getAtkStageSingleton(); - ImGui.Text( - $"X: {node->X} Y: {node->Y} " + - $"ScaleX: {node->ScaleX} ScaleY: {node->ScaleY} " + - $"Rotation: {node->Rotation} " + - $"Width: {node->Width} Height: {node->Height} " + - $"OriginX: {node->OriginX} OriginY: {node->OriginY}"); - ImGui.Text( - $"RGBA: 0x{node->Color.R:X2}{node->Color.G:X2}{node->Color.B:X2}{node->Color.A:X2} " + - $"AddRGB: {node->AddRed} {node->AddGreen} {node->AddBlue} " + - $"MultiplyRGB: {node->MultiplyRed} {node->MultiplyGreen} {node->MultiplyBlue}"); - } + var unitManagers = &stage->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList; - private bool DrawUnitListHeader(int index, uint count, ulong ptr, bool highlight) - { - ImGui.PushStyleColor(ImGuiCol.Text, highlight ? 0xFFAAAA00 : 0xFFFFFFFF); - if (!string.IsNullOrEmpty(this.searchInput) && !this.doingSearch) - { - ImGui.SetNextItemOpen(true, ImGuiCond.Always); - } - else if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) - { - ImGui.SetNextItemOpen(false, ImGuiCond.Always); - } + var searchStr = this.searchInput; + var searching = !string.IsNullOrEmpty(searchStr); - var treeNode = ImGui.TreeNode($"{this.listNames[index]}##unitList_{index}"); - ImGui.PopStyleColor(); - - ImGui.SameLine(); - ImGui.TextDisabled($"C:{count} {ptr:X}"); - return treeNode; - } - - private void DrawUnitBaseList() - { - var foundSelected = false; - var noResults = true; - var stage = this.getAtkStageSingleton(); - - var unitManagers = &stage->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList; - - var searchStr = this.searchInput; - var searching = !string.IsNullOrEmpty(searchStr); - - for (var i = 0; i < UnitListCount; i++) - { - var headerDrawn = false; - - var highlight = this.selectedUnitBase != null && this.selectedInList[i]; - this.selectedInList[i] = false; - var unitManager = &unitManagers[i]; - - var unitBaseArray = &unitManager->AtkUnitEntries; - - var headerOpen = true; - - if (!searching) + for (var i = 0; i < UnitListCount; i++) { - headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); - headerDrawn = true; - noResults = false; - } + var headerDrawn = false; - for (var j = 0; j < unitManager->Count && headerOpen; j++) - { - var unitBase = unitBaseArray[j]; - if (this.selectedUnitBase != null && unitBase == this.selectedUnitBase) - { - this.selectedInList[i] = true; - foundSelected = true; - } + var highlight = this.selectedUnitBase != null && this.selectedInList[i]; + this.selectedInList[i] = false; + var unitManager = &unitManagers[i]; - var name = Marshal.PtrToStringAnsi(new IntPtr(unitBase->Name)); - if (searching) - { - if (name == null || !name.ToLower().Contains(searchStr.ToLower())) continue; - } + var unitBaseArray = &unitManager->AtkUnitEntries; - noResults = false; - if (!headerDrawn) + var headerOpen = true; + + if (!searching) { headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); headerDrawn = true; + noResults = false; } - if (headerOpen) + for (var j = 0; j < unitManager->Count && headerOpen; j++) { - var visible = (unitBase->Flags & 0x20) == 0x20; - ImGui.PushStyleColor(ImGuiCol.Text, visible ? 0xFF00FF00 : 0xFF999999); - - if (ImGui.Selectable($"{name}##list{i}-{(ulong)unitBase:X}_{j}", this.selectedUnitBase == unitBase)) + var unitBase = unitBaseArray[j]; + if (this.selectedUnitBase != null && unitBase == this.selectedUnitBase) { - this.selectedUnitBase = unitBase; - foundSelected = true; this.selectedInList[i] = true; + foundSelected = true; } - ImGui.PopStyleColor(); + var name = Marshal.PtrToStringAnsi(new IntPtr(unitBase->Name)); + if (searching) + { + if (name == null || !name.ToLower().Contains(searchStr.ToLower())) continue; + } + + noResults = false; + if (!headerDrawn) + { + headerOpen = this.DrawUnitListHeader(i, unitManager->Count, (ulong)unitManager, highlight); + headerDrawn = true; + } + + if (headerOpen) + { + var visible = (unitBase->Flags & 0x20) == 0x20; + ImGui.PushStyleColor(ImGuiCol.Text, visible ? 0xFF00FF00 : 0xFF999999); + + if (ImGui.Selectable($"{name}##list{i}-{(ulong)unitBase:X}_{j}", this.selectedUnitBase == unitBase)) + { + this.selectedUnitBase = unitBase; + foundSelected = true; + this.selectedInList[i] = true; + } + + ImGui.PopStyleColor(); + } + } + + if (headerDrawn && headerOpen) + { + ImGui.TreePop(); + } + + if (this.selectedInList[i] == false && this.selectedUnitBase != null) + { + for (var j = 0; j < unitManager->Count; j++) + { + if (this.selectedUnitBase == null || unitBaseArray[j] != this.selectedUnitBase) continue; + this.selectedInList[i] = true; + foundSelected = true; + } } } - if (headerDrawn && headerOpen) + if (noResults) { - ImGui.TreePop(); + ImGui.TextDisabled("No Results"); } - if (this.selectedInList[i] == false && this.selectedUnitBase != null) + if (!foundSelected) { - for (var j = 0; j < unitManager->Count; j++) + this.selectedUnitBase = null; + } + + if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) + { + this.doingSearch = false; + } + else if (!this.doingSearch && !string.IsNullOrEmpty(this.searchInput)) + { + this.doingSearch = true; + } + } + + private Vector2 GetNodePosition(AtkResNode* node) + { + var pos = new Vector2(node->X, node->Y); + var par = node->ParentNode; + while (par != null) + { + pos *= new Vector2(par->ScaleX, par->ScaleY); + pos += new Vector2(par->X, par->Y); + par = par->ParentNode; + } + + return pos; + } + + private Vector2 GetNodeScale(AtkResNode* node) + { + if (node == null) return new Vector2(1, 1); + var scale = new Vector2(node->ScaleX, node->ScaleY); + while (node->ParentNode != null) + { + node = node->ParentNode; + scale *= new Vector2(node->ScaleX, node->ScaleY); + } + + return scale; + } + + private bool GetNodeVisible(AtkResNode* node) + { + if (node == null) return false; + while (node != null) + { + if ((node->Flags & (short)NodeFlags.Visible) != (short)NodeFlags.Visible) return false; + node = node->ParentNode; + } + + return true; + } + + private void DrawOutline(AtkResNode* node) + { + var position = this.GetNodePosition(node); + var scale = this.GetNodeScale(node); + var size = new Vector2(node->Width, node->Height) * scale; + + var nodeVisible = this.GetNodeVisible(node); + + position += ImGuiHelpers.MainViewport.Pos; + + ImGui.GetForegroundDrawList(ImGuiHelpers.MainViewport).AddRect(position, position + size, nodeVisible ? 0xFF00FF00 : 0xFF0000FF); + } + + private void PrintOutValue(ulong addr, IEnumerable path, Type type, object value) + { + if (type.IsPointer) + { + var val = (Pointer)value; + var unboxed = Pointer.Unbox(val); + if (unboxed != null) { - if (this.selectedUnitBase == null || unitBaseArray[j] != this.selectedUnitBase) continue; - this.selectedInList[i] = true; - foundSelected = true; - } - } - } + var unboxedAddr = (ulong)unboxed; + ClickToCopyText($"{(ulong)unboxed:X}"); + if (this.beginModule > 0 && unboxedAddr >= this.beginModule && unboxedAddr <= this.endModule) + { + ImGui.SameLine(); + ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); + ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - this.beginModule:X}"); + ImGui.PopStyleColor(); + } - if (noResults) - { - ImGui.TextDisabled("No Results"); - } - - if (!foundSelected) - { - this.selectedUnitBase = null; - } - - if (this.doingSearch && string.IsNullOrEmpty(this.searchInput)) - { - this.doingSearch = false; - } - else if (!this.doingSearch && !string.IsNullOrEmpty(this.searchInput)) - { - this.doingSearch = true; - } - } - - private Vector2 GetNodePosition(AtkResNode* node) - { - var pos = new Vector2(node->X, node->Y); - var par = node->ParentNode; - while (par != null) - { - pos *= new Vector2(par->ScaleX, par->ScaleY); - pos += new Vector2(par->X, par->Y); - par = par->ParentNode; - } - - return pos; - } - - private Vector2 GetNodeScale(AtkResNode* node) - { - if (node == null) return new Vector2(1, 1); - var scale = new Vector2(node->ScaleX, node->ScaleY); - while (node->ParentNode != null) - { - node = node->ParentNode; - scale *= new Vector2(node->ScaleX, node->ScaleY); - } - - return scale; - } - - private bool GetNodeVisible(AtkResNode* node) - { - if (node == null) return false; - while (node != null) - { - if ((node->Flags & (short)NodeFlags.Visible) != (short)NodeFlags.Visible) return false; - node = node->ParentNode; - } - - return true; - } - - private void DrawOutline(AtkResNode* node) - { - var position = this.GetNodePosition(node); - var scale = this.GetNodeScale(node); - var size = new Vector2(node->Width, node->Height) * scale; - - var nodeVisible = this.GetNodeVisible(node); - - position += ImGuiHelpers.MainViewport.Pos; - - ImGui.GetForegroundDrawList(ImGuiHelpers.MainViewport).AddRect(position, position + size, nodeVisible ? 0xFF00FF00 : 0xFF0000FF); - } - - private void PrintOutValue(ulong addr, IEnumerable path, Type type, object value) - { - if (type.IsPointer) - { - var val = (Pointer)value; - var unboxed = Pointer.Unbox(val); - if (unboxed != null) - { - var unboxedAddr = (ulong)unboxed; - ClickToCopyText($"{(ulong)unboxed:X}"); - if (this.beginModule > 0 && unboxedAddr >= this.beginModule && unboxedAddr <= this.endModule) - { - ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); - ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - this.beginModule:X}"); - ImGui.PopStyleColor(); - } - - try - { - var eType = type.GetElementType(); - var ptrObj = Marshal.PtrToStructure(new IntPtr(unboxed), eType); - ImGui.SameLine(); - this.PrintOutObject(ptrObj, (ulong)unboxed, new List(path)); - } - catch - { - // Ignored - } - } - else - { - ImGui.Text("null"); - } - } - else - { - if (!type.IsPrimitive) - { - this.PrintOutObject(value, addr, new List(path)); - } - else - { - ImGui.Text($"{value}"); - } - } - } - - private void PrintOutObject(object obj, ulong addr, List path, bool autoExpand = false) - { - if (this.endModule == 0 && this.beginModule == 0) - { - try - { - var processModule = Process.GetCurrentProcess().MainModule; - if (processModule != null) - { - this.beginModule = (ulong)processModule.BaseAddress.ToInt64(); - this.endModule = this.beginModule + (ulong)processModule.ModuleMemorySize; + try + { + var eType = type.GetElementType(); + var ptrObj = Marshal.PtrToStructure(new IntPtr(unboxed), eType); + ImGui.SameLine(); + this.PrintOutObject(ptrObj, (ulong)unboxed, new List(path)); + } + catch + { + // Ignored + } } else + { + ImGui.Text("null"); + } + } + else + { + if (!type.IsPrimitive) + { + this.PrintOutObject(value, addr, new List(path)); + } + else + { + ImGui.Text($"{value}"); + } + } + } + + private void PrintOutObject(object obj, ulong addr, List path, bool autoExpand = false) + { + if (this.endModule == 0 && this.beginModule == 0) + { + try + { + var processModule = Process.GetCurrentProcess().MainModule; + if (processModule != null) + { + this.beginModule = (ulong)processModule.BaseAddress.ToInt64(); + this.endModule = this.beginModule + (ulong)processModule.ModuleMemorySize; + } + else + { + this.endModule = 1; + } + } + catch { this.endModule = 1; } } - catch + + ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF); + if (autoExpand) { - this.endModule = 1; + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); } - } - ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF); - if (autoExpand) - { - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - } - - if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", path)}")) - { - ImGui.PopStyleColor(); - foreach (var f in obj.GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) + if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", path)}")) { - var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); - if (fixedBuffer != null) + ImGui.PopStyleColor(); + foreach (var f in obj.GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) { - ImGui.Text($"fixed"); + var fixedBuffer = (FixedBufferAttribute)f.GetCustomAttribute(typeof(FixedBufferAttribute)); + if (fixedBuffer != null) + { + ImGui.Text($"fixed"); + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); + } + else + { + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); + } + ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); + ImGui.SameLine(); + + this.PrintOutValue(addr, new List(path) { f.Name }, f.FieldType, f.GetValue(obj)); } - else + + foreach (var p in obj.GetType().GetProperties()) { - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}"); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); + ImGui.SameLine(); + ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); + ImGui.SameLine(); + + this.PrintOutValue(addr, new List(path) { p.Name }, p.PropertyType, p.GetValue(obj)); } - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); - ImGui.SameLine(); - - this.PrintOutValue(addr, new List(path) { f.Name }, f.FieldType, f.GetValue(obj)); + ImGui.TreePop(); } - - foreach (var p in obj.GetType().GetProperties()) + else { - ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}"); - ImGui.SameLine(); - ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); - ImGui.SameLine(); - - this.PrintOutValue(addr, new List(path) { p.Name }, p.PropertyType, p.GetValue(obj)); + ImGui.PopStyleColor(); } - - ImGui.TreePop(); - } - else - { - ImGui.PopStyleColor(); } } } diff --git a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs index b6f6f60ed..fa0a805c3 100644 --- a/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ChangelogWindow.cs @@ -6,20 +6,20 @@ using Dalamud.Interface.Windowing; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// For major updates, an in-game Changelog window. -/// -internal sealed class ChangelogWindow : Window +namespace Dalamud.Interface.Internal.Windows { /// - /// Whether the latest update warrants a changelog window. + /// For major updates, an in-game Changelog window. /// - public const string WarrantsChangelogForMajorMinor = "6.0."; + internal sealed class ChangelogWindow : Window + { + /// + /// Whether the latest update warrants a changelog window. + /// + public const string WarrantsChangelogForMajorMinor = "6.0."; - private const string ChangeLog = - @"This is the biggest update of the in-game addon to date. + private const string ChangeLog = + @"This is the biggest update of the in-game addon to date. We have redone most of the underlying systems, providing you with a better experience playing and browsing for plugins, including better performance, and developers with a better API and more comfortable development environment. We have also added some new features: @@ -29,99 +29,100 @@ We have also added some new features: If you note any issues or need help, please make sure to ask on our discord server."; - private const string UpdatePluginsInfo = - @"• All of your plugins were disabled automatically, due to this update. This is normal. + private const string UpdatePluginsInfo = + @"• All of your plugins were disabled automatically, due to this update. This is normal. • Open the plugin installer, then click 'update plugins'. Updated plugins should update and then re-enable themselves. => Please keep in mind that not all of your plugins may already be updated for the new version. => If some plugins are displayed with a red cross in the 'Installed Plugins' tab, they may not yet be available."; - private readonly string assemblyVersion = Util.AssemblyVersion; + private readonly string assemblyVersion = Util.AssemblyVersion; - /// - /// Initializes a new instance of the class. - /// - public ChangelogWindow() - : base("What's new in XIVLauncher?") - { - this.Namespace = "DalamudChangelogWindow"; - - this.Size = new Vector2(885, 463); - this.SizeCondition = ImGuiCond.Appearing; - } - - /// - public override void Draw() - { - ImGui.Text($"The in-game addon has been updated to version D{this.assemblyVersion}."); - - ImGuiHelpers.ScaledDummy(10); - - ImGui.Text("The following changes were introduced:"); - ImGui.TextWrapped(ChangeLog); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.TextColored(ImGuiColors.DalamudRed, " !!! ATTENTION !!!"); - - ImGui.TextWrapped(UpdatePluginsInfo); - - ImGuiHelpers.ScaledDummy(10); - - ImGui.Text("Thank you for using our tools!"); - - ImGuiHelpers.ScaledDummy(10); - - ImGui.PushFont(UiBuilder.IconFont); - - if (ImGui.Button(FontAwesomeIcon.Download.ToIconString())) + /// + /// Initializes a new instance of the class. + /// + public ChangelogWindow() + : base("What's new in XIVLauncher?") { - Service.Get().OpenPluginInstaller(); + this.Namespace = "DalamudChangelogWindow"; + + this.Size = new Vector2(885, 463); + this.SizeCondition = ImGuiCond.Appearing; } - if (ImGui.IsItemHovered()) + /// + public override void Draw() { - ImGui.PopFont(); - ImGui.SetTooltip("Open Plugin Installer"); + ImGui.Text($"The in-game addon has been updated to version D{this.assemblyVersion}."); + + ImGuiHelpers.ScaledDummy(10); + + ImGui.Text("The following changes were introduced:"); + ImGui.TextWrapped(ChangeLog); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.TextColored(ImGuiColors.DalamudRed, " !!! ATTENTION !!!"); + + ImGui.TextWrapped(UpdatePluginsInfo); + + ImGuiHelpers.ScaledDummy(10); + + ImGui.Text("Thank you for using our tools!"); + + ImGuiHelpers.ScaledDummy(10); + ImGui.PushFont(UiBuilder.IconFont); - } - ImGui.SameLine(); + if (ImGui.Button(FontAwesomeIcon.Download.ToIconString())) + { + Service.Get().OpenPluginInstaller(); + } - if (ImGui.Button(FontAwesomeIcon.LaughBeam.ToIconString())) - { - Process.Start("https://discord.gg/3NMcUV5"); - } + if (ImGui.IsItemHovered()) + { + ImGui.PopFont(); + ImGui.SetTooltip("Open Plugin Installer"); + ImGui.PushFont(UiBuilder.IconFont); + } + + ImGui.SameLine(); + + if (ImGui.Button(FontAwesomeIcon.LaughBeam.ToIconString())) + { + Process.Start("https://discord.gg/3NMcUV5"); + } + + if (ImGui.IsItemHovered()) + { + ImGui.PopFont(); + ImGui.SetTooltip("Join our Discord server"); + ImGui.PushFont(UiBuilder.IconFont); + } + + ImGui.SameLine(); + + if (ImGui.Button(FontAwesomeIcon.Globe.ToIconString())) + { + Process.Start("https://github.com/goatcorp/FFXIVQuickLauncher"); + } + + if (ImGui.IsItemHovered()) + { + ImGui.PopFont(); + ImGui.SetTooltip("See our GitHub repository"); + ImGui.PushFont(UiBuilder.IconFont); + } - if (ImGui.IsItemHovered()) - { ImGui.PopFont(); - ImGui.SetTooltip("Join our Discord server"); - ImGui.PushFont(UiBuilder.IconFont); - } - ImGui.SameLine(); + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(20, 0); + ImGui.SameLine(); - if (ImGui.Button(FontAwesomeIcon.Globe.ToIconString())) - { - Process.Start("https://github.com/goatcorp/FFXIVQuickLauncher"); - } - - if (ImGui.IsItemHovered()) - { - ImGui.PopFont(); - ImGui.SetTooltip("See our GitHub repository"); - ImGui.PushFont(UiBuilder.IconFont); - } - - ImGui.PopFont(); - - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(20, 0); - ImGui.SameLine(); - - if (ImGui.Button("Close")) - { - this.IsOpen = false; + if (ImGui.Button("Close")) + { + this.IsOpen = false; + } } } } diff --git a/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs b/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs index 8ae10bcd9..aeabf5e5f 100644 --- a/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ColorDemoWindow.cs @@ -7,25 +7,25 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Windowing; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// Color Demo Window to view custom ImGui colors. -/// -internal sealed class ColorDemoWindow : Window +namespace Dalamud.Interface.Internal.Windows { - private readonly List<(string Name, Vector4 Color)> colors; - /// - /// Initializes a new instance of the class. + /// Color Demo Window to view custom ImGui colors. /// - public ColorDemoWindow() - : base("Dalamud Colors Demo") + internal sealed class ColorDemoWindow : Window { - this.Size = new Vector2(600, 500); - this.SizeCondition = ImGuiCond.FirstUseEver; + private readonly List<(string Name, Vector4 Color)> colors; - this.colors = new List<(string Name, Vector4 Color)>() + /// + /// Initializes a new instance of the class. + /// + public ColorDemoWindow() + : base("Dalamud Colors Demo") + { + this.Size = new Vector2(600, 500); + this.SizeCondition = ImGuiCond.FirstUseEver; + + this.colors = new List<(string Name, Vector4 Color)>() { ("DalamudRed", ImGuiColors.DalamudRed), ("DalamudGrey", ImGuiColors.DalamudGrey), @@ -47,19 +47,20 @@ internal sealed class ColorDemoWindow : Window ("ParsedPink", ImGuiColors.ParsedPink), ("ParsedGold", ImGuiColors.ParsedGold), }.OrderBy(colorDemo => colorDemo.Name).ToList(); - } + } - /// - public override void Draw() - { - ImGui.Text("This is a collection of UI colors you can use in your plugin."); - - ImGui.Separator(); - - foreach (var property in typeof(ImGuiColors).GetProperties(BindingFlags.Public | BindingFlags.Static)) + /// + public override void Draw() { - var color = (Vector4)property.GetValue(null); - ImGui.TextColored(color, property.Name); + ImGui.Text("This is a collection of UI colors you can use in your plugin."); + + ImGui.Separator(); + + foreach (var property in typeof(ImGuiColors).GetProperties(BindingFlags.Public | BindingFlags.Static)) + { + var color = (Vector4)property.GetValue(null); + ImGui.TextColored(color, property.Name); + } } } } diff --git a/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs b/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs index 6df474bc2..a3c1f48d5 100644 --- a/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ComponentDemoWindow.cs @@ -9,153 +9,154 @@ using Dalamud.Interface.Components; using Dalamud.Interface.Windowing; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// Component Demo Window to view custom ImGui components. -/// -internal sealed class ComponentDemoWindow : Window +namespace Dalamud.Interface.Internal.Windows { - private static readonly TimeSpan DefaultEasingTime = new(0, 0, 0, 1700); - - private readonly List<(string Name, Action Demo)> componentDemos; - private readonly IReadOnlyList easings = new Easing[] + /// + /// Component Demo Window to view custom ImGui components. + /// + internal sealed class ComponentDemoWindow : Window { + private static readonly TimeSpan DefaultEasingTime = new(0, 0, 0, 1700); + + private readonly List<(string Name, Action Demo)> componentDemos; + private readonly IReadOnlyList easings = new Easing[] + { new InSine(DefaultEasingTime), new OutSine(DefaultEasingTime), new InOutSine(DefaultEasingTime), new InCubic(DefaultEasingTime), new OutCubic(DefaultEasingTime), new InOutCubic(DefaultEasingTime), new InQuint(DefaultEasingTime), new OutQuint(DefaultEasingTime), new InOutQuint(DefaultEasingTime), new InCirc(DefaultEasingTime), new OutCirc(DefaultEasingTime), new InOutCirc(DefaultEasingTime), new InElastic(DefaultEasingTime), new OutElastic(DefaultEasingTime), new InOutElastic(DefaultEasingTime), - }; - - private int animationTimeMs = (int)DefaultEasingTime.TotalMilliseconds; - private Vector4 defaultColor = ImGuiColors.DalamudOrange; - - /// - /// Initializes a new instance of the class. - /// - public ComponentDemoWindow() - : base("Dalamud Components Demo") - { - this.Size = new Vector2(600, 500); - this.SizeCondition = ImGuiCond.FirstUseEver; - - this.RespectCloseHotkey = false; - - this.componentDemos = new() - { - ("Test", ImGuiComponents.Test), - ("HelpMarker", HelpMarkerDemo), - ("IconButton", IconButtonDemo), - ("TextWithLabel", TextWithLabelDemo), - ("ColorPickerWithPalette", this.ColorPickerWithPaletteDemo), }; - } - /// - public override void OnOpen() - { - foreach (var easing in this.easings) + private int animationTimeMs = (int)DefaultEasingTime.TotalMilliseconds; + private Vector4 defaultColor = ImGuiColors.DalamudOrange; + + /// + /// Initializes a new instance of the class. + /// + public ComponentDemoWindow() + : base("Dalamud Components Demo") { - easing.Restart(); - } - } + this.Size = new Vector2(600, 500); + this.SizeCondition = ImGuiCond.FirstUseEver; - /// - public override void OnClose() - { - foreach (var easing in this.easings) - { - easing.Stop(); - } - } + this.RespectCloseHotkey = false; - /// - public override void Draw() - { - ImGui.Text("This is a collection of UI components you can use in your plugin."); - - for (var i = 0; i < this.componentDemos.Count; i++) - { - var componentDemo = this.componentDemos[i]; - - if (ImGui.CollapsingHeader($"{componentDemo.Name}###comp{i}")) + this.componentDemos = new() { - componentDemo.Demo(); - } + ("Test", ImGuiComponents.Test), + ("HelpMarker", HelpMarkerDemo), + ("IconButton", IconButtonDemo), + ("TextWithLabel", TextWithLabelDemo), + ("ColorPickerWithPalette", this.ColorPickerWithPaletteDemo), + }; } - if (ImGui.CollapsingHeader("Easing animations")) + /// + public override void OnOpen() { - this.EasingsDemo(); - } - } - - private static void HelpMarkerDemo() - { - ImGui.Text("Hover over the icon to learn more."); - ImGuiComponents.HelpMarker("help me!"); - } - - private static void IconButtonDemo() - { - ImGui.Text("Click on the icon to use as a button."); - ImGui.SameLine(); - if (ImGuiComponents.IconButton(1, FontAwesomeIcon.Carrot)) - { - ImGui.OpenPopup("IconButtonDemoPopup"); - } - - if (ImGui.BeginPopup("IconButtonDemoPopup")) - { - ImGui.Text("You clicked!"); - ImGui.EndPopup(); - } - } - - private static void TextWithLabelDemo() - { - ImGuiComponents.TextWithLabel("Label", "Hover to see more", "more"); - } - - private void EasingsDemo() - { - ImGui.SliderInt("Speed in MS", ref this.animationTimeMs, 200, 5000); - - foreach (var easing in this.easings) - { - easing.Duration = new TimeSpan(0, 0, 0, 0, this.animationTimeMs); - - if (!easing.IsRunning) - { - easing.Start(); - } - - var cursor = ImGui.GetCursorPos(); - var p1 = new Vector2(cursor.X + 5, cursor.Y); - var p2 = p1 + new Vector2(45, 0); - easing.Point1 = p1; - easing.Point2 = p2; - easing.Update(); - - if (easing.IsDone) + foreach (var easing in this.easings) { easing.Restart(); } + } - ImGui.SetCursorPos(easing.EasedPoint); - ImGui.Bullet(); + /// + public override void OnClose() + { + foreach (var easing in this.easings) + { + easing.Stop(); + } + } - ImGui.SetCursorPos(cursor + new Vector2(0, 10)); - ImGui.Text($"{easing.GetType().Name} ({easing.Value})"); - ImGuiHelpers.ScaledDummy(5); + /// + public override void Draw() + { + ImGui.Text("This is a collection of UI components you can use in your plugin."); + + for (var i = 0; i < this.componentDemos.Count; i++) + { + var componentDemo = this.componentDemos[i]; + + if (ImGui.CollapsingHeader($"{componentDemo.Name}###comp{i}")) + { + componentDemo.Demo(); + } + } + + if (ImGui.CollapsingHeader("Easing animations")) + { + this.EasingsDemo(); + } + } + + private static void HelpMarkerDemo() + { + ImGui.Text("Hover over the icon to learn more."); + ImGuiComponents.HelpMarker("help me!"); + } + + private static void IconButtonDemo() + { + ImGui.Text("Click on the icon to use as a button."); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(1, FontAwesomeIcon.Carrot)) + { + ImGui.OpenPopup("IconButtonDemoPopup"); + } + + if (ImGui.BeginPopup("IconButtonDemoPopup")) + { + ImGui.Text("You clicked!"); + ImGui.EndPopup(); + } + } + + private static void TextWithLabelDemo() + { + ImGuiComponents.TextWithLabel("Label", "Hover to see more", "more"); + } + + private void EasingsDemo() + { + ImGui.SliderInt("Speed in MS", ref this.animationTimeMs, 200, 5000); + + foreach (var easing in this.easings) + { + easing.Duration = new TimeSpan(0, 0, 0, 0, this.animationTimeMs); + + if (!easing.IsRunning) + { + easing.Start(); + } + + var cursor = ImGui.GetCursorPos(); + var p1 = new Vector2(cursor.X + 5, cursor.Y); + var p2 = p1 + new Vector2(45, 0); + easing.Point1 = p1; + easing.Point2 = p2; + easing.Update(); + + if (easing.IsDone) + { + easing.Restart(); + } + + ImGui.SetCursorPos(easing.EasedPoint); + ImGui.Bullet(); + + ImGui.SetCursorPos(cursor + new Vector2(0, 10)); + ImGui.Text($"{easing.GetType().Name} ({easing.Value})"); + ImGuiHelpers.ScaledDummy(5); + } + } + + private void ColorPickerWithPaletteDemo() + { + ImGui.Text("Click on the color button to use the picker."); + ImGui.SameLine(); + this.defaultColor = ImGuiComponents.ColorPickerWithPalette(1, "ColorPickerWithPalette Demo", this.defaultColor); } } - - private void ColorPickerWithPaletteDemo() - { - ImGui.Text("Click on the color button to use the picker."); - ImGui.SameLine(); - this.defaultColor = ImGuiComponents.ColorPickerWithPalette(1, "ColorPickerWithPalette Demo", this.defaultColor); - } } diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs index d72f11544..a34d758f7 100644 --- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs +++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs @@ -16,487 +16,488 @@ using ImGuiNET; using Serilog; using Serilog.Events; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// The window that displays the Dalamud log file in-game. -/// -internal class ConsoleWindow : Window, IDisposable +namespace Dalamud.Interface.Internal.Windows { - private readonly List logText = new(); - private readonly object renderLock = new(); - - private readonly string[] logLevelStrings = new[] { "None", "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" }; - - private List filteredLogText = new(); - private bool autoScroll; - private bool openAtStartup; - - private bool? lastCmdSuccess; - - private string commandText = string.Empty; - - private string textFilter = string.Empty; - private LogEventLevel? levelFilter = null; - private bool isFiltered = false; - - private int historyPos; - private List history = new(); - /// - /// Initializes a new instance of the class. + /// The window that displays the Dalamud log file in-game. /// - public ConsoleWindow() - : base("Dalamud Console") + internal class ConsoleWindow : Window, IDisposable { - var configuration = Service.Get(); + private readonly List logText = new(); + private readonly object renderLock = new(); - this.autoScroll = configuration.LogAutoScroll; - this.openAtStartup = configuration.LogOpenAtStartup; - SerilogEventSink.Instance.LogLine += this.OnLogLine; + private readonly string[] logLevelStrings = new[] { "None", "Verbose", "Debug", "Information", "Warning", "Error", "Fatal" }; - this.Size = new Vector2(500, 400); - this.SizeCondition = ImGuiCond.FirstUseEver; + private List filteredLogText = new(); + private bool autoScroll; + private bool openAtStartup; - this.RespectCloseHotkey = false; - } + private bool? lastCmdSuccess; - private List LogEntries => this.isFiltered ? this.filteredLogText : this.logText; + private string commandText = string.Empty; - /// - /// Dispose of managed and unmanaged resources. - /// - public void Dispose() - { - SerilogEventSink.Instance.LogLine -= this.OnLogLine; - } + private string textFilter = string.Empty; + private LogEventLevel? levelFilter = null; + private bool isFiltered = false; - /// - /// Clear the window of all log entries. - /// - public void Clear() - { - lock (this.renderLock) + private int historyPos; + private List history = new(); + + /// + /// Initializes a new instance of the class. + /// + public ConsoleWindow() + : base("Dalamud Console") { - this.logText.Clear(); - this.filteredLogText.Clear(); - } - } - - /// - /// Add a single log line to the display. - /// - /// The line to add. - /// The level of the event. - /// The of the event. - public void HandleLogLine(string line, LogEventLevel level, DateTimeOffset offset) - { - if (line.IndexOfAny(new[] { '\n', '\r' }) != -1) - { - var subLines = line.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); - - this.AddAndFilter(subLines[0], level, offset, false); - - for (var i = 1; i < subLines.Length; i++) - { - this.AddAndFilter(subLines[i], level, offset, true); - } - } - else - { - this.AddAndFilter(line, level, offset, false); - } - } - - /// - public override void Draw() - { - // Options menu - if (ImGui.BeginPopup("Options")) - { - var dalamud = Service.Get(); var configuration = Service.Get(); - if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll)) - { - configuration.LogAutoScroll = this.autoScroll; - configuration.Save(); - } + this.autoScroll = configuration.LogAutoScroll; + this.openAtStartup = configuration.LogOpenAtStartup; + SerilogEventSink.Instance.LogLine += this.OnLogLine; - if (ImGui.Checkbox("Open at startup", ref this.openAtStartup)) - { - configuration.LogOpenAtStartup = this.openAtStartup; - configuration.Save(); - } + this.Size = new Vector2(500, 400); + this.SizeCondition = ImGuiCond.FirstUseEver; - var prevLevel = (int)dalamud.LogLevelSwitch.MinimumLevel; - if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast().Select(x => x.ToString()).ToArray(), 6)) - { - dalamud.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel; - configuration.LogLevel = (LogEventLevel)prevLevel; - configuration.Save(); - } - - ImGui.EndPopup(); + this.RespectCloseHotkey = false; } - // Filter menu - if (ImGui.BeginPopup("Filters")) - { - ImGui.Checkbox("Enabled", ref this.isFiltered); + private List LogEntries => this.isFiltered ? this.filteredLogText : this.logText; - if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue)) + /// + /// Dispose of managed and unmanaged resources. + /// + public void Dispose() + { + SerilogEventSink.Instance.LogLine -= this.OnLogLine; + } + + /// + /// Clear the window of all log entries. + /// + public void Clear() + { + lock (this.renderLock) { - this.Refilter(); + this.logText.Clear(); + this.filteredLogText.Clear(); } + } - ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm."); - - var filterVal = this.levelFilter.HasValue ? (int)this.levelFilter.Value + 1 : 0; - if (ImGui.Combo("Level", ref filterVal, this.logLevelStrings, 7)) + /// + /// Add a single log line to the display. + /// + /// The line to add. + /// The level of the event. + /// The of the event. + public void HandleLogLine(string line, LogEventLevel level, DateTimeOffset offset) + { + if (line.IndexOfAny(new[] { '\n', '\r' }) != -1) { - this.levelFilter = (LogEventLevel)(filterVal - 1); - this.Refilter(); - } + var subLines = line.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); - ImGui.EndPopup(); - } + this.AddAndFilter(subLines[0], level, offset, false); - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) - ImGui.OpenPopup("Options"); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Options"); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Search)) - ImGui.OpenPopup("Filters"); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Filters"); - - ImGui.SameLine(); - var clear = ImGuiComponents.IconButton(FontAwesomeIcon.Trash); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Clear Log"); - - ImGui.SameLine(); - var copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Copy Log"); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull)) - Process.GetCurrentProcess().Kill(); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip("Kill game"); - - ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar); - - if (clear) - { - this.Clear(); - } - - if (copy) - { - ImGui.LogToClipboard(); - } - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - - ImGuiListClipperPtr clipper; - unsafe - { - clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); - } - - ImGui.PushFont(InterfaceManager.MonoFont); - - var childPos = ImGui.GetWindowPos(); - var childDrawList = ImGui.GetWindowDrawList(); - var childSize = ImGui.GetWindowSize(); - - var cursorDiv = ImGuiHelpers.GlobalScale * 92; - var cursorLogLevel = ImGuiHelpers.GlobalScale * 100; - var cursorLogLine = ImGuiHelpers.GlobalScale * 135; - - lock (this.renderLock) - { - clipper.Begin(this.LogEntries.Count); - while (clipper.Step()) - { - for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + for (var i = 1; i < subLines.Length; i++) { - var line = this.LogEntries[i]; - - if (!line.IsMultiline) - ImGui.Separator(); - - ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level)); - ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level)); - ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level)); - - ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns); - ImGui.SameLine(); - - ImGui.PopStyleColor(3); - - if (!line.IsMultiline) - { - ImGui.TextUnformatted(line.TimeStamp.ToString("HH:mm:ss.fff")); - ImGui.SameLine(); - ImGui.SetCursorPosX(cursorDiv); - ImGui.TextUnformatted("|"); - ImGui.SameLine(); - ImGui.SetCursorPosX(cursorLogLevel); - ImGui.TextUnformatted(this.GetTextForLogEventLevel(line.Level)); - ImGui.SameLine(); - } - - ImGui.SetCursorPosX(cursorLogLine); - ImGui.TextUnformatted(line.Line); + this.AddAndFilter(subLines[i], level, offset, true); } } - - clipper.End(); - } - - ImGui.PopFont(); - - ImGui.PopStyleVar(); - - if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) - { - ImGui.SetScrollHereY(1.0f); - } - - // Draw dividing line - var offset = ImGuiHelpers.GlobalScale * 127; - childDrawList.AddLine(new Vector2(childPos.X + offset, childPos.Y), new Vector2(childPos.X + offset, childPos.Y + childSize.Y), 0x4FFFFFFF, 1.0f); - - ImGui.EndChild(); - - var hadColor = false; - if (this.lastCmdSuccess.HasValue) - { - hadColor = true; - if (this.lastCmdSuccess.Value) - { - ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.HealerGreen - new Vector4(0, 0, 0, 0.7f)); - } else { - ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.DalamudRed - new Vector4(0, 0, 0, 0.7f)); + this.AddAndFilter(line, level, offset, false); } } - ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80); - - var getFocus = false; - unsafe + /// + public override void Draw() { - if (ImGui.InputText("##commandbox", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback)) + // Options menu + if (ImGui.BeginPopup("Options")) { - this.ProcessCommand(); - getFocus = true; + var dalamud = Service.Get(); + var configuration = Service.Get(); + + if (ImGui.Checkbox("Auto-scroll", ref this.autoScroll)) + { + configuration.LogAutoScroll = this.autoScroll; + configuration.Save(); + } + + if (ImGui.Checkbox("Open at startup", ref this.openAtStartup)) + { + configuration.LogOpenAtStartup = this.openAtStartup; + configuration.Save(); + } + + var prevLevel = (int)dalamud.LogLevelSwitch.MinimumLevel; + if (ImGui.Combo("Log Level", ref prevLevel, Enum.GetValues(typeof(LogEventLevel)).Cast().Select(x => x.ToString()).ToArray(), 6)) + { + dalamud.LogLevelSwitch.MinimumLevel = (LogEventLevel)prevLevel; + configuration.LogLevel = (LogEventLevel)prevLevel; + configuration.Save(); + } + + ImGui.EndPopup(); + } + + // Filter menu + if (ImGui.BeginPopup("Filters")) + { + ImGui.Checkbox("Enabled", ref this.isFiltered); + + if (ImGui.InputTextWithHint("##filterText", "Text Filter", ref this.textFilter, 255, ImGuiInputTextFlags.EnterReturnsTrue)) + { + this.Refilter(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, "Enter to confirm."); + + var filterVal = this.levelFilter.HasValue ? (int)this.levelFilter.Value + 1 : 0; + if (ImGui.Combo("Level", ref filterVal, this.logLevelStrings, 7)) + { + this.levelFilter = (LogEventLevel)(filterVal - 1); + this.Refilter(); + } + + ImGui.EndPopup(); } ImGui.SameLine(); - } - ImGui.SetItemDefaultFocus(); - if (getFocus) - ImGui.SetKeyboardFocusHere(-1); // Auto focus previous widget + if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) + ImGui.OpenPopup("Options"); - if (hadColor) - ImGui.PopStyleColor(); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Options"); - if (ImGui.Button("Send")) - { - this.ProcessCommand(); - } - } + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Search)) + ImGui.OpenPopup("Filters"); - private void ProcessCommand() - { - try - { - this.historyPos = -1; - for (var i = this.history.Count - 1; i >= 0; i--) - { - if (this.history[i] == this.commandText) - { - this.history.RemoveAt(i); - break; - } - } + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Filters"); - this.history.Add(this.commandText); + ImGui.SameLine(); + var clear = ImGuiComponents.IconButton(FontAwesomeIcon.Trash); - if (this.commandText == "clear" || this.commandText == "cls") + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Clear Log"); + + ImGui.SameLine(); + var copy = ImGuiComponents.IconButton(FontAwesomeIcon.Copy); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Copy Log"); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Skull)) + Process.GetCurrentProcess().Kill(); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip("Kill game"); + + ImGui.BeginChild("scrolling", new Vector2(0, ImGui.GetFrameHeightWithSpacing() - 55), false, ImGuiWindowFlags.AlwaysHorizontalScrollbar | ImGuiWindowFlags.AlwaysVerticalScrollbar); + + if (clear) { this.Clear(); - return; } - this.lastCmdSuccess = Service.Get().ProcessCommand("/" + this.commandText); - this.commandText = string.Empty; + if (copy) + { + ImGui.LogToClipboard(); + } - // TODO: Force scroll to bottom - } - catch (Exception ex) - { - Log.Error(ex, "Error during command dispatch"); - this.lastCmdSuccess = false; - } - } + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data) - { - var ptr = new ImGuiInputTextCallbackDataPtr(data); + ImGuiListClipperPtr clipper; + unsafe + { + clipper = new ImGuiListClipperPtr(ImGuiNative.ImGuiListClipper_ImGuiListClipper()); + } - switch (data->EventFlag) - { - case ImGuiInputTextFlags.CallbackCompletion: - var textBytes = new byte[data->BufTextLen]; - Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen); - var text = Encoding.UTF8.GetString(textBytes); + ImGui.PushFont(InterfaceManager.MonoFont); - var words = text.Split(); + var childPos = ImGui.GetWindowPos(); + var childDrawList = ImGui.GetWindowDrawList(); + var childSize = ImGui.GetWindowSize(); - // We can't do any completion for parameters at the moment since it just calls into CommandHandler - if (words.Length > 1) - return 0; + var cursorDiv = ImGuiHelpers.GlobalScale * 92; + var cursorLogLevel = ImGuiHelpers.GlobalScale * 100; + var cursorLogLine = ImGuiHelpers.GlobalScale * 135; - // TODO: Improve this, add partial completion - // https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484 - var candidates = Service.Get().Commands.Where(x => x.Key.Contains("/" + words[0])).ToList(); - if (candidates.Count > 0) + lock (this.renderLock) + { + clipper.Begin(this.LogEntries.Count); + while (clipper.Step()) { - ptr.DeleteChars(0, ptr.BufTextLen); - ptr.InsertChars(0, candidates[0].Key.Replace("/", string.Empty)); - } - - break; - case ImGuiInputTextFlags.CallbackHistory: - var prevPos = this.historyPos; - - if (ptr.EventKey == ImGuiKey.UpArrow) - { - if (this.historyPos == -1) - this.historyPos = this.history.Count - 1; - else if (this.historyPos > 0) - this.historyPos--; - } - else if (data->EventKey == ImGuiKey.DownArrow) - { - if (this.historyPos != -1) + for (var i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { - if (++this.historyPos >= this.history.Count) + var line = this.LogEntries[i]; + + if (!line.IsMultiline) + ImGui.Separator(); + + ImGui.PushStyleColor(ImGuiCol.Header, this.GetColorForLogEventLevel(line.Level)); + ImGui.PushStyleColor(ImGuiCol.HeaderActive, this.GetColorForLogEventLevel(line.Level)); + ImGui.PushStyleColor(ImGuiCol.HeaderHovered, this.GetColorForLogEventLevel(line.Level)); + + ImGui.Selectable("###consolenull", true, ImGuiSelectableFlags.AllowItemOverlap | ImGuiSelectableFlags.SpanAllColumns); + ImGui.SameLine(); + + ImGui.PopStyleColor(3); + + if (!line.IsMultiline) { - this.historyPos = -1; + ImGui.TextUnformatted(line.TimeStamp.ToString("HH:mm:ss.fff")); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursorDiv); + ImGui.TextUnformatted("|"); + ImGui.SameLine(); + ImGui.SetCursorPosX(cursorLogLevel); + ImGui.TextUnformatted(this.GetTextForLogEventLevel(line.Level)); + ImGui.SameLine(); } + + ImGui.SetCursorPosX(cursorLogLine); + ImGui.TextUnformatted(line.Line); } } - if (prevPos != this.historyPos) - { - var historyStr = this.historyPos >= 0 ? this.history[this.historyPos] : string.Empty; + clipper.End(); + } - ptr.DeleteChars(0, ptr.BufTextLen); - ptr.InsertChars(0, historyStr); + ImGui.PopFont(); + + ImGui.PopStyleVar(); + + if (this.autoScroll && ImGui.GetScrollY() >= ImGui.GetScrollMaxY()) + { + ImGui.SetScrollHereY(1.0f); + } + + // Draw dividing line + var offset = ImGuiHelpers.GlobalScale * 127; + childDrawList.AddLine(new Vector2(childPos.X + offset, childPos.Y), new Vector2(childPos.X + offset, childPos.Y + childSize.Y), 0x4FFFFFFF, 1.0f); + + ImGui.EndChild(); + + var hadColor = false; + if (this.lastCmdSuccess.HasValue) + { + hadColor = true; + if (this.lastCmdSuccess.Value) + { + ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.HealerGreen - new Vector4(0, 0, 0, 0.7f)); + } + else + { + ImGui.PushStyleColor(ImGuiCol.FrameBg, ImGuiColors.DalamudRed - new Vector4(0, 0, 0, 0.7f)); + } + } + + ImGui.SetNextItemWidth(ImGui.GetWindowSize().X - 80); + + var getFocus = false; + unsafe + { + if (ImGui.InputText("##commandbox", ref this.commandText, 255, ImGuiInputTextFlags.EnterReturnsTrue | ImGuiInputTextFlags.CallbackCompletion | ImGuiInputTextFlags.CallbackHistory, this.CommandInputCallback)) + { + this.ProcessCommand(); + getFocus = true; } - break; + ImGui.SameLine(); + } + + ImGui.SetItemDefaultFocus(); + if (getFocus) + ImGui.SetKeyboardFocusHere(-1); // Auto focus previous widget + + if (hadColor) + ImGui.PopStyleColor(); + + if (ImGui.Button("Send")) + { + this.ProcessCommand(); + } } - return 0; - } - - private void AddAndFilter(string line, LogEventLevel level, DateTimeOffset offset, bool isMultiline) - { - if (line.StartsWith("TROUBLESHOOTING:") || line.StartsWith("LASTEXCEPTION:")) - return; - - var entry = new LogEntry + private void ProcessCommand() { - IsMultiline = isMultiline, - Level = level, - Line = line, - TimeStamp = offset, + try + { + this.historyPos = -1; + for (var i = this.history.Count - 1; i >= 0; i--) + { + if (this.history[i] == this.commandText) + { + this.history.RemoveAt(i); + break; + } + } + + this.history.Add(this.commandText); + + if (this.commandText == "clear" || this.commandText == "cls") + { + this.Clear(); + return; + } + + this.lastCmdSuccess = Service.Get().ProcessCommand("/" + this.commandText); + this.commandText = string.Empty; + + // TODO: Force scroll to bottom + } + catch (Exception ex) + { + Log.Error(ex, "Error during command dispatch"); + this.lastCmdSuccess = false; + } + } + + private unsafe int CommandInputCallback(ImGuiInputTextCallbackData* data) + { + var ptr = new ImGuiInputTextCallbackDataPtr(data); + + switch (data->EventFlag) + { + case ImGuiInputTextFlags.CallbackCompletion: + var textBytes = new byte[data->BufTextLen]; + Marshal.Copy((IntPtr)data->Buf, textBytes, 0, data->BufTextLen); + var text = Encoding.UTF8.GetString(textBytes); + + var words = text.Split(); + + // We can't do any completion for parameters at the moment since it just calls into CommandHandler + if (words.Length > 1) + return 0; + + // TODO: Improve this, add partial completion + // https://github.com/ocornut/imgui/blob/master/imgui_demo.cpp#L6443-L6484 + var candidates = Service.Get().Commands.Where(x => x.Key.Contains("/" + words[0])).ToList(); + if (candidates.Count > 0) + { + ptr.DeleteChars(0, ptr.BufTextLen); + ptr.InsertChars(0, candidates[0].Key.Replace("/", string.Empty)); + } + + break; + case ImGuiInputTextFlags.CallbackHistory: + var prevPos = this.historyPos; + + if (ptr.EventKey == ImGuiKey.UpArrow) + { + if (this.historyPos == -1) + this.historyPos = this.history.Count - 1; + else if (this.historyPos > 0) + this.historyPos--; + } + else if (data->EventKey == ImGuiKey.DownArrow) + { + if (this.historyPos != -1) + { + if (++this.historyPos >= this.history.Count) + { + this.historyPos = -1; + } + } + } + + if (prevPos != this.historyPos) + { + var historyStr = this.historyPos >= 0 ? this.history[this.historyPos] : string.Empty; + + ptr.DeleteChars(0, ptr.BufTextLen); + ptr.InsertChars(0, historyStr); + } + + break; + } + + return 0; + } + + private void AddAndFilter(string line, LogEventLevel level, DateTimeOffset offset, bool isMultiline) + { + if (line.StartsWith("TROUBLESHOOTING:") || line.StartsWith("LASTEXCEPTION:")) + return; + + var entry = new LogEntry + { + IsMultiline = isMultiline, + Level = level, + Line = line, + TimeStamp = offset, + }; + + this.logText.Add(entry); + + if (!this.isFiltered) + return; + + if (this.IsFilterApplicable(entry)) + this.filteredLogText.Add(entry); + } + + private bool IsFilterApplicable(LogEntry entry) + { + if (this.levelFilter.HasValue) + { + return entry.Level == this.levelFilter.Value; + } + + if (!string.IsNullOrEmpty(this.textFilter)) + return entry.Line.Contains(this.textFilter); + + return true; + } + + private void Refilter() + { + lock (this.renderLock) + { + this.filteredLogText = this.logText.Where(this.IsFilterApplicable).ToList(); + } + } + + private string GetTextForLogEventLevel(LogEventLevel level) => level switch + { + LogEventLevel.Error => "ERR", + LogEventLevel.Verbose => "VRB", + LogEventLevel.Debug => "DBG", + LogEventLevel.Information => "INF", + LogEventLevel.Warning => "WRN", + LogEventLevel.Fatal => "FTL", + _ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"), }; - this.logText.Add(entry); - - if (!this.isFiltered) - return; - - if (this.IsFilterApplicable(entry)) - this.filteredLogText.Add(entry); - } - - private bool IsFilterApplicable(LogEntry entry) - { - if (this.levelFilter.HasValue) + private uint GetColorForLogEventLevel(LogEventLevel level) => level switch { - return entry.Level == this.levelFilter.Value; + LogEventLevel.Error => 0x800000EE, + LogEventLevel.Verbose => 0x00000000, + LogEventLevel.Debug => 0x00000000, + LogEventLevel.Information => 0x00000000, + LogEventLevel.Warning => 0x8A0070EE, + LogEventLevel.Fatal => 0xFF00000A, + _ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"), + }; + + private void OnLogLine(object sender, (string Line, LogEventLevel Level, DateTimeOffset Offset, Exception? Exception) logEvent) + { + this.HandleLogLine(logEvent.Line, logEvent.Level, logEvent.Offset); } - if (!string.IsNullOrEmpty(this.textFilter)) - return entry.Line.Contains(this.textFilter); - - return true; - } - - private void Refilter() - { - lock (this.renderLock) + private class LogEntry { - this.filteredLogText = this.logText.Where(this.IsFilterApplicable).ToList(); + public string Line { get; set; } + + public LogEventLevel Level { get; set; } + + public DateTimeOffset TimeStamp { get; set; } + + public bool IsMultiline { get; set; } } } - - private string GetTextForLogEventLevel(LogEventLevel level) => level switch - { - LogEventLevel.Error => "ERR", - LogEventLevel.Verbose => "VRB", - LogEventLevel.Debug => "DBG", - LogEventLevel.Information => "INF", - LogEventLevel.Warning => "WRN", - LogEventLevel.Fatal => "FTL", - _ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"), - }; - - private uint GetColorForLogEventLevel(LogEventLevel level) => level switch - { - LogEventLevel.Error => 0x800000EE, - LogEventLevel.Verbose => 0x00000000, - LogEventLevel.Debug => 0x00000000, - LogEventLevel.Information => 0x00000000, - LogEventLevel.Warning => 0x8A0070EE, - LogEventLevel.Fatal => 0xFF00000A, - _ => throw new ArgumentOutOfRangeException(level.ToString(), "Invalid LogEventLevel"), - }; - - private void OnLogLine(object sender, (string Line, LogEventLevel Level, DateTimeOffset Offset, Exception? Exception) logEvent) - { - this.HandleLogLine(logEvent.Line, logEvent.Level, logEvent.Offset); - } - - private class LogEntry - { - public string Line { get; set; } - - public LogEventLevel Level { get; set; } - - public DateTimeOffset TimeStamp { get; set; } - - public bool IsMultiline { get; set; } - } } diff --git a/Dalamud/Interface/Internal/Windows/CreditsWindow.cs b/Dalamud/Interface/Internal/Windows/CreditsWindow.cs index f80357323..7fd341048 100644 --- a/Dalamud/Interface/Internal/Windows/CreditsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/CreditsWindow.cs @@ -11,15 +11,15 @@ using Dalamud.Plugin.Internal; using ImGuiNET; using ImGuiScene; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// A window documenting contributors to the project. -/// -internal class CreditsWindow : Window, IDisposable +namespace Dalamud.Interface.Internal.Windows { - private const float CreditFPS = 60.0f; - private const string CreditsTextTempl = @" + /// + /// A window documenting contributors to the project. + /// + internal class CreditsWindow : Window, IDisposable + { + private const float CreditFPS = 60.0f; + private const string CreditsTextTempl = @" Dalamud A FFXIV Plugin Framework Version D{0} @@ -122,120 +122,121 @@ Contribute at: https://github.com/goatsoft/Dalamud Thank you for using XIVLauncher and Dalamud! "; - private readonly TextureWrap logoTexture; - private readonly Stopwatch creditsThrottler; + private readonly TextureWrap logoTexture; + private readonly Stopwatch creditsThrottler; - private string creditsText; + private string creditsText; - /// - /// Initializes a new instance of the class. - /// - public CreditsWindow() - : base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize, true) - { - var dalamud = Service.Get(); - var interfaceManager = Service.Get(); - - this.logoTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png")); - this.creditsThrottler = new(); - - this.Size = new Vector2(500, 400); - this.SizeCondition = ImGuiCond.Always; - - this.PositionCondition = ImGuiCond.Always; - - this.BgAlpha = 0.8f; - } - - /// - public override void OnOpen() - { - var pluginCredits = Service.Get().InstalledPlugins - .Where(plugin => plugin.Manifest != null) - .Select(plugin => $"{plugin.Manifest.Name} by {plugin.Manifest.Author}\n") - .Aggregate(string.Empty, (current, next) => $"{current}{next}"); - - this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits); - - Service.Get().SetBgm(132); - this.creditsThrottler.Restart(); - } - - /// - public override void OnClose() - { - this.creditsThrottler.Reset(); - Service.Get().SetBgm(9999); - } - - /// - public override void PreDraw() - { - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0)); - - base.PreDraw(); - } - - /// - public override void PostDraw() - { - ImGui.PopStyleVar(); - - base.PostDraw(); - } - - /// - public override void Draw() - { - var screenSize = ImGui.GetMainViewport().Size; - var windowSize = ImGui.GetWindowSize(); - - this.Position = (screenSize - windowSize) / 2; - - ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar); - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); - - ImGuiHelpers.ScaledDummy(0, 340f); - ImGui.Text(string.Empty); - - ImGui.SameLine(150f); - ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(190f, 190f)); - - ImGuiHelpers.ScaledDummy(0, 20f); - - var windowX = ImGui.GetWindowSize().X; - - foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) + /// + /// Initializes a new instance of the class. + /// + public CreditsWindow() + : base("Dalamud Credits", ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize, true) { - var lineLenX = ImGui.CalcTextSize(creditsLine).X; + var dalamud = Service.Get(); + var interfaceManager = Service.Get(); - ImGui.Dummy(new Vector2((windowX / 2) - (lineLenX / 2), 0f)); - ImGui.SameLine(); - ImGui.TextUnformatted(creditsLine); + this.logoTexture = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "logo.png")); + this.creditsThrottler = new(); + + this.Size = new Vector2(500, 400); + this.SizeCondition = ImGuiCond.Always; + + this.PositionCondition = ImGuiCond.Always; + + this.BgAlpha = 0.8f; } - ImGui.PopStyleVar(); - - if (this.creditsThrottler.Elapsed.TotalMilliseconds > (1000.0f / CreditFPS)) + /// + public override void OnOpen() { - var curY = ImGui.GetScrollY(); - var maxY = ImGui.GetScrollMaxY(); + var pluginCredits = Service.Get().InstalledPlugins + .Where(plugin => plugin.Manifest != null) + .Select(plugin => $"{plugin.Manifest.Name} by {plugin.Manifest.Author}\n") + .Aggregate(string.Empty, (current, next) => $"{current}{next}"); - if (curY < maxY - 1) + this.creditsText = string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits); + + Service.Get().SetBgm(132); + this.creditsThrottler.Restart(); + } + + /// + public override void OnClose() + { + this.creditsThrottler.Reset(); + Service.Get().SetBgm(9999); + } + + /// + public override void PreDraw() + { + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(0, 0)); + + base.PreDraw(); + } + + /// + public override void PostDraw() + { + ImGui.PopStyleVar(); + + base.PostDraw(); + } + + /// + public override void Draw() + { + var screenSize = ImGui.GetMainViewport().Size; + var windowSize = ImGui.GetWindowSize(); + + this.Position = (screenSize - windowSize) / 2; + + ImGui.BeginChild("scrolling", Vector2.Zero, false, ImGuiWindowFlags.NoScrollbar); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero); + + ImGuiHelpers.ScaledDummy(0, 340f); + ImGui.Text(string.Empty); + + ImGui.SameLine(150f); + ImGui.Image(this.logoTexture.ImGuiHandle, ImGuiHelpers.ScaledVector2(190f, 190f)); + + ImGuiHelpers.ScaledDummy(0, 20f); + + var windowX = ImGui.GetWindowSize().X; + + foreach (var creditsLine in this.creditsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None)) { - ImGui.SetScrollY(curY + 1); + var lineLenX = ImGui.CalcTextSize(creditsLine).X; + + ImGui.Dummy(new Vector2((windowX / 2) - (lineLenX / 2), 0f)); + ImGui.SameLine(); + ImGui.TextUnformatted(creditsLine); } + + ImGui.PopStyleVar(); + + if (this.creditsThrottler.Elapsed.TotalMilliseconds > (1000.0f / CreditFPS)) + { + var curY = ImGui.GetScrollY(); + var maxY = ImGui.GetScrollMaxY(); + + if (curY < maxY - 1) + { + ImGui.SetScrollY(curY + 1); + } + } + + ImGui.EndChild(); } - ImGui.EndChild(); - } - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() - { - this.logoTexture?.Dispose(); + /// + /// Disposes of managed and unmanaged resources. + /// + public void Dispose() + { + this.logoTexture?.Dispose(); + } } } diff --git a/Dalamud/Interface/Internal/Windows/DataWindow.cs b/Dalamud/Interface/Internal/Windows/DataWindow.cs index 561e414bc..4128d41d3 100644 --- a/Dalamud/Interface/Internal/Windows/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/DataWindow.cs @@ -43,619 +43,568 @@ using Newtonsoft.Json; using PInvoke; using Serilog; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// Class responsible for drawing the data/debug window. -/// -internal class DataWindow : Window +namespace Dalamud.Interface.Internal.Windows { - private readonly string[] dataKindNames = Enum.GetNames(typeof(DataKind)).Select(k => k.Replace("_", " ")).ToArray(); - - private bool wasReady; - private string serverOpString; - private DataKind currentKind; - - private bool drawCharas = false; - private float maxCharaDrawDistance = 20; - - private string inputSig = string.Empty; - private IntPtr sigResult = IntPtr.Zero; - - private string inputAddonName = string.Empty; - private int inputAddonIndex; - - private IntPtr findAgentInterfacePtr; - - private bool resolveGameData = false; - private bool resolveObjects = false; - - private UIDebug addonInspector = null; - - private Hook? messageBoxMinHook; - private bool hookUseMinHook = false; - - // IPC - private ICallGateProvider ipcPub; - private ICallGateSubscriber ipcSub; - private string callGateResponse = string.Empty; - - // Toast fields - private string inputTextToast = string.Empty; - private int toastPosition = 0; - private int toastSpeed = 0; - private int questToastPosition = 0; - private bool questToastSound = false; - private int questToastIconId = 0; - private bool questToastCheckmark = false; - - // Fly text fields - private int flyActor; - private FlyTextKind flyKind; - private int flyVal1; - private int flyVal2; - private string flyText1 = string.Empty; - private string flyText2 = string.Empty; - private int flyIcon; - private Vector4 flyColor = new(1, 0, 0, 1); - - // ImGui fields - private string inputTexPath = string.Empty; - private TextureWrap debugTex = null; - private Vector2 inputTexUv0 = Vector2.Zero; - private Vector2 inputTexUv1 = Vector2.One; - private Vector4 inputTintCol = Vector4.One; - private Vector2 inputTexScale = Vector2.Zero; - - private uint copyButtonIndex = 0; - /// - /// Initializes a new instance of the class. + /// Class responsible for drawing the data/debug window. /// - public DataWindow() - : base("Dalamud Data") + internal class DataWindow : Window { - this.Size = new Vector2(500, 500); - this.SizeCondition = ImGuiCond.FirstUseEver; + private readonly string[] dataKindNames = Enum.GetNames(typeof(DataKind)).Select(k => k.Replace("_", " ")).ToArray(); - this.RespectCloseHotkey = false; + private bool wasReady; + private string serverOpString; + private DataKind currentKind; - this.Load(); - } + private bool drawCharas = false; + private float maxCharaDrawDistance = 20; - private delegate int MessageBoxWDelegate( - IntPtr hWnd, - [MarshalAs(UnmanagedType.LPWStr)] string text, - [MarshalAs(UnmanagedType.LPWStr)] string caption, - NativeFunctions.MessageBoxType type); + private string inputSig = string.Empty; + private IntPtr sigResult = IntPtr.Zero; - private enum DataKind - { - Server_OpCode, - Address, - Object_Table, - Fate_Table, - Font_Test, - Party_List, - Buddy_List, - Plugin_IPC, - Condition, - Gauge, - Command, - Addon, - Addon_Inspector, - AtkArrayData_Browser, - StartInfo, - Target, - Toast, - FlyText, - ImGui, - Tex, - KeyState, - Gamepad, - Configuration, - TaskSched, - Hook, - } + private string inputAddonName = string.Empty; + private int inputAddonIndex; - /// - public override void OnOpen() - { - } + private IntPtr findAgentInterfacePtr; - /// - public override void OnClose() - { - } + private bool resolveGameData = false; + private bool resolveObjects = false; - /// - /// Set the DataKind dropdown menu. - /// - /// Data kind name, can be lower and/or without spaces. - public void SetDataKind(string dataKind) - { - if (string.IsNullOrEmpty(dataKind)) - return; + private UIDebug addonInspector = null; - dataKind = dataKind switch + private Hook? messageBoxMinHook; + private bool hookUseMinHook = false; + + // IPC + private ICallGateProvider ipcPub; + private ICallGateSubscriber ipcSub; + private string callGateResponse = string.Empty; + + // Toast fields + private string inputTextToast = string.Empty; + private int toastPosition = 0; + private int toastSpeed = 0; + private int questToastPosition = 0; + private bool questToastSound = false; + private int questToastIconId = 0; + private bool questToastCheckmark = false; + + // Fly text fields + private int flyActor; + private FlyTextKind flyKind; + private int flyVal1; + private int flyVal2; + private string flyText1 = string.Empty; + private string flyText2 = string.Empty; + private int flyIcon; + private Vector4 flyColor = new(1, 0, 0, 1); + + // ImGui fields + private string inputTexPath = string.Empty; + private TextureWrap debugTex = null; + private Vector2 inputTexUv0 = Vector2.Zero; + private Vector2 inputTexUv1 = Vector2.One; + private Vector4 inputTintCol = Vector4.One; + private Vector2 inputTexScale = Vector2.Zero; + + private uint copyButtonIndex = 0; + + /// + /// Initializes a new instance of the class. + /// + public DataWindow() + : base("Dalamud Data") { - "ai" => "Addon Inspector", - "at" => "Object Table", // Actor Table - "ot" => "Object Table", - _ => dataKind, - }; + this.Size = new Vector2(500, 500); + this.SizeCondition = ImGuiCond.FirstUseEver; - dataKind = dataKind.Replace(" ", string.Empty).ToLower(); + this.RespectCloseHotkey = false; - var matched = Enum.GetValues() - .Where(kind => Enum.GetName(kind).Replace("_", string.Empty).ToLower() == dataKind) - .FirstOrDefault(); - - if (matched != default) - { - this.currentKind = matched; - } - else - { - Service.Get().PrintError($"/xldata: Invalid data type {dataKind}"); - } - } - - /// - /// Draw the window via ImGui. - /// - public override void Draw() - { - this.copyButtonIndex = 0; - - // Main window - if (ImGui.Button("Force Reload")) this.Load(); - ImGui.SameLine(); - var copy = ImGui.Button("Copy all"); - ImGui.SameLine(); - - var currentKindIndex = (int)this.currentKind; - if (ImGui.Combo("Data kind", ref currentKindIndex, this.dataKindNames, this.dataKindNames.Length)) - { - this.currentKind = (DataKind)currentKindIndex; } - ImGui.Checkbox("Resolve GameData", ref this.resolveGameData); + private delegate int MessageBoxWDelegate( + IntPtr hWnd, + [MarshalAs(UnmanagedType.LPWStr)] string text, + [MarshalAs(UnmanagedType.LPWStr)] string caption, + NativeFunctions.MessageBoxType type); - ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.HorizontalScrollbar); - - if (copy) - ImGui.LogToClipboard(); - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0)); - - try + private enum DataKind { - if (this.wasReady) + Server_OpCode, + Address, + Object_Table, + Fate_Table, + Font_Test, + Party_List, + Buddy_List, + Plugin_IPC, + Condition, + Gauge, + Command, + Addon, + Addon_Inspector, + AtkArrayData_Browser, + StartInfo, + Target, + Toast, + FlyText, + ImGui, + Tex, + KeyState, + Gamepad, + Configuration, + TaskSched, + Hook, + } + + /// + public override void OnOpen() + { + } + + /// + public override void OnClose() + { + } + + /// + /// Set the DataKind dropdown menu. + /// + /// Data kind name, can be lower and/or without spaces. + public void SetDataKind(string dataKind) + { + if (string.IsNullOrEmpty(dataKind)) + return; + + dataKind = dataKind switch { - switch (this.currentKind) - { - case DataKind.Server_OpCode: - this.DrawServerOpCode(); - break; + "ai" => "Addon Inspector", + "at" => "Object Table", // Actor Table + "ot" => "Object Table", + _ => dataKind, + }; - case DataKind.Address: - this.DrawAddress(); - break; + dataKind = dataKind.Replace(" ", string.Empty).ToLower(); - case DataKind.Object_Table: - this.DrawObjectTable(); - break; + var matched = Enum.GetValues() + .Where(kind => Enum.GetName(kind).Replace("_", string.Empty).ToLower() == dataKind) + .FirstOrDefault(); - case DataKind.Fate_Table: - this.DrawFateTable(); - break; - - case DataKind.Font_Test: - this.DrawFontTest(); - break; - - case DataKind.Party_List: - this.DrawPartyList(); - break; - - case DataKind.Buddy_List: - this.DrawBuddyList(); - break; - - case DataKind.Plugin_IPC: - this.DrawPluginIPC(); - break; - - case DataKind.Condition: - this.DrawCondition(); - break; - - case DataKind.Gauge: - this.DrawGauge(); - break; - - case DataKind.Command: - this.DrawCommand(); - break; - - case DataKind.Addon: - this.DrawAddon(); - break; - - case DataKind.Addon_Inspector: - this.DrawAddonInspector(); - break; - - case DataKind.AtkArrayData_Browser: - this.DrawAtkArrayDataBrowser(); - break; - - case DataKind.StartInfo: - this.DrawStartInfo(); - break; - - case DataKind.Target: - this.DrawTarget(); - break; - - case DataKind.Toast: - this.DrawToast(); - break; - - case DataKind.FlyText: - this.DrawFlyText(); - break; - - case DataKind.ImGui: - this.DrawImGui(); - break; - - case DataKind.Tex: - this.DrawTex(); - break; - - case DataKind.KeyState: - this.DrawKeyState(); - break; - - case DataKind.Gamepad: - this.DrawGamepad(); - break; - - case DataKind.Configuration: - this.DrawConfiguration(); - break; - - case DataKind.TaskSched: - this.DrawTaskSched(); - break; - - case DataKind.Hook: - this.DrawHook(); - break; - } + if (matched != default) + { + this.currentKind = matched; } else { - ImGui.TextUnformatted("Data not ready."); - } - } - catch (Exception ex) - { - ImGui.TextUnformatted(ex.ToString()); - } - - ImGui.PopStyleVar(); - - ImGui.EndChild(); - } - - private int MessageBoxWDetour(IntPtr hwnd, string text, string caption, NativeFunctions.MessageBoxType type) - { - Log.Information("[DATAHOOK] {0} {1} {2} {3}", hwnd, text, caption, type); - - var result = this.messageBoxMinHook.Original(hwnd, "Cause Access Violation?", caption, NativeFunctions.MessageBoxType.YesNo); - - if (result == (int)User32.MessageBoxResult.IDYES) - { - Marshal.ReadByte(IntPtr.Zero); - } - - return result; - } - - private void DrawServerOpCode() - { - ImGui.TextUnformatted(this.serverOpString); - } - - private void DrawAddress() - { - ImGui.InputText(".text sig", ref this.inputSig, 400); - if (ImGui.Button("Resolve")) - { - try - { - var sigScanner = Service.Get(); - this.sigResult = sigScanner.ScanText(this.inputSig); - } - catch (KeyNotFoundException) - { - this.sigResult = new IntPtr(-1); + Service.Get().PrintError($"/xldata: Invalid data type {dataKind}"); } } - ImGui.Text($"Result: {this.sigResult.ToInt64():X}"); - ImGui.SameLine(); - if (ImGui.Button($"C{this.copyButtonIndex++}")) - ImGui.SetClipboardText(this.sigResult.ToInt64().ToString("x")); - - foreach (var debugScannedValue in BaseAddressResolver.DebugScannedValues) + /// + /// Draw the window via ImGui. + /// + public override void Draw() { - ImGui.TextUnformatted($"{debugScannedValue.Key}"); - foreach (var valueTuple in debugScannedValue.Value) - { - ImGui.TextUnformatted( - $" {valueTuple.ClassName} - 0x{valueTuple.Address.ToInt64():x}"); - ImGui.SameLine(); + this.copyButtonIndex = 0; - if (ImGui.Button($"C##copyAddress{this.copyButtonIndex++}")) - ImGui.SetClipboardText(valueTuple.Address.ToInt64().ToString("x")); - } - } - } - - private void DrawObjectTable() - { - var chatGui = Service.Get(); - var clientState = Service.Get(); - var framework = Service.Get(); - var gameGui = Service.Get(); - var objectTable = Service.Get(); - - var stateString = string.Empty; - - if (clientState.LocalPlayer == null) - { - ImGui.TextUnformatted("LocalPlayer null."); - } - else - { - stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n"; - stateString += $"ObjectTableLen: {objectTable.Length}\n"; - stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n"; - stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.GameData.Name : clientState.LocalPlayer.CurrentWorld.Id.ToString())}\n"; - stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.GameData.Name : clientState.LocalPlayer.HomeWorld.Id.ToString())}\n"; - stateString += $"LocalCID: {clientState.LocalContentId:X}\n"; - stateString += $"LastLinkedItem: {chatGui.LastLinkedItemId}\n"; - stateString += $"TerritoryType: {clientState.TerritoryType}\n\n"; - - ImGui.TextUnformatted(stateString); - - ImGui.Checkbox("Draw characters on screen", ref this.drawCharas); - ImGui.SliderFloat("Draw Distance", ref this.maxCharaDrawDistance, 2f, 40f); - - for (var i = 0; i < objectTable.Length; i++) - { - var obj = objectTable[i]; - - if (obj == null) - continue; - - this.PrintGameObject(obj, i.ToString()); - - if (this.drawCharas && gameGui.WorldToScreen(obj.Position, out var screenCoords)) - { - // So, while WorldToScreen will return false if the point is off of game client screen, to - // to avoid performance issues, we have to manually determine if creating a window would - // produce a new viewport, and skip rendering it if so - var objectText = $"{obj.Address.ToInt64():X}:{obj.ObjectId:X}[{i}] - {obj.ObjectKind} - {obj.Name}"; - - var screenPos = ImGui.GetMainViewport().Pos; - var screenSize = ImGui.GetMainViewport().Size; - - var windowSize = ImGui.CalcTextSize(objectText); - - // Add some extra safety padding - windowSize.X += ImGui.GetStyle().WindowPadding.X + 10; - windowSize.Y += ImGui.GetStyle().WindowPadding.Y + 10; - - if (screenCoords.X + windowSize.X > screenPos.X + screenSize.X || - screenCoords.Y + windowSize.Y > screenPos.Y + screenSize.Y) - continue; - - if (obj.YalmDistanceX > this.maxCharaDrawDistance) - continue; - - ImGui.SetNextWindowPos(new Vector2(screenCoords.X, screenCoords.Y)); - - ImGui.SetNextWindowBgAlpha(Math.Max(1f - (obj.YalmDistanceX / this.maxCharaDrawDistance), 0.2f)); - if (ImGui.Begin( - $"Actor{i}##ActorWindow{i}", - ImGuiWindowFlags.NoDecoration | - ImGuiWindowFlags.AlwaysAutoResize | - ImGuiWindowFlags.NoSavedSettings | - ImGuiWindowFlags.NoMove | - ImGuiWindowFlags.NoMouseInputs | - ImGuiWindowFlags.NoDocking | - ImGuiWindowFlags.NoFocusOnAppearing | - ImGuiWindowFlags.NoNav)) - ImGui.Text(objectText); - ImGui.End(); - } - } - } - } - - private void DrawFateTable() - { - var fateTable = Service.Get(); - var framework = Service.Get(); - - var stateString = string.Empty; - if (fateTable.Length == 0) - { - ImGui.TextUnformatted("No fates or data not ready."); - } - else - { - stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n"; - stateString += $"FateTableLen: {fateTable.Length}\n"; - - ImGui.TextUnformatted(stateString); - - for (var i = 0; i < fateTable.Length; i++) - { - var fate = fateTable[i]; - if (fate == null) - continue; - - var fateString = $"{fate.Address.ToInt64():X}:[{i}]" + - $" - Lv.{fate.Level} {fate.Name} ({fate.Progress}%)" + - $" - X{fate.Position.X} Y{fate.Position.Y} Z{fate.Position.Z}" + - $" - Territory {(this.resolveGameData ? (fate.TerritoryType.GameData?.Name ?? fate.TerritoryType.Id.ToString()) : fate.TerritoryType.Id.ToString())}\n"; - - fateString += $" StartTimeEpoch: {fate.StartTimeEpoch}" + - $" - Duration: {fate.Duration}" + - $" - State: {fate.State}" + - $" - GameData name: {(this.resolveGameData ? (fate.GameData?.Name ?? fate.FateId.ToString()) : fate.FateId.ToString())}"; - - ImGui.TextUnformatted(fateString); - ImGui.SameLine(); - if (ImGui.Button("C")) - { - ImGui.SetClipboardText(fate.Address.ToString("X")); - } - } - } - } - - private void DrawFontTest() - { - var specialChars = string.Empty; - - for (var i = 0xE020; i <= 0xE0DB; i++) - specialChars += $"0x{i:X} - {(SeIconChar)i} - {(char)i}\n"; - - ImGui.TextUnformatted(specialChars); - - foreach (var fontAwesomeIcon in Enum.GetValues(typeof(FontAwesomeIcon)).Cast()) - { - ImGui.Text(((int)fontAwesomeIcon.ToIconChar()).ToString("X") + " - "); + // Main window + if (ImGui.Button("Force Reload")) + this.Load(); + ImGui.SameLine(); + var copy = ImGui.Button("Copy all"); ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.Text(fontAwesomeIcon.ToIconString()); - ImGui.PopFont(); - } - } - - private void DrawPartyList() - { - var partyList = Service.Get(); - - ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); - - ImGui.Text($"GroupManager: {partyList.GroupManagerAddress.ToInt64():X}"); - ImGui.Text($"GroupList: {partyList.GroupListAddress.ToInt64():X}"); - ImGui.Text($"AllianceList: {partyList.AllianceListAddress.ToInt64():X}"); - - ImGui.Text($"{partyList.Length} Members"); - - for (var i = 0; i < partyList.Length; i++) - { - var member = partyList[i]; - if (member == null) + var currentKindIndex = (int)this.currentKind; + if (ImGui.Combo("Data kind", ref currentKindIndex, this.dataKindNames, this.dataKindNames.Length)) { - ImGui.Text($"[{i}] was null"); - continue; + this.currentKind = (DataKind)currentKindIndex; } - ImGui.Text($"[{i}] {member.Address.ToInt64():X} - {member.Name} - {member.GameObject.ObjectId}"); - if (this.resolveObjects) + ImGui.Checkbox("Resolve GameData", ref this.resolveGameData); + + ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.HorizontalScrollbar); + + if (copy) + ImGui.LogToClipboard(); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0)); + + try { - var actor = member.GameObject; - if (actor == null) + if (this.wasReady) { - ImGui.Text("Actor was null"); + switch (this.currentKind) + { + case DataKind.Server_OpCode: + this.DrawServerOpCode(); + break; + + case DataKind.Address: + this.DrawAddress(); + break; + + case DataKind.Object_Table: + this.DrawObjectTable(); + break; + + case DataKind.Fate_Table: + this.DrawFateTable(); + break; + + case DataKind.Font_Test: + this.DrawFontTest(); + break; + + case DataKind.Party_List: + this.DrawPartyList(); + break; + + case DataKind.Buddy_List: + this.DrawBuddyList(); + break; + + case DataKind.Plugin_IPC: + this.DrawPluginIPC(); + break; + + case DataKind.Condition: + this.DrawCondition(); + break; + + case DataKind.Gauge: + this.DrawGauge(); + break; + + case DataKind.Command: + this.DrawCommand(); + break; + + case DataKind.Addon: + this.DrawAddon(); + break; + + case DataKind.Addon_Inspector: + this.DrawAddonInspector(); + break; + + case DataKind.AtkArrayData_Browser: + this.DrawAtkArrayDataBrowser(); + break; + + case DataKind.StartInfo: + this.DrawStartInfo(); + break; + + case DataKind.Target: + this.DrawTarget(); + break; + + case DataKind.Toast: + this.DrawToast(); + break; + + case DataKind.FlyText: + this.DrawFlyText(); + break; + + case DataKind.ImGui: + this.DrawImGui(); + break; + + case DataKind.Tex: + this.DrawTex(); + break; + + case DataKind.KeyState: + this.DrawKeyState(); + break; + + case DataKind.Gamepad: + this.DrawGamepad(); + break; + + case DataKind.Configuration: + this.DrawConfiguration(); + break; + + case DataKind.TaskSched: + this.DrawTaskSched(); + break; + + case DataKind.Hook: + this.DrawHook(); + break; + } } else { - this.PrintGameObject(actor, "-"); + ImGui.TextUnformatted("Data not ready."); + } + } + catch (Exception ex) + { + ImGui.TextUnformatted(ex.ToString()); + } + + ImGui.PopStyleVar(); + + ImGui.EndChild(); + } + + private int MessageBoxWDetour(IntPtr hwnd, string text, string caption, NativeFunctions.MessageBoxType type) + { + Log.Information("[DATAHOOK] {0} {1} {2} {3}", hwnd, text, caption, type); + + var result = this.messageBoxMinHook.Original(hwnd, "Cause Access Violation?", caption, NativeFunctions.MessageBoxType.YesNo); + + if (result == (int)User32.MessageBoxResult.IDYES) + { + Marshal.ReadByte(IntPtr.Zero); + } + + return result; + } + + private void DrawServerOpCode() + { + ImGui.TextUnformatted(this.serverOpString); + } + + private void DrawAddress() + { + ImGui.InputText(".text sig", ref this.inputSig, 400); + if (ImGui.Button("Resolve")) + { + try + { + var sigScanner = Service.Get(); + this.sigResult = sigScanner.ScanText(this.inputSig); + } + catch (KeyNotFoundException) + { + this.sigResult = new IntPtr(-1); + } + } + + ImGui.Text($"Result: {this.sigResult.ToInt64():X}"); + ImGui.SameLine(); + if (ImGui.Button($"C{this.copyButtonIndex++}")) + ImGui.SetClipboardText(this.sigResult.ToInt64().ToString("x")); + + foreach (var debugScannedValue in BaseAddressResolver.DebugScannedValues) + { + ImGui.TextUnformatted($"{debugScannedValue.Key}"); + foreach (var valueTuple in debugScannedValue.Value) + { + ImGui.TextUnformatted( + $" {valueTuple.ClassName} - 0x{valueTuple.Address.ToInt64():x}"); + ImGui.SameLine(); + + if (ImGui.Button($"C##copyAddress{this.copyButtonIndex++}")) + ImGui.SetClipboardText(valueTuple.Address.ToInt64().ToString("x")); } } } - } - private void DrawBuddyList() - { - var buddyList = Service.Get(); - - ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); - - ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}"); + private void DrawObjectTable() { - var member = buddyList.CompanionBuddy; - if (member == null) + var chatGui = Service.Get(); + var clientState = Service.Get(); + var framework = Service.Get(); + var gameGui = Service.Get(); + var objectTable = Service.Get(); + + var stateString = string.Empty; + + if (clientState.LocalPlayer == null) { - ImGui.Text("[Companion] null"); + ImGui.TextUnformatted("LocalPlayer null."); } else { - ImGui.Text($"[Companion] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); + stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n"; + stateString += $"ObjectTableLen: {objectTable.Length}\n"; + stateString += $"LocalPlayerName: {clientState.LocalPlayer.Name}\n"; + stateString += $"CurrentWorldName: {(this.resolveGameData ? clientState.LocalPlayer.CurrentWorld.GameData.Name : clientState.LocalPlayer.CurrentWorld.Id.ToString())}\n"; + stateString += $"HomeWorldName: {(this.resolveGameData ? clientState.LocalPlayer.HomeWorld.GameData.Name : clientState.LocalPlayer.HomeWorld.Id.ToString())}\n"; + stateString += $"LocalCID: {clientState.LocalContentId:X}\n"; + stateString += $"LastLinkedItem: {chatGui.LastLinkedItemId}\n"; + stateString += $"TerritoryType: {clientState.TerritoryType}\n\n"; + + ImGui.TextUnformatted(stateString); + + ImGui.Checkbox("Draw characters on screen", ref this.drawCharas); + ImGui.SliderFloat("Draw Distance", ref this.maxCharaDrawDistance, 2f, 40f); + + for (var i = 0; i < objectTable.Length; i++) + { + var obj = objectTable[i]; + + if (obj == null) + continue; + + this.PrintGameObject(obj, i.ToString()); + + if (this.drawCharas && gameGui.WorldToScreen(obj.Position, out var screenCoords)) + { + // So, while WorldToScreen will return false if the point is off of game client screen, to + // to avoid performance issues, we have to manually determine if creating a window would + // produce a new viewport, and skip rendering it if so + var objectText = $"{obj.Address.ToInt64():X}:{obj.ObjectId:X}[{i}] - {obj.ObjectKind} - {obj.Name}"; + + var screenPos = ImGui.GetMainViewport().Pos; + var screenSize = ImGui.GetMainViewport().Size; + + var windowSize = ImGui.CalcTextSize(objectText); + + // Add some extra safety padding + windowSize.X += ImGui.GetStyle().WindowPadding.X + 10; + windowSize.Y += ImGui.GetStyle().WindowPadding.Y + 10; + + if (screenCoords.X + windowSize.X > screenPos.X + screenSize.X || + screenCoords.Y + windowSize.Y > screenPos.Y + screenSize.Y) + continue; + + if (obj.YalmDistanceX > this.maxCharaDrawDistance) + continue; + + ImGui.SetNextWindowPos(new Vector2(screenCoords.X, screenCoords.Y)); + + ImGui.SetNextWindowBgAlpha(Math.Max(1f - (obj.YalmDistanceX / this.maxCharaDrawDistance), 0.2f)); + if (ImGui.Begin( + $"Actor{i}##ActorWindow{i}", + ImGuiWindowFlags.NoDecoration | + ImGuiWindowFlags.AlwaysAutoResize | + ImGuiWindowFlags.NoSavedSettings | + ImGuiWindowFlags.NoMove | + ImGuiWindowFlags.NoMouseInputs | + ImGuiWindowFlags.NoDocking | + ImGuiWindowFlags.NoFocusOnAppearing | + ImGuiWindowFlags.NoNav)) + ImGui.Text(objectText); + ImGui.End(); + } + } + } + } + + private void DrawFateTable() + { + var fateTable = Service.Get(); + var framework = Service.Get(); + + var stateString = string.Empty; + if (fateTable.Length == 0) + { + ImGui.TextUnformatted("No fates or data not ready."); + } + else + { + stateString += $"FrameworkBase: {framework.Address.BaseAddress.ToInt64():X}\n"; + stateString += $"FateTableLen: {fateTable.Length}\n"; + + ImGui.TextUnformatted(stateString); + + for (var i = 0; i < fateTable.Length; i++) + { + var fate = fateTable[i]; + if (fate == null) + continue; + + var fateString = $"{fate.Address.ToInt64():X}:[{i}]" + + $" - Lv.{fate.Level} {fate.Name} ({fate.Progress}%)" + + $" - X{fate.Position.X} Y{fate.Position.Y} Z{fate.Position.Z}" + + $" - Territory {(this.resolveGameData ? (fate.TerritoryType.GameData?.Name ?? fate.TerritoryType.Id.ToString()) : fate.TerritoryType.Id.ToString())}\n"; + + fateString += $" StartTimeEpoch: {fate.StartTimeEpoch}" + + $" - Duration: {fate.Duration}" + + $" - State: {fate.State}" + + $" - GameData name: {(this.resolveGameData ? (fate.GameData?.Name ?? fate.FateId.ToString()) : fate.FateId.ToString())}"; + + ImGui.TextUnformatted(fateString); + ImGui.SameLine(); + if (ImGui.Button("C")) + { + ImGui.SetClipboardText(fate.Address.ToString("X")); + } + } + } + } + + private void DrawFontTest() + { + var specialChars = string.Empty; + + for (var i = 0xE020; i <= 0xE0DB; i++) + specialChars += $"0x{i:X} - {(SeIconChar)i} - {(char)i}\n"; + + ImGui.TextUnformatted(specialChars); + + foreach (var fontAwesomeIcon in Enum.GetValues(typeof(FontAwesomeIcon)).Cast()) + { + ImGui.Text(((int)fontAwesomeIcon.ToIconChar()).ToString("X") + " - "); + ImGui.SameLine(); + + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(fontAwesomeIcon.ToIconString()); + ImGui.PopFont(); + } + } + + private void DrawPartyList() + { + var partyList = Service.Get(); + + ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); + + ImGui.Text($"GroupManager: {partyList.GroupManagerAddress.ToInt64():X}"); + ImGui.Text($"GroupList: {partyList.GroupListAddress.ToInt64():X}"); + ImGui.Text($"AllianceList: {partyList.AllianceListAddress.ToInt64():X}"); + + ImGui.Text($"{partyList.Length} Members"); + + for (var i = 0; i < partyList.Length; i++) + { + var member = partyList[i]; + if (member == null) + { + ImGui.Text($"[{i}] was null"); + continue; + } + + ImGui.Text($"[{i}] {member.Address.ToInt64():X} - {member.Name} - {member.GameObject.ObjectId}"); if (this.resolveObjects) { - var gameObject = member.GameObject; - if (gameObject == null) + var actor = member.GameObject; + if (actor == null) { - ImGui.Text("GameObject was null"); + ImGui.Text("Actor was null"); } else { - this.PrintGameObject(gameObject, "-"); + this.PrintGameObject(actor, "-"); } } } } + private void DrawBuddyList() { - var member = buddyList.PetBuddy; - if (member == null) - { - ImGui.Text("[Pet] null"); - } - else - { - ImGui.Text($"[Pet] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); - if (this.resolveObjects) - { - var gameObject = member.GameObject; - if (gameObject == null) - { - ImGui.Text("GameObject was null"); - } - else - { - this.PrintGameObject(gameObject, "-"); - } - } - } - } + var buddyList = Service.Get(); - { - var count = buddyList.Length; - if (count == 0) + ImGui.Checkbox("Resolve Actors", ref this.resolveObjects); + + ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}"); { - ImGui.Text("[BattleBuddy] None present"); - } - else - { - for (var i = 0; i < count; i++) + var member = buddyList.CompanionBuddy; + if (member == null) { - var member = buddyList[i]; - ImGui.Text($"[BattleBuddy] [{i}] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); + ImGui.Text("[Companion] null"); + } + else + { + ImGui.Text($"[Companion] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); if (this.resolveObjects) { var gameObject = member.GameObject; @@ -670,1020 +619,1072 @@ internal class DataWindow : Window } } } - } - } - private void DrawPluginIPC() - { - if (this.ipcPub == null) - { - this.ipcPub = new CallGatePubSub("dataDemo1"); - - this.ipcPub.RegisterAction((msg) => { - Log.Information($"Data action was called: {msg}"); - }); - - this.ipcPub.RegisterFunc((msg) => - { - Log.Information($"Data func was called: {msg}"); - return Guid.NewGuid().ToString(); - }); - } - - if (this.ipcSub == null) - { - this.ipcSub = new CallGatePubSub("dataDemo1"); - this.ipcSub.Subscribe((msg) => - { - Log.Information("PONG1"); - }); - this.ipcSub.Subscribe((msg) => - { - Log.Information("PONG2"); - }); - this.ipcSub.Subscribe((msg) => - { - throw new Exception("PONG3"); - }); - } - - if (ImGui.Button("PING")) - { - this.ipcPub.SendMessage("PING"); - } - - if (ImGui.Button("Action")) - { - this.ipcSub.InvokeAction("button1"); - } - - if (ImGui.Button("Func")) - { - this.callGateResponse = this.ipcSub.InvokeFunc("button2"); - } - - if (!this.callGateResponse.IsNullOrEmpty()) - ImGui.Text($"Response: {this.callGateResponse}"); - } - - private void DrawCondition() - { - var condition = Service.Get(); - -#if DEBUG - ImGui.Text($"ptr: 0x{condition.Address.ToInt64():X}"); -#endif - - ImGui.Text("Current Conditions:"); - ImGui.Separator(); - - var didAny = false; - - for (var i = 0; i < Condition.MaxConditionEntries; i++) - { - var typedCondition = (ConditionFlag)i; - var cond = condition[typedCondition]; - - if (!cond) continue; - - didAny = true; - - ImGui.Text($"ID: {i} Enum: {typedCondition}"); - } - - if (!didAny) - ImGui.Text("None. Talk to a shop NPC or visit a market board to find out more!!!!!!!"); - } - - private void DrawGauge() - { - var clientState = Service.Get(); - var jobGauges = Service.Get(); - - var player = clientState.LocalPlayer; - if (player == null) - { - ImGui.Text("Player is not present"); - return; - } - - var jobID = player.ClassJob.Id; - if (jobID == 19) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.OathGauge)}: {gauge.OathGauge}"); - } - else if (jobID == 20) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Chakra)}: {gauge.Chakra}"); - } - else if (jobID == 21) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.BeastGauge)}: {gauge.BeastGauge}"); - } - else if (jobID == 22) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.BOTDTimer)}: {gauge.BOTDTimer}"); - ImGui.Text($"{nameof(gauge.BOTDState)}: {gauge.BOTDState}"); - ImGui.Text($"{nameof(gauge.EyeCount)}: {gauge.EyeCount}"); - } - else if (jobID == 23) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.SongTimer)}: {gauge.SongTimer}"); - ImGui.Text($"{nameof(gauge.Repertoire)}: {gauge.Repertoire}"); - ImGui.Text($"{nameof(gauge.SoulVoice)}: {gauge.SoulVoice}"); - ImGui.Text($"{nameof(gauge.Song)}: {gauge.Song}"); - } - else if (jobID == 24) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.LilyTimer)}: {gauge.LilyTimer}"); - ImGui.Text($"{nameof(gauge.Lily)}: {gauge.Lily}"); - ImGui.Text($"{nameof(gauge.BloodLily)}: {gauge.BloodLily}"); - } - else if (jobID == 25) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.EnochianTimer)}: {gauge.EnochianTimer}"); - ImGui.Text($"{nameof(gauge.ElementTimeRemaining)}: {gauge.ElementTimeRemaining}"); - ImGui.Text($"{nameof(gauge.PolyglotStacks)}: {gauge.PolyglotStacks}"); - ImGui.Text($"{nameof(gauge.UmbralHearts)}: {gauge.UmbralHearts}"); - ImGui.Text($"{nameof(gauge.UmbralIceStacks)}: {gauge.UmbralIceStacks}"); - ImGui.Text($"{nameof(gauge.AstralFireStacks)}: {gauge.AstralFireStacks}"); - ImGui.Text($"{nameof(gauge.InUmbralIce)}: {gauge.InUmbralIce}"); - ImGui.Text($"{nameof(gauge.InAstralFire)}: {gauge.InAstralFire}"); - ImGui.Text($"{nameof(gauge.IsEnochianActive)}: {gauge.IsEnochianActive}"); - } - else if (jobID == 27) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.TimerRemaining)}: {gauge.TimerRemaining}"); - ImGui.Text($"{nameof(gauge.ReturnSummon)}: {gauge.ReturnSummon}"); - ImGui.Text($"{nameof(gauge.ReturnSummonGlam)}: {gauge.ReturnSummonGlam}"); - ImGui.Text($"{nameof(gauge.AetherFlags)}: {gauge.AetherFlags}"); - ImGui.Text($"{nameof(gauge.IsPhoenixReady)}: {gauge.IsPhoenixReady}"); - ImGui.Text($"{nameof(gauge.IsBahamutReady)}: {gauge.IsBahamutReady}"); - ImGui.Text($"{nameof(gauge.HasAetherflowStacks)}: {gauge.HasAetherflowStacks}"); - } - else if (jobID == 28) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Aetherflow)}: {gauge.Aetherflow}"); - ImGui.Text($"{nameof(gauge.FairyGauge)}: {gauge.FairyGauge}"); - ImGui.Text($"{nameof(gauge.SeraphTimer)}: {gauge.SeraphTimer}"); - ImGui.Text($"{nameof(gauge.DismissedFairy)}: {gauge.DismissedFairy}"); - } - else if (jobID == 30) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.HutonTimer)}: {gauge.HutonTimer}"); - ImGui.Text($"{nameof(gauge.Ninki)}: {gauge.Ninki}"); - ImGui.Text($"{nameof(gauge.HutonManualCasts)}: {gauge.HutonManualCasts}"); - } - else if (jobID == 31) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.OverheatTimeRemaining)}: {gauge.OverheatTimeRemaining}"); - ImGui.Text($"{nameof(gauge.SummonTimeRemaining)}: {gauge.SummonTimeRemaining}"); - ImGui.Text($"{nameof(gauge.Heat)}: {gauge.Heat}"); - ImGui.Text($"{nameof(gauge.Battery)}: {gauge.Battery}"); - ImGui.Text($"{nameof(gauge.LastSummonBatteryPower)}: {gauge.LastSummonBatteryPower}"); - ImGui.Text($"{nameof(gauge.IsOverheated)}: {gauge.IsOverheated}"); - ImGui.Text($"{nameof(gauge.IsRobotActive)}: {gauge.IsRobotActive}"); - } - else if (jobID == 32) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Blood)}: {gauge.Blood}"); - ImGui.Text($"{nameof(gauge.DarksideTimeRemaining)}: {gauge.DarksideTimeRemaining}"); - ImGui.Text($"{nameof(gauge.ShadowTimeRemaining)}: {gauge.ShadowTimeRemaining}"); - ImGui.Text($"{nameof(gauge.HasDarkArts)}: {gauge.HasDarkArts}"); - } - else if (jobID == 33) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.DrawnCard)}: {gauge.DrawnCard}"); - foreach (var seal in Enum.GetValues(typeof(SealType)).Cast()) - { - var sealName = Enum.GetName(typeof(SealType), seal); - ImGui.Text($"{nameof(gauge.ContainsSeal)}({sealName}): {gauge.ContainsSeal(seal)}"); - } - } - else if (jobID == 34) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Kenki)}: {gauge.Kenki}"); - ImGui.Text($"{nameof(gauge.MeditationStacks)}: {gauge.MeditationStacks}"); - ImGui.Text($"{nameof(gauge.Sen)}: {gauge.Sen}"); - ImGui.Text($"{nameof(gauge.HasSetsu)}: {gauge.HasSetsu}"); - ImGui.Text($"{nameof(gauge.HasGetsu)}: {gauge.HasGetsu}"); - ImGui.Text($"{nameof(gauge.HasKa)}: {gauge.HasKa}"); - } - else if (jobID == 35) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.WhiteMana)}: {gauge.WhiteMana}"); - ImGui.Text($"{nameof(gauge.BlackMana)}: {gauge.BlackMana}"); - } - else if (jobID == 37) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Ammo)}: {gauge.Ammo}"); - ImGui.Text($"{nameof(gauge.MaxTimerDuration)}: {gauge.MaxTimerDuration}"); - ImGui.Text($"{nameof(gauge.AmmoComboStep)}: {gauge.AmmoComboStep}"); - } - else if (jobID == 38) - { - var gauge = jobGauges.Get(); - ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); - ImGui.Text($"{nameof(gauge.Feathers)}: {gauge.Feathers}"); - ImGui.Text($"{nameof(gauge.Esprit)}: {gauge.Esprit}"); - ImGui.Text($"{nameof(gauge.CompletedSteps)}: {gauge.CompletedSteps}"); - ImGui.Text($"{nameof(gauge.NextStep)}: {gauge.NextStep}"); - ImGui.Text($"{nameof(gauge.IsDancing)}: {gauge.IsDancing}"); - } - else - { - ImGui.Text("No supported gauge exists for this job."); - } - } - - private void DrawCommand() - { - var commandManager = Service.Get(); - - foreach (var command in commandManager.Commands) - { - ImGui.Text($"{command.Key}\n -> {command.Value.HelpMessage}\n -> In help: {command.Value.ShowInHelp}\n\n"); - } - } - - private unsafe void DrawAddon() - { - var gameGui = Service.Get(); - - ImGui.InputText("Addon name", ref this.inputAddonName, 256); - ImGui.InputInt("Addon Index", ref this.inputAddonIndex); - - if (this.inputAddonName.IsNullOrEmpty()) - return; - - var address = gameGui.GetAddonByName(this.inputAddonName, this.inputAddonIndex); - - if (address == IntPtr.Zero) - { - ImGui.Text("Null"); - return; - } - - var addon = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)address; - var name = MemoryHelper.ReadStringNullTerminated((IntPtr)addon->Name); - ImGui.TextUnformatted($"{name} - 0x{address.ToInt64():x}\n v:{addon->IsVisible} x:{addon->X} y:{addon->Y} s:{addon->Scale}, w:{addon->RootNode->Width}, h:{addon->RootNode->Height}"); - - if (ImGui.Button("Find Agent")) - { - this.findAgentInterfacePtr = gameGui.FindAgentInterface(address); - } - - if (this.findAgentInterfacePtr != IntPtr.Zero) - { - ImGui.TextUnformatted($"Agent: 0x{this.findAgentInterfacePtr.ToInt64():x}"); - ImGui.SameLine(); - - if (ImGui.Button("C")) - ImGui.SetClipboardText(this.findAgentInterfacePtr.ToInt64().ToString("x")); - } - } - - private void DrawAddonInspector() - { - this.addonInspector ??= new UIDebug(); - this.addonInspector.Draw(); - } - - private unsafe void DrawAtkArrayDataBrowser() - { - var fontWidth = ImGui.CalcTextSize("A").X; - var fontHeight = ImGui.GetTextLineHeightWithSpacing(); - var uiModule = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUiModule(); - - if (uiModule == null) - { - ImGui.Text("UIModule unavailable."); - return; - } - - var atkArrayDataHolder = &uiModule->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder; - - if (ImGui.BeginTabBar("AtkArrayDataBrowserTabBar")) - { - if (ImGui.BeginTabItem($"NumberArrayData [{atkArrayDataHolder->NumberArrayCount}]")) - { - if (ImGui.BeginTable("NumberArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) + var member = buddyList.PetBuddy; + if (member == null) { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var numberArrayIndex = 0; numberArrayIndex < atkArrayDataHolder->NumberArrayCount; numberArrayIndex++) + ImGui.Text("[Pet] null"); + } + else + { + ImGui.Text($"[Pet] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); + if (this.resolveObjects) { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayIndex} [{numberArrayIndex * 8:X}]"); - ImGui.TableNextColumn(); - var numberArrayData = atkArrayDataHolder->NumberArrays[numberArrayIndex]; - if (numberArrayData != null) + var gameObject = member.GameObject; + if (gameObject == null) { - ImGui.Text($"{numberArrayData->AtkArrayData.Size}"); - ImGui.TableNextColumn(); - if (ImGui.TreeNodeEx($"{(long)numberArrayData:X}###{numberArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) - { - ImGui.NewLine(); - var tableHeight = Math.Min(40, numberArrayData->AtkArrayData.Size + 4); - if (ImGui.BeginTable($"NumberArrayDataTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); - ImGui.TableSetupColumn("Hex", ImGuiTableColumnFlags.WidthFixed, fontWidth * 9); - ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, fontWidth * 12); - ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, fontWidth * 20); - ImGui.TableHeadersRow(); - for (var numberIndex = 0; numberIndex < numberArrayData->AtkArrayData.Size; numberIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{numberIndex}"); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayData->IntArray[numberIndex]:X}"); - ImGui.TableNextColumn(); - ImGui.Text($"{numberArrayData->IntArray[numberIndex]}"); - ImGui.TableNextColumn(); - ImGui.Text($"{*(float*)&numberArrayData->IntArray[numberIndex]}"); - } - - ImGui.EndTable(); - } - - ImGui.TreePop(); - } + ImGui.Text("GameObject was null"); } else { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); + this.PrintGameObject(gameObject, "-"); } } - - ImGui.EndTable(); } - - ImGui.EndTabItem(); } - if (ImGui.BeginTabItem($"StringArrayData [{atkArrayDataHolder->StringArrayCount}]")) { - if (ImGui.BeginTable("StringArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) + var count = buddyList.Length; + if (count == 0) { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); - ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var stringArrayIndex = 0; stringArrayIndex < atkArrayDataHolder->StringArrayCount; stringArrayIndex++) + ImGui.Text("[BattleBuddy] None present"); + } + else + { + for (var i = 0; i < count; i++) { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{stringArrayIndex} [{stringArrayIndex * 8:X}]"); - ImGui.TableNextColumn(); - var stringArrayData = atkArrayDataHolder->StringArrays[stringArrayIndex]; - if (stringArrayData != null) + var member = buddyList[i]; + ImGui.Text($"[BattleBuddy] [{i}] {member.Address.ToInt64():X} - {member.ObjectId} - {member.DataID}"); + if (this.resolveObjects) { - ImGui.Text($"{stringArrayData->AtkArrayData.Size}"); - ImGui.TableNextColumn(); - if (ImGui.TreeNodeEx($"{(long)stringArrayData:X}###{stringArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) + var gameObject = member.GameObject; + if (gameObject == null) { - ImGui.NewLine(); - var tableHeight = Math.Min(40, stringArrayData->AtkArrayData.Size + 4); - if (ImGui.BeginTable($"StringArrayDataTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) - { - ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); - ImGui.TableSetupColumn("String", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableHeadersRow(); - for (var stringIndex = 0; stringIndex < stringArrayData->AtkArrayData.Size; stringIndex++) - { - ImGui.TableNextRow(); - ImGui.TableNextColumn(); - ImGui.Text($"{stringIndex}"); - ImGui.TableNextColumn(); - if (stringArrayData->StringArray[stringIndex] != null) - { - ImGui.Text($"{MemoryHelper.ReadSeStringNullTerminated(new IntPtr(stringArrayData->StringArray[stringIndex]))}"); - } - else - { - ImGui.TextDisabled("--"); - } - } - - ImGui.EndTable(); - } - - ImGui.TreePop(); + ImGui.Text("GameObject was null"); + } + else + { + this.PrintGameObject(gameObject, "-"); } } - else - { - ImGui.TextDisabled("--"); - ImGui.TableNextColumn(); - ImGui.TextDisabled("--"); - } } - - ImGui.EndTable(); } - - ImGui.EndTabItem(); - } - - ImGui.EndTabBar(); - } - } - - private void DrawStartInfo() - { - var startInfo = Service.Get(); - - ImGui.Text(JsonConvert.SerializeObject(startInfo, Formatting.Indented)); - } - - private void DrawTarget() - { - var clientState = Service.Get(); - var targetMgr = Service.Get(); - - if (targetMgr.Target != null) - { - this.PrintGameObject(targetMgr.Target, "CurrentTarget"); - - ImGui.Text("Target"); - Util.ShowObject(targetMgr.Target); - - var tot = targetMgr.Target.TargetObject; - if (tot != null) - { - ImGuiHelpers.ScaledDummy(10); - - ImGui.Text("ToT"); - Util.ShowObject(tot); - } - - ImGuiHelpers.ScaledDummy(10); - } - - if (targetMgr.FocusTarget != null) - this.PrintGameObject(targetMgr.FocusTarget, "FocusTarget"); - - if (targetMgr.MouseOverTarget != null) - this.PrintGameObject(targetMgr.MouseOverTarget, "MouseOverTarget"); - - if (targetMgr.PreviousTarget != null) - this.PrintGameObject(targetMgr.PreviousTarget, "PreviousTarget"); - - if (targetMgr.SoftTarget != null) - this.PrintGameObject(targetMgr.SoftTarget, "SoftTarget"); - - if (ImGui.Button("Clear CT")) - targetMgr.ClearTarget(); - - if (ImGui.Button("Clear FT")) - targetMgr.ClearFocusTarget(); - - var localPlayer = clientState.LocalPlayer; - - if (localPlayer != null) - { - if (ImGui.Button("Set CT")) - targetMgr.SetTarget(localPlayer); - - if (ImGui.Button("Set FT")) - targetMgr.SetFocusTarget(localPlayer); - } - else - { - ImGui.Text("LocalPlayer is null."); - } - } - - private void DrawToast() - { - var toastGui = Service.Get(); - - ImGui.InputText("Toast text", ref this.inputTextToast, 200); - - ImGui.Combo("Toast Position", ref this.toastPosition, new[] { "Bottom", "Top", }, 2); - ImGui.Combo("Toast Speed", ref this.toastSpeed, new[] { "Slow", "Fast", }, 2); - ImGui.Combo("Quest Toast Position", ref this.questToastPosition, new[] { "Centre", "Right", "Left" }, 3); - ImGui.Checkbox("Quest Checkmark", ref this.questToastCheckmark); - ImGui.Checkbox("Quest Play Sound", ref this.questToastSound); - ImGui.InputInt("Quest Icon ID", ref this.questToastIconId); - - ImGuiHelpers.ScaledDummy(new Vector2(10, 10)); - - if (ImGui.Button("Show toast")) - { - toastGui.ShowNormal(this.inputTextToast, new ToastOptions - { - Position = (ToastPosition)this.toastPosition, - Speed = (ToastSpeed)this.toastSpeed, - }); - } - - if (ImGui.Button("Show Quest toast")) - { - toastGui.ShowQuest(this.inputTextToast, new QuestToastOptions - { - Position = (QuestToastPosition)this.questToastPosition, - DisplayCheckmark = this.questToastCheckmark, - IconId = (uint)this.questToastIconId, - PlaySound = this.questToastSound, - }); - } - - if (ImGui.Button("Show Error toast")) - { - toastGui.ShowError(this.inputTextToast); - } - } - - private void DrawFlyText() - { - if (ImGui.BeginCombo("Kind", this.flyKind.ToString())) - { - var names = Enum.GetNames(typeof(FlyTextKind)); - for (var i = 0; i < names.Length; i++) - { - if (ImGui.Selectable($"{names[i]} ({i})")) - this.flyKind = (FlyTextKind)i; - } - - ImGui.EndCombo(); - } - - ImGui.InputText("Text1", ref this.flyText1, 200); - ImGui.InputText("Text2", ref this.flyText2, 200); - - ImGui.InputInt("Val1", ref this.flyVal1); - ImGui.InputInt("Val2", ref this.flyVal2); - - ImGui.InputInt("Icon ID", ref this.flyIcon); - ImGui.ColorEdit4("Color", ref this.flyColor); - ImGui.InputInt("Actor Index", ref this.flyActor); - var sendColor = ImGui.ColorConvertFloat4ToU32(this.flyColor); - - if (ImGui.Button("Send")) - { - Service.Get().AddFlyText( - this.flyKind, - unchecked((uint)this.flyActor), - unchecked((uint)this.flyVal1), - unchecked((uint)this.flyVal2), - this.flyText1, - this.flyText2, - sendColor, - unchecked((uint)this.flyIcon)); - } - } - - private void DrawImGui() - { - var interfaceManager = Service.Get(); - var notifications = Service.Get(); - - ImGui.Text("Monitor count: " + ImGui.GetPlatformIO().Monitors.Size); - ImGui.Text("OverrideGameCursor: " + interfaceManager.OverrideGameCursor); - - ImGui.Button("THIS IS A BUTTON###hoverTestButton"); - interfaceManager.OverrideGameCursor = !ImGui.IsItemHovered(); - - ImGui.Separator(); - - ImGui.TextUnformatted($"WindowSystem.TimeSinceLastAnyFocus: {WindowSystem.TimeSinceLastAnyFocus.TotalMilliseconds:0}ms"); - - ImGui.Separator(); - - if (ImGui.Button("Add random notification")) - { - var rand = new Random(); - - var title = rand.Next(0, 5) switch - { - 0 => "This is a toast", - 1 => "Truly, a toast", - 2 => "I am testing this toast", - 3 => "I hope this looks right", - 4 => "Good stuff", - 5 => "Nice", - _ => null, - }; - - var type = rand.Next(0, 4) switch - { - 0 => Notifications.NotificationType.Error, - 1 => Notifications.NotificationType.Warning, - 2 => Notifications.NotificationType.Info, - 3 => Notifications.NotificationType.Success, - 4 => Notifications.NotificationType.None, - _ => Notifications.NotificationType.None, - }; - - var text = "Bla bla bla bla bla bla bla bla bla bla bla.\nBla bla bla bla bla bla bla bla bla bla bla bla bla bla."; - - notifications.AddNotification(text, title, type); - } - } - - private void DrawTex() - { - var dataManager = Service.Get(); - - ImGui.InputText("Tex Path", ref this.inputTexPath, 255); - ImGui.InputFloat2("UV0", ref this.inputTexUv0); - ImGui.InputFloat2("UV1", ref this.inputTexUv1); - ImGui.InputFloat4("Tint", ref this.inputTintCol); - ImGui.InputFloat2("Scale", ref this.inputTexScale); - - if (ImGui.Button("Load Tex")) - { - try - { - this.debugTex = dataManager.GetImGuiTexture(this.inputTexPath); - this.inputTexScale = new Vector2(this.debugTex.Width, this.debugTex.Height); - } - catch (Exception ex) - { - Log.Error(ex, "Could not load tex."); } } - ImGuiHelpers.ScaledDummy(10); - - if (this.debugTex != null) + private void DrawPluginIPC() { - ImGui.Image(this.debugTex.ImGuiHandle, this.inputTexScale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol); - ImGuiHelpers.ScaledDummy(5); - Util.ShowObject(this.debugTex); - } - } + if (this.ipcPub == null) + { + this.ipcPub = new CallGatePubSub("dataDemo1"); - private void DrawKeyState() - { - var keyState = Service.Get(); + this.ipcPub.RegisterAction((msg) => + { + Log.Information($"Data action was called: {msg}"); + }); - ImGui.Columns(4); + this.ipcPub.RegisterFunc((msg) => + { + Log.Information($"Data func was called: {msg}"); + return Guid.NewGuid().ToString(); + }); + } - var i = 0; - foreach (var vkCode in keyState.GetValidVirtualKeys()) - { - var code = (int)vkCode; - var value = keyState[code]; + if (this.ipcSub == null) + { + this.ipcSub = new CallGatePubSub("dataDemo1"); + this.ipcSub.Subscribe((msg) => + { + Log.Information("PONG1"); + }); + this.ipcSub.Subscribe((msg) => + { + Log.Information("PONG2"); + }); + this.ipcSub.Subscribe((msg) => + { + throw new Exception("PONG3"); + }); + } - ImGui.PushStyleColor(ImGuiCol.Text, value ? ImGuiColors.HealerGreen : ImGuiColors.DPSRed); + if (ImGui.Button("PING")) + { + this.ipcPub.SendMessage("PING"); + } - ImGui.Text($"{vkCode} ({code})"); + if (ImGui.Button("Action")) + { + this.ipcSub.InvokeAction("button1"); + } - ImGui.PopStyleColor(); + if (ImGui.Button("Func")) + { + this.callGateResponse = this.ipcSub.InvokeFunc("button2"); + } - i++; - if (i % 24 == 0) - ImGui.NextColumn(); + if (!this.callGateResponse.IsNullOrEmpty()) + ImGui.Text($"Response: {this.callGateResponse}"); } - ImGui.Columns(1); - } - - private void DrawGamepad() - { - var gamepadState = Service.Get(); - - static void DrawHelper(string text, uint mask, Func resolve) + private void DrawCondition() { - ImGui.Text($"{text} {mask:X4}"); - ImGui.Text($"DPadLeft {resolve(GamepadButtons.DpadLeft)} " + - $"DPadUp {resolve(GamepadButtons.DpadUp)} " + - $"DPadRight {resolve(GamepadButtons.DpadRight)} " + - $"DPadDown {resolve(GamepadButtons.DpadDown)} "); - ImGui.Text($"West {resolve(GamepadButtons.West)} " + - $"North {resolve(GamepadButtons.North)} " + - $"East {resolve(GamepadButtons.East)} " + - $"South {resolve(GamepadButtons.South)} "); - ImGui.Text($"L1 {resolve(GamepadButtons.L1)} " + - $"L2 {resolve(GamepadButtons.L2)} " + - $"R1 {resolve(GamepadButtons.R1)} " + - $"R2 {resolve(GamepadButtons.R2)} "); - ImGui.Text($"Select {resolve(GamepadButtons.Select)} " + - $"Start {resolve(GamepadButtons.Start)} " + - $"L3 {resolve(GamepadButtons.L3)} " + - $"R3 {resolve(GamepadButtons.R3)} "); - } - - ImGui.Text($"GamepadInput 0x{gamepadState.GamepadInputAddress.ToInt64():X}"); + var condition = Service.Get(); #if DEBUG - if (ImGui.IsItemHovered()) - ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); - - if (ImGui.IsItemClicked()) - ImGui.SetClipboardText($"0x{gamepadState.GamepadInputAddress.ToInt64():X}"); + ImGui.Text($"ptr: 0x{condition.Address.ToInt64():X}"); #endif - DrawHelper( - "Buttons Raw", - gamepadState.ButtonsRaw, - gamepadState.Raw); - DrawHelper( - "Buttons Pressed", - gamepadState.ButtonsPressed, - gamepadState.Pressed); - DrawHelper( - "Buttons Repeat", - gamepadState.ButtonsRepeat, - gamepadState.Repeat); - DrawHelper( - "Buttons Released", - gamepadState.ButtonsReleased, - gamepadState.Released); - ImGui.Text($"LeftStickLeft {gamepadState.LeftStickLeft:0.00} " + - $"LeftStickUp {gamepadState.LeftStickUp:0.00} " + - $"LeftStickRight {gamepadState.LeftStickRight:0.00} " + - $"LeftStickDown {gamepadState.LeftStickDown:0.00} "); - ImGui.Text($"RightStickLeft {gamepadState.RightStickLeft:0.00} " + - $"RightStickUp {gamepadState.RightStickUp:0.00} " + - $"RightStickRight {gamepadState.RightStickRight:0.00} " + - $"RightStickDown {gamepadState.RightStickDown:0.00} "); - } + ImGui.Text("Current Conditions:"); + ImGui.Separator(); - private void DrawConfiguration() - { - var config = Service.Get(); - Util.ShowObject(config); - } + var didAny = false; - private void DrawTaskSched() - { - if (ImGui.Button("Clear list")) - { - TaskTracker.Clear(); - } - - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(10); - ImGui.SameLine(); - - if (ImGui.Button("Short Task.Run")) - { - Task.Run(() => { Thread.Sleep(500); }); - } - - ImGui.SameLine(); - - if (ImGui.Button("Task in task(Delay)")) - { - Task.Run(async () => await this.TestTaskInTaskDelay()); - } - - ImGui.SameLine(); - - if (ImGui.Button("Task in task(Sleep)")) - { - Task.Run(async () => await this.TestTaskInTaskSleep()); - } - - ImGui.SameLine(); - - if (ImGui.Button("Faulting task")) - { - Task.Run(() => + for (var i = 0; i < Condition.MaxConditionEntries; i++) { - Thread.Sleep(200); + var typedCondition = (ConditionFlag)i; + var cond = condition[typedCondition]; - string a = null; - a.Contains("dalamud"); - }); - } + if (!cond) continue; - if (ImGui.Button("Drown in tasks")) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Task.Run(() => - { - for (var i = 0; i < 100; i++) - { - Thread.Sleep(1); - } - }); - } - }); - } - }); - } - }); - } - }); - } + didAny = true; - ImGui.SameLine(); - - ImGuiHelpers.ScaledDummy(20); - - // Needed to init the task tracker, if we're not on a debug build - var tracker = Service.GetNullable() ?? Service.Set(); - - for (var i = 0; i < TaskTracker.Tasks.Count; i++) - { - var task = TaskTracker.Tasks[i]; - var subTime = DateTime.Now; - if (task.Task == null) - subTime = task.FinishTime; - - switch (task.Status) - { - case TaskStatus.Created: - case TaskStatus.WaitingForActivation: - case TaskStatus.WaitingToRun: - ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudGrey); - break; - case TaskStatus.Running: - case TaskStatus.WaitingForChildrenToComplete: - ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedBlue); - break; - case TaskStatus.RanToCompletion: - ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedGreen); - break; - case TaskStatus.Canceled: - case TaskStatus.Faulted: - ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudRed); - break; - default: - throw new ArgumentOutOfRangeException(); + ImGui.Text($"ID: {i} Enum: {typedCondition}"); } - if (ImGui.CollapsingHeader($"#{task.Id} - {task.Status} {(subTime - task.StartTime).TotalMilliseconds}ms###task{i}")) + if (!didAny) + ImGui.Text("None. Talk to a shop NPC or visit a market board to find out more!!!!!!!"); + } + + private void DrawGauge() + { + var clientState = Service.Get(); + var jobGauges = Service.Get(); + + var player = clientState.LocalPlayer; + if (player == null) { - task.IsBeingViewed = true; + ImGui.Text("Player is not present"); + return; + } - if (ImGui.Button("CANCEL (May not work)")) + var jobID = player.ClassJob.Id; + if (jobID == 19) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.OathGauge)}: {gauge.OathGauge}"); + } + else if (jobID == 20) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Chakra)}: {gauge.Chakra}"); + } + else if (jobID == 21) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.BeastGauge)}: {gauge.BeastGauge}"); + } + else if (jobID == 22) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.BOTDTimer)}: {gauge.BOTDTimer}"); + ImGui.Text($"{nameof(gauge.BOTDState)}: {gauge.BOTDState}"); + ImGui.Text($"{nameof(gauge.EyeCount)}: {gauge.EyeCount}"); + } + else if (jobID == 23) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.SongTimer)}: {gauge.SongTimer}"); + ImGui.Text($"{nameof(gauge.Repertoire)}: {gauge.Repertoire}"); + ImGui.Text($"{nameof(gauge.SoulVoice)}: {gauge.SoulVoice}"); + ImGui.Text($"{nameof(gauge.Song)}: {gauge.Song}"); + } + else if (jobID == 24) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.LilyTimer)}: {gauge.LilyTimer}"); + ImGui.Text($"{nameof(gauge.Lily)}: {gauge.Lily}"); + ImGui.Text($"{nameof(gauge.BloodLily)}: {gauge.BloodLily}"); + } + else if (jobID == 25) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.EnochianTimer)}: {gauge.EnochianTimer}"); + ImGui.Text($"{nameof(gauge.ElementTimeRemaining)}: {gauge.ElementTimeRemaining}"); + ImGui.Text($"{nameof(gauge.PolyglotStacks)}: {gauge.PolyglotStacks}"); + ImGui.Text($"{nameof(gauge.UmbralHearts)}: {gauge.UmbralHearts}"); + ImGui.Text($"{nameof(gauge.UmbralIceStacks)}: {gauge.UmbralIceStacks}"); + ImGui.Text($"{nameof(gauge.AstralFireStacks)}: {gauge.AstralFireStacks}"); + ImGui.Text($"{nameof(gauge.InUmbralIce)}: {gauge.InUmbralIce}"); + ImGui.Text($"{nameof(gauge.InAstralFire)}: {gauge.InAstralFire}"); + ImGui.Text($"{nameof(gauge.IsEnochianActive)}: {gauge.IsEnochianActive}"); + } + else if (jobID == 27) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.TimerRemaining)}: {gauge.TimerRemaining}"); + ImGui.Text($"{nameof(gauge.ReturnSummon)}: {gauge.ReturnSummon}"); + ImGui.Text($"{nameof(gauge.ReturnSummonGlam)}: {gauge.ReturnSummonGlam}"); + ImGui.Text($"{nameof(gauge.AetherFlags)}: {gauge.AetherFlags}"); + ImGui.Text($"{nameof(gauge.IsPhoenixReady)}: {gauge.IsPhoenixReady}"); + ImGui.Text($"{nameof(gauge.IsBahamutReady)}: {gauge.IsBahamutReady}"); + ImGui.Text($"{nameof(gauge.HasAetherflowStacks)}: {gauge.HasAetherflowStacks}"); + } + else if (jobID == 28) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Aetherflow)}: {gauge.Aetherflow}"); + ImGui.Text($"{nameof(gauge.FairyGauge)}: {gauge.FairyGauge}"); + ImGui.Text($"{nameof(gauge.SeraphTimer)}: {gauge.SeraphTimer}"); + ImGui.Text($"{nameof(gauge.DismissedFairy)}: {gauge.DismissedFairy}"); + } + else if (jobID == 30) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.HutonTimer)}: {gauge.HutonTimer}"); + ImGui.Text($"{nameof(gauge.Ninki)}: {gauge.Ninki}"); + ImGui.Text($"{nameof(gauge.HutonManualCasts)}: {gauge.HutonManualCasts}"); + } + else if (jobID == 31) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.OverheatTimeRemaining)}: {gauge.OverheatTimeRemaining}"); + ImGui.Text($"{nameof(gauge.SummonTimeRemaining)}: {gauge.SummonTimeRemaining}"); + ImGui.Text($"{nameof(gauge.Heat)}: {gauge.Heat}"); + ImGui.Text($"{nameof(gauge.Battery)}: {gauge.Battery}"); + ImGui.Text($"{nameof(gauge.LastSummonBatteryPower)}: {gauge.LastSummonBatteryPower}"); + ImGui.Text($"{nameof(gauge.IsOverheated)}: {gauge.IsOverheated}"); + ImGui.Text($"{nameof(gauge.IsRobotActive)}: {gauge.IsRobotActive}"); + } + else if (jobID == 32) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Blood)}: {gauge.Blood}"); + ImGui.Text($"{nameof(gauge.DarksideTimeRemaining)}: {gauge.DarksideTimeRemaining}"); + ImGui.Text($"{nameof(gauge.ShadowTimeRemaining)}: {gauge.ShadowTimeRemaining}"); + ImGui.Text($"{nameof(gauge.HasDarkArts)}: {gauge.HasDarkArts}"); + } + else if (jobID == 33) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.DrawnCard)}: {gauge.DrawnCard}"); + foreach (var seal in Enum.GetValues(typeof(SealType)).Cast()) { - try - { - var cancelFunc = - typeof(Task).GetMethod("InternalCancel", BindingFlags.NonPublic | BindingFlags.Instance); - cancelFunc.Invoke(task, null); - } - catch (Exception ex) - { - Log.Error(ex, "Could not cancel task."); - } - } - - ImGuiHelpers.ScaledDummy(10); - - ImGui.TextUnformatted(task.StackTrace.ToString()); - - if (task.Exception != null) - { - ImGuiHelpers.ScaledDummy(15); - ImGui.TextColored(ImGuiColors.DalamudRed, "EXCEPTION:"); - ImGui.TextUnformatted(task.Exception.ToString()); + var sealName = Enum.GetName(typeof(SealType), seal); + ImGui.Text($"{nameof(gauge.ContainsSeal)}({sealName}): {gauge.ContainsSeal(seal)}"); } } + else if (jobID == 34) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Kenki)}: {gauge.Kenki}"); + ImGui.Text($"{nameof(gauge.MeditationStacks)}: {gauge.MeditationStacks}"); + ImGui.Text($"{nameof(gauge.Sen)}: {gauge.Sen}"); + ImGui.Text($"{nameof(gauge.HasSetsu)}: {gauge.HasSetsu}"); + ImGui.Text($"{nameof(gauge.HasGetsu)}: {gauge.HasGetsu}"); + ImGui.Text($"{nameof(gauge.HasKa)}: {gauge.HasKa}"); + } + else if (jobID == 35) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.WhiteMana)}: {gauge.WhiteMana}"); + ImGui.Text($"{nameof(gauge.BlackMana)}: {gauge.BlackMana}"); + } + else if (jobID == 37) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Ammo)}: {gauge.Ammo}"); + ImGui.Text($"{nameof(gauge.MaxTimerDuration)}: {gauge.MaxTimerDuration}"); + ImGui.Text($"{nameof(gauge.AmmoComboStep)}: {gauge.AmmoComboStep}"); + } + else if (jobID == 38) + { + var gauge = jobGauges.Get(); + ImGui.Text($"Address: 0x{gauge.Address.ToInt64():X}"); + ImGui.Text($"{nameof(gauge.Feathers)}: {gauge.Feathers}"); + ImGui.Text($"{nameof(gauge.Esprit)}: {gauge.Esprit}"); + ImGui.Text($"{nameof(gauge.CompletedSteps)}: {gauge.CompletedSteps}"); + ImGui.Text($"{nameof(gauge.NextStep)}: {gauge.NextStep}"); + ImGui.Text($"{nameof(gauge.IsDancing)}: {gauge.IsDancing}"); + } else { - task.IsBeingViewed = false; + ImGui.Text("No supported gauge exists for this job."); } - - ImGui.PopStyleColor(1); } - } - private void DrawHook() - { - try + private void DrawCommand() { - ImGui.Checkbox("Use MinHook", ref this.hookUseMinHook); + var commandManager = Service.Get(); - if (ImGui.Button("Create")) - this.messageBoxMinHook = Hook.FromSymbol("User32", "MessageBoxW", this.MessageBoxWDetour, this.hookUseMinHook); - - if (ImGui.Button("Enable")) - this.messageBoxMinHook?.Enable(); - - if (ImGui.Button("Disable")) - this.messageBoxMinHook?.Disable(); - - if (ImGui.Button("Call Original")) - this.messageBoxMinHook?.Original(IntPtr.Zero, "Hello from .Original", "Hook Test", NativeFunctions.MessageBoxType.Ok); - - if (ImGui.Button("Dispose")) + foreach (var command in commandManager.Commands) { - this.messageBoxMinHook?.Dispose(); - this.messageBoxMinHook = null; + ImGui.Text($"{command.Key}\n -> {command.Value.HelpMessage}\n -> In help: {command.Value.ShowInHelp}\n\n"); + } + } + + private unsafe void DrawAddon() + { + var gameGui = Service.Get(); + + ImGui.InputText("Addon name", ref this.inputAddonName, 256); + ImGui.InputInt("Addon Index", ref this.inputAddonIndex); + + if (this.inputAddonName.IsNullOrEmpty()) + return; + + var address = gameGui.GetAddonByName(this.inputAddonName, this.inputAddonIndex); + + if (address == IntPtr.Zero) + { + ImGui.Text("Null"); + return; } - if (ImGui.Button("Test")) - _ = NativeFunctions.MessageBoxW(IntPtr.Zero, "Hi", "Hello", NativeFunctions.MessageBoxType.Ok); + var addon = (FFXIVClientStructs.FFXIV.Component.GUI.AtkUnitBase*)address; + var name = MemoryHelper.ReadStringNullTerminated((IntPtr)addon->Name); + ImGui.TextUnformatted($"{name} - 0x{address.ToInt64():x}\n v:{addon->IsVisible} x:{addon->X} y:{addon->Y} s:{addon->Scale}, w:{addon->RootNode->Width}, h:{addon->RootNode->Height}"); - if (this.messageBoxMinHook != null) - ImGui.Text("Enabled: " + this.messageBoxMinHook?.IsEnabled); + if (ImGui.Button("Find Agent")) + { + this.findAgentInterfacePtr = gameGui.FindAgentInterface(address); + } + + if (this.findAgentInterfacePtr != IntPtr.Zero) + { + ImGui.TextUnformatted($"Agent: 0x{this.findAgentInterfacePtr.ToInt64():x}"); + ImGui.SameLine(); + + if (ImGui.Button("C")) + ImGui.SetClipboardText(this.findAgentInterfacePtr.ToInt64().ToString("x")); + } } - catch (Exception ex) + + private void DrawAddonInspector() { - Log.Error(ex, "MinHook error caught"); + this.addonInspector ??= new UIDebug(); + this.addonInspector.Draw(); } - } - private async Task TestTaskInTaskDelay() - { - await Task.Delay(5000); - } + private unsafe void DrawAtkArrayDataBrowser() + { + var fontWidth = ImGui.CalcTextSize("A").X; + var fontHeight = ImGui.GetTextLineHeightWithSpacing(); + var uiModule = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance()->GetUiModule(); + + if (uiModule == null) + { + ImGui.Text("UIModule unavailable."); + return; + } + + var atkArrayDataHolder = &uiModule->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder; + + if (ImGui.BeginTabBar("AtkArrayDataBrowserTabBar")) + { + if (ImGui.BeginTabItem($"NumberArrayData [{atkArrayDataHolder->NumberArrayCount}]")) + { + if (ImGui.BeginTable("NumberArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) + { + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); + ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableHeadersRow(); + for (var numberArrayIndex = 0; numberArrayIndex < atkArrayDataHolder->NumberArrayCount; numberArrayIndex++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text($"{numberArrayIndex} [{numberArrayIndex * 8:X}]"); + ImGui.TableNextColumn(); + var numberArrayData = atkArrayDataHolder->NumberArrays[numberArrayIndex]; + if (numberArrayData != null) + { + ImGui.Text($"{numberArrayData->AtkArrayData.Size}"); + ImGui.TableNextColumn(); + if (ImGui.TreeNodeEx($"{(long)numberArrayData:X}###{numberArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) + { + ImGui.NewLine(); + var tableHeight = Math.Min(40, numberArrayData->AtkArrayData.Size + 4); + if (ImGui.BeginTable($"NumberArrayDataTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) + { + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); + ImGui.TableSetupColumn("Hex", ImGuiTableColumnFlags.WidthFixed, fontWidth * 9); + ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, fontWidth * 12); + ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, fontWidth * 20); + ImGui.TableHeadersRow(); + for (var numberIndex = 0; numberIndex < numberArrayData->AtkArrayData.Size; numberIndex++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text($"{numberIndex}"); + ImGui.TableNextColumn(); + ImGui.Text($"{numberArrayData->IntArray[numberIndex]:X}"); + ImGui.TableNextColumn(); + ImGui.Text($"{numberArrayData->IntArray[numberIndex]}"); + ImGui.TableNextColumn(); + ImGui.Text($"{*(float*)&numberArrayData->IntArray[numberIndex]}"); + } + + ImGui.EndTable(); + } + + ImGui.TreePop(); + } + } + else + { + ImGui.TextDisabled("--"); + ImGui.TableNextColumn(); + ImGui.TextDisabled("--"); + } + } + + ImGui.EndTable(); + } + + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem($"StringArrayData [{atkArrayDataHolder->StringArrayCount}]")) + { + if (ImGui.BeginTable("StringArrayDataTable", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY)) + { + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); + ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); + ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableHeadersRow(); + for (var stringArrayIndex = 0; stringArrayIndex < atkArrayDataHolder->StringArrayCount; stringArrayIndex++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text($"{stringArrayIndex} [{stringArrayIndex * 8:X}]"); + ImGui.TableNextColumn(); + var stringArrayData = atkArrayDataHolder->StringArrays[stringArrayIndex]; + if (stringArrayData != null) + { + ImGui.Text($"{stringArrayData->AtkArrayData.Size}"); + ImGui.TableNextColumn(); + if (ImGui.TreeNodeEx($"{(long)stringArrayData:X}###{stringArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) + { + ImGui.NewLine(); + var tableHeight = Math.Min(40, stringArrayData->AtkArrayData.Size + 4); + if (ImGui.BeginTable($"StringArrayDataTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) + { + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); + ImGui.TableSetupColumn("String", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableHeadersRow(); + for (var stringIndex = 0; stringIndex < stringArrayData->AtkArrayData.Size; stringIndex++) + { + ImGui.TableNextRow(); + ImGui.TableNextColumn(); + ImGui.Text($"{stringIndex}"); + ImGui.TableNextColumn(); + if (stringArrayData->StringArray[stringIndex] != null) + { + ImGui.Text($"{MemoryHelper.ReadSeStringNullTerminated(new IntPtr(stringArrayData->StringArray[stringIndex]))}"); + } + else + { + ImGui.TextDisabled("--"); + } + } + + ImGui.EndTable(); + } + + ImGui.TreePop(); + } + } + else + { + ImGui.TextDisabled("--"); + ImGui.TableNextColumn(); + ImGui.TextDisabled("--"); + } + } + + ImGui.EndTable(); + } + + ImGui.EndTabItem(); + } + + ImGui.EndTabBar(); + } + } + + private void DrawStartInfo() + { + var startInfo = Service.Get(); + + ImGui.Text(JsonConvert.SerializeObject(startInfo, Formatting.Indented)); + } + + private void DrawTarget() + { + var clientState = Service.Get(); + var targetMgr = Service.Get(); + + if (targetMgr.Target != null) + { + this.PrintGameObject(targetMgr.Target, "CurrentTarget"); + + ImGui.Text("Target"); + Util.ShowObject(targetMgr.Target); + + var tot = targetMgr.Target.TargetObject; + if (tot != null) + { + ImGuiHelpers.ScaledDummy(10); + + ImGui.Text("ToT"); + Util.ShowObject(tot); + } + + ImGuiHelpers.ScaledDummy(10); + } + + if (targetMgr.FocusTarget != null) + this.PrintGameObject(targetMgr.FocusTarget, "FocusTarget"); + + if (targetMgr.MouseOverTarget != null) + this.PrintGameObject(targetMgr.MouseOverTarget, "MouseOverTarget"); + + if (targetMgr.PreviousTarget != null) + this.PrintGameObject(targetMgr.PreviousTarget, "PreviousTarget"); + + if (targetMgr.SoftTarget != null) + this.PrintGameObject(targetMgr.SoftTarget, "SoftTarget"); + + if (ImGui.Button("Clear CT")) + targetMgr.ClearTarget(); + + if (ImGui.Button("Clear FT")) + targetMgr.ClearFocusTarget(); + + var localPlayer = clientState.LocalPlayer; + + if (localPlayer != null) + { + if (ImGui.Button("Set CT")) + targetMgr.SetTarget(localPlayer); + + if (ImGui.Button("Set FT")) + targetMgr.SetFocusTarget(localPlayer); + } + else + { + ImGui.Text("LocalPlayer is null."); + } + } + + private void DrawToast() + { + var toastGui = Service.Get(); + + ImGui.InputText("Toast text", ref this.inputTextToast, 200); + + ImGui.Combo("Toast Position", ref this.toastPosition, new[] { "Bottom", "Top", }, 2); + ImGui.Combo("Toast Speed", ref this.toastSpeed, new[] { "Slow", "Fast", }, 2); + ImGui.Combo("Quest Toast Position", ref this.questToastPosition, new[] { "Centre", "Right", "Left" }, 3); + ImGui.Checkbox("Quest Checkmark", ref this.questToastCheckmark); + ImGui.Checkbox("Quest Play Sound", ref this.questToastSound); + ImGui.InputInt("Quest Icon ID", ref this.questToastIconId); + + ImGuiHelpers.ScaledDummy(new Vector2(10, 10)); + + if (ImGui.Button("Show toast")) + { + toastGui.ShowNormal(this.inputTextToast, new ToastOptions + { + Position = (ToastPosition)this.toastPosition, + Speed = (ToastSpeed)this.toastSpeed, + }); + } + + if (ImGui.Button("Show Quest toast")) + { + toastGui.ShowQuest(this.inputTextToast, new QuestToastOptions + { + Position = (QuestToastPosition)this.questToastPosition, + DisplayCheckmark = this.questToastCheckmark, + IconId = (uint)this.questToastIconId, + PlaySound = this.questToastSound, + }); + } + + if (ImGui.Button("Show Error toast")) + { + toastGui.ShowError(this.inputTextToast); + } + } + + private void DrawFlyText() + { + if (ImGui.BeginCombo("Kind", this.flyKind.ToString())) + { + var names = Enum.GetNames(typeof(FlyTextKind)); + for (var i = 0; i < names.Length; i++) + { + if (ImGui.Selectable($"{names[i]} ({i})")) + this.flyKind = (FlyTextKind)i; + } + + ImGui.EndCombo(); + } + + ImGui.InputText("Text1", ref this.flyText1, 200); + ImGui.InputText("Text2", ref this.flyText2, 200); + + ImGui.InputInt("Val1", ref this.flyVal1); + ImGui.InputInt("Val2", ref this.flyVal2); + + ImGui.InputInt("Icon ID", ref this.flyIcon); + ImGui.ColorEdit4("Color", ref this.flyColor); + ImGui.InputInt("Actor Index", ref this.flyActor); + var sendColor = ImGui.ColorConvertFloat4ToU32(this.flyColor); + + if (ImGui.Button("Send")) + { + Service.Get().AddFlyText( + this.flyKind, + unchecked((uint)this.flyActor), + unchecked((uint)this.flyVal1), + unchecked((uint)this.flyVal2), + this.flyText1, + this.flyText2, + sendColor, + unchecked((uint)this.flyIcon)); + } + } + + private void DrawImGui() + { + var interfaceManager = Service.Get(); + var notifications = Service.Get(); + + ImGui.Text("Monitor count: " + ImGui.GetPlatformIO().Monitors.Size); + ImGui.Text("OverrideGameCursor: " + interfaceManager.OverrideGameCursor); + + ImGui.Button("THIS IS A BUTTON###hoverTestButton"); + interfaceManager.OverrideGameCursor = !ImGui.IsItemHovered(); + + ImGui.Separator(); + + ImGui.TextUnformatted($"WindowSystem.TimeSinceLastAnyFocus: {WindowSystem.TimeSinceLastAnyFocus.TotalMilliseconds:0}ms"); + + ImGui.Separator(); + + if (ImGui.Button("Add random notification")) + { + var rand = new Random(); + + var title = rand.Next(0, 5) switch + { + 0 => "This is a toast", + 1 => "Truly, a toast", + 2 => "I am testing this toast", + 3 => "I hope this looks right", + 4 => "Good stuff", + 5 => "Nice", + _ => null, + }; + + var type = rand.Next(0, 4) switch + { + 0 => Notifications.NotificationType.Error, + 1 => Notifications.NotificationType.Warning, + 2 => Notifications.NotificationType.Info, + 3 => Notifications.NotificationType.Success, + 4 => Notifications.NotificationType.None, + _ => Notifications.NotificationType.None, + }; + + var text = "Bla bla bla bla bla bla bla bla bla bla bla.\nBla bla bla bla bla bla bla bla bla bla bla bla bla bla."; + + notifications.AddNotification(text, title, type); + } + } + + private void DrawTex() + { + var dataManager = Service.Get(); + + ImGui.InputText("Tex Path", ref this.inputTexPath, 255); + ImGui.InputFloat2("UV0", ref this.inputTexUv0); + ImGui.InputFloat2("UV1", ref this.inputTexUv1); + ImGui.InputFloat4("Tint", ref this.inputTintCol); + ImGui.InputFloat2("Scale", ref this.inputTexScale); + + if (ImGui.Button("Load Tex")) + { + try + { + this.debugTex = dataManager.GetImGuiTexture(this.inputTexPath); + this.inputTexScale = new Vector2(this.debugTex.Width, this.debugTex.Height); + } + catch (Exception ex) + { + Log.Error(ex, "Could not load tex."); + } + } + + ImGuiHelpers.ScaledDummy(10); + + if (this.debugTex != null) + { + ImGui.Image(this.debugTex.ImGuiHandle, this.inputTexScale, this.inputTexUv0, this.inputTexUv1, this.inputTintCol); + ImGuiHelpers.ScaledDummy(5); + Util.ShowObject(this.debugTex); + } + } + + private void DrawKeyState() + { + var keyState = Service.Get(); + + ImGui.Columns(4); + + var i = 0; + foreach (var vkCode in keyState.GetValidVirtualKeys()) + { + var code = (int)vkCode; + var value = keyState[code]; + + ImGui.PushStyleColor(ImGuiCol.Text, value ? ImGuiColors.HealerGreen : ImGuiColors.DPSRed); + + ImGui.Text($"{vkCode} ({code})"); + + ImGui.PopStyleColor(); + + i++; + if (i % 24 == 0) + ImGui.NextColumn(); + } + + ImGui.Columns(1); + } + + private void DrawGamepad() + { + var gamepadState = Service.Get(); + + static void DrawHelper(string text, uint mask, Func resolve) + { + ImGui.Text($"{text} {mask:X4}"); + ImGui.Text($"DPadLeft {resolve(GamepadButtons.DpadLeft)} " + + $"DPadUp {resolve(GamepadButtons.DpadUp)} " + + $"DPadRight {resolve(GamepadButtons.DpadRight)} " + + $"DPadDown {resolve(GamepadButtons.DpadDown)} "); + ImGui.Text($"West {resolve(GamepadButtons.West)} " + + $"North {resolve(GamepadButtons.North)} " + + $"East {resolve(GamepadButtons.East)} " + + $"South {resolve(GamepadButtons.South)} "); + ImGui.Text($"L1 {resolve(GamepadButtons.L1)} " + + $"L2 {resolve(GamepadButtons.L2)} " + + $"R1 {resolve(GamepadButtons.R1)} " + + $"R2 {resolve(GamepadButtons.R2)} "); + ImGui.Text($"Select {resolve(GamepadButtons.Select)} " + + $"Start {resolve(GamepadButtons.Start)} " + + $"L3 {resolve(GamepadButtons.L3)} " + + $"R3 {resolve(GamepadButtons.R3)} "); + } + + ImGui.Text($"GamepadInput 0x{gamepadState.GamepadInputAddress.ToInt64():X}"); + +#if DEBUG + if (ImGui.IsItemHovered()) + ImGui.SetMouseCursor(ImGuiMouseCursor.Hand); + + if (ImGui.IsItemClicked()) + ImGui.SetClipboardText($"0x{gamepadState.GamepadInputAddress.ToInt64():X}"); +#endif + + DrawHelper( + "Buttons Raw", + gamepadState.ButtonsRaw, + gamepadState.Raw); + DrawHelper( + "Buttons Pressed", + gamepadState.ButtonsPressed, + gamepadState.Pressed); + DrawHelper( + "Buttons Repeat", + gamepadState.ButtonsRepeat, + gamepadState.Repeat); + DrawHelper( + "Buttons Released", + gamepadState.ButtonsReleased, + gamepadState.Released); + ImGui.Text($"LeftStickLeft {gamepadState.LeftStickLeft:0.00} " + + $"LeftStickUp {gamepadState.LeftStickUp:0.00} " + + $"LeftStickRight {gamepadState.LeftStickRight:0.00} " + + $"LeftStickDown {gamepadState.LeftStickDown:0.00} "); + ImGui.Text($"RightStickLeft {gamepadState.RightStickLeft:0.00} " + + $"RightStickUp {gamepadState.RightStickUp:0.00} " + + $"RightStickRight {gamepadState.RightStickRight:0.00} " + + $"RightStickDown {gamepadState.RightStickDown:0.00} "); + } + + private void DrawConfiguration() + { + var config = Service.Get(); + Util.ShowObject(config); + } + + private void DrawTaskSched() + { + if (ImGui.Button("Clear list")) + { + TaskTracker.Clear(); + } + + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(10); + ImGui.SameLine(); + + if (ImGui.Button("Short Task.Run")) + { + Task.Run(() => { Thread.Sleep(500); }); + } + + ImGui.SameLine(); + + if (ImGui.Button("Task in task(Delay)")) + { + Task.Run(async () => await this.TestTaskInTaskDelay()); + } + + ImGui.SameLine(); + + if (ImGui.Button("Task in task(Sleep)")) + { + Task.Run(async () => await this.TestTaskInTaskSleep()); + } + + ImGui.SameLine(); + + if (ImGui.Button("Faulting task")) + { + Task.Run(() => + { + Thread.Sleep(200); + + string a = null; + a.Contains("dalamud"); + }); + } + + if (ImGui.Button("Drown in tasks")) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Task.Run(() => + { + for (var i = 0; i < 100; i++) + { + Thread.Sleep(1); + } + }); + } + }); + } + }); + } + }); + } + }); + } + + ImGui.SameLine(); + + ImGuiHelpers.ScaledDummy(20); + + // Needed to init the task tracker, if we're not on a debug build + var tracker = Service.GetNullable() ?? Service.Set(); + + for (var i = 0; i < TaskTracker.Tasks.Count; i++) + { + var task = TaskTracker.Tasks[i]; + var subTime = DateTime.Now; + if (task.Task == null) + subTime = task.FinishTime; + + switch (task.Status) + { + case TaskStatus.Created: + case TaskStatus.WaitingForActivation: + case TaskStatus.WaitingToRun: + ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudGrey); + break; + case TaskStatus.Running: + case TaskStatus.WaitingForChildrenToComplete: + ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedBlue); + break; + case TaskStatus.RanToCompletion: + ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.ParsedGreen); + break; + case TaskStatus.Canceled: + case TaskStatus.Faulted: + ImGui.PushStyleColor(ImGuiCol.Header, ImGuiColors.DalamudRed); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (ImGui.CollapsingHeader($"#{task.Id} - {task.Status} {(subTime - task.StartTime).TotalMilliseconds}ms###task{i}")) + { + task.IsBeingViewed = true; + + if (ImGui.Button("CANCEL (May not work)")) + { + try + { + var cancelFunc = + typeof(Task).GetMethod("InternalCancel", BindingFlags.NonPublic | BindingFlags.Instance); + cancelFunc.Invoke(task, null); + } + catch (Exception ex) + { + Log.Error(ex, "Could not cancel task."); + } + } + + ImGuiHelpers.ScaledDummy(10); + + ImGui.TextUnformatted(task.StackTrace.ToString()); + + if (task.Exception != null) + { + ImGuiHelpers.ScaledDummy(15); + ImGui.TextColored(ImGuiColors.DalamudRed, "EXCEPTION:"); + ImGui.TextUnformatted(task.Exception.ToString()); + } + } + else + { + task.IsBeingViewed = false; + } + + ImGui.PopStyleColor(1); + } + } + + private void DrawHook() + { + try + { + ImGui.Checkbox("Use MinHook", ref this.hookUseMinHook); + + if (ImGui.Button("Create")) + this.messageBoxMinHook = Hook.FromSymbol("User32", "MessageBoxW", this.MessageBoxWDetour, this.hookUseMinHook); + + if (ImGui.Button("Enable")) + this.messageBoxMinHook?.Enable(); + + if (ImGui.Button("Disable")) + this.messageBoxMinHook?.Disable(); + + if (ImGui.Button("Call Original")) + this.messageBoxMinHook?.Original(IntPtr.Zero, "Hello from .Original", "Hook Test", NativeFunctions.MessageBoxType.Ok); + + if (ImGui.Button("Dispose")) + { + this.messageBoxMinHook?.Dispose(); + this.messageBoxMinHook = null; + } + + if (ImGui.Button("Test")) + _ = NativeFunctions.MessageBoxW(IntPtr.Zero, "Hi", "Hello", NativeFunctions.MessageBoxType.Ok); + + if (this.messageBoxMinHook != null) + ImGui.Text("Enabled: " + this.messageBoxMinHook?.IsEnabled); + } + catch (Exception ex) + { + Log.Error(ex, "MinHook error caught"); + } + } + + private async Task TestTaskInTaskDelay() + { + await Task.Delay(5000); + } #pragma warning disable 1998 - private async Task TestTaskInTaskSleep() + private async Task TestTaskInTaskSleep() #pragma warning restore 1998 - { - Thread.Sleep(5000); - } - - private void Load() - { - var dataManager = Service.Get(); - - if (dataManager.IsDataReady) { - this.serverOpString = JsonConvert.SerializeObject(dataManager.ServerOpCodes, Formatting.Indented); - this.wasReady = true; - } - } - - private void PrintGameObject(GameObject actor, string tag) - { - var actorString = - $"{actor.Address.ToInt64():X}:{actor.ObjectId:X}[{tag}] - {actor.ObjectKind} - {actor.Name} - X{actor.Position.X} Y{actor.Position.Y} Z{actor.Position.Z} D{actor.YalmDistanceX} R{actor.Rotation} - Target: {actor.TargetObjectId:X}\n"; - - if (actor is Npc npc) - actorString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n"; - - if (actor is Character chara) - { - actorString += - $" Level: {chara.Level} ClassJob: {(this.resolveGameData ? chara.ClassJob.GameData.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; + Thread.Sleep(5000); } - if (actor is PlayerCharacter pc) + private void Load() { - actorString += - $" HomeWorld: {(this.resolveGameData ? pc.HomeWorld.GameData.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(this.resolveGameData ? pc.CurrentWorld.GameData.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n"; + var dataManager = Service.Get(); + + if (dataManager.IsDataReady) + { + this.serverOpString = JsonConvert.SerializeObject(dataManager.ServerOpCodes, Formatting.Indented); + this.wasReady = true; + } } - ImGui.TextUnformatted(actorString); - ImGui.SameLine(); - if (ImGui.Button($"C##{this.copyButtonIndex++}")) + private void PrintGameObject(GameObject actor, string tag) { - ImGui.SetClipboardText(actor.Address.ToInt64().ToString("X")); + var actorString = + $"{actor.Address.ToInt64():X}:{actor.ObjectId:X}[{tag}] - {actor.ObjectKind} - {actor.Name} - X{actor.Position.X} Y{actor.Position.Y} Z{actor.Position.Z} D{actor.YalmDistanceX} R{actor.Rotation} - Target: {actor.TargetObjectId:X}\n"; + + if (actor is Npc npc) + actorString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n"; + + if (actor is Character chara) + { + actorString += + $" Level: {chara.Level} ClassJob: {(this.resolveGameData ? chara.ClassJob.GameData.Name : chara.ClassJob.Id.ToString())} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")} StatusFlags: {chara.StatusFlags}\n"; + } + + if (actor is PlayerCharacter pc) + { + actorString += + $" HomeWorld: {(this.resolveGameData ? pc.HomeWorld.GameData.Name : pc.HomeWorld.Id.ToString())} CurrentWorld: {(this.resolveGameData ? pc.CurrentWorld.GameData.Name : pc.CurrentWorld.Id.ToString())} FC: {pc.CompanyTag}\n"; + } + + ImGui.TextUnformatted(actorString); + ImGui.SameLine(); + if (ImGui.Button($"C##{this.copyButtonIndex++}")) + { + ImGui.SetClipboardText(actor.Address.ToInt64().ToString("X")); + } } } } diff --git a/Dalamud/Interface/Internal/Windows/GamepadModeNotifierWindow.cs b/Dalamud/Interface/Internal/Windows/GamepadModeNotifierWindow.cs index e95c510d3..17fa7850a 100644 --- a/Dalamud/Interface/Internal/Windows/GamepadModeNotifierWindow.cs +++ b/Dalamud/Interface/Internal/Windows/GamepadModeNotifierWindow.cs @@ -4,45 +4,46 @@ using CheapLoc; using Dalamud.Interface.Windowing; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// Class responsible for drawing a notifier on screen that gamepad mode is active. -/// -internal class GamepadModeNotifierWindow : Window +namespace Dalamud.Interface.Internal.Windows { /// - /// Initializes a new instance of the class. + /// Class responsible for drawing a notifier on screen that gamepad mode is active. /// - public GamepadModeNotifierWindow() - : base( - "###DalamudGamepadModeNotifier", - ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs - | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoNav - | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings, - true) + internal class GamepadModeNotifierWindow : Window { - this.Size = Vector2.Zero; - this.SizeCondition = ImGuiCond.Always; - this.IsOpen = false; + /// + /// Initializes a new instance of the class. + /// + public GamepadModeNotifierWindow() + : base( + "###DalamudGamepadModeNotifier", + ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs + | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoBackground | ImGuiWindowFlags.NoNav + | ImGuiWindowFlags.NoInputs | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoSavedSettings, + true) + { + this.Size = Vector2.Zero; + this.SizeCondition = ImGuiCond.Always; + this.IsOpen = false; - this.RespectCloseHotkey = false; - } + this.RespectCloseHotkey = false; + } - /// - /// Draws a light grey-ish, main-viewport-big filled rect in the background draw list alongside a text indicating gamepad mode. - /// - public override void Draw() - { - var drawList = ImGui.GetBackgroundDrawList(); - drawList.PushClipRectFullScreen(); - drawList.AddRectFilled(Vector2.Zero, ImGuiHelpers.MainViewport.Size, 0x661A1A1A); - drawList.AddText( - Vector2.One, - 0xFFFFFFFF, - Loc.Localize( - "DalamudGamepadModeNotifierText", - "Gamepad mode is ON. Press L1+L3 to deactivate, press R3 to toggle PluginInstaller.")); - drawList.PopClipRect(); + /// + /// Draws a light grey-ish, main-viewport-big filled rect in the background draw list alongside a text indicating gamepad mode. + /// + public override void Draw() + { + var drawList = ImGui.GetBackgroundDrawList(); + drawList.PushClipRectFullScreen(); + drawList.AddRectFilled(Vector2.Zero, ImGuiHelpers.MainViewport.Size, 0x661A1A1A); + drawList.AddText( + Vector2.One, + 0xFFFFFFFF, + Loc.Localize( + "DalamudGamepadModeNotifierText", + "Gamepad mode is ON. Press L1+L3 to deactivate, press R3 to toggle PluginInstaller.")); + drawList.PopClipRect(); + } } } diff --git a/Dalamud/Interface/Internal/Windows/IMEWindow.cs b/Dalamud/Interface/Internal/Windows/IMEWindow.cs index 3ae92560a..5c936d4e9 100644 --- a/Dalamud/Interface/Internal/Windows/IMEWindow.cs +++ b/Dalamud/Interface/Internal/Windows/IMEWindow.cs @@ -5,64 +5,65 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.Windowing; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// A window for displaying IME details. -/// -internal class IMEWindow : Window +namespace Dalamud.Interface.Internal.Windows { - private const int ImePageSize = 9; - /// - /// Initializes a new instance of the class. + /// A window for displaying IME details. /// - public IMEWindow() - : base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize) + internal class IMEWindow : Window { - this.Size = new Vector2(100, 200); - this.SizeCondition = ImGuiCond.FirstUseEver; + private const int ImePageSize = 9; - this.RespectCloseHotkey = false; - } - - /// - public override void Draw() - { - var ime = Service.GetNullable(); - - if (ime == null || !ime.IsEnabled) + /// + /// Initializes a new instance of the class. + /// + public IMEWindow() + : base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize) { - ImGui.Text("IME is unavailable."); - return; + this.Size = new Vector2(100, 200); + this.SizeCondition = ImGuiCond.FirstUseEver; + + this.RespectCloseHotkey = false; } - ImGui.Text(ime.ImmComp); - - ImGui.Separator(); - - var native = ime.ImmCandNative; - for (var i = 0; i < ime.ImmCand.Count; i++) + /// + public override void Draw() { - var selected = i == (native.Selection % ImePageSize); + var ime = Service.GetNullable(); - if (selected) - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); + if (ime == null || !ime.IsEnabled) + { + ImGui.Text("IME is unavailable."); + return; + } - ImGui.Text($"{i + 1}. {ime.ImmCand[i]}"); + ImGui.Text(ime.ImmComp); - if (selected) - ImGui.PopStyleColor(); + ImGui.Separator(); + + var native = ime.ImmCandNative; + for (var i = 0; i < ime.ImmCand.Count; i++) + { + var selected = i == (native.Selection % ImePageSize); + + if (selected) + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); + + ImGui.Text($"{i + 1}. {ime.ImmCand[i]}"); + + if (selected) + ImGui.PopStyleColor(); + } + + var totalIndex = native.Selection + 1; + var totalSize = native.Count; + + var pageStart = native.PageStart; + var pageIndex = (pageStart / ImePageSize) + 1; + var pageCount = (totalSize / ImePageSize) + 1; + + ImGui.Separator(); + ImGui.Text($"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"); } - - var totalIndex = native.Selection + 1; - var totalSize = native.Count; - - var pageStart = native.PageStart; - var pageIndex = (pageStart / ImePageSize) + 1; - var pageCount = (totalSize / ImePageSize) + 1; - - ImGui.Separator(); - ImGui.Text($"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"); } } diff --git a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs index c27b9db5a..6313a1c43 100644 --- a/Dalamud/Interface/Internal/Windows/PluginImageCache.cs +++ b/Dalamud/Interface/Internal/Windows/PluginImageCache.cs @@ -14,330 +14,394 @@ using Dalamud.Utility; using ImGuiScene; using Serilog; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// A cache for plugin icons and images. -/// -internal class PluginImageCache : IDisposable +namespace Dalamud.Interface.Internal.Windows { /// - /// Maximum plugin image width. + /// A cache for plugin icons and images. /// - public const int PluginImageWidth = 730; - - /// - /// Maximum plugin image height. - /// - public const int PluginImageHeight = 380; - - /// - /// Maximum plugin icon width. - /// - public const int PluginIconWidth = 512; - - /// - /// Maximum plugin height. - /// - public const int PluginIconHeight = 512; - - // TODO: Change back to master after release - private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/{0}/{1}/images/{2}"; - - private BlockingCollection> downloadQueue = new(); - private CancellationTokenSource downloadToken = new(); - - private Dictionary pluginIconMap = new(); - private Dictionary pluginImagesMap = new(); - - /// - /// Initializes a new instance of the class. - /// - public PluginImageCache() + internal class PluginImageCache : IDisposable { - var dalamud = Service.Get(); - var interfaceManager = Service.Get(); + /// + /// Maximum plugin image width. + /// + public const int PluginImageWidth = 730; - this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png")); - this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png")); - this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png")); + /// + /// Maximum plugin image height. + /// + public const int PluginImageHeight = 380; - var task = new Task( - () => this.DownloadTask(this.downloadToken.Token), - this.downloadToken.Token, - TaskCreationOptions.LongRunning); - task.Start(); - } + /// + /// Maximum plugin icon width. + /// + public const int PluginIconWidth = 512; - /// - /// Gets the default plugin icon. - /// - public TextureWrap DefaultIcon { get; } + /// + /// Maximum plugin height. + /// + public const int PluginIconHeight = 512; - /// - /// Gets the plugin trouble icon overlay. - /// - public TextureWrap TroubleIcon { get; } + // TODO: Change back to master after release + private const string MainRepoImageUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/{0}/{1}/images/{2}"; - /// - /// Gets the plugin update icon overlay. - /// - public TextureWrap UpdateIcon { get; } + private BlockingCollection> downloadQueue = new(); + private CancellationTokenSource downloadToken = new(); - /// - public void Dispose() - { - this.DefaultIcon?.Dispose(); - this.TroubleIcon?.Dispose(); - this.UpdateIcon?.Dispose(); + private Dictionary pluginIconMap = new(); + private Dictionary pluginImagesMap = new(); - this.downloadToken?.Cancel(); - this.downloadToken?.Dispose(); - this.downloadQueue?.CompleteAdding(); - this.downloadQueue?.Dispose(); - - foreach (var icon in this.pluginIconMap.Values) + /// + /// Initializes a new instance of the class. + /// + public PluginImageCache() { - icon?.Dispose(); + var dalamud = Service.Get(); + var interfaceManager = Service.Get(); + + this.DefaultIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "defaultIcon.png")); + this.TroubleIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "troubleIcon.png")); + this.UpdateIcon = interfaceManager.LoadImage(Path.Combine(dalamud.AssetDirectory.FullName, "UIRes", "updateIcon.png")); + + var task = new Task( + () => this.DownloadTask(this.downloadToken.Token), + this.downloadToken.Token, + TaskCreationOptions.LongRunning); + task.Start(); } - foreach (var images in this.pluginImagesMap.Values) + /// + /// Gets the default plugin icon. + /// + public TextureWrap DefaultIcon { get; } + + /// + /// Gets the plugin trouble icon overlay. + /// + public TextureWrap TroubleIcon { get; } + + /// + /// Gets the plugin update icon overlay. + /// + public TextureWrap UpdateIcon { get; } + + /// + public void Dispose() { - foreach (var image in images) + this.DefaultIcon?.Dispose(); + this.TroubleIcon?.Dispose(); + this.UpdateIcon?.Dispose(); + + this.downloadToken?.Cancel(); + this.downloadToken?.Dispose(); + this.downloadQueue?.CompleteAdding(); + this.downloadQueue?.Dispose(); + + foreach (var icon in this.pluginIconMap.Values) { - image?.Dispose(); + icon?.Dispose(); } - } - this.pluginIconMap.Clear(); - this.pluginImagesMap.Clear(); - } - - /// - /// Clear the cache of downloaded icons. - /// - public void ClearIconCache() - { - this.pluginIconMap.Clear(); - this.pluginImagesMap.Clear(); - } - - /// - /// Try to get the icon associated with the internal name of a plugin. - /// Uses the name within the manifest to search. - /// - /// The installed plugin, if available. - /// The plugin manifest. - /// If the plugin was third party sourced. - /// Cached image textures, or an empty array. - /// True if an entry exists, may be null if currently downloading. - public bool TryGetIcon(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap? iconTexture) - { - if (this.pluginIconMap.TryGetValue(manifest.InternalName, out iconTexture)) - return true; - - iconTexture = null; - this.pluginIconMap.Add(manifest.InternalName, iconTexture); - - if (!this.downloadQueue.IsCompleted) - { - this.downloadQueue.Add(async () => await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty)); - } - - return false; - } - - /// - /// Try to get any images associated with the internal name of a plugin. - /// Uses the name within the manifest to search. - /// - /// The installed plugin, if available. - /// The plugin manifest. - /// If the plugin was third party sourced. - /// Cached image textures, or an empty array. - /// True if the image array exists, may be empty if currently downloading. - public bool TryGetImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap?[] imageTextures) - { - if (this.pluginImagesMap.TryGetValue(manifest.InternalName, out imageTextures)) - return true; - - imageTextures = Array.Empty(); - this.pluginImagesMap.Add(manifest.InternalName, imageTextures); - - if (!this.downloadQueue.IsCompleted) - { - this.downloadQueue.Add(async () => await this.DownloadPluginImagesAsync(plugin, manifest, isThirdParty)); - } - - return false; - } - - private async void DownloadTask(CancellationToken token) - { - while (true) - { - try + foreach (var images in this.pluginImagesMap.Values) { - if (token.IsCancellationRequested) + foreach (var image in images) + { + image?.Dispose(); + } + } + + this.pluginIconMap.Clear(); + this.pluginImagesMap.Clear(); + } + + /// + /// Clear the cache of downloaded icons. + /// + public void ClearIconCache() + { + this.pluginIconMap.Clear(); + this.pluginImagesMap.Clear(); + } + + /// + /// Try to get the icon associated with the internal name of a plugin. + /// Uses the name within the manifest to search. + /// + /// The installed plugin, if available. + /// The plugin manifest. + /// If the plugin was third party sourced. + /// Cached image textures, or an empty array. + /// True if an entry exists, may be null if currently downloading. + public bool TryGetIcon(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap? iconTexture) + { + if (this.pluginIconMap.TryGetValue(manifest.InternalName, out iconTexture)) + return true; + + iconTexture = null; + this.pluginIconMap.Add(manifest.InternalName, iconTexture); + + if (!this.downloadQueue.IsCompleted) + { + this.downloadQueue.Add(async () => await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty)); + } + + return false; + } + + /// + /// Try to get any images associated with the internal name of a plugin. + /// Uses the name within the manifest to search. + /// + /// The installed plugin, if available. + /// The plugin manifest. + /// If the plugin was third party sourced. + /// Cached image textures, or an empty array. + /// True if the image array exists, may be empty if currently downloading. + public bool TryGetImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, out TextureWrap?[] imageTextures) + { + if (this.pluginImagesMap.TryGetValue(manifest.InternalName, out imageTextures)) + return true; + + imageTextures = Array.Empty(); + this.pluginImagesMap.Add(manifest.InternalName, imageTextures); + + if (!this.downloadQueue.IsCompleted) + { + this.downloadQueue.Add(async () => await this.DownloadPluginImagesAsync(plugin, manifest, isThirdParty)); + } + + return false; + } + + private async void DownloadTask(CancellationToken token) + { + while (true) + { + try + { + if (token.IsCancellationRequested) + return; + + if (!this.downloadQueue.TryTake(out var task, -1, token)) + return; + + await task.Invoke(); + } + catch (OperationCanceledException) + { + // Shutdown signal. + break; + } + catch (Exception ex) + { + Log.Error(ex, "An unhandled exception occurred in the plugin image downloader"); + } + } + + Log.Debug("Plugin image downloader has shutdown"); + } + + private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty) + { + var interfaceManager = Service.Get(); + var pluginManager = Service.Get(); + + static bool TryLoadIcon(byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap icon) + { + icon = interfaceManager.LoadImage(bytes); + + if (icon == null) + { + Log.Error($"Could not load icon for {manifest.InternalName} at {loc}"); + return false; + } + + if (icon.Width > PluginIconWidth || icon.Height > PluginIconHeight) + { + Log.Error($"Icon for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginIconWidth}x{PluginIconHeight})."); + return false; + } + + if (icon.Height != icon.Width) + { + Log.Error($"Icon for {manifest.InternalName} at {loc} was not square."); + return false; + } + + return true; + } + + if (plugin != null && plugin.IsDev) + { + var file = this.GetPluginIconFileInfo(plugin); + if (file != null) + { + Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}"); + + var bytes = await File.ReadAllBytesAsync(file.FullName); + if (!TryLoadIcon(bytes, file.FullName, manifest, interfaceManager, out var icon)) + return; + + this.pluginIconMap[manifest.InternalName] = icon; + Log.Verbose($"Plugin icon for {manifest.InternalName} loaded from disk"); + + return; + } + + // Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null. + // So instead, set the value manually so we download from the urls specified. + isThirdParty = true; + } + + var useTesting = pluginManager.UseTesting(manifest); + var url = this.GetPluginIconUrl(manifest, isThirdParty, useTesting); + + if (!url.IsNullOrEmpty()) + { + Log.Verbose($"Downloading icon for {manifest.InternalName} from {url}"); + + HttpResponseMessage data; + try + { + data = await Util.HttpClient.GetAsync(url); + } + catch (InvalidOperationException) + { + Log.Error($"Plugin icon for {manifest.InternalName} has an Invalid URI"); + return; + } + catch (Exception ex) + { + Log.Error(ex, $"An unexpected error occurred with the icon for {manifest.InternalName}"); + return; + } + + if (data.StatusCode == HttpStatusCode.NotFound) return; - if (!this.downloadQueue.TryTake(out var task, -1, token)) - return; + data.EnsureSuccessStatusCode(); - await task.Invoke(); - } - catch (OperationCanceledException) - { - // Shutdown signal. - break; - } - catch (Exception ex) - { - Log.Error(ex, "An unhandled exception occurred in the plugin image downloader"); - } - } - - Log.Debug("Plugin image downloader has shutdown"); - } - - private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty) - { - var interfaceManager = Service.Get(); - var pluginManager = Service.Get(); - - static bool TryLoadIcon(byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap icon) - { - icon = interfaceManager.LoadImage(bytes); - - if (icon == null) - { - Log.Error($"Could not load icon for {manifest.InternalName} at {loc}"); - return false; - } - - if (icon.Width > PluginIconWidth || icon.Height > PluginIconHeight) - { - Log.Error($"Icon for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginIconWidth}x{PluginIconHeight})."); - return false; - } - - if (icon.Height != icon.Width) - { - Log.Error($"Icon for {manifest.InternalName} at {loc} was not square."); - return false; - } - - return true; - } - - if (plugin != null && plugin.IsDev) - { - var file = this.GetPluginIconFileInfo(plugin); - if (file != null) - { - Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}"); - - var bytes = await File.ReadAllBytesAsync(file.FullName); - if (!TryLoadIcon(bytes, file.FullName, manifest, interfaceManager, out var icon)) + var bytes = await data.Content.ReadAsByteArrayAsync(); + if (!TryLoadIcon(bytes, url, manifest, interfaceManager, out var icon)) return; this.pluginIconMap[manifest.InternalName] = icon; - Log.Verbose($"Plugin icon for {manifest.InternalName} loaded from disk"); + Log.Verbose($"Plugin icon for {manifest.InternalName} downloaded"); return; } - // Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null. - // So instead, set the value manually so we download from the urls specified. - isThirdParty = true; + Log.Verbose($"Plugin icon for {manifest.InternalName} is not available"); } - var useTesting = pluginManager.UseTesting(manifest); - var url = this.GetPluginIconUrl(manifest, isThirdParty, useTesting); - - if (!url.IsNullOrEmpty()) + private async Task DownloadPluginImagesAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty) { - Log.Verbose($"Downloading icon for {manifest.InternalName} from {url}"); + var interfaceManager = Service.Get(); + var pluginManager = Service.Get(); - HttpResponseMessage data; - try + static bool TryLoadImage(int i, byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap image) { - data = await Util.HttpClient.GetAsync(url); - } - catch (InvalidOperationException) - { - Log.Error($"Plugin icon for {manifest.InternalName} has an Invalid URI"); - return; - } - catch (Exception ex) - { - Log.Error(ex, $"An unexpected error occurred with the icon for {manifest.InternalName}"); - return; + image = interfaceManager.LoadImage(bytes); + + if (image == null) + { + Log.Error($"Could not load image{i + 1} for {manifest.InternalName} at {loc}"); + return false; + } + + if (image.Width > PluginImageWidth || image.Height > PluginImageHeight) + { + Log.Error($"Plugin image{i + 1} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginImageWidth}x{PluginImageHeight})."); + return false; + } + + return true; } - if (data.StatusCode == HttpStatusCode.NotFound) - return; - - data.EnsureSuccessStatusCode(); - - var bytes = await data.Content.ReadAsByteArrayAsync(); - if (!TryLoadIcon(bytes, url, manifest, interfaceManager, out var icon)) - return; - - this.pluginIconMap[manifest.InternalName] = icon; - Log.Verbose($"Plugin icon for {manifest.InternalName} downloaded"); - - return; - } - - Log.Verbose($"Plugin icon for {manifest.InternalName} is not available"); - } - - private async Task DownloadPluginImagesAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty) - { - var interfaceManager = Service.Get(); - var pluginManager = Service.Get(); - - static bool TryLoadImage(int i, byte[] bytes, string loc, PluginManifest manifest, InterfaceManager interfaceManager, out TextureWrap image) - { - image = interfaceManager.LoadImage(bytes); - - if (image == null) + if (plugin != null && plugin.IsDev) { - Log.Error($"Could not load image{i + 1} for {manifest.InternalName} at {loc}"); - return false; + var files = this.GetPluginImageFileInfos(plugin); + if (files != null) + { + var didAny = false; + var pluginImages = new TextureWrap[files.Count]; + for (var i = 0; i < files.Count; i++) + { + var file = files[i]; + + if (file == null) + continue; + + Log.Verbose($"Loading image{i + 1} for {manifest.InternalName} from {file.FullName}"); + var bytes = await File.ReadAllBytesAsync(file.FullName); + + if (!TryLoadImage(i, bytes, file.FullName, manifest, interfaceManager, out var image)) + continue; + + Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} loaded from disk"); + pluginImages[i] = image; + + didAny = true; + } + + if (didAny) + { + Log.Verbose($"Plugin images for {manifest.InternalName} loaded from disk"); + + if (pluginImages.Contains(null)) + pluginImages = pluginImages.Where(image => image != null).ToArray(); + + this.pluginImagesMap[manifest.InternalName] = pluginImages; + + return; + } + } + + // Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null. + // So instead, set the value manually so we download from the urls specified. + isThirdParty = true; } - if (image.Width > PluginImageWidth || image.Height > PluginImageHeight) - { - Log.Error($"Plugin image{i + 1} for {manifest.InternalName} at {loc} was larger than the maximum allowed resolution ({PluginImageWidth}x{PluginImageHeight})."); - return false; - } - - return true; - } - - if (plugin != null && plugin.IsDev) - { - var files = this.GetPluginImageFileInfos(plugin); - if (files != null) + var useTesting = pluginManager.UseTesting(manifest); + var urls = this.GetPluginImageUrls(manifest, isThirdParty, useTesting); + if (urls != null) { var didAny = false; - var pluginImages = new TextureWrap[files.Count]; - for (var i = 0; i < files.Count; i++) + var pluginImages = new TextureWrap[urls.Count]; + for (var i = 0; i < urls.Count; i++) { - var file = files[i]; + var url = urls[i]; - if (file == null) + if (url.IsNullOrEmpty()) continue; - Log.Verbose($"Loading image{i + 1} for {manifest.InternalName} from {file.FullName}"); - var bytes = await File.ReadAllBytesAsync(file.FullName); + Log.Verbose($"Downloading image{i + 1} for {manifest.InternalName} from {url}"); - if (!TryLoadImage(i, bytes, file.FullName, manifest, interfaceManager, out var image)) + HttpResponseMessage data; + try + { + data = await Util.HttpClient.GetAsync(url); + } + catch (InvalidOperationException) + { + Log.Error($"Plugin image{i + 1} for {manifest.InternalName} has an Invalid URI"); + continue; + } + catch (Exception ex) + { + Log.Error(ex, $"An unexpected error occurred with image{i + 1} for {manifest.InternalName}"); + continue; + } + + if (data.StatusCode == HttpStatusCode.NotFound) continue; - Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} loaded from disk"); + data.EnsureSuccessStatusCode(); + + var bytes = await data.Content.ReadAsByteArrayAsync(); + if (!TryLoadImage(i, bytes, url, manifest, interfaceManager, out var image)) + continue; + + Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} downloaded"); pluginImages[i] = image; didAny = true; @@ -345,7 +409,7 @@ internal class PluginImageCache : IDisposable if (didAny) { - Log.Verbose($"Plugin images for {manifest.InternalName} loaded from disk"); + Log.Verbose($"Plugin images for {manifest.InternalName} downloaded"); if (pluginImages.Contains(null)) pluginImages = pluginImages.Where(image => image != null).ToArray(); @@ -356,130 +420,67 @@ internal class PluginImageCache : IDisposable } } - // Dev plugins are likely going to look like a main repo plugin, the InstalledFrom field is going to be null. - // So instead, set the value manually so we download from the urls specified. - isThirdParty = true; + Log.Verbose($"Images for {manifest.InternalName} are not available"); } - var useTesting = pluginManager.UseTesting(manifest); - var urls = this.GetPluginImageUrls(manifest, isThirdParty, useTesting); - if (urls != null) + private string? GetPluginIconUrl(PluginManifest manifest, bool isThirdParty, bool isTesting) { - var didAny = false; - var pluginImages = new TextureWrap[urls.Count]; - for (var i = 0; i < urls.Count; i++) + if (isThirdParty) + return manifest.IconUrl; + + return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png"); + } + + private List? GetPluginImageUrls(PluginManifest manifest, bool isThirdParty, bool isTesting) + { + if (isThirdParty) { - var url = urls[i]; - - if (url.IsNullOrEmpty()) - continue; - - Log.Verbose($"Downloading image{i + 1} for {manifest.InternalName} from {url}"); - - HttpResponseMessage data; - try + if (manifest.ImageUrls?.Count > 5) { - data = await Util.HttpClient.GetAsync(url); - } - catch (InvalidOperationException) - { - Log.Error($"Plugin image{i + 1} for {manifest.InternalName} has an Invalid URI"); - continue; - } - catch (Exception ex) - { - Log.Error(ex, $"An unexpected error occurred with image{i + 1} for {manifest.InternalName}"); - continue; + Log.Warning($"Plugin {manifest.InternalName} has too many images"); + return manifest.ImageUrls.Take(5).ToList(); } - if (data.StatusCode == HttpStatusCode.NotFound) - continue; - - data.EnsureSuccessStatusCode(); - - var bytes = await data.Content.ReadAsByteArrayAsync(); - if (!TryLoadImage(i, bytes, url, manifest, interfaceManager, out var image)) - continue; - - Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} downloaded"); - pluginImages[i] = image; - - didAny = true; + return manifest.ImageUrls; } - if (didAny) + var output = new List(); + for (var i = 1; i <= 5; i++) { - Log.Verbose($"Plugin images for {manifest.InternalName} downloaded"); - - if (pluginImages.Contains(null)) - pluginImages = pluginImages.Where(image => image != null).ToArray(); - - this.pluginImagesMap[manifest.InternalName] = pluginImages; - - return; - } - } - - Log.Verbose($"Images for {manifest.InternalName} are not available"); - } - - private string? GetPluginIconUrl(PluginManifest manifest, bool isThirdParty, bool isTesting) - { - if (isThirdParty) - return manifest.IconUrl; - - return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png"); - } - - private List? GetPluginImageUrls(PluginManifest manifest, bool isThirdParty, bool isTesting) - { - if (isThirdParty) - { - if (manifest.ImageUrls?.Count > 5) - { - Log.Warning($"Plugin {manifest.InternalName} has too many images"); - return manifest.ImageUrls.Take(5).ToList(); + output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png")); } - return manifest.ImageUrls; + return output; } - var output = new List(); - for (var i = 1; i <= 5; i++) + private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin) { - output.Add(MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, $"image{i}.png")); - } + var pluginDir = plugin.DllFile.Directory; - return output; - } - - private FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin) - { - var pluginDir = plugin.DllFile.Directory; - - var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png")); - if (devUrl.Exists) - return devUrl; - - return null; - } - - private List GetPluginImageFileInfos(LocalPlugin? plugin) - { - var pluginDir = plugin.DllFile.Directory; - var output = new List(); - for (var i = 1; i <= 5; i++) - { - var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", $"image{i}.png")); + var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png")); if (devUrl.Exists) - { - output.Add(devUrl); - continue; - } + return devUrl; - output.Add(null); + return null; } - return output; + private List GetPluginImageFileInfos(LocalPlugin? plugin) + { + var pluginDir = plugin.DllFile.Directory; + var output = new List(); + for (var i = 1; i <= 5; i++) + { + var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", $"image{i}.png")); + if (devUrl.Exists) + { + output.Add(devUrl); + continue; + } + + output.Add(null); + } + + return output; + } } } diff --git a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs index abe569ec8..9c300e438 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstallerWindow.cs @@ -25,817 +25,1784 @@ using Dalamud.Utility; using ImGuiNET; using ImGuiScene; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// Class responsible for drawing the plugin installer. -/// -internal class PluginInstallerWindow : Window, IDisposable +namespace Dalamud.Interface.Internal.Windows { - private static readonly ModuleLog Log = new("PLUGINW"); - - private readonly Vector4 changelogBgColor = new(0.114f, 0.584f, 0.192f, 0.678f); - private readonly Vector4 changelogTextColor = new(0.812f, 1.000f, 0.816f, 1.000f); - - private readonly PluginCategoryManager categoryManager = new(); - private readonly PluginImageCache imageCache = new(); - - #region Image Tester State - - private string[] testerImagePaths = new string[5]; - private string testerIconPath = string.Empty; - - private TextureWrap?[] testerImages; - private TextureWrap? testerIcon; - - private bool testerError = false; - private bool testerUpdateAvailable = false; - - #endregion - - private bool errorModalDrawing = true; - private bool errorModalOnNextFrame = false; - private string errorModalMessage = string.Empty; - - private bool feedbackModalDrawing = true; - private bool feedbackModalOnNextFrame = false; - private string feedbackModalBody = string.Empty; - private string feedbackModalContact = string.Empty; - private bool feedbackModalIncludeException = false; - private PluginManifest? feedbackPlugin = null; - - private int updatePluginCount = 0; - private List? updatedPlugins; - - private List pluginListAvailable = new(); - private List pluginListInstalled = new(); - private List pluginListUpdatable = new(); - private bool hasDevPlugins = false; - - private string searchText = string.Empty; - - private PluginSortKind sortKind = PluginSortKind.Alphabetical; - private string filterText = Locs.SortBy_Alphabetical; - - private OperationStatus installStatus = OperationStatus.Idle; - private OperationStatus updateStatus = OperationStatus.Idle; - - private List openPluginCollapsibles = new(); - /// - /// Initializes a new instance of the class. + /// Class responsible for drawing the plugin installer. /// - public PluginInstallerWindow() - : base( - Locs.WindowTitle + (Service.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller", - ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar) + internal class PluginInstallerWindow : Window, IDisposable { - this.IsOpen = true; + private static readonly ModuleLog Log = new("PLUGINW"); - this.Size = new Vector2(830, 570); - this.SizeCondition = ImGuiCond.FirstUseEver; + private readonly Vector4 changelogBgColor = new(0.114f, 0.584f, 0.192f, 0.678f); + private readonly Vector4 changelogTextColor = new(0.812f, 1.000f, 0.816f, 1.000f); - this.SizeConstraints = new WindowSizeConstraints + private readonly PluginCategoryManager categoryManager = new(); + private readonly PluginImageCache imageCache = new(); + + #region Image Tester State + + private string[] testerImagePaths = new string[5]; + private string testerIconPath = string.Empty; + + private TextureWrap?[] testerImages; + private TextureWrap? testerIcon; + + private bool testerError = false; + private bool testerUpdateAvailable = false; + + #endregion + + private bool errorModalDrawing = true; + private bool errorModalOnNextFrame = false; + private string errorModalMessage = string.Empty; + + private bool feedbackModalDrawing = true; + private bool feedbackModalOnNextFrame = false; + private string feedbackModalBody = string.Empty; + private string feedbackModalContact = string.Empty; + private bool feedbackModalIncludeException = false; + private PluginManifest? feedbackPlugin = null; + + private int updatePluginCount = 0; + private List? updatedPlugins; + + private List pluginListAvailable = new(); + private List pluginListInstalled = new(); + private List pluginListUpdatable = new(); + private bool hasDevPlugins = false; + + private string searchText = string.Empty; + + private PluginSortKind sortKind = PluginSortKind.Alphabetical; + private string filterText = Locs.SortBy_Alphabetical; + + private OperationStatus installStatus = OperationStatus.Idle; + private OperationStatus updateStatus = OperationStatus.Idle; + + private List openPluginCollapsibles = new(); + + /// + /// Initializes a new instance of the class. + /// + public PluginInstallerWindow() + : base( + Locs.WindowTitle + (Service.Get().DoPluginTest ? Locs.WindowTitleMod_Testing : string.Empty) + "###XlPluginInstaller", + ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar) { - MinimumSize = this.Size.Value, - MaximumSize = new Vector2(5000, 5000), - }; + this.IsOpen = true; - var pluginManager = Service.Get(); + this.Size = new Vector2(830, 570); + this.SizeCondition = ImGuiCond.FirstUseEver; - // For debugging - if (pluginManager.PluginsReady) - this.OnInstalledPluginsChanged(); + this.SizeConstraints = new WindowSizeConstraints + { + MinimumSize = this.Size.Value, + MaximumSize = new Vector2(5000, 5000), + }; - pluginManager.OnAvailablePluginsChanged += this.OnAvailablePluginsChanged; - pluginManager.OnInstalledPluginsChanged += this.OnInstalledPluginsChanged; + var pluginManager = Service.Get(); - for (var i = 0; i < this.testerImagePaths.Length; i++) - { - this.testerImagePaths[i] = string.Empty; + // For debugging + if (pluginManager.PluginsReady) + this.OnInstalledPluginsChanged(); + + pluginManager.OnAvailablePluginsChanged += this.OnAvailablePluginsChanged; + pluginManager.OnInstalledPluginsChanged += this.OnInstalledPluginsChanged; + + for (var i = 0; i < this.testerImagePaths.Length; i++) + { + this.testerImagePaths[i] = string.Empty; + } } - } - private enum OperationStatus - { - Idle, - InProgress, - Complete, - } - - private enum PluginSortKind - { - Alphabetical, - DownloadCount, - LastUpdate, - NewOrNot, - } - - /// - public void Dispose() - { - var pluginManager = Service.Get(); - - pluginManager.OnAvailablePluginsChanged -= this.OnAvailablePluginsChanged; - pluginManager.OnInstalledPluginsChanged -= this.OnInstalledPluginsChanged; - - this.imageCache?.Dispose(); - } - - /// - public override void OnOpen() - { - var pluginManager = Service.Get(); - - _ = pluginManager.ReloadPluginMastersAsync(); - - this.updatePluginCount = 0; - this.updatedPlugins = null; - - this.searchText = string.Empty; - this.sortKind = PluginSortKind.Alphabetical; - this.filterText = Locs.SortBy_Alphabetical; - } - - /// - public override void OnClose() - { - Service.Get().Save(); - } - - /// - public override void Draw() - { - this.DrawHeader(); - this.DrawPluginCategories(); - this.DrawFooter(); - this.DrawErrorModal(); - this.DrawFeedbackModal(); - } - - /// - /// Clear the icon and image caches, forcing a fresh download. - /// - public void ClearIconCache() - { - this.imageCache.ClearIconCache(); - } - - private void DrawHeader() - { - var style = ImGui.GetStyle(); - var windowSize = ImGui.GetWindowContentRegionMax(); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); - - var searchInputWidth = 240 * ImGuiHelpers.GlobalScale; - - var sortByText = Locs.SortBy_Label; - var sortByTextWidth = ImGui.CalcTextSize(sortByText).X; - var sortSelectables = new (string Localization, PluginSortKind SortKind)[] + private enum OperationStatus { + Idle, + InProgress, + Complete, + } + + private enum PluginSortKind + { + Alphabetical, + DownloadCount, + LastUpdate, + NewOrNot, + } + + /// + public void Dispose() + { + var pluginManager = Service.Get(); + + pluginManager.OnAvailablePluginsChanged -= this.OnAvailablePluginsChanged; + pluginManager.OnInstalledPluginsChanged -= this.OnInstalledPluginsChanged; + + this.imageCache?.Dispose(); + } + + /// + public override void OnOpen() + { + var pluginManager = Service.Get(); + + _ = pluginManager.ReloadPluginMastersAsync(); + + this.updatePluginCount = 0; + this.updatedPlugins = null; + + this.searchText = string.Empty; + this.sortKind = PluginSortKind.Alphabetical; + this.filterText = Locs.SortBy_Alphabetical; + } + + /// + public override void OnClose() + { + Service.Get().Save(); + } + + /// + public override void Draw() + { + this.DrawHeader(); + this.DrawPluginCategories(); + this.DrawFooter(); + this.DrawErrorModal(); + this.DrawFeedbackModal(); + } + + /// + /// Clear the icon and image caches, forcing a fresh download. + /// + public void ClearIconCache() + { + this.imageCache.ClearIconCache(); + } + + private void DrawHeader() + { + var style = ImGui.GetStyle(); + var windowSize = ImGui.GetWindowContentRegionMax(); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); + + var searchInputWidth = 240 * ImGuiHelpers.GlobalScale; + + var sortByText = Locs.SortBy_Label; + var sortByTextWidth = ImGui.CalcTextSize(sortByText).X; + var sortSelectables = new (string Localization, PluginSortKind SortKind)[] + { (Locs.SortBy_Alphabetical, PluginSortKind.Alphabetical), (Locs.SortBy_DownloadCounts, PluginSortKind.DownloadCount), (Locs.SortBy_LastUpdate, PluginSortKind.LastUpdate), (Locs.SortBy_NewOrNot, PluginSortKind.NewOrNot), - }; - var longestSelectableWidth = sortSelectables.Select(t => ImGui.CalcTextSize(t.Localization).X).Max(); - var selectableWidth = longestSelectableWidth + (style.FramePadding.X * 2); // This does not include the label - var sortSelectWidth = selectableWidth + sortByTextWidth + style.ItemInnerSpacing.X; // Item spacing between the selectable and the label + }; + var longestSelectableWidth = sortSelectables.Select(t => ImGui.CalcTextSize(t.Localization).X).Max(); + var selectableWidth = longestSelectableWidth + (style.FramePadding.X * 2); // This does not include the label + var sortSelectWidth = selectableWidth + sortByTextWidth + style.ItemInnerSpacing.X; // Item spacing between the selectable and the label - var headerText = Locs.Header_Hint; - var headerTextSize = ImGui.CalcTextSize(headerText); - ImGui.Text(headerText); + var headerText = Locs.Header_Hint; + var headerTextSize = ImGui.CalcTextSize(headerText); + ImGui.Text(headerText); - ImGui.SameLine(); - - // Shift down a little to align with the middle of the header text - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2); - - ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth); - ImGui.SetNextItemWidth(searchInputWidth); - if (ImGui.InputTextWithHint("###XlPluginInstaller_Search", Locs.Header_SearchPlaceholder, ref this.searchText, 100)) - { - this.UpdateCategoriesOnSearchChange(); - } - - ImGui.SameLine(); - ImGui.SetCursorPosX(windowSize.X - sortSelectWidth); - ImGui.SetNextItemWidth(selectableWidth); - if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton)) - { - foreach (var selectable in sortSelectables) - { - if (ImGui.Selectable(selectable.Localization)) - { - this.sortKind = selectable.SortKind; - this.filterText = selectable.Localization; - - this.ResortPlugins(); - } - } - - ImGui.EndCombo(); - } - } - - private void DrawFooter() - { - var configuration = Service.Get(); - var pluginManager = Service.Get(); - - var windowSize = ImGui.GetWindowContentRegionMax(); - var placeholderButtonSize = ImGuiHelpers.GetButtonSize("placeholder"); - - ImGui.Separator(); - - ImGui.SetCursorPosY(windowSize.Y - placeholderButtonSize.Y); - - this.DrawUpdatePluginsButton(); - - ImGui.SameLine(); - if (ImGui.Button(Locs.FooterButton_Settings)) - { - Service.Get().OpenSettings(); - } - - // If any dev plugins are installed, allow a shortcut for the /xldev menu item - if (this.hasDevPlugins) - { ImGui.SameLine(); - if (ImGui.Button(Locs.FooterButton_ScanDevPlugins)) + + // Shift down a little to align with the middle of the header text + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + (headerTextSize.Y / 4) - 2); + + ImGui.SetCursorPosX(windowSize.X - sortSelectWidth - style.ItemSpacing.X - searchInputWidth); + ImGui.SetNextItemWidth(searchInputWidth); + if (ImGui.InputTextWithHint("###XlPluginInstaller_Search", Locs.Header_SearchPlaceholder, ref this.searchText, 100)) { - pluginManager.ScanDevPlugins(); + this.UpdateCategoriesOnSearchChange(); } - } - var closeText = Locs.FooterButton_Close; - var closeButtonSize = ImGuiHelpers.GetButtonSize(closeText); - - ImGui.SameLine(windowSize.X - closeButtonSize.X - 20); - if (ImGui.Button(closeText)) - { - this.IsOpen = false; - configuration.Save(); - } - } - - private void DrawUpdatePluginsButton() - { - var pluginManager = Service.Get(); - var notifications = Service.Get(); - - var ready = pluginManager.PluginsReady && pluginManager.ReposReady; - - if (!ready || this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress) - { - ImGuiComponents.DisabledButton(Locs.FooterButton_UpdatePlugins); - } - else if (this.updateStatus == OperationStatus.Complete) - { - ImGui.Button(this.updatePluginCount > 0 - ? Locs.FooterButton_UpdateComplete(this.updatePluginCount) - : Locs.FooterButton_NoUpdates); - } - else - { - if (ImGui.Button(Locs.FooterButton_UpdatePlugins)) + ImGui.SameLine(); + ImGui.SetCursorPosX(windowSize.X - sortSelectWidth); + ImGui.SetNextItemWidth(selectableWidth); + if (ImGui.BeginCombo(sortByText, this.filterText, ImGuiComboFlags.NoArrowButton)) { - this.updateStatus = OperationStatus.InProgress; - - Task.Run(() => pluginManager.UpdatePluginsAsync()) - .ContinueWith(task => + foreach (var selectable in sortSelectables) + { + if (ImGui.Selectable(selectable.Localization)) { - this.updateStatus = OperationStatus.Complete; + this.sortKind = selectable.SortKind; + this.filterText = selectable.Localization; - if (task.IsFaulted) - { - this.updatePluginCount = 0; - this.updatedPlugins = null; - this.DisplayErrorContinuation(task, Locs.ErrorModal_UpdaterFatal); - } - else - { - this.updatedPlugins = task.Result.Where(res => res.WasUpdated).ToList(); - this.updatePluginCount = this.updatedPlugins.Count; - - var errorPlugins = task.Result.Where(res => !res.WasUpdated).ToList(); - var errorPluginCount = errorPlugins.Count; - - if (errorPluginCount > 0) - { - var errorMessage = this.updatePluginCount > 0 - ? Locs.ErrorModal_UpdaterFailPartial(this.updatePluginCount, errorPluginCount) - : Locs.ErrorModal_UpdaterFail(errorPluginCount); - - var hintInsert = errorPlugins - .Aggregate(string.Empty, (current, pluginUpdateStatus) => $"{current}* {pluginUpdateStatus.InternalName}\n") - .TrimEnd(); - errorMessage += Locs.ErrorModal_HintBlame(hintInsert); - - this.DisplayErrorContinuation(task, errorMessage); - } - - if (this.updatePluginCount > 0) - { - pluginManager.PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox); - notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success); - } - else if (this.updatePluginCount == 0) - { - notifications.AddNotification(Locs.Notifications_NoUpdatesFound, Locs.Notifications_NoUpdatesFoundTitle, NotificationType.Info); - } - } - }); - } - } - } - - private void DrawErrorModal() - { - var modalTitle = Locs.ErrorModal_Title; - - if (ImGui.BeginPopupModal(modalTitle, ref this.errorModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) - { - ImGui.Text(this.errorModalMessage); - ImGui.Spacing(); - - var buttonWidth = 120f; - ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); - - if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) - { - ImGui.CloseCurrentPopup(); - } - - ImGui.EndPopup(); - } - - if (this.errorModalOnNextFrame) - { - ImGui.OpenPopup(modalTitle); - this.errorModalOnNextFrame = false; - this.errorModalDrawing = true; - } - } - - private void DrawFeedbackModal() - { - var modalTitle = Locs.FeedbackModal_Title; - - if (ImGui.BeginPopupModal(modalTitle, ref this.feedbackModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) - { - ImGui.Text(Locs.FeedbackModal_Text(this.feedbackPlugin.Name)); - - if (this.pluginListUpdatable.Any( - up => up.InstalledPlugin.Manifest.InternalName == this.feedbackPlugin?.InternalName)) - { - ImGui.TextColored(ImGuiColors.DalamudRed, Locs.FeedbackModal_HasUpdate); - } - - ImGui.Spacing(); - - ImGui.InputTextMultiline("###FeedbackContent", ref this.feedbackModalBody, 1000, new Vector2(400, 200)); - - ImGui.Spacing(); - - ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100); - - ImGui.Checkbox(Locs.FeedbackModal_IncludeLastError, ref this.feedbackModalIncludeException); - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_IncludeLastErrorHint); - - ImGui.Spacing(); - - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_Hint); - - var buttonWidth = 120f; - ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); - - if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) - { - if (this.feedbackPlugin != null) - { - Task.Run(async () => await BugBait.SendFeedback(this.feedbackPlugin, this.feedbackModalBody, this.feedbackModalContact, this.feedbackModalIncludeException)) - .ContinueWith( - t => - { - var notif = Service.Get(); - if (t.IsCanceled || t.IsFaulted) - notif.AddNotification(Locs.FeedbackModal_NotificationError, Locs.FeedbackModal_Title, NotificationType.Error); - else - notif.AddNotification(Locs.FeedbackModal_NotificationSuccess, Locs.FeedbackModal_Title, NotificationType.Success); - }); - } - else - { - Log.Error("FeedbackPlugin was null."); + this.ResortPlugins(); + } } - ImGui.CloseCurrentPopup(); + ImGui.EndCombo(); + } + } + + private void DrawFooter() + { + var configuration = Service.Get(); + var pluginManager = Service.Get(); + + var windowSize = ImGui.GetWindowContentRegionMax(); + var placeholderButtonSize = ImGuiHelpers.GetButtonSize("placeholder"); + + ImGui.Separator(); + + ImGui.SetCursorPosY(windowSize.Y - placeholderButtonSize.Y); + + this.DrawUpdatePluginsButton(); + + ImGui.SameLine(); + if (ImGui.Button(Locs.FooterButton_Settings)) + { + Service.Get().OpenSettings(); } - ImGui.EndPopup(); - } - - if (this.feedbackModalOnNextFrame) - { - ImGui.OpenPopup(modalTitle); - this.feedbackModalOnNextFrame = false; - this.feedbackModalDrawing = true; - this.feedbackModalBody = string.Empty; - this.feedbackModalContact = string.Empty; - this.feedbackModalIncludeException = false; - } - } - - /* - private void DrawPluginTabBar() - { - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3)); - - if (ImGui.BeginTabBar("PluginsTabBar", ImGuiTabBarFlags.NoTooltip)) - { - this.DrawPluginTab(Locs.TabTitle_AvailablePlugins, this.DrawAvailablePluginList); - this.DrawPluginTab(Locs.TabTitle_InstalledPlugins, this.DrawInstalledPluginList); - + // If any dev plugins are installed, allow a shortcut for the /xldev menu item if (this.hasDevPlugins) { - this.DrawPluginTab(Locs.TabTitle_InstalledDevPlugins, this.DrawInstalledDevPluginList); - this.DrawPluginTab("Image/Icon Tester", this.DrawImageTester); + ImGui.SameLine(); + if (ImGui.Button(Locs.FooterButton_ScanDevPlugins)) + { + pluginManager.ScanDevPlugins(); + } + } + + var closeText = Locs.FooterButton_Close; + var closeButtonSize = ImGuiHelpers.GetButtonSize(closeText); + + ImGui.SameLine(windowSize.X - closeButtonSize.X - 20); + if (ImGui.Button(closeText)) + { + this.IsOpen = false; + configuration.Save(); } } - ImGui.PopStyleVar(); - } - */ - - /* - private void DrawPluginTab(string title, Action drawPluginList) - { - if (ImGui.BeginTabItem(title)) + private void DrawUpdatePluginsButton() { - ImGui.BeginChild($"Scrolling{title}", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); + var pluginManager = Service.Get(); + var notifications = Service.Get(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); + var ready = pluginManager.PluginsReady && pluginManager.ReposReady; - var ready = this.DrawPluginListLoading(); - - if (ready) + if (!ready || this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress) { - drawPluginList(); + ImGuiComponents.DisabledButton(Locs.FooterButton_UpdatePlugins); } - - ImGui.EndChild(); - - ImGui.EndTabItem(); - } - } - */ - - private void DrawAvailablePluginList() - { - var pluginList = this.pluginListAvailable; - - if (pluginList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoCompatible); - return; - } - - var filteredManifests = pluginList - .Where(rm => !this.IsManifestFiltered(rm)) - .ToList(); - - if (filteredManifests.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); - return; - } - - // get list to show and reset category dirty flag - var categoryManifestsList = this.categoryManager.GetCurrentCategoryContent(filteredManifests); - - var i = 0; - foreach (var manifest in categoryManifestsList) - { - var remoteManifest = manifest as RemotePluginManifest; - var (isInstalled, plugin) = this.IsManifestInstalled(remoteManifest); - - ImGui.PushID($"{manifest.InternalName}{manifest.AssemblyVersion}"); - if (isInstalled) + else if (this.updateStatus == OperationStatus.Complete) { - this.DrawInstalledPlugin(plugin, i++, true); + ImGui.Button(this.updatePluginCount > 0 + ? Locs.FooterButton_UpdateComplete(this.updatePluginCount) + : Locs.FooterButton_NoUpdates); } else { - this.DrawAvailablePlugin(remoteManifest, i++); + if (ImGui.Button(Locs.FooterButton_UpdatePlugins)) + { + this.updateStatus = OperationStatus.InProgress; + + Task.Run(() => pluginManager.UpdatePluginsAsync()) + .ContinueWith(task => + { + this.updateStatus = OperationStatus.Complete; + + if (task.IsFaulted) + { + this.updatePluginCount = 0; + this.updatedPlugins = null; + this.DisplayErrorContinuation(task, Locs.ErrorModal_UpdaterFatal); + } + else + { + this.updatedPlugins = task.Result.Where(res => res.WasUpdated).ToList(); + this.updatePluginCount = this.updatedPlugins.Count; + + var errorPlugins = task.Result.Where(res => !res.WasUpdated).ToList(); + var errorPluginCount = errorPlugins.Count; + + if (errorPluginCount > 0) + { + var errorMessage = this.updatePluginCount > 0 + ? Locs.ErrorModal_UpdaterFailPartial(this.updatePluginCount, errorPluginCount) + : Locs.ErrorModal_UpdaterFail(errorPluginCount); + + var hintInsert = errorPlugins + .Aggregate(string.Empty, (current, pluginUpdateStatus) => $"{current}* {pluginUpdateStatus.InternalName}\n") + .TrimEnd(); + errorMessage += Locs.ErrorModal_HintBlame(hintInsert); + + this.DisplayErrorContinuation(task, errorMessage); + } + + if (this.updatePluginCount > 0) + { + pluginManager.PrintUpdatedPlugins(this.updatedPlugins, Locs.PluginUpdateHeader_Chatbox); + notifications.AddNotification(Locs.Notifications_UpdatesInstalled(this.updatePluginCount), Locs.Notifications_UpdatesInstalledTitle, NotificationType.Success); + } + else if (this.updatePluginCount == 0) + { + notifications.AddNotification(Locs.Notifications_NoUpdatesFound, Locs.Notifications_NoUpdatesFoundTitle, NotificationType.Info); + } + } + }); + } + } + } + + private void DrawErrorModal() + { + var modalTitle = Locs.ErrorModal_Title; + + if (ImGui.BeginPopupModal(modalTitle, ref this.errorModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) + { + ImGui.Text(this.errorModalMessage); + ImGui.Spacing(); + + var buttonWidth = 120f; + ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); + + if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) + { + ImGui.CloseCurrentPopup(); + } + + ImGui.EndPopup(); + } + + if (this.errorModalOnNextFrame) + { + ImGui.OpenPopup(modalTitle); + this.errorModalOnNextFrame = false; + this.errorModalDrawing = true; + } + } + + private void DrawFeedbackModal() + { + var modalTitle = Locs.FeedbackModal_Title; + + if (ImGui.BeginPopupModal(modalTitle, ref this.feedbackModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) + { + ImGui.Text(Locs.FeedbackModal_Text(this.feedbackPlugin.Name)); + + if (this.pluginListUpdatable.Any( + up => up.InstalledPlugin.Manifest.InternalName == this.feedbackPlugin?.InternalName)) + { + ImGui.TextColored(ImGuiColors.DalamudRed, Locs.FeedbackModal_HasUpdate); + } + + ImGui.Spacing(); + + ImGui.InputTextMultiline("###FeedbackContent", ref this.feedbackModalBody, 1000, new Vector2(400, 200)); + + ImGui.Spacing(); + + ImGui.InputText(Locs.FeedbackModal_ContactInformation, ref this.feedbackModalContact, 100); + + ImGui.Checkbox(Locs.FeedbackModal_IncludeLastError, ref this.feedbackModalIncludeException); + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_IncludeLastErrorHint); + + ImGui.Spacing(); + + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.FeedbackModal_Hint); + + var buttonWidth = 120f; + ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); + + if (ImGui.Button(Locs.ErrorModalButton_Ok, new Vector2(buttonWidth, 40))) + { + if (this.feedbackPlugin != null) + { + Task.Run(async () => await BugBait.SendFeedback(this.feedbackPlugin, this.feedbackModalBody, this.feedbackModalContact, this.feedbackModalIncludeException)) + .ContinueWith( + t => + { + var notif = Service.Get(); + if (t.IsCanceled || t.IsFaulted) + notif.AddNotification(Locs.FeedbackModal_NotificationError, Locs.FeedbackModal_Title, NotificationType.Error); + else + notif.AddNotification(Locs.FeedbackModal_NotificationSuccess, Locs.FeedbackModal_Title, NotificationType.Success); + }); + } + else + { + Log.Error("FeedbackPlugin was null."); + } + + ImGui.CloseCurrentPopup(); + } + + ImGui.EndPopup(); + } + + if (this.feedbackModalOnNextFrame) + { + ImGui.OpenPopup(modalTitle); + this.feedbackModalOnNextFrame = false; + this.feedbackModalDrawing = true; + this.feedbackModalBody = string.Empty; + this.feedbackModalContact = string.Empty; + this.feedbackModalIncludeException = false; + } + } + + /* + private void DrawPluginTabBar() + { + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - (5 * ImGuiHelpers.GlobalScale)); + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3)); + + if (ImGui.BeginTabBar("PluginsTabBar", ImGuiTabBarFlags.NoTooltip)) + { + this.DrawPluginTab(Locs.TabTitle_AvailablePlugins, this.DrawAvailablePluginList); + this.DrawPluginTab(Locs.TabTitle_InstalledPlugins, this.DrawInstalledPluginList); + + if (this.hasDevPlugins) + { + this.DrawPluginTab(Locs.TabTitle_InstalledDevPlugins, this.DrawInstalledDevPluginList); + this.DrawPluginTab("Image/Icon Tester", this.DrawImageTester); + } + } + + ImGui.PopStyleVar(); + } + */ + + /* + private void DrawPluginTab(string title, Action drawPluginList) + { + if (ImGui.BeginTabItem(title)) + { + ImGui.BeginChild($"Scrolling{title}", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); + + var ready = this.DrawPluginListLoading(); + + if (ready) + { + drawPluginList(); + } + + ImGui.EndChild(); + + ImGui.EndTabItem(); + } + } + */ + + private void DrawAvailablePluginList() + { + var pluginList = this.pluginListAvailable; + + if (pluginList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoCompatible); + return; + } + + var filteredManifests = pluginList + .Where(rm => !this.IsManifestFiltered(rm)) + .ToList(); + + if (filteredManifests.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + return; + } + + // get list to show and reset category dirty flag + var categoryManifestsList = this.categoryManager.GetCurrentCategoryContent(filteredManifests); + + var i = 0; + foreach (var manifest in categoryManifestsList) + { + var remoteManifest = manifest as RemotePluginManifest; + var (isInstalled, plugin) = this.IsManifestInstalled(remoteManifest); + + ImGui.PushID($"{manifest.InternalName}{manifest.AssemblyVersion}"); + if (isInstalled) + { + this.DrawInstalledPlugin(plugin, i++, true); + } + else + { + this.DrawAvailablePlugin(remoteManifest, i++); + } + + ImGui.PopID(); + } + } + + private void DrawInstalledPluginList() + { + var pluginList = this.pluginListInstalled; + + if (pluginList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); + return; + } + + var filteredList = pluginList + .Where(plugin => !this.IsManifestFiltered(plugin.Manifest)) + .ToList(); + + if (filteredList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + return; + } + + var i = 0; + foreach (var plugin in filteredList) + { + this.DrawInstalledPlugin(plugin, i++); + } + } + + private void DrawInstalledDevPluginList() + { + var pluginList = this.pluginListInstalled + .Where(plugin => plugin.IsDev) + .ToList(); + + if (pluginList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); + return; + } + + var filteredList = pluginList + .Where(plugin => !this.IsManifestFiltered(plugin.Manifest)) + .ToList(); + + if (filteredList.Count == 0) + { + ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); + return; + } + + var i = 0; + foreach (var plugin in filteredList) + { + this.DrawInstalledPlugin(plugin, i++); + } + } + + private void DrawPluginCategories() + { + var useContentHeight = -40f; // button height + spacing + var useMenuWidth = 180f; // works fine as static value, table can be resized by user + + var useContentWidth = ImGui.GetContentRegionAvail().X; + + if (ImGui.BeginChild("InstallerCategories", new Vector2(useContentWidth, useContentHeight * ImGuiHelpers.GlobalScale))) + { + ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, ImGuiHelpers.ScaledVector2(5, 0)); + if (ImGui.BeginTable("##InstallerCategoriesCont", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersInnerV)) + { + ImGui.TableSetupColumn("##InstallerCategoriesSelector", ImGuiTableColumnFlags.WidthFixed, useMenuWidth * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("##InstallerCategoriesBody", ImGuiTableColumnFlags.WidthStretch); + ImGui.TableNextRow(); + + ImGui.TableNextColumn(); + this.DrawPluginCategorySelectors(); + + ImGui.TableNextColumn(); + if (ImGui.BeginChild("ScrollingPlugins", new Vector2(-1, 0), false, ImGuiWindowFlags.NoBackground)) + { + this.DrawPluginCategoryContent(); + } + + ImGui.EndChild(); + ImGui.EndTable(); + } + + ImGui.PopStyleVar(); + ImGui.EndChild(); + } + } + + private void DrawPluginCategorySelectors() + { + var colorSearchHighlight = Vector4.One; + unsafe + { + var colorPtr = ImGui.GetStyleColorVec4(ImGuiCol.NavHighlight); + if (colorPtr != null) + { + colorSearchHighlight = *colorPtr; + } + } + + for (var groupIdx = 0; groupIdx < this.categoryManager.GroupList.Length; groupIdx++) + { + var groupInfo = this.categoryManager.GroupList[groupIdx]; + var canShowGroup = (groupInfo.GroupKind != PluginCategoryManager.GroupKind.DevTools) || this.hasDevPlugins; + if (!canShowGroup) + { + continue; + } + + ImGui.SetNextItemOpen(groupIdx == this.categoryManager.CurrentGroupIdx); + if (ImGui.CollapsingHeader(groupInfo.Name, groupIdx == this.categoryManager.CurrentGroupIdx ? ImGuiTreeNodeFlags.OpenOnDoubleClick : ImGuiTreeNodeFlags.None)) + { + if (this.categoryManager.CurrentGroupIdx != groupIdx) + { + this.categoryManager.CurrentGroupIdx = groupIdx; + } + + ImGui.Indent(); + var categoryItemSize = new Vector2(ImGui.GetContentRegionAvail().X - (5 * ImGuiHelpers.GlobalScale), ImGui.GetTextLineHeight()); + for (var categoryIdx = 0; categoryIdx < groupInfo.Categories.Count; categoryIdx++) + { + var categoryInfo = Array.Find(this.categoryManager.CategoryList, x => x.CategoryId == groupInfo.Categories[categoryIdx]); + + var hasSearchHighlight = this.categoryManager.IsCategoryHighlighted(categoryInfo.CategoryId); + if (hasSearchHighlight) + { + ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight); + } + + if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx, ImGuiSelectableFlags.None, categoryItemSize)) + { + this.categoryManager.CurrentCategoryIdx = categoryIdx; + } + + if (hasSearchHighlight) + { + ImGui.PopStyleColor(); + } + } + + ImGui.Unindent(); + + if (groupIdx != this.categoryManager.GroupList.Length - 1) + { + ImGuiHelpers.ScaledDummy(5); + } + } + } + } + + private void DrawPluginCategoryContent() + { + var ready = this.DrawPluginListLoading(); + if (!this.categoryManager.IsSelectionValid || !ready) + { + return; + } + + ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3)); + + var groupInfo = this.categoryManager.GroupList[this.categoryManager.CurrentGroupIdx]; + if (this.categoryManager.IsContentDirty) + { + // reset opened list of collapsibles when switching between categories + this.openPluginCollapsibles.Clear(); + + // do NOT reset dirty flag when Available group is selected, it will be handled by DrawAvailablePluginList() + if (groupInfo.GroupKind != PluginCategoryManager.GroupKind.Available) + { + this.categoryManager.ResetContentDirty(); + } + } + + if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.DevTools) + { + // this one is never sorted and remains in hardcoded order from group ctor + switch (this.categoryManager.CurrentCategoryIdx) + { + case 0: + this.DrawInstalledDevPluginList(); + break; + + case 1: + this.DrawImageTester(); + break; + + default: + // umm, there's nothing else, keep handled set and just skip drawing... + break; + } + } + else if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.Installed) + { + this.DrawInstalledPluginList(); + } + else + { + this.DrawAvailablePluginList(); + } + + ImGui.PopStyleVar(); + } + + private void DrawImageTester() + { + var sectionSize = ImGuiHelpers.GlobalScale * 66; + var startCursor = ImGui.GetCursorPos(); + + ImGui.PushStyleColor(ImGuiCol.Button, true ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero); + + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); + ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); + + ImGui.Button($"###pluginTesterCollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize)); + + ImGui.PopStyleVar(); + + ImGui.PopStyleColor(3); + + ImGui.SetCursorPos(startCursor); + + var hasIcon = this.testerIcon != null; + + var iconTex = this.imageCache.DefaultIcon; + if (hasIcon) iconTex = this.testerIcon; + + var iconSize = ImGuiHelpers.ScaledVector2(64, 64); + + var cursorBeforeImage = ImGui.GetCursorPos(); + ImGui.Image(iconTex.ImGuiHandle, iconSize); + ImGui.SameLine(); + + if (this.testerError) + { + ImGui.SetCursorPos(cursorBeforeImage); + ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); + ImGui.SameLine(); + } + else if (this.testerUpdateAvailable) + { + ImGui.SetCursorPos(cursorBeforeImage); + ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); + ImGui.SameLine(); + } + + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + + var cursor = ImGui.GetCursorPos(); + // Name + ImGui.Text("My Cool Plugin"); + + // Download count + var downloadCountText = Locs.PluginBody_AuthorWithDownloadCount("Plugin Enjoyer", 69420); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText); + + cursor.Y += ImGui.GetTextLineHeightWithSpacing(); + ImGui.SetCursorPos(cursor); + + // Description + ImGui.TextWrapped("This plugin does very many great things."); + + startCursor.Y += sectionSize; + ImGui.SetCursorPos(startCursor); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Indent(); + + // Description + ImGui.TextWrapped("This is a description.\nIt has multiple lines.\nTruly descriptive."); + + ImGuiHelpers.ScaledDummy(5); + + // Controls + var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; + + var versionString = "1.0.0.0"; + + if (disabled) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString)); + } + else + { + var buttonText = Locs.PluginButton_InstallVersion(versionString); + ImGui.Button($"{buttonText}##{buttonText}testing"); + } + + this.DrawVisitRepoUrlButton("https://google.com"); + + if (this.testerImages != null) + { + ImGuiHelpers.ScaledDummy(5); + + const float thumbFactor = 2.7f; + + var scrollBarSize = 15; + ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarSize, scrollBarSize); + ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, Vector4.Zero); + + var width = ImGui.GetWindowWidth(); + + if (ImGui.BeginChild( + "pluginTestingImageScrolling", + new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize), + false, + ImGuiWindowFlags.HorizontalScrollbar | + ImGuiWindowFlags.NoScrollWithMouse | + ImGuiWindowFlags.NoBackground)) + { + if (this.testerImages != null && this.testerImages is { Length: > 0 }) + { + for (var i = 0; i < this.testerImages.Length; i++) + { + var popupId = $"pluginTestingImage{i}"; + var image = this.testerImages[i]; + if (image == null) + continue; + + ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0); + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); + + if (ImGui.BeginPopup(popupId)) + { + if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height))) + ImGui.CloseCurrentPopup(); + + ImGui.EndPopup(); + } + + ImGui.PopStyleVar(3); + + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); + + float xAct = image.Width; + float yAct = image.Height; + float xMax = PluginImageCache.PluginImageWidth; + float yMax = PluginImageCache.PluginImageHeight; + + // scale image if undersized + if (xAct < xMax && yAct < yMax) + { + var scale = Math.Min(xMax / xAct, yMax / yAct); + xAct *= scale; + yAct *= scale; + } + + var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor); + if (ImGui.ImageButton(image.ImGuiHandle, size)) + ImGui.OpenPopup(popupId); + + ImGui.PopStyleVar(); + + if (i < this.testerImages.Length - 1) + { + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + } + } + } + } + + ImGui.EndChild(); + + ImGui.PopStyleVar(); + ImGui.PopStyleColor(); + + ImGui.Unindent(); + } + + ImGuiHelpers.ScaledDummy(20); + + ImGui.InputText("Icon Path", ref this.testerIconPath, 1000); + ImGui.InputText("Image 1 Path", ref this.testerImagePaths[0], 1000); + ImGui.InputText("Image 2 Path", ref this.testerImagePaths[1], 1000); + ImGui.InputText("Image 3 Path", ref this.testerImagePaths[2], 1000); + ImGui.InputText("Image 4 Path", ref this.testerImagePaths[3], 1000); + ImGui.InputText("Image 5 Path", ref this.testerImagePaths[4], 1000); + + var im = Service.Get(); + if (ImGui.Button("Load")) + { + try + { + if (this.testerIcon != null) + { + this.testerIcon.Dispose(); + this.testerIcon = null; + } + + if (!this.testerIconPath.IsNullOrEmpty()) + { + this.testerIcon = im.LoadImage(this.testerIconPath); + } + + this.testerImages = new TextureWrap[this.testerImagePaths.Length]; + + for (var i = 0; i < this.testerImagePaths.Length; i++) + { + if (this.testerImagePaths[i].IsNullOrEmpty()) + continue; + + if (this.testerImages[i] != null) + { + this.testerImages[i].Dispose(); + this.testerImages[i] = null; + } + + this.testerImages[i] = im.LoadImage(this.testerImagePaths[i]); + } + } + catch (Exception ex) + { + Log.Error(ex, "Could not load plugin images for testing."); + } + } + + ImGui.Checkbox("Failed", ref this.testerError); + ImGui.Checkbox("Has Update", ref this.testerUpdateAvailable); + } + + private bool DrawPluginListLoading() + { + var pluginManager = Service.Get(); + + if (pluginManager.SafeMode) + { + ImGui.Text(Locs.TabBody_SafeMode); + return false; + } + + var ready = pluginManager.PluginsReady && pluginManager.ReposReady; + + if (!ready) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_LoadingPlugins); + } + + var failedRepos = pluginManager.Repos + .Where(repo => repo.State == PluginRepositoryState.Fail) + .ToArray(); + + if (failedRepos.Length > 0) + { + var failText = Locs.TabBody_DownloadFailed; + var aggFailText = failedRepos + .Select(repo => $"{failText} ({repo.PluginMasterUrl})") + .Aggregate((s1, s2) => $"{s1}\n{s2}"); + + ImGui.TextColored(ImGuiColors.DalamudRed, aggFailText); + } + + return ready; + } + + private bool DrawPluginCollapsingHeader(string label, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, bool trouble, bool updateAvailable, bool isNew, Action drawContextMenuAction, int index) + { + ImGui.Separator(); + + var isOpen = this.openPluginCollapsibles.Contains(index); + + var sectionSize = ImGuiHelpers.GlobalScale * 66; + var startCursor = ImGui.GetCursorPos(); + + ImGui.PushStyleColor(ImGuiCol.Button, isOpen ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero); + + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); + ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); + ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); + + if (ImGui.Button($"###plugin{index}CollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize))) + { + if (isOpen) + { + this.openPluginCollapsibles.Remove(index); + } + else + { + this.openPluginCollapsibles.Add(index); + } + + isOpen = !isOpen; + } + + drawContextMenuAction?.Invoke(); + + ImGui.PopStyleVar(); + + ImGui.PopStyleColor(3); + + ImGui.SetCursorPos(startCursor); + + var iconTex = this.imageCache.DefaultIcon; + var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex); + if (hasIcon && cachedIconTex != null) + { + iconTex = cachedIconTex; + } + + var iconSize = ImGuiHelpers.ScaledVector2(64, 64); + + var cursorBeforeImage = ImGui.GetCursorPos(); + ImGui.Image(iconTex.ImGuiHandle, iconSize); + ImGui.SameLine(); + + if (updateAvailable) + { + ImGui.SetCursorPos(cursorBeforeImage); + ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); + ImGui.SameLine(); + } + else if (trouble) + { + ImGui.SetCursorPos(cursorBeforeImage); + ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); + ImGui.SameLine(); + } + + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + + var cursor = ImGui.GetCursorPos(); + + // Name + ImGui.Text(label); + + // Download count + var downloadCountText = manifest.DownloadCount > 0 + ? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount) + : Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText); + + if (isNew) + { + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.TankBlue, Locs.PluginTitleMod_New); + } + + cursor.Y += ImGui.GetTextLineHeightWithSpacing(); + ImGui.SetCursorPos(cursor); + + // Outdated warning + if (plugin is { IsOutdated: true, IsBanned: false }) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.TextWrapped(Locs.PluginBody_Outdated); + ImGui.PopStyleColor(); + } + + // Banned warning + if (plugin is { IsBanned: true }) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); + ImGui.TextWrapped(plugin.BanReason.IsNullOrEmpty() + ? Locs.PluginBody_Banned + : Locs.PluginBody_BannedReason(plugin.BanReason)); + + ImGui.PopStyleColor(); + } + + // Description + if (plugin is null or { IsOutdated: false, IsBanned: false }) + { + if (!string.IsNullOrWhiteSpace(manifest.Punchline)) + { + ImGui.TextWrapped(manifest.Punchline); + } + else if (!string.IsNullOrWhiteSpace(manifest.Description)) + { + const int punchlineLen = 200; + var firstLine = manifest.Description.Split(new[] { '\r', '\n' })[0]; + + ImGui.TextWrapped(firstLine.Length < punchlineLen + ? firstLine + : firstLine[..punchlineLen]); + } + } + + startCursor.Y += sectionSize; + ImGui.SetCursorPos(startCursor); + + return isOpen; + } + + private void DrawAvailablePlugin(RemotePluginManifest manifest, int index) + { + var configuration = Service.Get(); + var notifications = Service.Get(); + var pluginManager = Service.Get(); + + var useTesting = pluginManager.UseTesting(manifest); + var wasSeen = this.WasPluginSeen(manifest.InternalName); + + // Check for valid versions + if ((useTesting && manifest.TestingAssemblyVersion == null) || manifest.AssemblyVersion == null) + { + // Without a valid version, quit + return; + } + + // Name + var label = manifest.Name; + + // Testing + if (useTesting) + { + label += Locs.PluginTitleMod_TestingVersion; + } + + ImGui.PushID($"available{index}{manifest.InternalName}"); + + var isThirdParty = manifest.SourceRepo.IsThirdParty; + if (this.DrawPluginCollapsingHeader(label, null, manifest, isThirdParty, false, false, !wasSeen, () => this.DrawAvailablePluginContextMenu(manifest), index)) + { + if (!wasSeen) + configuration.SeenPluginInternalName.Add(manifest.InternalName); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Indent(); + + // Installable from + if (manifest.SourceRepo.IsThirdParty) + { + var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.SourceRepo.PluginMasterUrl); + ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText); + + ImGuiHelpers.ScaledDummy(2); + } + + // Description + if (!string.IsNullOrWhiteSpace(manifest.Description)) + { + ImGui.TextWrapped(manifest.Description); + } + + ImGuiHelpers.ScaledDummy(5); + + // Controls + var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; + + var versionString = useTesting + ? $"{manifest.TestingAssemblyVersion}" + : $"{manifest.AssemblyVersion}"; + + if (disabled) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString)); + } + else + { + var buttonText = Locs.PluginButton_InstallVersion(versionString); + if (ImGui.Button($"{buttonText}##{buttonText}{index}")) + { + this.installStatus = OperationStatus.InProgress; + + Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting, PluginLoadReason.Installer)) + .ContinueWith(task => + { + // There is no need to set as Complete for an individual plugin installation + this.installStatus = OperationStatus.Idle; + if (this.DisplayErrorContinuation(task, Locs.ErrorModal_InstallFail(manifest.Name))) + { + if (task.Result.State == PluginState.Loaded) + { + notifications.AddNotification(Locs.Notifications_PluginInstalled(manifest.Name), Locs.Notifications_PluginInstalledTitle, NotificationType.Success); + } + else + { + notifications.AddNotification(Locs.Notifications_PluginNotInstalled(manifest.Name), Locs.Notifications_PluginNotInstalledTitle, NotificationType.Error); + this.ShowErrorModal(Locs.ErrorModal_InstallFail(manifest.Name)); + } + } + }); + } + } + + this.DrawVisitRepoUrlButton(manifest.RepoUrl); + + if (!manifest.SourceRepo.IsThirdParty) + { + this.DrawSendFeedbackButton(manifest); + } + + ImGuiHelpers.ScaledDummy(5); + + if (this.DrawPluginImages(null, manifest, isThirdParty, index)) + ImGuiHelpers.ScaledDummy(5); + + ImGui.Unindent(); } ImGui.PopID(); } - } - private void DrawInstalledPluginList() - { - var pluginList = this.pluginListInstalled; - - if (pluginList.Count == 0) + private void DrawAvailablePluginContextMenu(PluginManifest manifest) { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); - return; - } + var configuration = Service.Get(); + var pluginManager = Service.Get(); + var startInfo = Service.Get(); - var filteredList = pluginList - .Where(plugin => !this.IsManifestFiltered(plugin.Manifest)) - .ToList(); - - if (filteredList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); - return; - } - - var i = 0; - foreach (var plugin in filteredList) - { - this.DrawInstalledPlugin(plugin, i++); - } - } - - private void DrawInstalledDevPluginList() - { - var pluginList = this.pluginListInstalled - .Where(plugin => plugin.IsDev) - .ToList(); - - if (pluginList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_SearchNoInstalled); - return; - } - - var filteredList = pluginList - .Where(plugin => !this.IsManifestFiltered(plugin.Manifest)) - .ToList(); - - if (filteredList.Count == 0) - { - ImGui.TextColored(ImGuiColors.DalamudGrey2, Locs.TabBody_SearchNoMatching); - return; - } - - var i = 0; - foreach (var plugin in filteredList) - { - this.DrawInstalledPlugin(plugin, i++); - } - } - - private void DrawPluginCategories() - { - var useContentHeight = -40f; // button height + spacing - var useMenuWidth = 180f; // works fine as static value, table can be resized by user - - var useContentWidth = ImGui.GetContentRegionAvail().X; - - if (ImGui.BeginChild("InstallerCategories", new Vector2(useContentWidth, useContentHeight * ImGuiHelpers.GlobalScale))) - { - ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, ImGuiHelpers.ScaledVector2(5, 0)); - if (ImGui.BeginTable("##InstallerCategoriesCont", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersInnerV)) + if (ImGui.BeginPopupContextItem("ItemContextMenu")) { - ImGui.TableSetupColumn("##InstallerCategoriesSelector", ImGuiTableColumnFlags.WidthFixed, useMenuWidth * ImGuiHelpers.GlobalScale); - ImGui.TableSetupColumn("##InstallerCategoriesBody", ImGuiTableColumnFlags.WidthStretch); - ImGui.TableNextRow(); - - ImGui.TableNextColumn(); - this.DrawPluginCategorySelectors(); - - ImGui.TableNextColumn(); - if (ImGui.BeginChild("ScrollingPlugins", new Vector2(-1, 0), false, ImGuiWindowFlags.NoBackground)) + if (ImGui.Selectable(Locs.PluginContext_MarkAllSeen)) { - this.DrawPluginCategoryContent(); + configuration.SeenPluginInternalName.AddRange(this.pluginListAvailable.Select(x => x.InternalName)); + configuration.Save(); + pluginManager.RefilterPluginMasters(); + } + + if (ImGui.Selectable(Locs.PluginContext_HidePlugin)) + { + Log.Debug($"Adding {manifest.InternalName} to hidden plugins"); + configuration.HiddenPluginInternalName.Add(manifest.InternalName); + configuration.Save(); + pluginManager.RefilterPluginMasters(); + } + + if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfig)) + { + Log.Debug($"Deleting config for {manifest.InternalName}"); + + this.installStatus = OperationStatus.InProgress; + + Task.Run(() => + { + pluginManager.PluginConfigs.Delete(manifest.InternalName); + + var path = Path.Combine(startInfo.PluginDirectory, manifest.InternalName); + if (Directory.Exists(path)) + Directory.Delete(path, true); + }) + .ContinueWith(task => + { + this.installStatus = OperationStatus.Idle; + + this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(manifest.InternalName)); + }); + } + + ImGui.EndPopup(); + } + } + + private void DrawInstalledPlugin(LocalPlugin plugin, int index, bool showInstalled = false) + { + var configuration = Service.Get(); + var commandManager = Service.Get(); + var pluginManager = Service.Get(); + var startInfo = Service.Get(); + + var trouble = false; + + // Name + var label = plugin.Manifest.Name; + + // Testing + if (plugin.Manifest.Testing) + { + label += Locs.PluginTitleMod_TestingVersion; + } + + // Freshly installed + if (showInstalled) + { + label += Locs.PluginTitleMod_Installed; + } + + // Disabled + if (plugin.IsDisabled) + { + label += Locs.PluginTitleMod_Disabled; + trouble = true; + } + + // Load error + if (plugin.State == PluginState.LoadError) + { + label += Locs.PluginTitleMod_LoadError; + trouble = true; + } + + // Unload error + if (plugin.State == PluginState.UnloadError) + { + label += Locs.PluginTitleMod_UnloadError; + trouble = true; + } + + var availablePluginUpdate = this.pluginListUpdatable.FirstOrDefault(up => up.InstalledPlugin == plugin); + // Update available + if (availablePluginUpdate != default) + { + label += Locs.PluginTitleMod_HasUpdate; + } + + // Freshly updated + var thisWasUpdated = false; + if (this.updatedPlugins != null && !plugin.IsDev) + { + var update = this.updatedPlugins.FirstOrDefault(update => update.InternalName == plugin.Manifest.InternalName); + if (update != default) + { + if (update.WasUpdated) + { + thisWasUpdated = true; + label += Locs.PluginTitleMod_Updated; + } + else + { + label += Locs.PluginTitleMod_UpdateFailed; + } + } + } + + // Outdated API level + if (plugin.IsOutdated) + { + label += Locs.PluginTitleMod_OutdatedError; + trouble = true; + } + + // Banned + if (plugin.IsBanned) + { + label += Locs.PluginTitleMod_BannedError; + trouble = true; + } + + ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}"); + + if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, () => this.DrawInstalledPluginContextMenu(plugin), index)) + { + if (!this.WasPluginSeen(plugin.Manifest.InternalName)) + configuration.SeenPluginInternalName.Add(plugin.Manifest.InternalName); + + var manifest = plugin.Manifest; + + ImGui.Indent(); + + // Name + ImGui.Text(manifest.Name); + + // Download count + var downloadText = plugin.IsDev + ? Locs.PluginBody_AuthorWithoutDownloadCount(manifest.Author) + : manifest.DownloadCount > 0 + ? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount) + : Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadText); + + var isThirdParty = manifest.IsThirdParty; + var canFeedback = !isThirdParty && !plugin.IsDev && plugin.Manifest.DalamudApiLevel == PluginManager.DalamudApiLevel; + + // Installed from + if (plugin.IsDev) + { + var fileText = Locs.PluginBody_DevPluginPath(plugin.DllFile.FullName); + ImGui.TextColored(ImGuiColors.DalamudGrey3, fileText); + } + else if (isThirdParty) + { + var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.InstalledFromUrl); + ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText); + } + + // Description + if (!string.IsNullOrWhiteSpace(manifest.Description)) + { + ImGui.TextWrapped(manifest.Description); + } + + // Available commands (if loaded) + if (plugin.IsLoaded) + { + var commands = commandManager.Commands + .Where(cInfo => cInfo.Value.ShowInHelp && cInfo.Value.LoaderAssemblyName == plugin.Manifest.InternalName) + .ToArray(); + + if (commands.Any()) + { + ImGui.Dummy(ImGuiHelpers.ScaledVector2(10f, 10f)); + foreach (var command in commands) + { + ImGui.TextWrapped($"{command.Key} → {command.Value.HelpMessage}"); + } + } + } + + // Controls + this.DrawPluginControlButton(plugin); + this.DrawDevPluginButtons(plugin); + this.DrawDeletePluginButton(plugin); + this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl); + + if (canFeedback) + { + this.DrawSendFeedbackButton(plugin.Manifest); + } + + if (availablePluginUpdate != default) + this.DrawUpdateSinglePluginButton(availablePluginUpdate); + + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.AssemblyVersion}"); + + if (plugin.IsDev) + { + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.DalamudRed, Locs.PluginBody_DeleteDevPlugin); + } + + ImGuiHelpers.ScaledDummy(5); + + if (this.DrawPluginImages(plugin, manifest, isThirdParty, index)) + ImGuiHelpers.ScaledDummy(5); + + ImGui.Unindent(); + } + + if (thisWasUpdated && !plugin.Manifest.Changelog.IsNullOrEmpty()) + { + ImGuiHelpers.ScaledDummy(5); + + ImGui.PushStyleColor(ImGuiCol.ChildBg, this.changelogBgColor); + ImGui.PushStyleColor(ImGuiCol.Text, this.changelogTextColor); + + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(7, 5)); + + if (ImGui.BeginChild("##changelog", new Vector2(-1, 100), true, ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.AlwaysAutoResize)) + { + ImGui.Text("Changelog:"); + ImGuiHelpers.ScaledDummy(2); + ImGui.TextWrapped(plugin.Manifest.Changelog); } ImGui.EndChild(); - ImGui.EndTable(); + + ImGui.PopStyleVar(); + ImGui.PopStyleColor(2); } - ImGui.PopStyleVar(); - ImGui.EndChild(); - } - } - - private void DrawPluginCategorySelectors() - { - var colorSearchHighlight = Vector4.One; - unsafe - { - var colorPtr = ImGui.GetStyleColorVec4(ImGuiCol.NavHighlight); - if (colorPtr != null) - { - colorSearchHighlight = *colorPtr; - } + ImGui.PopID(); } - for (var groupIdx = 0; groupIdx < this.categoryManager.GroupList.Length; groupIdx++) + private void DrawInstalledPluginContextMenu(LocalPlugin plugin) { - var groupInfo = this.categoryManager.GroupList[groupIdx]; - var canShowGroup = (groupInfo.GroupKind != PluginCategoryManager.GroupKind.DevTools) || this.hasDevPlugins; - if (!canShowGroup) - { - continue; - } + var pluginManager = Service.Get(); - ImGui.SetNextItemOpen(groupIdx == this.categoryManager.CurrentGroupIdx); - if (ImGui.CollapsingHeader(groupInfo.Name, groupIdx == this.categoryManager.CurrentGroupIdx ? ImGuiTreeNodeFlags.OpenOnDoubleClick : ImGuiTreeNodeFlags.None)) + if (ImGui.BeginPopupContextItem("InstalledItemContextMenu")) { - if (this.categoryManager.CurrentGroupIdx != groupIdx) + if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfigReload)) { - this.categoryManager.CurrentGroupIdx = groupIdx; + Log.Debug($"Deleting config for {plugin.Manifest.InternalName}"); + + this.installStatus = OperationStatus.InProgress; + + Task.Run(() => pluginManager.DeleteConfiguration(plugin)) + .ContinueWith(task => + { + this.installStatus = OperationStatus.Idle; + + this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(plugin.Name)); + }); } - ImGui.Indent(); - var categoryItemSize = new Vector2(ImGui.GetContentRegionAvail().X - (5 * ImGuiHelpers.GlobalScale), ImGui.GetTextLineHeight()); - for (var categoryIdx = 0; categoryIdx < groupInfo.Categories.Count; categoryIdx++) + ImGui.EndPopup(); + } + } + + private void DrawPluginControlButton(LocalPlugin plugin) + { + var configuration = Service.Get(); + var notifications = Service.Get(); + var pluginManager = Service.Get(); + var startInfo = Service.Get(); + + // Disable everything if the updater is running or another plugin is operating + var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; + + // Disable everything if the plugin is outdated + disabled = disabled || (plugin.IsOutdated && !configuration.LoadAllApiLevels) || plugin.IsBanned; + + if (plugin.State == PluginState.InProgress) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_Working); + } + else if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError) + { + if (disabled) { - var categoryInfo = Array.Find(this.categoryManager.CategoryList, x => x.CategoryId == groupInfo.Categories[categoryIdx]); - - var hasSearchHighlight = this.categoryManager.IsCategoryHighlighted(categoryInfo.CategoryId); - if (hasSearchHighlight) + ImGuiComponents.DisabledButton(Locs.PluginButton_Disable); + } + else + { + if (ImGui.Button(Locs.PluginButton_Disable)) { - ImGui.PushStyleColor(ImGuiCol.Text, colorSearchHighlight); - } + Task.Run(() => + { + var unloadTask = Task.Run(() => plugin.Unload()) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name)); - if (ImGui.Selectable(categoryInfo.Name, this.categoryManager.CurrentCategoryIdx == categoryIdx, ImGuiSelectableFlags.None, categoryItemSize)) - { - this.categoryManager.CurrentCategoryIdx = categoryIdx; - } + unloadTask.Wait(); + if (!unloadTask.Result) + return; - if (hasSearchHighlight) - { - ImGui.PopStyleColor(); + var disableTask = Task.Run(() => plugin.Disable()) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name)); + + disableTask.Wait(); + if (!disableTask.Result) + return; + + if (!plugin.IsDev) + { + pluginManager.RemovePlugin(plugin); + } + + notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); + }); } } - ImGui.Unindent(); - - if (groupIdx != this.categoryManager.GroupList.Length - 1) + if (plugin.State == PluginState.Loaded) { - ImGuiHelpers.ScaledDummy(5); + // Only if the plugin isn't broken. + this.DrawOpenPluginSettingsButton(plugin); } } - } - } - - private void DrawPluginCategoryContent() - { - var ready = this.DrawPluginListLoading(); - if (!this.categoryManager.IsSelectionValid || !ready) - { - return; - } - - ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, ImGuiHelpers.ScaledVector2(1, 3)); - - var groupInfo = this.categoryManager.GroupList[this.categoryManager.CurrentGroupIdx]; - if (this.categoryManager.IsContentDirty) - { - // reset opened list of collapsibles when switching between categories - this.openPluginCollapsibles.Clear(); - - // do NOT reset dirty flag when Available group is selected, it will be handled by DrawAvailablePluginList() - if (groupInfo.GroupKind != PluginCategoryManager.GroupKind.Available) + else if (plugin.State == PluginState.Unloaded) { - this.categoryManager.ResetContentDirty(); + if (disabled) + { + ImGuiComponents.DisabledButton(Locs.PluginButton_Load); + } + else + { + if (ImGui.Button(Locs.PluginButton_Load)) + { + Task.Run(() => + { + var enableTask = Task.Run(() => plugin.Enable()) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_EnableFail(plugin.Name)); + + enableTask.Wait(); + if (!enableTask.Result) + return; + + var loadTask = Task.Run(() => plugin.Load(PluginLoadReason.Installer)) + .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name)); + + loadTask.Wait(); + if (!loadTask.Result) + return; + }); + } + } + } + else if (plugin.State == PluginState.UnloadError) + { + ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); } } - if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.DevTools) + private void DrawUpdateSinglePluginButton(AvailablePluginUpdate update) { - // this one is never sorted and remains in hardcoded order from group ctor - switch (this.categoryManager.CurrentCategoryIdx) - { - case 0: - this.DrawInstalledDevPluginList(); - break; + var pluginManager = Service.Get(); - case 1: - this.DrawImageTester(); - break; - - default: - // umm, there's nothing else, keep handled set and just skip drawing... - break; - } - } - else if (groupInfo.GroupKind == PluginCategoryManager.GroupKind.Installed) - { - this.DrawInstalledPluginList(); - } - else - { - this.DrawAvailablePluginList(); - } - - ImGui.PopStyleVar(); - } - - private void DrawImageTester() - { - var sectionSize = ImGuiHelpers.GlobalScale * 66; - var startCursor = ImGui.GetCursorPos(); - - ImGui.PushStyleColor(ImGuiCol.Button, true ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero); - - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); - ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); - - ImGui.Button($"###pluginTesterCollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize)); - - ImGui.PopStyleVar(); - - ImGui.PopStyleColor(3); - - ImGui.SetCursorPos(startCursor); - - var hasIcon = this.testerIcon != null; - - var iconTex = this.imageCache.DefaultIcon; - if (hasIcon) iconTex = this.testerIcon; - - var iconSize = ImGuiHelpers.ScaledVector2(64, 64); - - var cursorBeforeImage = ImGui.GetCursorPos(); - ImGui.Image(iconTex.ImGuiHandle, iconSize); - ImGui.SameLine(); - - if (this.testerError) - { - ImGui.SetCursorPos(cursorBeforeImage); - ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Download)) + { + this.installStatus = OperationStatus.InProgress; + + Task.Run(async () => await pluginManager.UpdateSinglePluginAsync(update, true, false)) + .ContinueWith(task => + { + // There is no need to set as Complete for an individual plugin installation + this.installStatus = OperationStatus.Idle; + + var errorMessage = Locs.ErrorModal_SingleUpdateFail(update.UpdateManifest.Name); + this.DisplayErrorContinuation(task, errorMessage); + + if (!task.Result.WasUpdated) + { + this.ShowErrorModal(errorMessage); + } + }); + } + + if (ImGui.IsItemHovered()) + { + var updateVersion = update.UseTesting + ? update.UpdateManifest.TestingAssemblyVersion + : update.UpdateManifest.AssemblyVersion; + ImGui.SetTooltip(Locs.PluginButtonToolTip_UpdateSingle(updateVersion.ToString())); + } } - else if (this.testerUpdateAvailable) + + private void DrawOpenPluginSettingsButton(LocalPlugin plugin) + { + if (plugin.DalamudInterface?.UiBuilder?.HasConfigUi ?? false) + { + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) + { + try + { + plugin.DalamudInterface.UiBuilder.OpenConfig(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error during OpenConfigUi: {plugin.Name}"); + } + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.PluginButtonToolTip_OpenConfiguration); + } + } + } + + private void DrawSendFeedbackButton(PluginManifest manifest) { - ImGui.SetCursorPos(cursorBeforeImage); - ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Comment)) + { + this.feedbackPlugin = manifest; + this.feedbackModalOnNextFrame = true; + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.FeedbackModal_Title); + } } - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - - var cursor = ImGui.GetCursorPos(); - // Name - ImGui.Text("My Cool Plugin"); - - // Download count - var downloadCountText = Locs.PluginBody_AuthorWithDownloadCount("Plugin Enjoyer", 69420); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText); - - cursor.Y += ImGui.GetTextLineHeightWithSpacing(); - ImGui.SetCursorPos(cursor); - - // Description - ImGui.TextWrapped("This plugin does very many great things."); - - startCursor.Y += sectionSize; - ImGui.SetCursorPos(startCursor); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Indent(); - - // Description - ImGui.TextWrapped("This is a description.\nIt has multiple lines.\nTruly descriptive."); - - ImGuiHelpers.ScaledDummy(5); - - // Controls - var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; - - var versionString = "1.0.0.0"; - - if (disabled) + private void DrawDevPluginButtons(LocalPlugin localPlugin) { - ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString)); - } - else - { - var buttonText = Locs.PluginButton_InstallVersion(versionString); - ImGui.Button($"{buttonText}##{buttonText}testing"); + var configuration = Service.Get(); + + if (localPlugin is LocalDevPlugin plugin) + { + // https://colorswall.com/palette/2868/ + var greenColor = new Vector4(0x5C, 0xB8, 0x5C, 0xFF) / 0xFF; + var redColor = new Vector4(0xD9, 0x53, 0x4F, 0xFF) / 0xFF; + + // Load on boot + ImGui.PushStyleColor(ImGuiCol.Button, plugin.StartOnBoot ? greenColor : redColor); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.StartOnBoot ? greenColor : redColor); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.PowerOff)) + { + plugin.StartOnBoot ^= true; + configuration.Save(); + } + + ImGui.PopStyleColor(2); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.PluginButtonToolTip_StartOnBoot); + } + + // Automatic reload + ImGui.PushStyleColor(ImGuiCol.Button, plugin.AutomaticReload ? greenColor : redColor); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.AutomaticReload ? greenColor : redColor); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.SyncAlt)) + { + plugin.AutomaticReload ^= true; + configuration.Save(); + } + + ImGui.PopStyleColor(2); + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.PluginButtonToolTip_AutomaticReloading); + } + } } - this.DrawVisitRepoUrlButton("https://google.com"); - - if (this.testerImages != null) + private void DrawDeletePluginButton(LocalPlugin plugin) { - ImGuiHelpers.ScaledDummy(5); + var unloaded = plugin.State == PluginState.Unloaded; + var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned); + + if (!showButton) + return; + + var pluginManager = Service.Get(); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt)) + { + try + { + plugin.DllFile.Delete(); + pluginManager.RemovePlugin(plugin); + } + catch (Exception ex) + { + Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}"); + + this.errorModalMessage = Locs.ErrorModal_DeleteFail(plugin.Name); + this.errorModalDrawing = true; + this.errorModalOnNextFrame = true; + } + } + + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePlugin); + } + } + + private void DrawVisitRepoUrlButton(string? repoUrl) + { + if (!string.IsNullOrEmpty(repoUrl) && repoUrl.StartsWith("https://")) + { + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Globe)) + { + try + { + _ = Process.Start(new ProcessStartInfo() + { + FileName = repoUrl, + UseShellExecute = true, + }); + } + catch (Exception ex) + { + Log.Error(ex, $"Could not open repoUrl: {repoUrl}"); + } + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Locs.PluginButtonToolTip_VisitPluginUrl); + } + } + + private bool DrawPluginImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, int index) + { + var hasImages = this.imageCache.TryGetImages(plugin, manifest, isThirdParty, out var imageTextures); + if (!hasImages || imageTextures.Length == 0) + return false; const float thumbFactor = 2.7f; @@ -845,1446 +1812,480 @@ internal class PluginInstallerWindow : Window, IDisposable var width = ImGui.GetWindowWidth(); - if (ImGui.BeginChild( - "pluginTestingImageScrolling", - new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize), - false, - ImGuiWindowFlags.HorizontalScrollbar | - ImGuiWindowFlags.NoScrollWithMouse | - ImGuiWindowFlags.NoBackground)) + if (ImGui.BeginChild($"plugin{index}ImageScrolling", new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoBackground)) { - if (this.testerImages != null && this.testerImages is { Length: > 0 }) + for (var i = 0; i < imageTextures.Length; i++) { - for (var i = 0; i < this.testerImages.Length; i++) - { - var popupId = $"pluginTestingImage{i}"; - var image = this.testerImages[i]; - if (image == null) - continue; - - ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0); - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - - if (ImGui.BeginPopup(popupId)) - { - if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height))) - ImGui.CloseCurrentPopup(); - - ImGui.EndPopup(); - } - - ImGui.PopStyleVar(3); - - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - - float xAct = image.Width; - float yAct = image.Height; - float xMax = PluginImageCache.PluginImageWidth; - float yMax = PluginImageCache.PluginImageHeight; - - // scale image if undersized - if (xAct < xMax && yAct < yMax) - { - var scale = Math.Min(xMax / xAct, yMax / yAct); - xAct *= scale; - yAct *= scale; - } - - var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor); - if (ImGui.ImageButton(image.ImGuiHandle, size)) - ImGui.OpenPopup(popupId); - - ImGui.PopStyleVar(); - - if (i < this.testerImages.Length - 1) - { - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - } - } - } - } - - ImGui.EndChild(); - - ImGui.PopStyleVar(); - ImGui.PopStyleColor(); - - ImGui.Unindent(); - } - - ImGuiHelpers.ScaledDummy(20); - - ImGui.InputText("Icon Path", ref this.testerIconPath, 1000); - ImGui.InputText("Image 1 Path", ref this.testerImagePaths[0], 1000); - ImGui.InputText("Image 2 Path", ref this.testerImagePaths[1], 1000); - ImGui.InputText("Image 3 Path", ref this.testerImagePaths[2], 1000); - ImGui.InputText("Image 4 Path", ref this.testerImagePaths[3], 1000); - ImGui.InputText("Image 5 Path", ref this.testerImagePaths[4], 1000); - - var im = Service.Get(); - if (ImGui.Button("Load")) - { - try - { - if (this.testerIcon != null) - { - this.testerIcon.Dispose(); - this.testerIcon = null; - } - - if (!this.testerIconPath.IsNullOrEmpty()) - { - this.testerIcon = im.LoadImage(this.testerIconPath); - } - - this.testerImages = new TextureWrap[this.testerImagePaths.Length]; - - for (var i = 0; i < this.testerImagePaths.Length; i++) - { - if (this.testerImagePaths[i].IsNullOrEmpty()) + var image = imageTextures[i]; + if (image == null) continue; - if (this.testerImages[i] != null) + ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0); + ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); + + var popupId = $"plugin{index}image{i}"; + if (ImGui.BeginPopup(popupId)) { - this.testerImages[i].Dispose(); - this.testerImages[i] = null; + if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height))) + ImGui.CloseCurrentPopup(); + + ImGui.EndPopup(); } - this.testerImages[i] = im.LoadImage(this.testerImagePaths[i]); - } - } - catch (Exception ex) - { - Log.Error(ex, "Could not load plugin images for testing."); - } - } + ImGui.PopStyleVar(3); - ImGui.Checkbox("Failed", ref this.testerError); - ImGui.Checkbox("Has Update", ref this.testerUpdateAvailable); - } + ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - private bool DrawPluginListLoading() - { - var pluginManager = Service.Get(); + float xAct = image.Width; + float yAct = image.Height; + float xMax = PluginImageCache.PluginImageWidth; + float yMax = PluginImageCache.PluginImageHeight; - if (pluginManager.SafeMode) - { - ImGui.Text(Locs.TabBody_SafeMode); - return false; - } - - var ready = pluginManager.PluginsReady && pluginManager.ReposReady; - - if (!ready) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, Locs.TabBody_LoadingPlugins); - } - - var failedRepos = pluginManager.Repos - .Where(repo => repo.State == PluginRepositoryState.Fail) - .ToArray(); - - if (failedRepos.Length > 0) - { - var failText = Locs.TabBody_DownloadFailed; - var aggFailText = failedRepos - .Select(repo => $"{failText} ({repo.PluginMasterUrl})") - .Aggregate((s1, s2) => $"{s1}\n{s2}"); - - ImGui.TextColored(ImGuiColors.DalamudRed, aggFailText); - } - - return ready; - } - - private bool DrawPluginCollapsingHeader(string label, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, bool trouble, bool updateAvailable, bool isNew, Action drawContextMenuAction, int index) - { - ImGui.Separator(); - - var isOpen = this.openPluginCollapsibles.Contains(index); - - var sectionSize = ImGuiHelpers.GlobalScale * 66; - var startCursor = ImGui.GetCursorPos(); - - ImGui.PushStyleColor(ImGuiCol.Button, isOpen ? new Vector4(0.5f, 0.5f, 0.5f, 0.1f) : Vector4.Zero); - - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.5f, 0.5f, 0.5f, 0.2f)); - ImGui.PushStyleColor(ImGuiCol.ButtonActive, new Vector4(0.5f, 0.5f, 0.5f, 0.35f)); - ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0); - - if (ImGui.Button($"###plugin{index}CollapsibleBtn", new Vector2(ImGui.GetWindowWidth() - (ImGuiHelpers.GlobalScale * 35), sectionSize))) - { - if (isOpen) - { - this.openPluginCollapsibles.Remove(index); - } - else - { - this.openPluginCollapsibles.Add(index); - } - - isOpen = !isOpen; - } - - drawContextMenuAction?.Invoke(); - - ImGui.PopStyleVar(); - - ImGui.PopStyleColor(3); - - ImGui.SetCursorPos(startCursor); - - var iconTex = this.imageCache.DefaultIcon; - var hasIcon = this.imageCache.TryGetIcon(plugin, manifest, isThirdParty, out var cachedIconTex); - if (hasIcon && cachedIconTex != null) - { - iconTex = cachedIconTex; - } - - var iconSize = ImGuiHelpers.ScaledVector2(64, 64); - - var cursorBeforeImage = ImGui.GetCursorPos(); - ImGui.Image(iconTex.ImGuiHandle, iconSize); - ImGui.SameLine(); - - if (updateAvailable) - { - ImGui.SetCursorPos(cursorBeforeImage); - ImGui.Image(this.imageCache.UpdateIcon.ImGuiHandle, iconSize); - ImGui.SameLine(); - } - else if (trouble) - { - ImGui.SetCursorPos(cursorBeforeImage); - ImGui.Image(this.imageCache.TroubleIcon.ImGuiHandle, iconSize); - ImGui.SameLine(); - } - - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - - var cursor = ImGui.GetCursorPos(); - - // Name - ImGui.Text(label); - - // Download count - var downloadCountText = manifest.DownloadCount > 0 - ? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount) - : Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText); - - if (isNew) - { - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.TankBlue, Locs.PluginTitleMod_New); - } - - cursor.Y += ImGui.GetTextLineHeightWithSpacing(); - ImGui.SetCursorPos(cursor); - - // Outdated warning - if (plugin is { IsOutdated: true, IsBanned: false }) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(Locs.PluginBody_Outdated); - ImGui.PopStyleColor(); - } - - // Banned warning - if (plugin is { IsBanned: true }) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImGui.TextWrapped(plugin.BanReason.IsNullOrEmpty() - ? Locs.PluginBody_Banned - : Locs.PluginBody_BannedReason(plugin.BanReason)); - - ImGui.PopStyleColor(); - } - - // Description - if (plugin is null or { IsOutdated: false, IsBanned: false }) - { - if (!string.IsNullOrWhiteSpace(manifest.Punchline)) - { - ImGui.TextWrapped(manifest.Punchline); - } - else if (!string.IsNullOrWhiteSpace(manifest.Description)) - { - const int punchlineLen = 200; - var firstLine = manifest.Description.Split(new[] { '\r', '\n' })[0]; - - ImGui.TextWrapped(firstLine.Length < punchlineLen - ? firstLine - : firstLine[..punchlineLen]); - } - } - - startCursor.Y += sectionSize; - ImGui.SetCursorPos(startCursor); - - return isOpen; - } - - private void DrawAvailablePlugin(RemotePluginManifest manifest, int index) - { - var configuration = Service.Get(); - var notifications = Service.Get(); - var pluginManager = Service.Get(); - - var useTesting = pluginManager.UseTesting(manifest); - var wasSeen = this.WasPluginSeen(manifest.InternalName); - - // Check for valid versions - if ((useTesting && manifest.TestingAssemblyVersion == null) || manifest.AssemblyVersion == null) - { - // Without a valid version, quit - return; - } - - // Name - var label = manifest.Name; - - // Testing - if (useTesting) - { - label += Locs.PluginTitleMod_TestingVersion; - } - - ImGui.PushID($"available{index}{manifest.InternalName}"); - - var isThirdParty = manifest.SourceRepo.IsThirdParty; - if (this.DrawPluginCollapsingHeader(label, null, manifest, isThirdParty, false, false, !wasSeen, () => this.DrawAvailablePluginContextMenu(manifest), index)) - { - if (!wasSeen) - configuration.SeenPluginInternalName.Add(manifest.InternalName); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Indent(); - - // Installable from - if (manifest.SourceRepo.IsThirdParty) - { - var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.SourceRepo.PluginMasterUrl); - ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText); - - ImGuiHelpers.ScaledDummy(2); - } - - // Description - if (!string.IsNullOrWhiteSpace(manifest.Description)) - { - ImGui.TextWrapped(manifest.Description); - } - - ImGuiHelpers.ScaledDummy(5); - - // Controls - var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; - - var versionString = useTesting - ? $"{manifest.TestingAssemblyVersion}" - : $"{manifest.AssemblyVersion}"; - - if (disabled) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_InstallVersion(versionString)); - } - else - { - var buttonText = Locs.PluginButton_InstallVersion(versionString); - if (ImGui.Button($"{buttonText}##{buttonText}{index}")) - { - this.installStatus = OperationStatus.InProgress; - - Task.Run(() => pluginManager.InstallPluginAsync(manifest, useTesting, PluginLoadReason.Installer)) - .ContinueWith(task => - { - // There is no need to set as Complete for an individual plugin installation - this.installStatus = OperationStatus.Idle; - if (this.DisplayErrorContinuation(task, Locs.ErrorModal_InstallFail(manifest.Name))) - { - if (task.Result.State == PluginState.Loaded) - { - notifications.AddNotification(Locs.Notifications_PluginInstalled(manifest.Name), Locs.Notifications_PluginInstalledTitle, NotificationType.Success); - } - else - { - notifications.AddNotification(Locs.Notifications_PluginNotInstalled(manifest.Name), Locs.Notifications_PluginNotInstalledTitle, NotificationType.Error); - this.ShowErrorModal(Locs.ErrorModal_InstallFail(manifest.Name)); - } - } - }); - } - } - - this.DrawVisitRepoUrlButton(manifest.RepoUrl); - - if (!manifest.SourceRepo.IsThirdParty) - { - this.DrawSendFeedbackButton(manifest); - } - - ImGuiHelpers.ScaledDummy(5); - - if (this.DrawPluginImages(null, manifest, isThirdParty, index)) - ImGuiHelpers.ScaledDummy(5); - - ImGui.Unindent(); - } - - ImGui.PopID(); - } - - private void DrawAvailablePluginContextMenu(PluginManifest manifest) - { - var configuration = Service.Get(); - var pluginManager = Service.Get(); - var startInfo = Service.Get(); - - if (ImGui.BeginPopupContextItem("ItemContextMenu")) - { - if (ImGui.Selectable(Locs.PluginContext_MarkAllSeen)) - { - configuration.SeenPluginInternalName.AddRange(this.pluginListAvailable.Select(x => x.InternalName)); - configuration.Save(); - pluginManager.RefilterPluginMasters(); - } - - if (ImGui.Selectable(Locs.PluginContext_HidePlugin)) - { - Log.Debug($"Adding {manifest.InternalName} to hidden plugins"); - configuration.HiddenPluginInternalName.Add(manifest.InternalName); - configuration.Save(); - pluginManager.RefilterPluginMasters(); - } - - if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfig)) - { - Log.Debug($"Deleting config for {manifest.InternalName}"); - - this.installStatus = OperationStatus.InProgress; - - Task.Run(() => + // scale image if undersized + if (xAct < xMax && yAct < yMax) { - pluginManager.PluginConfigs.Delete(manifest.InternalName); + var scale = Math.Min(xMax / xAct, yMax / yAct); + xAct *= scale; + yAct *= scale; + } - var path = Path.Combine(startInfo.PluginDirectory, manifest.InternalName); - if (Directory.Exists(path)) - Directory.Delete(path, true); - }) - .ContinueWith(task => + var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor); + if (ImGui.ImageButton(image.ImGuiHandle, size)) + ImGui.OpenPopup(popupId); + + ImGui.PopStyleVar(); + + if (i < imageTextures.Length - 1) { - this.installStatus = OperationStatus.Idle; - - this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(manifest.InternalName)); - }); - } - - ImGui.EndPopup(); - } - } - - private void DrawInstalledPlugin(LocalPlugin plugin, int index, bool showInstalled = false) - { - var configuration = Service.Get(); - var commandManager = Service.Get(); - var pluginManager = Service.Get(); - var startInfo = Service.Get(); - - var trouble = false; - - // Name - var label = plugin.Manifest.Name; - - // Testing - if (plugin.Manifest.Testing) - { - label += Locs.PluginTitleMod_TestingVersion; - } - - // Freshly installed - if (showInstalled) - { - label += Locs.PluginTitleMod_Installed; - } - - // Disabled - if (plugin.IsDisabled) - { - label += Locs.PluginTitleMod_Disabled; - trouble = true; - } - - // Load error - if (plugin.State == PluginState.LoadError) - { - label += Locs.PluginTitleMod_LoadError; - trouble = true; - } - - // Unload error - if (plugin.State == PluginState.UnloadError) - { - label += Locs.PluginTitleMod_UnloadError; - trouble = true; - } - - var availablePluginUpdate = this.pluginListUpdatable.FirstOrDefault(up => up.InstalledPlugin == plugin); - // Update available - if (availablePluginUpdate != default) - { - label += Locs.PluginTitleMod_HasUpdate; - } - - // Freshly updated - var thisWasUpdated = false; - if (this.updatedPlugins != null && !plugin.IsDev) - { - var update = this.updatedPlugins.FirstOrDefault(update => update.InternalName == plugin.Manifest.InternalName); - if (update != default) - { - if (update.WasUpdated) - { - thisWasUpdated = true; - label += Locs.PluginTitleMod_Updated; - } - else - { - label += Locs.PluginTitleMod_UpdateFailed; - } - } - } - - // Outdated API level - if (plugin.IsOutdated) - { - label += Locs.PluginTitleMod_OutdatedError; - trouble = true; - } - - // Banned - if (plugin.IsBanned) - { - label += Locs.PluginTitleMod_BannedError; - trouble = true; - } - - ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}"); - - if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, () => this.DrawInstalledPluginContextMenu(plugin), index)) - { - if (!this.WasPluginSeen(plugin.Manifest.InternalName)) - configuration.SeenPluginInternalName.Add(plugin.Manifest.InternalName); - - var manifest = plugin.Manifest; - - ImGui.Indent(); - - // Name - ImGui.Text(manifest.Name); - - // Download count - var downloadText = plugin.IsDev - ? Locs.PluginBody_AuthorWithoutDownloadCount(manifest.Author) - : manifest.DownloadCount > 0 - ? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount) - : Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadText); - - var isThirdParty = manifest.IsThirdParty; - var canFeedback = !isThirdParty && !plugin.IsDev && plugin.Manifest.DalamudApiLevel == PluginManager.DalamudApiLevel; - - // Installed from - if (plugin.IsDev) - { - var fileText = Locs.PluginBody_DevPluginPath(plugin.DllFile.FullName); - ImGui.TextColored(ImGuiColors.DalamudGrey3, fileText); - } - else if (isThirdParty) - { - var repoText = Locs.PluginBody_Plugin3rdPartyRepo(manifest.InstalledFromUrl); - ImGui.TextColored(ImGuiColors.DalamudGrey3, repoText); - } - - // Description - if (!string.IsNullOrWhiteSpace(manifest.Description)) - { - ImGui.TextWrapped(manifest.Description); - } - - // Available commands (if loaded) - if (plugin.IsLoaded) - { - var commands = commandManager.Commands - .Where(cInfo => cInfo.Value.ShowInHelp && cInfo.Value.LoaderAssemblyName == plugin.Manifest.InternalName) - .ToArray(); - - if (commands.Any()) - { - ImGui.Dummy(ImGuiHelpers.ScaledVector2(10f, 10f)); - foreach (var command in commands) - { - ImGui.TextWrapped($"{command.Key} → {command.Value.HelpMessage}"); + ImGui.SameLine(); + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); } } } - // Controls - this.DrawPluginControlButton(plugin); - this.DrawDevPluginButtons(plugin); - this.DrawDeletePluginButton(plugin); - this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl); - - if (canFeedback) - { - this.DrawSendFeedbackButton(plugin.Manifest); - } - - if (availablePluginUpdate != default) - this.DrawUpdateSinglePluginButton(availablePluginUpdate); - - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudGrey3, $" v{plugin.Manifest.AssemblyVersion}"); - - if (plugin.IsDev) - { - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.DalamudRed, Locs.PluginBody_DeleteDevPlugin); - } - - ImGuiHelpers.ScaledDummy(5); - - if (this.DrawPluginImages(plugin, manifest, isThirdParty, index)) - ImGuiHelpers.ScaledDummy(5); - - ImGui.Unindent(); - } - - if (thisWasUpdated && !plugin.Manifest.Changelog.IsNullOrEmpty()) - { - ImGuiHelpers.ScaledDummy(5); - - ImGui.PushStyleColor(ImGuiCol.ChildBg, this.changelogBgColor); - ImGui.PushStyleColor(ImGuiCol.Text, this.changelogTextColor); - - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(7, 5)); - - if (ImGui.BeginChild("##changelog", new Vector2(-1, 100), true, ImGuiWindowFlags.NoNavFocus | ImGuiWindowFlags.NoNavInputs | ImGuiWindowFlags.AlwaysAutoResize)) - { - ImGui.Text("Changelog:"); - ImGuiHelpers.ScaledDummy(2); - ImGui.TextWrapped(plugin.Manifest.Changelog); - } - ImGui.EndChild(); ImGui.PopStyleVar(); - ImGui.PopStyleColor(2); + ImGui.PopStyleColor(); + + return true; } - ImGui.PopID(); - } - - private void DrawInstalledPluginContextMenu(LocalPlugin plugin) - { - var pluginManager = Service.Get(); - - if (ImGui.BeginPopupContextItem("InstalledItemContextMenu")) + private bool IsManifestFiltered(PluginManifest manifest) { - if (ImGui.Selectable(Locs.PluginContext_DeletePluginConfigReload)) + var searchString = this.searchText.ToLowerInvariant(); + var hasSearchString = !string.IsNullOrWhiteSpace(searchString); + + return hasSearchString && !( + manifest.Name.ToLowerInvariant().Contains(searchString) || + manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase) || + (manifest.Tags != null && manifest.Tags.Contains(searchString, StringComparer.InvariantCultureIgnoreCase))); + } + + private (bool IsInstalled, LocalPlugin Plugin) IsManifestInstalled(RemotePluginManifest manifest) + { + var plugin = this.pluginListInstalled.FirstOrDefault(plugin => plugin.Manifest.InternalName == manifest.InternalName); + var isInstalled = plugin != default; + + return (isInstalled, plugin); + } + + private void OnAvailablePluginsChanged() + { + var pluginManager = Service.Get(); + + // By removing installed plugins only when the available plugin list changes (basically when the window is + // opened), plugins that have been newly installed remain in the available plugin list as installed. + this.pluginListAvailable = pluginManager.AvailablePlugins + .Where(manifest => !this.IsManifestInstalled(manifest).IsInstalled) + .ToList(); + this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); + this.ResortPlugins(); + + this.UpdateCategoriesOnPluginsChange(); + } + + private void OnInstalledPluginsChanged() + { + var pluginManager = Service.Get(); + + this.pluginListInstalled = pluginManager.InstalledPlugins.ToList(); + this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); + this.hasDevPlugins = this.pluginListInstalled.Any(plugin => plugin.IsDev); + this.ResortPlugins(); + + this.UpdateCategoriesOnPluginsChange(); + } + + private void ResortPlugins() + { + switch (this.sortKind) { - Log.Debug($"Deleting config for {plugin.Manifest.InternalName}"); - - this.installStatus = OperationStatus.InProgress; - - Task.Run(() => pluginManager.DeleteConfiguration(plugin)) - .ContinueWith(task => - { - this.installStatus = OperationStatus.Idle; - - this.DisplayErrorContinuation(task, Locs.ErrorModal_DeleteConfigFail(plugin.Name)); - }); + case PluginSortKind.Alphabetical: + this.pluginListAvailable.Sort((p1, p2) => p1.Name.CompareTo(p2.Name)); + this.pluginListInstalled.Sort((p1, p2) => p1.Manifest.Name.CompareTo(p2.Manifest.Name)); + break; + case PluginSortKind.DownloadCount: + this.pluginListAvailable.Sort((p1, p2) => p2.DownloadCount.CompareTo(p1.DownloadCount)); + this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.DownloadCount.CompareTo(p1.Manifest.DownloadCount)); + break; + case PluginSortKind.LastUpdate: + this.pluginListAvailable.Sort((p1, p2) => p2.LastUpdate.CompareTo(p1.LastUpdate)); + this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.LastUpdate.CompareTo(p1.Manifest.LastUpdate)); + break; + case PluginSortKind.NewOrNot: + this.pluginListAvailable.Sort((p1, p2) => this.WasPluginSeen(p1.InternalName) + .CompareTo(this.WasPluginSeen(p2.InternalName))); + this.pluginListInstalled.Sort((p1, p2) => this.WasPluginSeen(p1.Manifest.InternalName) + .CompareTo(this.WasPluginSeen(p2.Manifest.InternalName))); + break; + default: + throw new InvalidEnumArgumentException("Unknown plugin sort type."); } - - ImGui.EndPopup(); } - } - private void DrawPluginControlButton(LocalPlugin plugin) - { - var configuration = Service.Get(); - var notifications = Service.Get(); - var pluginManager = Service.Get(); - var startInfo = Service.Get(); + private bool WasPluginSeen(string internalName) => + Service.Get().SeenPluginInternalName.Contains(internalName); - // Disable everything if the updater is running or another plugin is operating - var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress; - - // Disable everything if the plugin is outdated - disabled = disabled || (plugin.IsOutdated && !configuration.LoadAllApiLevels) || plugin.IsBanned; - - if (plugin.State == PluginState.InProgress) + /// + /// A continuation task that displays any errors received into the error modal. + /// + /// The previous task. + /// An error message to be displayed. + /// A value indicating whether to continue with the next task. + private bool DisplayErrorContinuation(Task task, object state) { - ImGuiComponents.DisabledButton(Locs.PluginButton_Working); - } - else if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError) - { - if (disabled) + if (task.IsFaulted) { - ImGuiComponents.DisabledButton(Locs.PluginButton_Disable); - } - else - { - if (ImGui.Button(Locs.PluginButton_Disable)) + var errorModalMessage = state as string; + + foreach (var ex in task.Exception.InnerExceptions) { - Task.Run(() => + if (ex is PluginException) { - var unloadTask = Task.Run(() => plugin.Unload()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_UnloadFail(plugin.Name)); - - unloadTask.Wait(); - if (!unloadTask.Result) - return; - - var disableTask = Task.Run(() => plugin.Disable()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_DisableFail(plugin.Name)); - - disableTask.Wait(); - if (!disableTask.Result) - return; - - if (!plugin.IsDev) - { - pluginManager.RemovePlugin(plugin); - } - - notifications.AddNotification(Locs.Notifications_PluginDisabled(plugin.Manifest.Name), Locs.Notifications_PluginDisabledTitle, NotificationType.Success); - }); - } - } - - if (plugin.State == PluginState.Loaded) - { - // Only if the plugin isn't broken. - this.DrawOpenPluginSettingsButton(plugin); - } - } - else if (plugin.State == PluginState.Unloaded) - { - if (disabled) - { - ImGuiComponents.DisabledButton(Locs.PluginButton_Load); - } - else - { - if (ImGui.Button(Locs.PluginButton_Load)) - { - Task.Run(() => - { - var enableTask = Task.Run(() => plugin.Enable()) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_EnableFail(plugin.Name)); - - enableTask.Wait(); - if (!enableTask.Result) - return; - - var loadTask = Task.Run(() => plugin.Load(PluginLoadReason.Installer)) - .ContinueWith(this.DisplayErrorContinuation, Locs.ErrorModal_LoadFail(plugin.Name)); - - loadTask.Wait(); - if (!loadTask.Result) - return; - }); - } - } - } - else if (plugin.State == PluginState.UnloadError) - { - ImGuiComponents.DisabledButton(FontAwesomeIcon.Frown); - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Locs.PluginButtonToolTip_UnloadFailed); - } - } - - private void DrawUpdateSinglePluginButton(AvailablePluginUpdate update) - { - var pluginManager = Service.Get(); - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Download)) - { - this.installStatus = OperationStatus.InProgress; - - Task.Run(async () => await pluginManager.UpdateSinglePluginAsync(update, true, false)) - .ContinueWith(task => - { - // There is no need to set as Complete for an individual plugin installation - this.installStatus = OperationStatus.Idle; - - var errorMessage = Locs.ErrorModal_SingleUpdateFail(update.UpdateManifest.Name); - this.DisplayErrorContinuation(task, errorMessage); - - if (!task.Result.WasUpdated) - { - this.ShowErrorModal(errorMessage); + Log.Error(ex, "Plugin installer threw an error"); +#if DEBUG + if (!string.IsNullOrEmpty(ex.Message)) + errorModalMessage += $"\n\n{ex.Message}"; +#endif } - }); - } - - if (ImGui.IsItemHovered()) - { - var updateVersion = update.UseTesting - ? update.UpdateManifest.TestingAssemblyVersion - : update.UpdateManifest.AssemblyVersion; - ImGui.SetTooltip(Locs.PluginButtonToolTip_UpdateSingle(updateVersion.ToString())); - } - } - - private void DrawOpenPluginSettingsButton(LocalPlugin plugin) - { - if (plugin.DalamudInterface?.UiBuilder?.HasConfigUi ?? false) - { - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) - { - try - { - plugin.DalamudInterface.UiBuilder.OpenConfig(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error during OpenConfigUi: {plugin.Name}"); - } - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_OpenConfiguration); - } - } - } - - private void DrawSendFeedbackButton(PluginManifest manifest) - { - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Comment)) - { - this.feedbackPlugin = manifest; - this.feedbackModalOnNextFrame = true; - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.FeedbackModal_Title); - } - } - - private void DrawDevPluginButtons(LocalPlugin localPlugin) - { - var configuration = Service.Get(); - - if (localPlugin is LocalDevPlugin plugin) - { - // https://colorswall.com/palette/2868/ - var greenColor = new Vector4(0x5C, 0xB8, 0x5C, 0xFF) / 0xFF; - var redColor = new Vector4(0xD9, 0x53, 0x4F, 0xFF) / 0xFF; - - // Load on boot - ImGui.PushStyleColor(ImGuiCol.Button, plugin.StartOnBoot ? greenColor : redColor); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.StartOnBoot ? greenColor : redColor); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.PowerOff)) - { - plugin.StartOnBoot ^= true; - configuration.Save(); - } - - ImGui.PopStyleColor(2); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_StartOnBoot); - } - - // Automatic reload - ImGui.PushStyleColor(ImGuiCol.Button, plugin.AutomaticReload ? greenColor : redColor); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, plugin.AutomaticReload ? greenColor : redColor); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.SyncAlt)) - { - plugin.AutomaticReload ^= true; - configuration.Save(); - } - - ImGui.PopStyleColor(2); - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_AutomaticReloading); - } - } - } - - private void DrawDeletePluginButton(LocalPlugin plugin) - { - var unloaded = plugin.State == PluginState.Unloaded; - var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned); - - if (!showButton) - return; - - var pluginManager = Service.Get(); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt)) - { - try - { - plugin.DllFile.Delete(); - pluginManager.RemovePlugin(plugin); - } - catch (Exception ex) - { - Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}"); - - this.errorModalMessage = Locs.ErrorModal_DeleteFail(plugin.Name); - this.errorModalDrawing = true; - this.errorModalOnNextFrame = true; - } - } - - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePlugin); - } - } - - private void DrawVisitRepoUrlButton(string? repoUrl) - { - if (!string.IsNullOrEmpty(repoUrl) && repoUrl.StartsWith("https://")) - { - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Globe)) - { - try - { - _ = Process.Start(new ProcessStartInfo() + else { - FileName = repoUrl, - UseShellExecute = true, - }); - } - catch (Exception ex) - { - Log.Error(ex, $"Could not open repoUrl: {repoUrl}"); - } - } - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Locs.PluginButtonToolTip_VisitPluginUrl); - } - } - - private bool DrawPluginImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, int index) - { - var hasImages = this.imageCache.TryGetImages(plugin, manifest, isThirdParty, out var imageTextures); - if (!hasImages || imageTextures.Length == 0) - return false; - - const float thumbFactor = 2.7f; - - var scrollBarSize = 15; - ImGui.PushStyleVar(ImGuiStyleVar.ScrollbarSize, scrollBarSize); - ImGui.PushStyleColor(ImGuiCol.ScrollbarBg, Vector4.Zero); - - var width = ImGui.GetWindowWidth(); - - if (ImGui.BeginChild($"plugin{index}ImageScrolling", new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageCache.PluginImageHeight / thumbFactor) + scrollBarSize), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoBackground)) - { - for (var i = 0; i < imageTextures.Length; i++) - { - var image = imageTextures[i]; - if (image == null) - continue; - - ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0); - ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - - var popupId = $"plugin{index}image{i}"; - if (ImGui.BeginPopup(popupId)) - { - if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height))) - ImGui.CloseCurrentPopup(); - - ImGui.EndPopup(); - } - - ImGui.PopStyleVar(3); - - ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - - float xAct = image.Width; - float yAct = image.Height; - float xMax = PluginImageCache.PluginImageWidth; - float yMax = PluginImageCache.PluginImageHeight; - - // scale image if undersized - if (xAct < xMax && yAct < yMax) - { - var scale = Math.Min(xMax / xAct, yMax / yAct); - xAct *= scale; - yAct *= scale; - } - - var size = ImGuiHelpers.ScaledVector2(xAct / thumbFactor, yAct / thumbFactor); - if (ImGui.ImageButton(image.ImGuiHandle, size)) - ImGui.OpenPopup(popupId); - - ImGui.PopStyleVar(); - - if (i < imageTextures.Length - 1) - { - ImGui.SameLine(); - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - } - } - } - - ImGui.EndChild(); - - ImGui.PopStyleVar(); - ImGui.PopStyleColor(); - - return true; - } - - private bool IsManifestFiltered(PluginManifest manifest) - { - var searchString = this.searchText.ToLowerInvariant(); - var hasSearchString = !string.IsNullOrWhiteSpace(searchString); - - return hasSearchString && !( - manifest.Name.ToLowerInvariant().Contains(searchString) || - manifest.Author.Equals(this.searchText, StringComparison.InvariantCultureIgnoreCase) || - (manifest.Tags != null && manifest.Tags.Contains(searchString, StringComparer.InvariantCultureIgnoreCase))); - } - - private (bool IsInstalled, LocalPlugin Plugin) IsManifestInstalled(RemotePluginManifest manifest) - { - var plugin = this.pluginListInstalled.FirstOrDefault(plugin => plugin.Manifest.InternalName == manifest.InternalName); - var isInstalled = plugin != default; - - return (isInstalled, plugin); - } - - private void OnAvailablePluginsChanged() - { - var pluginManager = Service.Get(); - - // By removing installed plugins only when the available plugin list changes (basically when the window is - // opened), plugins that have been newly installed remain in the available plugin list as installed. - this.pluginListAvailable = pluginManager.AvailablePlugins - .Where(manifest => !this.IsManifestInstalled(manifest).IsInstalled) - .ToList(); - this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); - this.ResortPlugins(); - - this.UpdateCategoriesOnPluginsChange(); - } - - private void OnInstalledPluginsChanged() - { - var pluginManager = Service.Get(); - - this.pluginListInstalled = pluginManager.InstalledPlugins.ToList(); - this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList(); - this.hasDevPlugins = this.pluginListInstalled.Any(plugin => plugin.IsDev); - this.ResortPlugins(); - - this.UpdateCategoriesOnPluginsChange(); - } - - private void ResortPlugins() - { - switch (this.sortKind) - { - case PluginSortKind.Alphabetical: - this.pluginListAvailable.Sort((p1, p2) => p1.Name.CompareTo(p2.Name)); - this.pluginListInstalled.Sort((p1, p2) => p1.Manifest.Name.CompareTo(p2.Manifest.Name)); - break; - case PluginSortKind.DownloadCount: - this.pluginListAvailable.Sort((p1, p2) => p2.DownloadCount.CompareTo(p1.DownloadCount)); - this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.DownloadCount.CompareTo(p1.Manifest.DownloadCount)); - break; - case PluginSortKind.LastUpdate: - this.pluginListAvailable.Sort((p1, p2) => p2.LastUpdate.CompareTo(p1.LastUpdate)); - this.pluginListInstalled.Sort((p1, p2) => p2.Manifest.LastUpdate.CompareTo(p1.Manifest.LastUpdate)); - break; - case PluginSortKind.NewOrNot: - this.pluginListAvailable.Sort((p1, p2) => this.WasPluginSeen(p1.InternalName) - .CompareTo(this.WasPluginSeen(p2.InternalName))); - this.pluginListInstalled.Sort((p1, p2) => this.WasPluginSeen(p1.Manifest.InternalName) - .CompareTo(this.WasPluginSeen(p2.Manifest.InternalName))); - break; - default: - throw new InvalidEnumArgumentException("Unknown plugin sort type."); - } - } - - private bool WasPluginSeen(string internalName) => - Service.Get().SeenPluginInternalName.Contains(internalName); - - /// - /// A continuation task that displays any errors received into the error modal. - /// - /// The previous task. - /// An error message to be displayed. - /// A value indicating whether to continue with the next task. - private bool DisplayErrorContinuation(Task task, object state) - { - if (task.IsFaulted) - { - var errorModalMessage = state as string; - - foreach (var ex in task.Exception.InnerExceptions) - { - if (ex is PluginException) - { - Log.Error(ex, "Plugin installer threw an error"); + Log.Error(ex, "Plugin installer threw an unexpected error"); #if DEBUG - if (!string.IsNullOrEmpty(ex.Message)) - errorModalMessage += $"\n\n{ex.Message}"; -#endif - } - else - { - Log.Error(ex, "Plugin installer threw an unexpected error"); -#if DEBUG - if (!string.IsNullOrEmpty(ex.Message)) - errorModalMessage += $"\n\n{ex.Message}"; + if (!string.IsNullOrEmpty(ex.Message)) + errorModalMessage += $"\n\n{ex.Message}"; #endif + } } + + this.ShowErrorModal(errorModalMessage); + + return false; } - this.ShowErrorModal(errorModalMessage); - - return false; + return true; } - return true; - } - - private void ShowErrorModal(string message) - { - this.errorModalMessage = message; - this.errorModalDrawing = true; - this.errorModalOnNextFrame = true; - } - - private void UpdateCategoriesOnSearchChange() - { - if (string.IsNullOrEmpty(this.searchText)) + private void ShowErrorModal(string message) { - this.categoryManager.SetCategoryHighlightsForPlugins(null); + this.errorModalMessage = message; + this.errorModalDrawing = true; + this.errorModalOnNextFrame = true; } - else + + private void UpdateCategoriesOnSearchChange() { - var pluginsMatchingSearch = this.pluginListAvailable.Where(rm => !this.IsManifestFiltered(rm)); - this.categoryManager.SetCategoryHighlightsForPlugins(pluginsMatchingSearch); + if (string.IsNullOrEmpty(this.searchText)) + { + this.categoryManager.SetCategoryHighlightsForPlugins(null); + } + else + { + var pluginsMatchingSearch = this.pluginListAvailable.Where(rm => !this.IsManifestFiltered(rm)); + this.categoryManager.SetCategoryHighlightsForPlugins(pluginsMatchingSearch); + } } - } - private void UpdateCategoriesOnPluginsChange() - { - this.categoryManager.BuildCategories(this.pluginListAvailable); - this.UpdateCategoriesOnSearchChange(); - } + private void UpdateCategoriesOnPluginsChange() + { + this.categoryManager.BuildCategories(this.pluginListAvailable); + this.UpdateCategoriesOnSearchChange(); + } - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Disregard here")] - private static class Locs - { - #region Window Title + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Disregard here")] + private static class Locs + { + #region Window Title - public static string WindowTitle => Loc.Localize("InstallerHeader", "Plugin Installer"); + public static string WindowTitle => Loc.Localize("InstallerHeader", "Plugin Installer"); - public static string WindowTitleMod_Testing => Loc.Localize("InstallerHeaderTesting", " (TESTING)"); + public static string WindowTitleMod_Testing => Loc.Localize("InstallerHeaderTesting", " (TESTING)"); - #endregion + #endregion - #region Header + #region Header - public static string Header_Hint => Loc.Localize("InstallerHint", "This window allows you to install and remove in-game plugins.\nThey are made by third-party developers."); + public static string Header_Hint => Loc.Localize("InstallerHint", "This window allows you to install and remove in-game plugins.\nThey are made by third-party developers."); - public static string Header_SearchPlaceholder => Loc.Localize("InstallerSearch", "Search"); + public static string Header_SearchPlaceholder => Loc.Localize("InstallerSearch", "Search"); - #endregion + #endregion - #region SortBy + #region SortBy - public static string SortBy_Alphabetical => Loc.Localize("InstallerAlphabetical", "Alphabetical"); + public static string SortBy_Alphabetical => Loc.Localize("InstallerAlphabetical", "Alphabetical"); - public static string SortBy_DownloadCounts => Loc.Localize("InstallerDownloadCount", "Download Count"); + public static string SortBy_DownloadCounts => Loc.Localize("InstallerDownloadCount", "Download Count"); - public static string SortBy_LastUpdate => Loc.Localize("InstallerLastUpdate", "Last Update"); + public static string SortBy_LastUpdate => Loc.Localize("InstallerLastUpdate", "Last Update"); - public static string SortBy_NewOrNot => Loc.Localize("InstallerNewOrNot", "New or not"); + public static string SortBy_NewOrNot => Loc.Localize("InstallerNewOrNot", "New or not"); - public static string SortBy_Label => Loc.Localize("InstallerSortBy", "Sort By"); + public static string SortBy_Label => Loc.Localize("InstallerSortBy", "Sort By"); - #endregion + #endregion - #region Tabs + #region Tabs - public static string TabTitle_AvailablePlugins => Loc.Localize("InstallerAvailablePlugins", "Available Plugins"); + public static string TabTitle_AvailablePlugins => Loc.Localize("InstallerAvailablePlugins", "Available Plugins"); - public static string TabTitle_InstalledPlugins => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins"); + public static string TabTitle_InstalledPlugins => Loc.Localize("InstallerInstalledPlugins", "Installed Plugins"); - public static string TabTitle_InstalledDevPlugins => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins"); + public static string TabTitle_InstalledDevPlugins => Loc.Localize("InstallerInstalledDevPlugins", "Installed Dev Plugins"); - #endregion + #endregion - #region Tab body + #region Tab body - public static string TabBody_LoadingPlugins => Loc.Localize("InstallerLoading", "Loading plugins..."); + public static string TabBody_LoadingPlugins => Loc.Localize("InstallerLoading", "Loading plugins..."); - public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed."); + public static string TabBody_DownloadFailed => Loc.Localize("InstallerDownloadFailed", "Download failed."); - public static string TabBody_SafeMode => Loc.Localize("InstallerSafeMode", "Dalamud is running in Plugin Safe Mode, restart to activate plugins."); + public static string TabBody_SafeMode => Loc.Localize("InstallerSafeMode", "Dalamud is running in Plugin Safe Mode, restart to activate plugins."); - #endregion + #endregion - #region Search text + #region Search text - public static string TabBody_SearchNoMatching => Loc.Localize("InstallerNoMatching", "No plugins were found matching your search."); + public static string TabBody_SearchNoMatching => Loc.Localize("InstallerNoMatching", "No plugins were found matching your search."); - public static string TabBody_SearchNoCompatible => Loc.Localize("InstallerNoCompatible", "No compatible plugins were found :( Please restart your game and try again."); + public static string TabBody_SearchNoCompatible => Loc.Localize("InstallerNoCompatible", "No compatible plugins were found :( Please restart your game and try again."); - public static string TabBody_SearchNoInstalled => Loc.Localize("InstallerNoInstalled", "No plugins are currently installed. You can install them from the Available Plugins tab."); + public static string TabBody_SearchNoInstalled => Loc.Localize("InstallerNoInstalled", "No plugins are currently installed. You can install them from the Available Plugins tab."); - #endregion + #endregion - #region Plugin title text + #region Plugin title text - public static string PluginTitleMod_Installed => Loc.Localize("InstallerInstalled", " (installed)"); + public static string PluginTitleMod_Installed => Loc.Localize("InstallerInstalled", " (installed)"); - public static string PluginTitleMod_Disabled => Loc.Localize("InstallerDisabled", " (disabled)"); + public static string PluginTitleMod_Disabled => Loc.Localize("InstallerDisabled", " (disabled)"); - public static string PluginTitleMod_Unloaded => Loc.Localize("InstallerUnloaded", " (unloaded)"); + public static string PluginTitleMod_Unloaded => Loc.Localize("InstallerUnloaded", " (unloaded)"); - public static string PluginTitleMod_HasUpdate => Loc.Localize("InstallerHasUpdate", " (has update)"); + public static string PluginTitleMod_HasUpdate => Loc.Localize("InstallerHasUpdate", " (has update)"); - public static string PluginTitleMod_Updated => Loc.Localize("InstallerUpdated", " (updated)"); + public static string PluginTitleMod_Updated => Loc.Localize("InstallerUpdated", " (updated)"); - public static string PluginTitleMod_TestingVersion => Loc.Localize("InstallerTestingVersion", " (testing version)"); + public static string PluginTitleMod_TestingVersion => Loc.Localize("InstallerTestingVersion", " (testing version)"); - public static string PluginTitleMod_UpdateFailed => Loc.Localize("InstallerUpdateFailed", " (update failed)"); + public static string PluginTitleMod_UpdateFailed => Loc.Localize("InstallerUpdateFailed", " (update failed)"); - public static string PluginTitleMod_LoadError => Loc.Localize("InstallerLoadError", " (load error)"); + public static string PluginTitleMod_LoadError => Loc.Localize("InstallerLoadError", " (load error)"); - public static string PluginTitleMod_UnloadError => Loc.Localize("InstallerUnloadError", " (unload error)"); + public static string PluginTitleMod_UnloadError => Loc.Localize("InstallerUnloadError", " (unload error)"); - public static string PluginTitleMod_OutdatedError => Loc.Localize("InstallerOutdatedError", " (outdated)"); + public static string PluginTitleMod_OutdatedError => Loc.Localize("InstallerOutdatedError", " (outdated)"); - public static string PluginTitleMod_BannedError => Loc.Localize("InstallerBannedError", " (banned)"); + public static string PluginTitleMod_BannedError => Loc.Localize("InstallerBannedError", " (banned)"); - public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!"); + public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!"); - #endregion + #endregion - #region Plugin context menu + #region Plugin context menu - public static string PluginContext_MarkAllSeen => Loc.Localize("InstallerMarkAllSeen", "Mark all as seen"); + public static string PluginContext_MarkAllSeen => Loc.Localize("InstallerMarkAllSeen", "Mark all as seen"); - public static string PluginContext_HidePlugin => Loc.Localize("InstallerHidePlugin", "Hide from installer"); + public static string PluginContext_HidePlugin => Loc.Localize("InstallerHidePlugin", "Hide from installer"); - public static string PluginContext_DeletePluginConfig => Loc.Localize("InstallerDeletePluginConfig", "Reset plugin"); + public static string PluginContext_DeletePluginConfig => Loc.Localize("InstallerDeletePluginConfig", "Reset plugin"); - public static string PluginContext_DeletePluginConfigReload => Loc.Localize("InstallerDeletePluginConfigReload", "Reset plugin settings & reload"); + public static string PluginContext_DeletePluginConfigReload => Loc.Localize("InstallerDeletePluginConfigReload", "Reset plugin settings & reload"); - #endregion + #endregion - #region Plugin body + #region Plugin body - public static string PluginBody_AuthorWithoutDownloadCount(string author) => Loc.Localize("InstallerAuthorWithoutDownloadCount", " by {0}").Format(author); + public static string PluginBody_AuthorWithoutDownloadCount(string author) => Loc.Localize("InstallerAuthorWithoutDownloadCount", " by {0}").Format(author); - public static string PluginBody_AuthorWithDownloadCount(string author, long count) => Loc.Localize("InstallerAuthorWithDownloadCount", " by {0}, {1} downloads").Format(author, count); + public static string PluginBody_AuthorWithDownloadCount(string author, long count) => Loc.Localize("InstallerAuthorWithDownloadCount", " by {0}, {1} downloads").Format(author, count); - public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}, download count unavailable").Format(author); + public static string PluginBody_AuthorWithDownloadCountUnavailable(string author) => Loc.Localize("InstallerAuthorWithDownloadCountUnavailable", " by {0}, download count unavailable").Format(author); - public static string PluginBody_DevPluginPath(string path) => Loc.Localize("InstallerDevPluginPath", "From {0}").Format(path); + public static string PluginBody_DevPluginPath(string path) => Loc.Localize("InstallerDevPluginPath", "From {0}").Format(path); - public static string PluginBody_Plugin3rdPartyRepo(string url) => Loc.Localize("InstallerPlugin3rdPartyRepo", "From custom plugin repository {0}").Format(url); + public static string PluginBody_Plugin3rdPartyRepo(string url) => Loc.Localize("InstallerPlugin3rdPartyRepo", "From custom plugin repository {0}").Format(url); - public static string PluginBody_AvailableDevPlugin => Loc.Localize("InstallerDevPlugin", " This plugin is available in one of your repos, please remove it from the devPlugins folder."); + public static string PluginBody_AvailableDevPlugin => Loc.Localize("InstallerDevPlugin", " This plugin is available in one of your repos, please remove it from the devPlugins folder."); - public static string PluginBody_DeleteDevPlugin => Loc.Localize("InstallerDeleteDevPlugin ", " To delete this plugin, please remove it from the devPlugins folder."); + public static string PluginBody_DeleteDevPlugin => Loc.Localize("InstallerDeleteDevPlugin ", " To delete this plugin, please remove it from the devPlugins folder."); - public static string PluginBody_Outdated => Loc.Localize("InstallerOutdatedPluginBody ", "This plugin is outdated and incompatible at the moment. Please wait for it to be updated by its author."); + public static string PluginBody_Outdated => Loc.Localize("InstallerOutdatedPluginBody ", "This plugin is outdated and incompatible at the moment. Please wait for it to be updated by its author."); - public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin version is banned due to incompatibilities and not available at the moment. Please wait for it to be updated by its author."); + public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin version is banned due to incompatibilities and not available at the moment. Please wait for it to be updated by its author."); - public static string PluginBody_BannedReason(string message) => - Loc.Localize("InstallerBannedPluginBodyReason ", "This plugin is banned: {0}").Format(message); + public static string PluginBody_BannedReason(string message) => + Loc.Localize("InstallerBannedPluginBodyReason ", "This plugin is banned: {0}").Format(message); - #endregion + #endregion - #region Plugin buttons + #region Plugin buttons - public static string PluginButton_InstallVersion(string version) => Loc.Localize("InstallerInstall", "Install v{0}").Format(version); + public static string PluginButton_InstallVersion(string version) => Loc.Localize("InstallerInstall", "Install v{0}").Format(version); - public static string PluginButton_Working => Loc.Localize("InstallerWorking", "Working"); + public static string PluginButton_Working => Loc.Localize("InstallerWorking", "Working"); - public static string PluginButton_Disable => Loc.Localize("InstallerDisable", "Disable"); + public static string PluginButton_Disable => Loc.Localize("InstallerDisable", "Disable"); - public static string PluginButton_Load => Loc.Localize("InstallerLoad", "Load"); + public static string PluginButton_Load => Loc.Localize("InstallerLoad", "Load"); - public static string PluginButton_Unload => Loc.Localize("InstallerUnload", "Unload"); + public static string PluginButton_Unload => Loc.Localize("InstallerUnload", "Unload"); - #endregion + #endregion - #region Plugin button tooltips + #region Plugin button tooltips - public static string PluginButtonToolTip_OpenConfiguration => Loc.Localize("InstallerOpenConfig", "Open Configuration"); + public static string PluginButtonToolTip_OpenConfiguration => Loc.Localize("InstallerOpenConfig", "Open Configuration"); - public static string PluginButtonToolTip_StartOnBoot => Loc.Localize("InstallerStartOnBoot", "Start on boot"); + public static string PluginButtonToolTip_StartOnBoot => Loc.Localize("InstallerStartOnBoot", "Start on boot"); - public static string PluginButtonToolTip_AutomaticReloading => Loc.Localize("InstallerAutomaticReloading", "Automatic reloading"); + public static string PluginButtonToolTip_AutomaticReloading => Loc.Localize("InstallerAutomaticReloading", "Automatic reloading"); - public static string PluginButtonToolTip_DeletePlugin => Loc.Localize("InstallerDeletePlugin ", "Delete plugin"); + public static string PluginButtonToolTip_DeletePlugin => Loc.Localize("InstallerDeletePlugin ", "Delete plugin"); - public static string PluginButtonToolTip_VisitPluginUrl => Loc.Localize("InstallerVisitPluginUrl", "Visit plugin URL"); + public static string PluginButtonToolTip_VisitPluginUrl => Loc.Localize("InstallerVisitPluginUrl", "Visit plugin URL"); - public static string PluginButtonToolTip_UpdateSingle(string version) => Loc.Localize("InstallerUpdateSingle", "Update to {0}").Format(version); + public static string PluginButtonToolTip_UpdateSingle(string version) => Loc.Localize("InstallerUpdateSingle", "Update to {0}").Format(version); - public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerUnloadFailedTooltip", "Plugin unload failed, please restart your game and try again."); + public static string PluginButtonToolTip_UnloadFailed => Loc.Localize("InstallerUnloadFailedTooltip", "Plugin unload failed, please restart your game and try again."); - #endregion + #endregion - #region Notifications + #region Notifications - public static string Notifications_PluginInstalledTitle => Loc.Localize("NotificationsPluginInstalledTitle", "Plugin installed!"); + public static string Notifications_PluginInstalledTitle => Loc.Localize("NotificationsPluginInstalledTitle", "Plugin installed!"); - public static string Notifications_PluginInstalled(string name) => Loc.Localize("NotificationsPluginInstalled", "'{0}' was successfully installed.").Format(name); + public static string Notifications_PluginInstalled(string name) => Loc.Localize("NotificationsPluginInstalled", "'{0}' was successfully installed.").Format(name); - public static string Notifications_PluginNotInstalledTitle => Loc.Localize("NotificationsPluginNotInstalledTitle", "Plugin not installed!"); + public static string Notifications_PluginNotInstalledTitle => Loc.Localize("NotificationsPluginNotInstalledTitle", "Plugin not installed!"); - public static string Notifications_PluginNotInstalled(string name) => Loc.Localize("NotificationsPluginNotInstalled", "'{0}' failed to install.").Format(name); + public static string Notifications_PluginNotInstalled(string name) => Loc.Localize("NotificationsPluginNotInstalled", "'{0}' failed to install.").Format(name); - public static string Notifications_NoUpdatesFoundTitle => Loc.Localize("NotificationsNoUpdatesFoundTitle", "No updates found!"); + public static string Notifications_NoUpdatesFoundTitle => Loc.Localize("NotificationsNoUpdatesFoundTitle", "No updates found!"); - public static string Notifications_NoUpdatesFound => Loc.Localize("NotificationsNoUpdatesFound", "No updates were found."); + public static string Notifications_NoUpdatesFound => Loc.Localize("NotificationsNoUpdatesFound", "No updates were found."); - public static string Notifications_UpdatesInstalledTitle => Loc.Localize("NotificationsUpdatesInstalledTitle", "Updates installed!"); + public static string Notifications_UpdatesInstalledTitle => Loc.Localize("NotificationsUpdatesInstalledTitle", "Updates installed!"); - public static string Notifications_UpdatesInstalled(int count) => Loc.Localize("NotificationsUpdatesInstalled", "Updates for {0} of your plugins were installed.").Format(count); + public static string Notifications_UpdatesInstalled(int count) => Loc.Localize("NotificationsUpdatesInstalled", "Updates for {0} of your plugins were installed.").Format(count); - public static string Notifications_PluginDisabledTitle => Loc.Localize("NotificationsPluginDisabledTitle", "Plugin disabled!"); + public static string Notifications_PluginDisabledTitle => Loc.Localize("NotificationsPluginDisabledTitle", "Plugin disabled!"); - public static string Notifications_PluginDisabled(string name) => Loc.Localize("NotificationsPluginDisabled", "'{0}' was disabled.").Format(name); + public static string Notifications_PluginDisabled(string name) => Loc.Localize("NotificationsPluginDisabled", "'{0}' was disabled.").Format(name); - #endregion + #endregion - #region Footer + #region Footer - public static string FooterButton_UpdatePlugins => Loc.Localize("InstallerUpdatePlugins", "Update plugins"); + public static string FooterButton_UpdatePlugins => Loc.Localize("InstallerUpdatePlugins", "Update plugins"); - public static string FooterButton_InProgress => Loc.Localize("InstallerInProgress", "Install in progress..."); + public static string FooterButton_InProgress => Loc.Localize("InstallerInProgress", "Install in progress..."); - public static string FooterButton_NoUpdates => Loc.Localize("InstallerNoUpdates", "No updates found!"); + public static string FooterButton_NoUpdates => Loc.Localize("InstallerNoUpdates", "No updates found!"); - public static string FooterButton_UpdateComplete(int count) => Loc.Localize("InstallerUpdateComplete", "{0} plugins updated!").Format(count); + public static string FooterButton_UpdateComplete(int count) => Loc.Localize("InstallerUpdateComplete", "{0} plugins updated!").Format(count); - public static string FooterButton_Settings => Loc.Localize("InstallerSettings", "Settings"); + public static string FooterButton_Settings => Loc.Localize("InstallerSettings", "Settings"); - public static string FooterButton_ScanDevPlugins => Loc.Localize("InstallerScanDevPlugins", "Scan Dev Plugins"); + public static string FooterButton_ScanDevPlugins => Loc.Localize("InstallerScanDevPlugins", "Scan Dev Plugins"); - public static string FooterButton_Close => Loc.Localize("InstallerClose", "Close"); + public static string FooterButton_Close => Loc.Localize("InstallerClose", "Close"); - #endregion + #endregion - #region Error modal + #region Error modal - public static string ErrorModal_Title => Loc.Localize("InstallerError", "Installer Error"); + public static string ErrorModal_Title => Loc.Localize("InstallerError", "Installer Error"); - public static string ErrorModal_InstallContactAuthor => Loc.Localize( - "InstallerContactAuthor", - "Please restart your game and try again. If this error occurs again, please contact the plugin author."); + public static string ErrorModal_InstallContactAuthor => Loc.Localize( + "InstallerContactAuthor", + "Please restart your game and try again. If this error occurs again, please contact the plugin author."); - public static string ErrorModal_InstallFail(string name) => Loc.Localize("InstallerInstallFail", "Failed to install plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + public static string ErrorModal_InstallFail(string name) => Loc.Localize("InstallerInstallFail", "Failed to install plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - public static string ErrorModal_SingleUpdateFail(string name) => Loc.Localize("InstallerSingleUpdateFail", "Failed to update plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + public static string ErrorModal_SingleUpdateFail(string name) => Loc.Localize("InstallerSingleUpdateFail", "Failed to update plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - public static string ErrorModal_DeleteConfigFail(string name) => Loc.Localize("InstallerDeleteConfigFail", "Failed to reset the plugin {0}.\n\nThe plugin may not support this action. You can try deleting the configuration manually while the game is shut down - please see the FAQ.").Format(name); + public static string ErrorModal_DeleteConfigFail(string name) => Loc.Localize("InstallerDeleteConfigFail", "Failed to reset the plugin {0}.\n\nThe plugin may not support this action. You can try deleting the configuration manually while the game is shut down - please see the FAQ.").Format(name); - public static string ErrorModal_EnableFail(string name) => Loc.Localize("InstallerEnableFail", "Failed to enable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + public static string ErrorModal_EnableFail(string name) => Loc.Localize("InstallerEnableFail", "Failed to enable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - public static string ErrorModal_DisableFail(string name) => Loc.Localize("InstallerDisableFail", "Failed to disable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + public static string ErrorModal_DisableFail(string name) => Loc.Localize("InstallerDisableFail", "Failed to disable plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - public static string ErrorModal_UnloadFail(string name) => Loc.Localize("InstallerUnloadFail", "Failed to unload plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + public static string ErrorModal_UnloadFail(string name) => Loc.Localize("InstallerUnloadFail", "Failed to unload plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - public static string ErrorModal_LoadFail(string name) => Loc.Localize("InstallerLoadFail", "Failed to load plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + public static string ErrorModal_LoadFail(string name) => Loc.Localize("InstallerLoadFail", "Failed to load plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - public static string ErrorModal_DeleteFail(string name) => Loc.Localize("InstallerDeleteFail", "Failed to delete plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); + public static string ErrorModal_DeleteFail(string name) => Loc.Localize("InstallerDeleteFail", "Failed to delete plugin {0}.\n{1}").Format(name, ErrorModal_InstallContactAuthor); - public static string ErrorModal_UpdaterFatal => Loc.Localize("InstallerUpdaterFatal", "Failed to update plugins.\nPlease restart your game and try again. If this error occurs again, please complain."); + public static string ErrorModal_UpdaterFatal => Loc.Localize("InstallerUpdaterFatal", "Failed to update plugins.\nPlease restart your game and try again. If this error occurs again, please complain."); - public static string ErrorModal_UpdaterFail(int failCount) => Loc.Localize("InstallerUpdaterFail", "Failed to update {0} plugins.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(failCount); + public static string ErrorModal_UpdaterFail(int failCount) => Loc.Localize("InstallerUpdaterFail", "Failed to update {0} plugins.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(failCount); - public static string ErrorModal_UpdaterFailPartial(int successCount, int failCount) => Loc.Localize("InstallerUpdaterFailPartial", "Updated {0} plugins, failed to update {1}.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(successCount, failCount); + public static string ErrorModal_UpdaterFailPartial(int successCount, int failCount) => Loc.Localize("InstallerUpdaterFailPartial", "Updated {0} plugins, failed to update {1}.\nPlease restart your game and try again. If this error occurs again, please complain.").Format(successCount, failCount); - public static string ErrorModal_HintBlame(string plugins) => Loc.Localize("InstallerErrorPluginInfo", "\n\nThe following plugins caused these issues:\n\n{0}\nYou may try removing these plugins manually and reinstalling them.").Format(plugins); + public static string ErrorModal_HintBlame(string plugins) => Loc.Localize("InstallerErrorPluginInfo", "\n\nThe following plugins caused these issues:\n\n{0}\nYou may try removing these plugins manually and reinstalling them.").Format(plugins); - // public static string ErrorModal_Hint => Loc.Localize("InstallerErrorHint", "The plugin installer ran into an issue or the plugin is incompatible.\nPlease restart the game and report this error on our discord."); + // public static string ErrorModal_Hint => Loc.Localize("InstallerErrorHint", "The plugin installer ran into an issue or the plugin is incompatible.\nPlease restart the game and report this error on our discord."); - #endregion + #endregion - #region Feedback Modal + #region Feedback Modal - public static string FeedbackModal_Title => Loc.Localize("InstallerFeedback", "Send Feedback"); + public static string FeedbackModal_Title => Loc.Localize("InstallerFeedback", "Send Feedback"); - public static string FeedbackModal_Text(string pluginName) => Loc.Localize("InstallerFeedbackInfo", "You can send feedback to the developer of \"{0}\" here.\nYou can include your Discord tag or email address if you wish to give them the opportunity to answer.").Format(pluginName); + public static string FeedbackModal_Text(string pluginName) => Loc.Localize("InstallerFeedbackInfo", "You can send feedback to the developer of \"{0}\" here.\nYou can include your Discord tag or email address if you wish to give them the opportunity to answer.").Format(pluginName); - public static string FeedbackModal_HasUpdate => Loc.Localize("InstallerFeedbackHasUpdate", "A new version of this plugin is available, please update before reporting bugs."); + public static string FeedbackModal_HasUpdate => Loc.Localize("InstallerFeedbackHasUpdate", "A new version of this plugin is available, please update before reporting bugs."); - public static string FeedbackModal_ContactInformation => Loc.Localize("InstallerFeedbackContactInfo", "Contact information"); + public static string FeedbackModal_ContactInformation => Loc.Localize("InstallerFeedbackContactInfo", "Contact information"); - public static string FeedbackModal_IncludeLastError => Loc.Localize("InstallerFeedbackIncludeLastError", "Include last error message"); + public static string FeedbackModal_IncludeLastError => Loc.Localize("InstallerFeedbackIncludeLastError", "Include last error message"); - public static string FeedbackModal_IncludeLastErrorHint => Loc.Localize("InstallerFeedbackIncludeLastErrorHint", "This option can give the plugin developer useful feedback on what exactly went wrong."); + public static string FeedbackModal_IncludeLastErrorHint => Loc.Localize("InstallerFeedbackIncludeLastErrorHint", "This option can give the plugin developer useful feedback on what exactly went wrong."); - public static string FeedbackModal_Hint => Loc.Localize("InstallerFeedbackHint", "All plugin developers will be able to see your feedback.\nPlease never include any personal or revealing information.\nIf you chose to include the last error message, information like your Windows username may be included.\n\nThe collected feedback is not stored on our end and immediately relayed to Discord."); + public static string FeedbackModal_Hint => Loc.Localize("InstallerFeedbackHint", "All plugin developers will be able to see your feedback.\nPlease never include any personal or revealing information.\nIf you chose to include the last error message, information like your Windows username may be included.\n\nThe collected feedback is not stored on our end and immediately relayed to Discord."); - public static string FeedbackModal_NotificationSuccess => Loc.Localize("InstallerFeedbackNotificationSuccess", "Your feedback was sent successfully!"); + public static string FeedbackModal_NotificationSuccess => Loc.Localize("InstallerFeedbackNotificationSuccess", "Your feedback was sent successfully!"); - public static string FeedbackModal_NotificationError => Loc.Localize("InstallerFeedbackNotificationError", "Your feedback could not be sent."); + public static string FeedbackModal_NotificationError => Loc.Localize("InstallerFeedbackNotificationError", "Your feedback could not be sent."); - #endregion + #endregion - #region Plugin Update chatbox + #region Plugin Update chatbox - public static string PluginUpdateHeader_Chatbox => Loc.Localize("DalamudPluginUpdates", "Updates:"); + public static string PluginUpdateHeader_Chatbox => Loc.Localize("DalamudPluginUpdates", "Updates:"); - #endregion + #endregion - #region Error modal buttons + #region Error modal buttons - public static string ErrorModalButton_Ok => Loc.Localize("OK", "OK"); + public static string ErrorModalButton_Ok => Loc.Localize("OK", "OK"); - #endregion + #endregion + } } } diff --git a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs index 986f01b7b..257b67857 100644 --- a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs @@ -11,263 +11,264 @@ using Dalamud.Plugin.Internal; using Dalamud.Plugin.Internal.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// This window displays plugin statistics for troubleshooting. -/// -internal class PluginStatWindow : Window +namespace Dalamud.Interface.Internal.Windows { - private bool showDalamudHooks; - /// - /// Initializes a new instance of the class. + /// This window displays plugin statistics for troubleshooting. /// - public PluginStatWindow() - : base("Plugin Statistics###DalamudPluginStatWindow") + internal class PluginStatWindow : Window { - this.RespectCloseHotkey = false; - } + private bool showDalamudHooks; - /// - public override void Draw() - { - var pluginManager = Service.Get(); - - ImGui.BeginTabBar("Stat Tabs"); - - if (ImGui.BeginTabItem("Draw times")) + /// + /// Initializes a new instance of the class. + /// + public PluginStatWindow() + : base("Plugin Statistics###DalamudPluginStatWindow") { - var doStats = UiBuilder.DoStats; - - if (ImGui.Checkbox("Enable Draw Time Tracking", ref doStats)) - { - UiBuilder.DoStats = doStats; - } - - if (doStats) - { - ImGui.SameLine(); - if (ImGui.Button("Reset")) - { - foreach (var plugin in pluginManager.InstalledPlugins) - { - plugin.DalamudInterface.UiBuilder.LastDrawTime = -1; - plugin.DalamudInterface.UiBuilder.MaxDrawTime = -1; - plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Clear(); - } - } - - ImGui.Columns(4); - ImGui.SetColumnWidth(0, 180f); - ImGui.SetColumnWidth(1, 100f); - ImGui.SetColumnWidth(2, 100f); - ImGui.SetColumnWidth(3, 100f); - - ImGui.Text("Plugin"); - ImGui.NextColumn(); - - ImGui.Text("Last"); - ImGui.NextColumn(); - - ImGui.Text("Longest"); - ImGui.NextColumn(); - - ImGui.Text("Average"); - ImGui.NextColumn(); - - ImGui.Separator(); - - foreach (var plugin in pluginManager.InstalledPlugins.Where(plugin => plugin.State == PluginState.Loaded)) - { - ImGui.Text(plugin.Manifest.Name); - ImGui.NextColumn(); - - ImGui.Text($"{plugin.DalamudInterface.UiBuilder.LastDrawTime / 10000f:F4}ms"); - ImGui.NextColumn(); - - ImGui.Text($"{plugin.DalamudInterface.UiBuilder.MaxDrawTime / 10000f:F4}ms"); - ImGui.NextColumn(); - - if (plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Count > 0) - { - ImGui.Text($"{plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Average() / 10000f:F4}ms"); - } - else - { - ImGui.Text("-"); - } - - ImGui.NextColumn(); - } - - ImGui.Columns(1); - } - - ImGui.EndTabItem(); + this.RespectCloseHotkey = false; } - if (ImGui.BeginTabItem("Framework times")) + /// + public override void Draw() { - var doStats = Framework.StatsEnabled; + var pluginManager = Service.Get(); - if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats)) - { - Framework.StatsEnabled = doStats; - } + ImGui.BeginTabBar("Stat Tabs"); - if (doStats) + if (ImGui.BeginTabItem("Draw times")) { - ImGui.SameLine(); - if (ImGui.Button("Reset")) + var doStats = UiBuilder.DoStats; + + if (ImGui.Checkbox("Enable Draw Time Tracking", ref doStats)) { - Framework.StatsHistory.Clear(); + UiBuilder.DoStats = doStats; } + if (doStats) + { + ImGui.SameLine(); + if (ImGui.Button("Reset")) + { + foreach (var plugin in pluginManager.InstalledPlugins) + { + plugin.DalamudInterface.UiBuilder.LastDrawTime = -1; + plugin.DalamudInterface.UiBuilder.MaxDrawTime = -1; + plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Clear(); + } + } + + ImGui.Columns(4); + ImGui.SetColumnWidth(0, 180f); + ImGui.SetColumnWidth(1, 100f); + ImGui.SetColumnWidth(2, 100f); + ImGui.SetColumnWidth(3, 100f); + + ImGui.Text("Plugin"); + ImGui.NextColumn(); + + ImGui.Text("Last"); + ImGui.NextColumn(); + + ImGui.Text("Longest"); + ImGui.NextColumn(); + + ImGui.Text("Average"); + ImGui.NextColumn(); + + ImGui.Separator(); + + foreach (var plugin in pluginManager.InstalledPlugins.Where(plugin => plugin.State == PluginState.Loaded)) + { + ImGui.Text(plugin.Manifest.Name); + ImGui.NextColumn(); + + ImGui.Text($"{plugin.DalamudInterface.UiBuilder.LastDrawTime / 10000f:F4}ms"); + ImGui.NextColumn(); + + ImGui.Text($"{plugin.DalamudInterface.UiBuilder.MaxDrawTime / 10000f:F4}ms"); + ImGui.NextColumn(); + + if (plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Count > 0) + { + ImGui.Text($"{plugin.DalamudInterface.UiBuilder.DrawTimeHistory.Average() / 10000f:F4}ms"); + } + else + { + ImGui.Text("-"); + } + + ImGui.NextColumn(); + } + + ImGui.Columns(1); + } + + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("Framework times")) + { + var doStats = Framework.StatsEnabled; + + if (ImGui.Checkbox("Enable Framework Update Tracking", ref doStats)) + { + Framework.StatsEnabled = doStats; + } + + if (doStats) + { + ImGui.SameLine(); + if (ImGui.Button("Reset")) + { + Framework.StatsHistory.Clear(); + } + + ImGui.Columns(4); + + ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 300); + ImGui.SetColumnWidth(1, 100f); + ImGui.SetColumnWidth(2, 100f); + ImGui.SetColumnWidth(3, 100f); + + ImGui.Text("Method"); + ImGui.NextColumn(); + + ImGui.Text("Last"); + ImGui.NextColumn(); + + ImGui.Text("Longest"); + ImGui.NextColumn(); + + ImGui.Text("Average"); + ImGui.NextColumn(); + + ImGui.Separator(); + ImGui.Separator(); + + foreach (var handlerHistory in Framework.StatsHistory) + { + if (handlerHistory.Value.Count == 0) + continue; + + ImGui.SameLine(); + + ImGui.Text($"{handlerHistory.Key}"); + ImGui.NextColumn(); + + ImGui.Text($"{handlerHistory.Value.Last():F4}ms"); + ImGui.NextColumn(); + + ImGui.Text($"{handlerHistory.Value.Max():F4}ms"); + ImGui.NextColumn(); + + ImGui.Text($"{handlerHistory.Value.Average():F4}ms"); + ImGui.NextColumn(); + + ImGui.Separator(); + } + + ImGui.Columns(0); + } + + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("Hooks")) + { ImGui.Columns(4); - ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 300); - ImGui.SetColumnWidth(1, 100f); + ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 330); + ImGui.SetColumnWidth(1, 180f); ImGui.SetColumnWidth(2, 100f); ImGui.SetColumnWidth(3, 100f); - ImGui.Text("Method"); + ImGui.Text("Detour Method"); + ImGui.SameLine(); + + ImGui.Text(" "); + ImGui.SameLine(); + + ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref this.showDalamudHooks); ImGui.NextColumn(); - ImGui.Text("Last"); + ImGui.Text("Address"); ImGui.NextColumn(); - ImGui.Text("Longest"); + ImGui.Text("Status"); ImGui.NextColumn(); - ImGui.Text("Average"); + ImGui.Text("Backend"); ImGui.NextColumn(); ImGui.Separator(); ImGui.Separator(); - foreach (var handlerHistory in Framework.StatsHistory) + foreach (var trackedHook in HookManager.TrackedHooks) { - if (handlerHistory.Value.Count == 0) - continue; + try + { + if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) + continue; - ImGui.SameLine(); + ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}"); + ImGui.TextDisabled(trackedHook.Assembly.FullName); + ImGui.NextColumn(); + if (!trackedHook.Hook.IsDisposed) + { + ImGui.Text($"{trackedHook.Hook.Address.ToInt64():X}"); + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}"); + } - ImGui.Text($"{handlerHistory.Key}"); - ImGui.NextColumn(); + var processMemoryOffset = trackedHook.InProcessMemory; + if (processMemoryOffset.HasValue) + { + ImGui.Text($"ffxiv_dx11.exe + {processMemoryOffset:X}"); + if (ImGui.IsItemClicked()) + { + ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}"); + } + } + } - ImGui.Text($"{handlerHistory.Value.Last():F4}ms"); - ImGui.NextColumn(); + ImGui.NextColumn(); - ImGui.Text($"{handlerHistory.Value.Max():F4}ms"); - ImGui.NextColumn(); + if (trackedHook.Hook.IsDisposed) + { + ImGui.Text("Disposed"); + } + else + { + ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled"); + } - ImGui.Text($"{handlerHistory.Value.Average():F4}ms"); - ImGui.NextColumn(); + ImGui.NextColumn(); + + ImGui.Text(trackedHook.Hook.BackendName); + + ImGui.NextColumn(); + } + catch (Exception ex) + { + ImGui.Text(ex.Message); + ImGui.NextColumn(); + while (ImGui.GetColumnIndex() != 0) ImGui.NextColumn(); + } ImGui.Separator(); } - ImGui.Columns(0); + ImGui.Columns(); } - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem("Hooks")) - { - ImGui.Columns(4); - - ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 330); - ImGui.SetColumnWidth(1, 180f); - ImGui.SetColumnWidth(2, 100f); - ImGui.SetColumnWidth(3, 100f); - - ImGui.Text("Detour Method"); - ImGui.SameLine(); - - ImGui.Text(" "); - ImGui.SameLine(); - - ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref this.showDalamudHooks); - ImGui.NextColumn(); - - ImGui.Text("Address"); - ImGui.NextColumn(); - - ImGui.Text("Status"); - ImGui.NextColumn(); - - ImGui.Text("Backend"); - ImGui.NextColumn(); - - ImGui.Separator(); - ImGui.Separator(); - - foreach (var trackedHook in HookManager.TrackedHooks) + if (ImGui.IsWindowAppearing()) { - try - { - if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) - continue; - - ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}"); - ImGui.TextDisabled(trackedHook.Assembly.FullName); - ImGui.NextColumn(); - if (!trackedHook.Hook.IsDisposed) - { - ImGui.Text($"{trackedHook.Hook.Address.ToInt64():X}"); - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}"); - } - - var processMemoryOffset = trackedHook.InProcessMemory; - if (processMemoryOffset.HasValue) - { - ImGui.Text($"ffxiv_dx11.exe + {processMemoryOffset:X}"); - if (ImGui.IsItemClicked()) - { - ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}"); - } - } - } - - ImGui.NextColumn(); - - if (trackedHook.Hook.IsDisposed) - { - ImGui.Text("Disposed"); - } - else - { - ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled"); - } - - ImGui.NextColumn(); - - ImGui.Text(trackedHook.Hook.BackendName); - - ImGui.NextColumn(); - } - catch (Exception ex) - { - ImGui.Text(ex.Message); - ImGui.NextColumn(); - while (ImGui.GetColumnIndex() != 0) ImGui.NextColumn(); - } - - ImGui.Separator(); + HookManager.TrackedHooks.RemoveAll(h => h.Hook.IsDisposed); } - ImGui.Columns(); + ImGui.EndTabBar(); } - - if (ImGui.IsWindowAppearing()) - { - HookManager.TrackedHooks.RemoveAll(h => h.Hook.IsDisposed); - } - - ImGui.EndTabBar(); } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs index 242e93a49..a8cb3e4a1 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ActorTableAgingStep.cs @@ -2,46 +2,47 @@ using Dalamud.Game.ClientState.Objects; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for the Actor Table. -/// -internal class ActorTableAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private int index = 0; - - /// - public string Name => "Test ActorTable"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for the Actor Table. + /// + internal class ActorTableAgingStep : IAgingStep { - var objectTable = Service.Get(); + private int index = 0; - ImGui.Text("Checking actor table..."); + /// + public string Name => "Test ActorTable"; - if (this.index == objectTable.Length - 1) + /// + public SelfTestStepResult RunStep() { - return SelfTestStepResult.Pass; - } + var objectTable = Service.Get(); - var actor = objectTable[this.index]; - this.index++; + ImGui.Text("Checking actor table..."); + + if (this.index == objectTable.Length - 1) + { + return SelfTestStepResult.Pass; + } + + var actor = objectTable[this.index]; + this.index++; + + if (actor == null) + { + return SelfTestStepResult.Waiting; + } + + Util.ShowObject(actor); - if (actor == null) - { return SelfTestStepResult.Waiting; } - Util.ShowObject(actor); - - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored + /// + public void CleanUp() + { + // ignored + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs index c7b6213d4..02613dee2 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ChatAgingStep.cs @@ -3,70 +3,71 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for Chat. -/// -internal class ChatAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private int step = 0; - private bool subscribed = false; - private bool hasPassed = false; - - /// - public string Name => "Test Chat"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for Chat. + /// + internal class ChatAgingStep : IAgingStep { - var chatGui = Service.Get(); + private int step = 0; + private bool subscribed = false; + private bool hasPassed = false; - switch (this.step) + /// + public string Name => "Test Chat"; + + /// + public SelfTestStepResult RunStep() { - case 0: - chatGui.Print("Testing!"); - this.step++; + var chatGui = Service.Get(); - break; + switch (this.step) + { + case 0: + chatGui.Print("Testing!"); + this.step++; - case 1: - ImGui.Text("Type \"/e DALAMUD\" in chat..."); + break; - if (!this.subscribed) - { - this.subscribed = true; - chatGui.ChatMessage += this.ChatOnOnChatMessage; - } + case 1: + ImGui.Text("Type \"/e DALAMUD\" in chat..."); - if (this.hasPassed) - { - chatGui.ChatMessage -= this.ChatOnOnChatMessage; - this.subscribed = false; - return SelfTestStepResult.Pass; - } + if (!this.subscribed) + { + this.subscribed = true; + chatGui.ChatMessage += this.ChatOnOnChatMessage; + } - break; + if (this.hasPassed) + { + chatGui.ChatMessage -= this.ChatOnOnChatMessage; + this.subscribed = false; + return SelfTestStepResult.Pass; + } + + break; + } + + return SelfTestStepResult.Waiting; } - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - var chatGui = Service.Get(); - - chatGui.ChatMessage -= this.ChatOnOnChatMessage; - this.subscribed = false; - } - - private void ChatOnOnChatMessage( - XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool ishandled) - { - if (type == XivChatType.Echo && message.TextValue == "DALAMUD") + /// + public void CleanUp() { - this.hasPassed = true; + var chatGui = Service.Get(); + + chatGui.ChatMessage -= this.ChatOnOnChatMessage; + this.subscribed = false; + } + + private void ChatOnOnChatMessage( + XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool ishandled) + { + if (type == XivChatType.Echo && message.TextValue == "DALAMUD") + { + this.hasPassed = true; + } } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs index fee692ab8..501920343 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ConditionAgingStep.cs @@ -2,35 +2,36 @@ using Dalamud.Game.ClientState.Conditions; using ImGuiNET; using Serilog; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for Condition. -/// -internal class ConditionAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - /// - public string Name => "Test Condition"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for Condition. + /// + internal class ConditionAgingStep : IAgingStep { - var condition = Service.Get(); + /// + public string Name => "Test Condition"; - if (!condition.Any()) + /// + public SelfTestStepResult RunStep() { - Log.Error("No condition flags present."); - return SelfTestStepResult.Fail; + var condition = Service.Get(); + + if (!condition.Any()) + { + Log.Error("No condition flags present."); + return SelfTestStepResult.Fail; + } + + ImGui.Text("Please jump..."); + + return condition[ConditionFlag.Jumping] ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; } - ImGui.Text("Please jump..."); - - return condition[ConditionFlag.Jumping] ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored + /// + public void CleanUp() + { + // ignored + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs index d301cb1ff..487a6e572 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/EnterTerritoryAgingStep.cs @@ -1,69 +1,70 @@ using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for Territory Change. -/// -internal class EnterTerritoryAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private readonly ushort territory; - private readonly string terriName; - private bool subscribed = false; - private bool hasPassed = false; - /// - /// Initializes a new instance of the class. + /// Test setup for Territory Change. /// - /// The territory to check for. - /// Name to show. - public EnterTerritoryAgingStep(ushort terri, string name) + internal class EnterTerritoryAgingStep : IAgingStep { - this.terriName = name; - this.territory = terri; - } + private readonly ushort territory; + private readonly string terriName; + private bool subscribed = false; + private bool hasPassed = false; - /// - public string Name => $"Enter Terri: {this.terriName}"; - - /// - public SelfTestStepResult RunStep() - { - var clientState = Service.Get(); - - ImGui.TextUnformatted(this.Name); - - if (!this.subscribed) + /// + /// Initializes a new instance of the class. + /// + /// The territory to check for. + /// Name to show. + public EnterTerritoryAgingStep(ushort terri, string name) { - clientState.TerritoryChanged += this.ClientStateOnTerritoryChanged; - this.subscribed = true; + this.terriName = name; + this.territory = terri; } - if (this.hasPassed) + /// + public string Name => $"Enter Terri: {this.terriName}"; + + /// + public SelfTestStepResult RunStep() { + var clientState = Service.Get(); + + ImGui.TextUnformatted(this.Name); + + if (!this.subscribed) + { + clientState.TerritoryChanged += this.ClientStateOnTerritoryChanged; + this.subscribed = true; + } + + if (this.hasPassed) + { + clientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; + this.subscribed = false; + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + var clientState = Service.Get(); + clientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; this.subscribed = false; - return SelfTestStepResult.Pass; } - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - var clientState = Service.Get(); - - clientState.TerritoryChanged -= this.ClientStateOnTerritoryChanged; - this.subscribed = false; - } - - private void ClientStateOnTerritoryChanged(object sender, ushort e) - { - if (e == this.territory) + private void ClientStateOnTerritoryChanged(object sender, ushort e) { - this.hasPassed = true; + if (e == this.territory) + { + this.hasPassed = true; + } } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs index a8fe60aa9..b037d06ca 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/FateTableAgingStep.cs @@ -2,46 +2,47 @@ using Dalamud.Game.ClientState.Fates; using Dalamud.Utility; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for the Fate Table. -/// -internal class FateTableAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private int index = 0; - - /// - public string Name => "Test FateTable"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for the Fate Table. + /// + internal class FateTableAgingStep : IAgingStep { - var fateTable = Service.Get(); + private int index = 0; - ImGui.Text("Checking fate table..."); + /// + public string Name => "Test FateTable"; - if (this.index == fateTable.Length - 1) + /// + public SelfTestStepResult RunStep() { - return SelfTestStepResult.Pass; - } + var fateTable = Service.Get(); - var actor = fateTable[this.index]; - this.index++; + ImGui.Text("Checking fate table..."); + + if (this.index == fateTable.Length - 1) + { + return SelfTestStepResult.Pass; + } + + var actor = fateTable[this.index]; + this.index++; + + if (actor == null) + { + return SelfTestStepResult.Waiting; + } + + Util.ShowObject(actor); - if (actor == null) - { return SelfTestStepResult.Waiting; } - Util.ShowObject(actor); - - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored + /// + public void CleanUp() + { + // ignored + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs index 31ba8e5d6..320e0d55c 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/GamepadStateAgingStep.cs @@ -1,36 +1,37 @@ using Dalamud.Game.ClientState.GamePad; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for the Gamepad State. -/// -internal class GamepadStateAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - /// - public string Name => "Test GamePadState"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for the Gamepad State. + /// + internal class GamepadStateAgingStep : IAgingStep { - var gamepadState = Service.Get(); + /// + public string Name => "Test GamePadState"; - ImGui.Text("Hold down North, East, L1"); - - if (gamepadState.Pressed(GamepadButtons.North) == 1 - && gamepadState.Pressed(GamepadButtons.East) == 1 - && gamepadState.Pressed(GamepadButtons.L1) == 1) + /// + public SelfTestStepResult RunStep() { - return SelfTestStepResult.Pass; + var gamepadState = Service.Get(); + + ImGui.Text("Hold down North, East, L1"); + + if (gamepadState.Pressed(GamepadButtons.North) == 1 + && gamepadState.Pressed(GamepadButtons.East) == 1 + && gamepadState.Pressed(GamepadButtons.L1) == 1) + { + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; } - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored + /// + public void CleanUp() + { + // ignored + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs index 43798968e..7a63d6ba7 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HandledExceptionAgingStep.cs @@ -1,34 +1,35 @@ using System; using System.Runtime.InteropServices; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test dedicated to handling of Access Violations. -/// -internal class HandledExceptionAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - /// - public string Name => "Test Handled Exception"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test dedicated to handling of Access Violations. + /// + internal class HandledExceptionAgingStep : IAgingStep { - try + /// + public string Name => "Test Handled Exception"; + + /// + public SelfTestStepResult RunStep() { - Marshal.ReadByte(IntPtr.Zero); - } - catch (AccessViolationException) - { - return SelfTestStepResult.Pass; + try + { + Marshal.ReadByte(IntPtr.Zero); + } + catch (AccessViolationException) + { + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Fail; } - return SelfTestStepResult.Fail; - } - - /// - public void CleanUp() - { - // ignored + /// + public void CleanUp() + { + // ignored + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs index b4b069487..713d0ebc4 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/HoverAgingStep.cs @@ -1,57 +1,58 @@ using Dalamud.Game.Gui; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for the Hover events. -/// -internal class HoverAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private bool clearedItem = false; - private bool clearedAction = false; - - /// - public string Name => "Test Hover"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for the Hover events. + /// + internal class HoverAgingStep : IAgingStep { - var gameGui = Service.Get(); + private bool clearedItem = false; + private bool clearedAction = false; - if (!this.clearedItem) + /// + public string Name => "Test Hover"; + + /// + public SelfTestStepResult RunStep() { - ImGui.Text("Hover WHM soul crystal..."); + var gameGui = Service.Get(); - if (gameGui.HoveredItem == 4547) + if (!this.clearedItem) { - this.clearedItem = true; + ImGui.Text("Hover WHM soul crystal..."); + + if (gameGui.HoveredItem == 4547) + { + this.clearedItem = true; + } } - } - if (!this.clearedAction) - { - ImGui.Text("Hover \"Open Linkshells\" action..."); - - if (gameGui.HoveredAction != null && - gameGui.HoveredAction.ActionKind == HoverActionKind.MainCommand && - gameGui.HoveredAction.ActionID == 28) + if (!this.clearedAction) { - this.clearedAction = true; + ImGui.Text("Hover \"Open Linkshells\" action..."); + + if (gameGui.HoveredAction != null && + gameGui.HoveredAction.ActionKind == HoverActionKind.MainCommand && + gameGui.HoveredAction.ActionID == 28) + { + this.clearedAction = true; + } } + + if (this.clearedItem && this.clearedAction) + { + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; } - if (this.clearedItem && this.clearedAction) + /// + public void CleanUp() { - return SelfTestStepResult.Pass; + // ignored } - - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs index 680961344..6315f9d05 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/IAgingStep.cs @@ -1,23 +1,24 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Interface for test implementations. -/// -internal interface IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { /// - /// Gets the name of the test. + /// Interface for test implementations. /// - public string Name { get; } + internal interface IAgingStep + { + /// + /// Gets the name of the test. + /// + public string Name { get; } - /// - /// Run the test step, once per frame it is active. - /// - /// The result of this frame, test is discarded once a result other than is returned. - public SelfTestStepResult RunStep(); + /// + /// Run the test step, once per frame it is active. + /// + /// The result of this frame, test is discarded once a result other than is returned. + public SelfTestStepResult RunStep(); - /// - /// Clean up this test. - /// - public void CleanUp(); + /// + /// Clean up this test. + /// + public void CleanUp(); + } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs index c1ae9289a..20b645b15 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/KeyStateAgingStep.cs @@ -1,38 +1,39 @@ using Dalamud.Game.ClientState.Keys; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for the Key State. -/// -internal class KeyStateAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - /// - public string Name => "Test KeyState"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for the Key State. + /// + internal class KeyStateAgingStep : IAgingStep { - var keyState = Service.Get(); + /// + public string Name => "Test KeyState"; - ImGui.Text("Hold down D,A,L,M,U"); - - if (keyState[VirtualKey.D] - && keyState[VirtualKey.A] - && keyState[VirtualKey.L] - && keyState[VirtualKey.M] - && keyState[VirtualKey.U]) + /// + public SelfTestStepResult RunStep() { - return SelfTestStepResult.Pass; + var keyState = Service.Get(); + + ImGui.Text("Hold down D,A,L,M,U"); + + if (keyState[VirtualKey.D] + && keyState[VirtualKey.A] + && keyState[VirtualKey.L] + && keyState[VirtualKey.M] + && keyState[VirtualKey.U]) + { + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; } - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored + /// + public void CleanUp() + { + // ignored + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs index c1dba389f..47a7f9bc9 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LoginEventAgingStep.cs @@ -3,56 +3,57 @@ using System; using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for the login events. -/// -internal class LoginEventAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private bool subscribed = false; - private bool hasPassed = false; - - /// - public string Name => "Test Log-In"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for the login events. + /// + internal class LoginEventAgingStep : IAgingStep { - var clientState = Service.Get(); + private bool subscribed = false; + private bool hasPassed = false; - ImGui.Text("Log in now..."); + /// + public string Name => "Test Log-In"; - if (!this.subscribed) + /// + public SelfTestStepResult RunStep() { - clientState.Login += this.ClientStateOnOnLogin; - this.subscribed = true; + var clientState = Service.Get(); + + ImGui.Text("Log in now..."); + + if (!this.subscribed) + { + clientState.Login += this.ClientStateOnOnLogin; + this.subscribed = true; + } + + if (this.hasPassed) + { + clientState.Login -= this.ClientStateOnOnLogin; + this.subscribed = false; + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; } - if (this.hasPassed) + /// + public void CleanUp() { - clientState.Login -= this.ClientStateOnOnLogin; - this.subscribed = false; - return SelfTestStepResult.Pass; + var clientState = Service.Get(); + + if (this.subscribed) + { + clientState.Login -= this.ClientStateOnOnLogin; + this.subscribed = false; + } } - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - var clientState = Service.Get(); - - if (this.subscribed) + private void ClientStateOnOnLogin(object sender, EventArgs e) { - clientState.Login -= this.ClientStateOnOnLogin; - this.subscribed = false; + this.hasPassed = true; } } - - private void ClientStateOnOnLogin(object sender, EventArgs e) - { - this.hasPassed = true; - } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs index 2cb7b30f9..8c216eb19 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LogoutEventAgingStep.cs @@ -3,56 +3,57 @@ using System; using Dalamud.Game.ClientState; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for the login events. -/// -internal class LogoutEventAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private bool subscribed = false; - private bool hasPassed = false; - - /// - public string Name => "Test Log-In"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for the login events. + /// + internal class LogoutEventAgingStep : IAgingStep { - var clientState = Service.Get(); + private bool subscribed = false; + private bool hasPassed = false; - ImGui.Text("Log out now..."); + /// + public string Name => "Test Log-In"; - if (!this.subscribed) + /// + public SelfTestStepResult RunStep() { - clientState.Logout += this.ClientStateOnOnLogout; - this.subscribed = true; + var clientState = Service.Get(); + + ImGui.Text("Log out now..."); + + if (!this.subscribed) + { + clientState.Logout += this.ClientStateOnOnLogout; + this.subscribed = true; + } + + if (this.hasPassed) + { + clientState.Logout -= this.ClientStateOnOnLogout; + this.subscribed = false; + return SelfTestStepResult.Pass; + } + + return SelfTestStepResult.Waiting; } - if (this.hasPassed) + /// + public void CleanUp() { - clientState.Logout -= this.ClientStateOnOnLogout; - this.subscribed = false; - return SelfTestStepResult.Pass; + var clientState = Service.Get(); + + if (this.subscribed) + { + clientState.Logout -= this.ClientStateOnOnLogout; + this.subscribed = false; + } } - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - var clientState = Service.Get(); - - if (this.subscribed) + private void ClientStateOnOnLogout(object sender, EventArgs e) { - clientState.Logout -= this.ClientStateOnOnLogout; - this.subscribed = false; + this.hasPassed = true; } } - - private void ClientStateOnOnLogout(object sender, EventArgs e) - { - this.hasPassed = true; - } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs index a07b21e54..e1ab91978 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/LuminaAgingStep.cs @@ -5,37 +5,38 @@ using Dalamud.Data; using Dalamud.Utility; using Lumina.Excel; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for Lumina. -/// -/// ExcelRow to run test on. -internal class LuminaAgingStep : IAgingStep - where T : ExcelRow +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private int step = 0; - private List rows; - - /// - public string Name => "Test Lumina"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for Lumina. + /// + /// ExcelRow to run test on. + internal class LuminaAgingStep : IAgingStep + where T : ExcelRow { - var dataManager = Service.Get(); + private int step = 0; + private List rows; - this.rows ??= dataManager.GetExcelSheet().ToList(); + /// + public string Name => "Test Lumina"; - Util.ShowObject(this.rows[this.step]); + /// + public SelfTestStepResult RunStep() + { + var dataManager = Service.Get(); - this.step++; - return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; - } + this.rows ??= dataManager.GetExcelSheet().ToList(); - /// - public void CleanUp() - { - // ignored + Util.ShowObject(this.rows[this.step]); + + this.step++; + return this.step >= this.rows.Count ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + // ignored + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs index eea015ad8..6e8edbe29 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/PartyFinderAgingStep.cs @@ -2,56 +2,57 @@ using Dalamud.Game.Gui.PartyFinder; using Dalamud.Game.Gui.PartyFinder.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for Party Finder events. -/// -internal class PartyFinderAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private bool subscribed = false; - private bool hasPassed = false; - - /// - public string Name => "Test Party Finder"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for Party Finder events. + /// + internal class PartyFinderAgingStep : IAgingStep { - var partyFinderGui = Service.Get(); + private bool subscribed = false; + private bool hasPassed = false; - if (!this.subscribed) + /// + public string Name => "Test Party Finder"; + + /// + public SelfTestStepResult RunStep() { - partyFinderGui.ReceiveListing += this.PartyFinderOnReceiveListing; - this.subscribed = true; + var partyFinderGui = Service.Get(); + + if (!this.subscribed) + { + partyFinderGui.ReceiveListing += this.PartyFinderOnReceiveListing; + this.subscribed = true; + } + + if (this.hasPassed) + { + partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing; + this.subscribed = false; + return SelfTestStepResult.Pass; + } + + ImGui.Text("Open Party Finder"); + + return SelfTestStepResult.Waiting; } - if (this.hasPassed) + /// + public void CleanUp() { - partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing; - this.subscribed = false; - return SelfTestStepResult.Pass; + var partyFinderGui = Service.Get(); + + if (this.subscribed) + { + partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing; + this.subscribed = false; + } } - ImGui.Text("Open Party Finder"); - - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - var partyFinderGui = Service.Get(); - - if (this.subscribed) + private void PartyFinderOnReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args) { - partyFinderGui.ReceiveListing -= this.PartyFinderOnReceiveListing; - this.subscribed = false; + this.hasPassed = true; } } - - private void PartyFinderOnReceiveListing(PartyFinderListing listing, PartyFinderListingEventArgs args) - { - this.hasPassed = true; - } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs index 97cf9d604..6866cdd52 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/TargetAgingStep.cs @@ -3,73 +3,74 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.Types; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for targets. -/// -internal class TargetAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private int step = 0; - - /// - public string Name => "Test Target"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for targets. + /// + internal class TargetAgingStep : IAgingStep { - var targetManager = Service.Get(); + private int step = 0; - switch (this.step) + /// + public string Name => "Test Target"; + + /// + public SelfTestStepResult RunStep() { - case 0: - targetManager.ClearTarget(); - targetManager.ClearFocusTarget(); + var targetManager = Service.Get(); - this.step++; + switch (this.step) + { + case 0: + targetManager.ClearTarget(); + targetManager.ClearFocusTarget(); - break; - - case 1: - ImGui.Text("Target a player..."); - - var cTarget = targetManager.Target; - if (cTarget is PlayerCharacter) - { this.step++; - } - break; + break; - case 2: - ImGui.Text("Focus-Target a Battle NPC..."); + case 1: + ImGui.Text("Target a player..."); - var fTarget = targetManager.FocusTarget; - if (fTarget is BattleNpc) - { - this.step++; - } + var cTarget = targetManager.Target; + if (cTarget is PlayerCharacter) + { + this.step++; + } - break; + break; - case 3: - ImGui.Text("Soft-Target an EventObj..."); + case 2: + ImGui.Text("Focus-Target a Battle NPC..."); - var sTarget = targetManager.FocusTarget; - if (sTarget is EventObj) - { - return SelfTestStepResult.Pass; - } + var fTarget = targetManager.FocusTarget; + if (fTarget is BattleNpc) + { + this.step++; + } - break; + break; + + case 3: + ImGui.Text("Soft-Target an EventObj..."); + + var sTarget = targetManager.FocusTarget; + if (sTarget is EventObj) + { + return SelfTestStepResult.Pass; + } + + break; + } + + return SelfTestStepResult.Waiting; } - return SelfTestStepResult.Waiting; - } - - /// - public void CleanUp() - { - // ignored + /// + public void CleanUp() + { + // ignored + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs index f02eafc99..239f8650e 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/ToastAgingStep.cs @@ -1,29 +1,30 @@ using Dalamud.Game.Gui.Toast; -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test setup for toasts. -/// -internal class ToastAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - /// - public string Name => "Test Toasts"; - - /// - public SelfTestStepResult RunStep() + /// + /// Test setup for toasts. + /// + internal class ToastAgingStep : IAgingStep { - var toastGui = Service.Get(); + /// + public string Name => "Test Toasts"; - toastGui.ShowNormal("Normal Toast"); - toastGui.ShowError("Error Toast"); + /// + public SelfTestStepResult RunStep() + { + var toastGui = Service.Get(); - return SelfTestStepResult.Pass; - } + toastGui.ShowNormal("Normal Toast"); + toastGui.ShowError("Error Toast"); - /// - public void CleanUp() - { - // ignored + return SelfTestStepResult.Pass; + } + + /// + public void CleanUp() + { + // ignored + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs index 54aeee145..57e7e99ac 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/AgingSteps/WaitFramesAgingStep.cs @@ -1,37 +1,38 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps; - -/// -/// Test that waits N frames. -/// -internal class WaitFramesAgingStep : IAgingStep +namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps { - private readonly int frames; - private int cFrames; - /// - /// Initializes a new instance of the class. + /// Test that waits N frames. /// - /// Amount of frames to wait. - public WaitFramesAgingStep(int frames) + internal class WaitFramesAgingStep : IAgingStep { - this.frames = frames; - this.cFrames = frames; - } + private readonly int frames; + private int cFrames; - /// - public string Name => $"Wait {this.cFrames} frames"; + /// + /// Initializes a new instance of the class. + /// + /// Amount of frames to wait. + public WaitFramesAgingStep(int frames) + { + this.frames = frames; + this.cFrames = frames; + } - /// - public SelfTestStepResult RunStep() - { - this.cFrames--; + /// + public string Name => $"Wait {this.cFrames} frames"; - return this.cFrames <= 0 ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; - } + /// + public SelfTestStepResult RunStep() + { + this.cFrames--; - /// - public void CleanUp() - { - this.cFrames = this.frames; + return this.cFrames <= 0 ? SelfTestStepResult.Pass : SelfTestStepResult.Waiting; + } + + /// + public void CleanUp() + { + this.cFrames = this.frames; + } } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestStepResult.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestStepResult.cs index 36842c934..70dddcab9 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestStepResult.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestStepResult.cs @@ -1,27 +1,28 @@ -namespace Dalamud.Interface.Internal.Windows.SelfTest; - -/// -/// Enum declaring result states of tests. -/// -internal enum SelfTestStepResult +namespace Dalamud.Interface.Internal.Windows.SelfTest { /// - /// Test was not ran. + /// Enum declaring result states of tests. /// - NotRan, + internal enum SelfTestStepResult + { + /// + /// Test was not ran. + /// + NotRan, - /// - /// Test is waiting for completion. - /// - Waiting, + /// + /// Test is waiting for completion. + /// + Waiting, - /// - /// Test has failed. - /// - Fail, + /// + /// Test has failed. + /// + Fail, - /// - /// Test has passed. - /// - Pass, + /// + /// Test has passed. + /// + Pass, + } } diff --git a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs index 47afef69f..d97fad7bc 100644 --- a/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SelfTest/SelfTestWindow.cs @@ -11,245 +11,246 @@ using Dalamud.Logging.Internal; using ImGuiNET; using Lumina.Excel.GeneratedSheets; -namespace Dalamud.Interface.Internal.Windows.SelfTest; - -/// -/// Window for the Self-Test logic. -/// -internal class SelfTestWindow : Window +namespace Dalamud.Interface.Internal.Windows.SelfTest { - private static readonly ModuleLog Log = new("AGING"); - - private readonly List steps = - new() - { - new LoginEventAgingStep(), - new WaitFramesAgingStep(1000), - new EnterTerritoryAgingStep(148, "Central Shroud"), - new ActorTableAgingStep(), - new FateTableAgingStep(), - new ConditionAgingStep(), - new ToastAgingStep(), - new TargetAgingStep(), - new KeyStateAgingStep(), - new GamepadStateAgingStep(), - new ChatAgingStep(), - new HoverAgingStep(), - new LuminaAgingStep(), - new PartyFinderAgingStep(), - new HandledExceptionAgingStep(), - new LogoutEventAgingStep(), - }; - - private readonly List<(SelfTestStepResult Result, TimeSpan? Duration)> stepResults = new(); - - private bool selfTestRunning = false; - private int currentStep = 0; - - private DateTimeOffset lastTestStart; - /// - /// Initializes a new instance of the class. + /// Window for the Self-Test logic. /// - public SelfTestWindow() - : base("Dalamud Self-Test", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse) + internal class SelfTestWindow : Window { - this.Size = new Vector2(800, 800); - this.SizeCondition = ImGuiCond.FirstUseEver; + private static readonly ModuleLog Log = new("AGING"); - this.RespectCloseHotkey = false; - } - - /// - public override void Draw() - { - if (this.selfTestRunning) - { - if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop)) + private readonly List steps = + new() { - this.StopTests(); + new LoginEventAgingStep(), + new WaitFramesAgingStep(1000), + new EnterTerritoryAgingStep(148, "Central Shroud"), + new ActorTableAgingStep(), + new FateTableAgingStep(), + new ConditionAgingStep(), + new ToastAgingStep(), + new TargetAgingStep(), + new KeyStateAgingStep(), + new GamepadStateAgingStep(), + new ChatAgingStep(), + new HoverAgingStep(), + new LuminaAgingStep(), + new PartyFinderAgingStep(), + new HandledExceptionAgingStep(), + new LogoutEventAgingStep(), + }; + + private readonly List<(SelfTestStepResult Result, TimeSpan? Duration)> stepResults = new(); + + private bool selfTestRunning = false; + private int currentStep = 0; + + private DateTimeOffset lastTestStart; + + /// + /// Initializes a new instance of the class. + /// + public SelfTestWindow() + : base("Dalamud Self-Test", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse) + { + this.Size = new Vector2(800, 800); + this.SizeCondition = ImGuiCond.FirstUseEver; + + this.RespectCloseHotkey = false; + } + + /// + public override void Draw() + { + if (this.selfTestRunning) + { + if (ImGuiComponents.IconButton(FontAwesomeIcon.Stop)) + { + this.StopTests(); + } + + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.StepForward)) + { + this.stepResults.Add((SelfTestStepResult.NotRan, null)); + this.currentStep++; + this.lastTestStart = DateTimeOffset.Now; + + if (this.currentStep >= this.steps.Count) + { + this.StopTests(); + } + } + } + else + { + if (ImGuiComponents.IconButton(FontAwesomeIcon.Play)) + { + this.selfTestRunning = true; + this.currentStep = 0; + this.stepResults.Clear(); + this.lastTestStart = DateTimeOffset.Now; + } } ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.StepForward)) - { - this.stepResults.Add((SelfTestStepResult.NotRan, null)); - this.currentStep++; - this.lastTestStart = DateTimeOffset.Now; + ImGui.TextUnformatted($"Step: {this.currentStep} / {this.steps.Count}"); - if (this.currentStep >= this.steps.Count) + ImGuiHelpers.ScaledDummy(10); + + this.DrawResultTable(); + + ImGuiHelpers.ScaledDummy(10); + + if (this.currentStep >= this.steps.Count) + { + if (this.selfTestRunning) { this.StopTests(); } + + if (this.stepResults.Any(x => x.Result == SelfTestStepResult.Fail)) + { + ImGui.TextColored(ImGuiColors.DalamudRed, "One or more checks failed!"); + } + else + { + ImGui.TextColored(ImGuiColors.HealerGreen, "All checks passed!"); + } + + return; } - } - else - { - if (ImGuiComponents.IconButton(FontAwesomeIcon.Play)) + + if (!this.selfTestRunning) { - this.selfTestRunning = true; - this.currentStep = 0; - this.stepResults.Clear(); + return; + } + + ImGui.Separator(); + + var step = this.steps[this.currentStep]; + ImGui.TextUnformatted($"Current: {step.Name}"); + + ImGuiHelpers.ScaledDummy(10); + + SelfTestStepResult result; + try + { + result = step.RunStep(); + } + catch (Exception ex) + { + Log.Error(ex, $"Step failed: {step.Name}"); + result = SelfTestStepResult.Fail; + } + + ImGui.Separator(); + + if (result != SelfTestStepResult.Waiting) + { + var duration = DateTimeOffset.Now - this.lastTestStart; + this.currentStep++; + this.stepResults.Add((result, duration)); + this.lastTestStart = DateTimeOffset.Now; } } - ImGui.SameLine(); - - ImGui.TextUnformatted($"Step: {this.currentStep} / {this.steps.Count}"); - - ImGuiHelpers.ScaledDummy(10); - - this.DrawResultTable(); - - ImGuiHelpers.ScaledDummy(10); - - if (this.currentStep >= this.steps.Count) + private void DrawResultTable() { - if (this.selfTestRunning) + if (ImGui.BeginTable("agingResultTable", 4, ImGuiTableFlags.Borders)) { - this.StopTests(); - } + ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f); + ImGui.TableSetupColumn("Name"); + ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f); + ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f); - if (this.stepResults.Any(x => x.Result == SelfTestStepResult.Fail)) - { - ImGui.TextColored(ImGuiColors.DalamudRed, "One or more checks failed!"); - } - else - { - ImGui.TextColored(ImGuiColors.HealerGreen, "All checks passed!"); - } + ImGui.TableHeadersRow(); - return; - } - - if (!this.selfTestRunning) - { - return; - } - - ImGui.Separator(); - - var step = this.steps[this.currentStep]; - ImGui.TextUnformatted($"Current: {step.Name}"); - - ImGuiHelpers.ScaledDummy(10); - - SelfTestStepResult result; - try - { - result = step.RunStep(); - } - catch (Exception ex) - { - Log.Error(ex, $"Step failed: {step.Name}"); - result = SelfTestStepResult.Fail; - } - - ImGui.Separator(); - - if (result != SelfTestStepResult.Waiting) - { - var duration = DateTimeOffset.Now - this.lastTestStart; - this.currentStep++; - this.stepResults.Add((result, duration)); - - this.lastTestStart = DateTimeOffset.Now; - } - } - - private void DrawResultTable() - { - if (ImGui.BeginTable("agingResultTable", 4, ImGuiTableFlags.Borders)) - { - ImGui.TableSetupColumn("###index", ImGuiTableColumnFlags.WidthFixed, 12f); - ImGui.TableSetupColumn("Name"); - ImGui.TableSetupColumn("Result", ImGuiTableColumnFlags.WidthFixed, 40f); - ImGui.TableSetupColumn("Duration", ImGuiTableColumnFlags.WidthFixed, 90f); - - ImGui.TableHeadersRow(); - - for (var i = 0; i < this.steps.Count; i++) - { - var step = this.steps[i]; - ImGui.TableNextRow(); - - ImGui.TableSetColumnIndex(0); - ImGui.Text(i.ToString()); - - ImGui.TableSetColumnIndex(1); - ImGui.Text(step.Name); - - ImGui.TableSetColumnIndex(2); - ImGui.PushFont(Interface.Internal.InterfaceManager.MonoFont); - if (this.stepResults.Count > i) + for (var i = 0; i < this.steps.Count; i++) { - var result = this.stepResults[i]; + var step = this.steps[i]; + ImGui.TableNextRow(); - switch (result.Result) + ImGui.TableSetColumnIndex(0); + ImGui.Text(i.ToString()); + + ImGui.TableSetColumnIndex(1); + ImGui.Text(step.Name); + + ImGui.TableSetColumnIndex(2); + ImGui.PushFont(Interface.Internal.InterfaceManager.MonoFont); + if (this.stepResults.Count > i) { - case SelfTestStepResult.Pass: - ImGui.TextColored(ImGuiColors.HealerGreen, "PASS"); - break; - case SelfTestStepResult.Fail: - ImGui.TextColored(ImGuiColors.DalamudRed, "FAIL"); - break; - default: - ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); - break; - } - } - else - { - if (this.selfTestRunning && this.currentStep == i) - { - ImGui.TextColored(ImGuiColors.DalamudGrey, "WAIT"); + var result = this.stepResults[i]; + + switch (result.Result) + { + case SelfTestStepResult.Pass: + ImGui.TextColored(ImGuiColors.HealerGreen, "PASS"); + break; + case SelfTestStepResult.Fail: + ImGui.TextColored(ImGuiColors.DalamudRed, "FAIL"); + break; + default: + ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); + break; + } } else { - ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); + if (this.selfTestRunning && this.currentStep == i) + { + ImGui.TextColored(ImGuiColors.DalamudGrey, "WAIT"); + } + else + { + ImGui.TextColored(ImGuiColors.DalamudGrey, "NR"); + } } - } - ImGui.PopFont(); + ImGui.PopFont(); - ImGui.TableSetColumnIndex(3); - if (this.stepResults.Count > i) - { - var (_, duration) = this.stepResults[i]; - - if (duration.HasValue) + ImGui.TableSetColumnIndex(3); + if (this.stepResults.Count > i) { - ImGui.TextUnformatted(duration.Value.ToString("g")); + var (_, duration) = this.stepResults[i]; + + if (duration.HasValue) + { + ImGui.TextUnformatted(duration.Value.ToString("g")); + } } - } - else - { - if (this.selfTestRunning && this.currentStep == i) + else { - ImGui.TextUnformatted((DateTimeOffset.Now - this.lastTestStart).ToString("g")); + if (this.selfTestRunning && this.currentStep == i) + { + ImGui.TextUnformatted((DateTimeOffset.Now - this.lastTestStart).ToString("g")); + } } } + + ImGui.EndTable(); } - - ImGui.EndTable(); } - } - private void StopTests() - { - this.selfTestRunning = false; - - foreach (var agingStep in this.steps) + private void StopTests() { - try + this.selfTestRunning = false; + + foreach (var agingStep in this.steps) { - agingStep.CleanUp(); - } - catch (Exception ex) - { - Log.Error(ex, $"Could not clean up AgingStep: {agingStep.Name}"); + try + { + agingStep.CleanUp(); + } + catch (Exception ex) + { + Log.Error(ex, $"Could not clean up AgingStep: {agingStep.Name}"); + } } } } diff --git a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs index b4fe6ba1a..e33ac4e4c 100644 --- a/Dalamud/Interface/Internal/Windows/SettingsWindow.cs +++ b/Dalamud/Interface/Internal/Windows/SettingsWindow.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Threading.Tasks; using CheapLoc; +using Dalamud.Configuration; using Dalamud.Configuration.Internal; using Dalamud.Game.Text; using Dalamud.Interface.Colors; @@ -14,716 +15,717 @@ using Dalamud.Interface.Windowing; using Dalamud.Plugin.Internal; using ImGuiNET; -namespace Dalamud.Interface.Internal.Windows; - -/// -/// The window that allows for general configuration of Dalamud itself. -/// -internal class SettingsWindow : Window +namespace Dalamud.Interface.Internal.Windows { - private const float MinScale = 0.3f; - private const float MaxScale = 2.0f; - - private readonly string[] languages; - private readonly string[] locLanguages; - private int langIndex; - - private XivChatType dalamudMessagesChatType; - - private bool doCfTaskBarFlash; - private bool doCfChatMessage; - - private float globalUiScale; - private bool doToggleUiHide; - private bool doToggleUiHideDuringCutscenes; - private bool doToggleUiHideDuringGpose; - private bool doDocking; - private bool doViewport; - private bool doGamepad; - private bool doFocus; - - private List thirdRepoList; - private bool thirdRepoListChanged; - private string thirdRepoTempUrl = string.Empty; - private string thirdRepoAddError = string.Empty; - - private List devPluginLocations; - private bool devPluginLocationsChanged; - private string devPluginTempLocation = string.Empty; - private string devPluginLocationAddError = string.Empty; - - private bool printPluginsWelcomeMsg; - private bool autoUpdatePlugins; - private bool doButtonsSystemMenu; - private bool disableRmtFiltering; - - #region Experimental - - private bool doPluginTest; - - #endregion - /// - /// Initializes a new instance of the class. + /// The window that allows for general configuration of Dalamud itself. /// - public SettingsWindow() - : base(Loc.Localize("DalamudSettingsHeader", "Dalamud Settings") + "###XlSettings2", ImGuiWindowFlags.NoCollapse) + internal class SettingsWindow : Window { - var configuration = Service.Get(); + private const float MinScale = 0.3f; + private const float MaxScale = 2.0f; - this.Size = new Vector2(740, 550); - this.SizeCondition = ImGuiCond.FirstUseEver; + private readonly string[] languages; + private readonly string[] locLanguages; + private int langIndex; - this.dalamudMessagesChatType = configuration.GeneralChatType; + private XivChatType dalamudMessagesChatType; - this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash; - this.doCfChatMessage = configuration.DutyFinderChatMessage; + private bool doCfTaskBarFlash; + private bool doCfChatMessage; - this.globalUiScale = configuration.GlobalUiScale; - this.doToggleUiHide = configuration.ToggleUiHide; - this.doToggleUiHideDuringCutscenes = configuration.ToggleUiHideDuringCutscenes; - this.doToggleUiHideDuringGpose = configuration.ToggleUiHideDuringGpose; + private float globalUiScale; + private bool doToggleUiHide; + private bool doToggleUiHideDuringCutscenes; + private bool doToggleUiHideDuringGpose; + private bool doDocking; + private bool doViewport; + private bool doGamepad; + private bool doFocus; - this.doDocking = configuration.IsDocking; - this.doViewport = !configuration.IsDisableViewport; - this.doGamepad = configuration.IsGamepadNavigationEnabled; - this.doFocus = configuration.IsFocusManagementEnabled; + private List thirdRepoList; + private bool thirdRepoListChanged; + private string thirdRepoTempUrl = string.Empty; + private string thirdRepoAddError = string.Empty; - this.doPluginTest = configuration.DoPluginTest; - this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); - this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); + private List devPluginLocations; + private bool devPluginLocationsChanged; + private string devPluginTempLocation = string.Empty; + private string devPluginLocationAddError = string.Empty; - this.printPluginsWelcomeMsg = configuration.PrintPluginsWelcomeMsg; - this.autoUpdatePlugins = configuration.AutoUpdatePlugins; - this.doButtonsSystemMenu = configuration.DoButtonsSystemMenu; - this.disableRmtFiltering = configuration.DisableRmtFiltering; + private bool printPluginsWelcomeMsg; + private bool autoUpdatePlugins; + private bool doButtonsSystemMenu; + private bool disableRmtFiltering; - this.languages = Localization.ApplicableLangCodes.Prepend("en").ToArray(); - try - { - if (string.IsNullOrEmpty(configuration.LanguageOverride)) - { - var currentUiLang = CultureInfo.CurrentUICulture; + #region Experimental - if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x)) - this.langIndex = Array.IndexOf(this.languages, currentUiLang.TwoLetterISOLanguageName); - else - this.langIndex = 0; - } - else - { - this.langIndex = Array.IndexOf(this.languages, configuration.LanguageOverride); - } - } - catch (Exception) - { - this.langIndex = 0; - } - - try - { - var locLanguagesList = new List(); - string locLanguage; - foreach (var language in this.languages) - { - if (language != "ko") - { - locLanguage = CultureInfo.GetCultureInfo(language).NativeName; - locLanguagesList.Add(char.ToUpper(locLanguage[0]) + locLanguage[1..]); - } - else - { - locLanguagesList.Add("Korean"); - } - } - - this.locLanguages = locLanguagesList.ToArray(); - } - catch (Exception) - { - this.locLanguages = this.languages; // Languages not localized, only codes. - } - } - - /// - public override void OnOpen() - { - this.thirdRepoListChanged = false; - this.devPluginLocationsChanged = false; - } - - /// - public override void OnClose() - { - var configuration = Service.Get(); - - ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; - this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); - this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); - } - - /// - public override void Draw() - { - var windowSize = ImGui.GetWindowSize(); - ImGui.BeginChild("scrolling", new Vector2(windowSize.X - 5 - (5 * ImGuiHelpers.GlobalScale), windowSize.Y - 35 - (35 * ImGuiHelpers.GlobalScale)), false, ImGuiWindowFlags.HorizontalScrollbar); - - if (ImGui.BeginTabBar("SetTabBar")) - { - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsGeneral", "General"))) - { - this.DrawGeneralTab(); - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsVisual", "Look & Feel"))) - { - this.DrawLookAndFeelTab(); - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsExperimental", "Experimental"))) - { - this.DrawExperimentalTab(); - ImGui.EndTabItem(); - } - - ImGui.EndTabBar(); - } - - ImGui.EndChild(); - - this.DrawSaveCloseButtons(); - } - - /// - /// Transform byte count to human readable format. - /// - /// Number of bytes. - /// Human readable version. - private static string FormatBytes(long bytes) - { - string[] suffix = { "B", "KB", "MB", "GB", "TB" }; - int i; - double dblSByte = bytes; - for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024) - { - dblSByte = bytes / 1024.0; - } - - return $"{dblSByte:0.##} {suffix[i]}"; - } - - private void DrawGeneralTab() - { - ImGui.Text(Loc.Localize("DalamudSettingsLanguage", "Language")); - ImGui.Combo("##XlLangCombo", ref this.langIndex, this.locLanguages, this.locLanguages.Length); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsLanguageHint", "Select the language Dalamud will be displayed in.")); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Text(Loc.Localize("DalamudSettingsChannel", "General Chat Channel")); - if (ImGui.BeginCombo("##XlChatTypeCombo", this.dalamudMessagesChatType.ToString())) - { - foreach (var type in Enum.GetValues(typeof(XivChatType)).Cast()) - { - if (ImGui.Selectable(type.ToString(), type == this.dalamudMessagesChatType)) - { - this.dalamudMessagesChatType = type; - } - } - - ImGui.EndCombo(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsChannelHint", "Select the chat channel that is to be used for general Dalamud messages.")); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsFlash", "Flash FFXIV window on duty pop"), ref this.doCfTaskBarFlash); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFlashHint", "Flash the FFXIV window in your task bar when a duty is ready.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsDutyFinderMessage", "Chatlog message on duty pop"), ref this.doCfChatMessage); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDutyFinderMessageHint", "Send a message in FFXIV chat when a duty is ready.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsg", "Display loaded plugins in the welcome message"), ref this.printPluginsWelcomeMsg); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsgHint", "Display loaded plugins in FFXIV chat when logging in with a character.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsAutoUpdatePlugins", "Auto-update plugins"), ref this.autoUpdatePlugins); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdatePluginsMsgHint", "Automatically update plugins when logging in with a character.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsSystemMenu", "Dalamud buttons in system menu"), ref this.doButtonsSystemMenu); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsSystemMenuMsgHint", "Add buttons for Dalamud plugins and settings to the system menu.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingsDisableRmtFiltering", "Disable RMT Filtering"), ref this.disableRmtFiltering); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDisableRmtFilteringMsgHint", "Disable dalamud's built-in RMT ad filtering.")); - } - - private void DrawLookAndFeelTab() - { - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); - ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global UI Scale")); - ImGui.SameLine(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3); - if (ImGui.Button("Reset")) - { - this.globalUiScale = 1.0f; - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - } - - if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref this.globalUiScale, 0.005f, MinScale, MaxScale, "%.2f")) - ImGui.GetIO().FontGlobalScale = this.globalUiScale; - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale all XIVLauncher UI elements - useful for 4K displays.")); - - ImGuiHelpers.ScaledDummy(10, 16); - - if (ImGui.Button(Loc.Localize("DalamudSettingsOpenStyleEditor", "Open Style Editor"))) - { - Service.Get().OpenStyleEditor(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsStyleEditorHint", "Modify the look & feel of Dalamud windows.")); - - ImGuiHelpers.ScaledDummy(10, 16); - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideOptOutNote", "Plugins may independently opt out of the settings below.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHide", "Hide plugin UI when the game UI is toggled off"), ref this.doToggleUiHide); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideHint", "Hide any open windows by plugins when toggling the game overlay.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringCutscenes", "Hide plugin UI during cutscenes"), ref this.doToggleUiHideDuringCutscenes); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringCutscenesHint", "Hide any open windows by plugins during cutscenes.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringGpose", "Hide plugin UI while gpose is active"), ref this.doToggleUiHideDuringGpose); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringGposeHint", "Hide any open windows by plugins while gpose is active.")); - - ImGuiHelpers.ScaledDummy(10, 16); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleFocusManagement", "Use escape to close Dalamud windows"), ref this.doFocus); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleFocusManagementHint", "This will cause Dalamud windows to behave like in-game windows when pressing escape.\nThey will close one after another until all are closed. May not work for all plugins.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleViewports", "Enable multi-monitor windows"), ref this.doViewport); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleViewportsHint", "This will allow you move plugin windows onto other monitors.\nWill only work in Borderless Window or Windowed mode.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleDocking", "Enable window docking"), ref this.doDocking); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows.")); - - ImGui.Checkbox(Loc.Localize("DalamudSettingToggleGamepadNavigation", "Control plugins via gamepad"), ref this.doGamepad); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and plugin navigation via L1+L3.\nToggle the PluginInstaller window via R3 if ImGui navigation is enabled.")); - } - - private void DrawExperimentalTab() - { - var configuration = Service.Get(); - var pluginManager = Service.Get(); - - #region Plugin testing - - ImGui.Checkbox(Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"), ref this.doPluginTest); - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPluginTestHint", "Receive testing prereleases for plugins.")); - ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingsPluginTestWarning", "Testing plugins may not have been vetted before being published. Please only enable this if you are aware of the risks.")); + private bool doPluginTest; #endregion - ImGuiHelpers.ScaledDummy(12); - - #region Hidden plugins - - if (ImGui.Button(Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"))) + /// + /// Initializes a new instance of the class. + /// + public SettingsWindow() + : base(Loc.Localize("DalamudSettingsHeader", "Dalamud Settings") + "###XlSettings2", ImGuiWindowFlags.NoCollapse) { - configuration.HiddenPluginInternalName.Clear(); - pluginManager.RefilterPluginMasters(); + var configuration = Service.Get(); + + this.Size = new Vector2(740, 550); + this.SizeCondition = ImGuiCond.FirstUseEver; + + this.dalamudMessagesChatType = configuration.GeneralChatType; + + this.doCfTaskBarFlash = configuration.DutyFinderTaskbarFlash; + this.doCfChatMessage = configuration.DutyFinderChatMessage; + + this.globalUiScale = configuration.GlobalUiScale; + this.doToggleUiHide = configuration.ToggleUiHide; + this.doToggleUiHideDuringCutscenes = configuration.ToggleUiHideDuringCutscenes; + this.doToggleUiHideDuringGpose = configuration.ToggleUiHideDuringGpose; + + this.doDocking = configuration.IsDocking; + this.doViewport = !configuration.IsDisableViewport; + this.doGamepad = configuration.IsGamepadNavigationEnabled; + this.doFocus = configuration.IsFocusManagementEnabled; + + this.doPluginTest = configuration.DoPluginTest; + this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); + this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); + + this.printPluginsWelcomeMsg = configuration.PrintPluginsWelcomeMsg; + this.autoUpdatePlugins = configuration.AutoUpdatePlugins; + this.doButtonsSystemMenu = configuration.DoButtonsSystemMenu; + this.disableRmtFiltering = configuration.DisableRmtFiltering; + + this.languages = Localization.ApplicableLangCodes.Prepend("en").ToArray(); + try + { + if (string.IsNullOrEmpty(configuration.LanguageOverride)) + { + var currentUiLang = CultureInfo.CurrentUICulture; + + if (Localization.ApplicableLangCodes.Any(x => currentUiLang.TwoLetterISOLanguageName == x)) + this.langIndex = Array.IndexOf(this.languages, currentUiLang.TwoLetterISOLanguageName); + else + this.langIndex = 0; + } + else + { + this.langIndex = Array.IndexOf(this.languages, configuration.LanguageOverride); + } + } + catch (Exception) + { + this.langIndex = 0; + } + + try + { + var locLanguagesList = new List(); + string locLanguage; + foreach (var language in this.languages) + { + if (language != "ko") + { + locLanguage = CultureInfo.GetCultureInfo(language).NativeName; + locLanguagesList.Add(char.ToUpper(locLanguage[0]) + locLanguage[1..]); + } + else + { + locLanguagesList.Add("Korean"); + } + } + + this.locLanguages = locLanguagesList.ToArray(); + } + catch (Exception) + { + this.locLanguages = this.languages; // Languages not localized, only codes. + } } - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsClearHiddenHint", "Restore plugins you have previously hidden from the plugin installer.")); - - #endregion - - ImGuiHelpers.ScaledDummy(12); - - this.DrawCustomReposSection(); - - ImGuiHelpers.ScaledDummy(12); - - this.DrawDevPluginLocationsSection(); - - ImGuiHelpers.ScaledDummy(12); - - ImGui.TextColored(ImGuiColors.DalamudGrey, "Total memory used by Dalamud & Plugins: " + FormatBytes(GC.GetTotalMemory(false))); - } - - private void DrawCustomReposSection() - { - ImGui.Text(Loc.Localize("DalamudSettingsCustomRepo", "Custom Plugin Repositories")); - if (this.thirdRepoListChanged) + /// + public override void OnOpen() { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); + this.thirdRepoListChanged = false; + this.devPluginLocationsChanged = false; + } + + /// + public override void OnClose() + { + var configuration = Service.Get(); + + ImGui.GetIO().FontGlobalScale = configuration.GlobalUiScale; + this.thirdRepoList = configuration.ThirdRepoList.Select(x => x.Clone()).ToList(); + this.devPluginLocations = configuration.DevPluginLoadLocations.Select(x => x.Clone()).ToList(); + } + + /// + public override void Draw() + { + var windowSize = ImGui.GetWindowSize(); + ImGui.BeginChild("scrolling", new Vector2(windowSize.X - 5 - (5 * ImGuiHelpers.GlobalScale), windowSize.Y - 35 - (35 * ImGuiHelpers.GlobalScale)), false, ImGuiWindowFlags.HorizontalScrollbar); + + if (ImGui.BeginTabBar("SetTabBar")) + { + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsGeneral", "General"))) + { + this.DrawGeneralTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsVisual", "Look & Feel"))) + { + this.DrawLookAndFeelTab(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(Loc.Localize("DalamudSettingsExperimental", "Experimental"))) + { + this.DrawExperimentalTab(); + ImGui.EndTabItem(); + } + + ImGui.EndTabBar(); + } + + ImGui.EndChild(); + + this.DrawSaveCloseButtons(); + } + + /// + /// Transform byte count to human readable format. + /// + /// Number of bytes. + /// Human readable version. + private static string FormatBytes(long bytes) + { + string[] suffix = { "B", "KB", "MB", "GB", "TB" }; + int i; + double dblSByte = bytes; + for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024) + { + dblSByte = bytes / 1024.0; + } + + return $"{dblSByte:0.##} {suffix[i]}"; + } + + private void DrawGeneralTab() + { + ImGui.Text(Loc.Localize("DalamudSettingsLanguage", "Language")); + ImGui.Combo("##XlLangCombo", ref this.langIndex, this.locLanguages, this.locLanguages.Length); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsLanguageHint", "Select the language Dalamud will be displayed in.")); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Text(Loc.Localize("DalamudSettingsChannel", "General Chat Channel")); + if (ImGui.BeginCombo("##XlChatTypeCombo", this.dalamudMessagesChatType.ToString())) + { + foreach (var type in Enum.GetValues(typeof(XivChatType)).Cast()) + { + if (ImGui.Selectable(type.ToString(), type == this.dalamudMessagesChatType)) + { + this.dalamudMessagesChatType = type; + } + } + + ImGui.EndCombo(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsChannelHint", "Select the chat channel that is to be used for general Dalamud messages.")); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsFlash", "Flash FFXIV window on duty pop"), ref this.doCfTaskBarFlash); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsFlashHint", "Flash the FFXIV window in your task bar when a duty is ready.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsDutyFinderMessage", "Chatlog message on duty pop"), ref this.doCfChatMessage); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDutyFinderMessageHint", "Send a message in FFXIV chat when a duty is ready.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsg", "Display loaded plugins in the welcome message"), ref this.printPluginsWelcomeMsg); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPrintPluginsWelcomeMsgHint", "Display loaded plugins in FFXIV chat when logging in with a character.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsAutoUpdatePlugins", "Auto-update plugins"), ref this.autoUpdatePlugins); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsAutoUpdatePluginsMsgHint", "Automatically update plugins when logging in with a character.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsSystemMenu", "Dalamud buttons in system menu"), ref this.doButtonsSystemMenu); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsSystemMenuMsgHint", "Add buttons for Dalamud plugins and settings to the system menu.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingsDisableRmtFiltering", "Disable RMT Filtering"), ref this.disableRmtFiltering); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDisableRmtFilteringMsgHint", "Disable dalamud's built-in RMT ad filtering.")); + } + + private void DrawLookAndFeelTab() + { + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + 3); + ImGui.Text(Loc.Localize("DalamudSettingsGlobalUiScale", "Global UI Scale")); ImGui.SameLine(); - ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)")); - ImGui.PopStyleColor(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 3); + if (ImGui.Button("Reset")) + { + this.globalUiScale = 1.0f; + ImGui.GetIO().FontGlobalScale = this.globalUiScale; + } + + if (ImGui.DragFloat("##DalamudSettingsGlobalUiScaleDrag", ref this.globalUiScale, 0.005f, MinScale, MaxScale, "%.2f")) + ImGui.GetIO().FontGlobalScale = this.globalUiScale; + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsGlobalUiScaleHint", "Scale all XIVLauncher UI elements - useful for 4K displays.")); + + ImGuiHelpers.ScaledDummy(10, 16); + + if (ImGui.Button(Loc.Localize("DalamudSettingsOpenStyleEditor", "Open Style Editor"))) + { + Service.Get().OpenStyleEditor(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsStyleEditorHint", "Modify the look & feel of Dalamud windows.")); + + ImGuiHelpers.ScaledDummy(10, 16); + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideOptOutNote", "Plugins may independently opt out of the settings below.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHide", "Hide plugin UI when the game UI is toggled off"), ref this.doToggleUiHide); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideHint", "Hide any open windows by plugins when toggling the game overlay.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringCutscenes", "Hide plugin UI during cutscenes"), ref this.doToggleUiHideDuringCutscenes); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringCutscenesHint", "Hide any open windows by plugins during cutscenes.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleUiHideDuringGpose", "Hide plugin UI while gpose is active"), ref this.doToggleUiHideDuringGpose); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleUiHideDuringGposeHint", "Hide any open windows by plugins while gpose is active.")); + + ImGuiHelpers.ScaledDummy(10, 16); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleFocusManagement", "Use escape to close Dalamud windows"), ref this.doFocus); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleFocusManagementHint", "This will cause Dalamud windows to behave like in-game windows when pressing escape.\nThey will close one after another until all are closed. May not work for all plugins.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleViewports", "Enable multi-monitor windows"), ref this.doViewport); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleViewportsHint", "This will allow you move plugin windows onto other monitors.\nWill only work in Borderless Window or Windowed mode.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleDocking", "Enable window docking"), ref this.doDocking); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleDockingHint", "This will allow you to fuse and tab plugin windows.")); + + ImGui.Checkbox(Loc.Localize("DalamudSettingToggleGamepadNavigation", "Control plugins via gamepad"), ref this.doGamepad); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingToggleGamepadNavigationHint", "This will allow you to toggle between game and plugin navigation via L1+L3.\nToggle the PluginInstaller window via R3 if ImGui navigation is enabled.")); } - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingCustomRepoHint", "Add custom plugin repositories.")); - ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning", "We cannot take any responsibility for third-party plugins and repositories.\nTake care when installing third-party plugins from untrusted sources.")); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Columns(4); - ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionWidth() - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale)); - - ImGui.Separator(); - - ImGui.Text("#"); - ImGui.NextColumn(); - ImGui.Text("URL"); - ImGui.NextColumn(); - ImGui.Text("Enabled"); - ImGui.NextColumn(); - ImGui.Text(string.Empty); - ImGui.NextColumn(); - - ImGui.Separator(); - - ImGui.Text("0"); - ImGui.NextColumn(); - ImGui.Text("XIVLauncher"); - ImGui.NextColumn(); - ImGui.NextColumn(); - ImGui.NextColumn(); - ImGui.Separator(); - - ThirdPartyRepoSettings repoToRemove = null; - - var repoNumber = 1; - foreach (var thirdRepoSetting in this.thirdRepoList) + private void DrawExperimentalTab() { - var isEnabled = thirdRepoSetting.IsEnabled; + var configuration = Service.Get(); + var pluginManager = Service.Get(); - ImGui.PushID($"thirdRepo_{thirdRepoSetting.Url}"); + #region Plugin testing + + ImGui.Checkbox(Loc.Localize("DalamudSettingsPluginTest", "Get plugin testing builds"), ref this.doPluginTest); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsPluginTestHint", "Receive testing prereleases for plugins.")); + ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingsPluginTestWarning", "Testing plugins may not have been vetted before being published. Please only enable this if you are aware of the risks.")); + + #endregion + + ImGuiHelpers.ScaledDummy(12); + + #region Hidden plugins + + if (ImGui.Button(Loc.Localize("DalamudSettingsClearHidden", "Clear hidden plugins"))) + { + configuration.HiddenPluginInternalName.Clear(); + pluginManager.RefilterPluginMasters(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsClearHiddenHint", "Restore plugins you have previously hidden from the plugin installer.")); + + #endregion + + ImGuiHelpers.ScaledDummy(12); + + this.DrawCustomReposSection(); + + ImGuiHelpers.ScaledDummy(12); + + this.DrawDevPluginLocationsSection(); + + ImGuiHelpers.ScaledDummy(12); + + ImGui.TextColored(ImGuiColors.DalamudGrey, "Total memory used by Dalamud & Plugins: " + FormatBytes(GC.GetTotalMemory(false))); + } + + private void DrawCustomReposSection() + { + ImGui.Text(Loc.Localize("DalamudSettingsCustomRepo", "Custom Plugin Repositories")); + if (this.thirdRepoListChanged) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); + ImGui.SameLine(); + ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)")); + ImGui.PopStyleColor(); + } + + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingCustomRepoHint", "Add custom plugin repositories.")); + ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("DalamudSettingCustomRepoWarning", "We cannot take any responsibility for third-party plugins and repositories.\nTake care when installing third-party plugins from untrusted sources.")); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Columns(4); + ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionWidth() - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale)); + + ImGui.Separator(); + + ImGui.Text("#"); + ImGui.NextColumn(); + ImGui.Text("URL"); + ImGui.NextColumn(); + ImGui.Text("Enabled"); + ImGui.NextColumn(); + ImGui.Text(string.Empty); + ImGui.NextColumn(); + + ImGui.Separator(); + + ImGui.Text("0"); + ImGui.NextColumn(); + ImGui.Text("XIVLauncher"); + ImGui.NextColumn(); + ImGui.NextColumn(); + ImGui.NextColumn(); + ImGui.Separator(); + + ThirdPartyRepoSettings repoToRemove = null; + + var repoNumber = 1; + foreach (var thirdRepoSetting in this.thirdRepoList) + { + var isEnabled = thirdRepoSetting.IsEnabled; + + ImGui.PushID($"thirdRepo_{thirdRepoSetting.Url}"); + + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2)); + ImGui.Text(repoNumber.ToString()); + ImGui.NextColumn(); + + ImGui.SetNextItemWidth(-1); + var url = thirdRepoSetting.Url; + if (ImGui.InputText($"##thirdRepoInput", ref url, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) + { + var contains = this.thirdRepoList.Select(repo => repo.Url).Contains(url); + if (thirdRepoSetting.Url == url) + { + // no change. + } + else if (contains && thirdRepoSetting.Url != url) + { + this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); + Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); + } + else + { + thirdRepoSetting.Url = url; + this.thirdRepoListChanged = url != thirdRepoSetting.Url; + } + } + + ImGui.NextColumn(); + + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale)); + ImGui.Checkbox("##thirdRepoCheck", ref isEnabled); + ImGui.NextColumn(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + { + repoToRemove = thirdRepoSetting; + } + + ImGui.PopID(); + + ImGui.NextColumn(); + ImGui.Separator(); + + thirdRepoSetting.IsEnabled = isEnabled; + + repoNumber++; + } + + if (repoToRemove != null) + { + this.thirdRepoList.Remove(repoToRemove); + this.thirdRepoListChanged = true; + } ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2)); ImGui.Text(repoNumber.ToString()); ImGui.NextColumn(); - ImGui.SetNextItemWidth(-1); - var url = thirdRepoSetting.Url; - if (ImGui.InputText($"##thirdRepoInput", ref url, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) + ImGui.InputText("##thirdRepoUrlInput", ref this.thirdRepoTempUrl, 300); + ImGui.NextColumn(); + // Enabled button + ImGui.NextColumn(); + if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { - var contains = this.thirdRepoList.Select(repo => repo.Url).Contains(url); - if (thirdRepoSetting.Url == url) - { - // no change. - } - else if (contains && thirdRepoSetting.Url != url) + this.thirdRepoTempUrl = this.thirdRepoTempUrl.TrimEnd(); + if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase))) { this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); } else { - thirdRepoSetting.Url = url; - this.thirdRepoListChanged = url != thirdRepoSetting.Url; + this.thirdRepoList.Add(new ThirdPartyRepoSettings + { + Url = this.thirdRepoTempUrl, + IsEnabled = true, + }); + this.thirdRepoListChanged = true; + this.thirdRepoTempUrl = string.Empty; } } - ImGui.NextColumn(); + ImGui.Columns(1); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale)); - ImGui.Checkbox("##thirdRepoCheck", ref isEnabled); - ImGui.NextColumn(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + if (!string.IsNullOrEmpty(this.thirdRepoAddError)) { - repoToRemove = thirdRepoSetting; + ImGui.TextColored(new Vector4(1, 0, 0, 1), this.thirdRepoAddError); + } + } + + private void DrawDevPluginLocationsSection() + { + ImGui.Text(Loc.Localize("DalamudSettingsDevPluginLocation", "Dev Plugin Locations")); + if (this.devPluginLocationsChanged) + { + ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); + ImGui.SameLine(); + ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)")); + ImGui.PopStyleColor(); } - ImGui.PopID(); + ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add additional dev plugin load locations.\nThese can be either the directory or DLL path.")); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.Columns(4); + ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionWidth() - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale)); + ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale)); - ImGui.NextColumn(); ImGui.Separator(); - thirdRepoSetting.IsEnabled = isEnabled; + ImGui.Text("#"); + ImGui.NextColumn(); + ImGui.Text("Path"); + ImGui.NextColumn(); + ImGui.Text("Enabled"); + ImGui.NextColumn(); + ImGui.Text(string.Empty); + ImGui.NextColumn(); - repoNumber++; - } + ImGui.Separator(); - if (repoToRemove != null) - { - this.thirdRepoList.Remove(repoToRemove); - this.thirdRepoListChanged = true; - } + DevPluginLocationSettings locationToRemove = null; - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(repoNumber.ToString()).X / 2)); - ImGui.Text(repoNumber.ToString()); - ImGui.NextColumn(); - ImGui.SetNextItemWidth(-1); - ImGui.InputText("##thirdRepoUrlInput", ref this.thirdRepoTempUrl, 300); - ImGui.NextColumn(); - // Enabled button - ImGui.NextColumn(); - if (!string.IsNullOrEmpty(this.thirdRepoTempUrl) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) - { - this.thirdRepoTempUrl = this.thirdRepoTempUrl.TrimEnd(); - if (this.thirdRepoList.Any(r => string.Equals(r.Url, this.thirdRepoTempUrl, StringComparison.InvariantCultureIgnoreCase))) + var locNumber = 1; + foreach (var devPluginLocationSetting in this.devPluginLocations) { - this.thirdRepoAddError = Loc.Localize("DalamudThirdRepoExists", "Repo already exists."); - Task.Delay(5000).ContinueWith(t => this.thirdRepoAddError = string.Empty); - } - else - { - this.thirdRepoList.Add(new ThirdPartyRepoSettings + var isEnabled = devPluginLocationSetting.IsEnabled; + + ImGui.PushID($"devPluginLocation_{devPluginLocationSetting.Path}"); + + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2)); + ImGui.Text(locNumber.ToString()); + ImGui.NextColumn(); + + ImGui.SetNextItemWidth(-1); + var path = devPluginLocationSetting.Path; + if (ImGui.InputText($"##devPluginLocationInput", ref path, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) { - Url = this.thirdRepoTempUrl, - IsEnabled = true, - }); - this.thirdRepoListChanged = true; - this.thirdRepoTempUrl = string.Empty; + var contains = this.devPluginLocations.Select(loc => loc.Path).Contains(path); + if (devPluginLocationSetting.Path == path) + { + // no change. + } + else if (contains && devPluginLocationSetting.Path != path) + { + this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); + Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); + } + else + { + devPluginLocationSetting.Path = path; + this.devPluginLocationsChanged = path != devPluginLocationSetting.Path; + } + } + + ImGui.NextColumn(); + + ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale)); + ImGui.Checkbox("##devPluginLocationCheck", ref isEnabled); + ImGui.NextColumn(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + { + locationToRemove = devPluginLocationSetting; + } + + ImGui.PopID(); + + ImGui.NextColumn(); + ImGui.Separator(); + + devPluginLocationSetting.IsEnabled = isEnabled; + + locNumber++; } - } - ImGui.Columns(1); - - if (!string.IsNullOrEmpty(this.thirdRepoAddError)) - { - ImGui.TextColored(new Vector4(1, 0, 0, 1), this.thirdRepoAddError); - } - } - - private void DrawDevPluginLocationsSection() - { - ImGui.Text(Loc.Localize("DalamudSettingsDevPluginLocation", "Dev Plugin Locations")); - if (this.devPluginLocationsChanged) - { - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); - ImGui.SameLine(); - ImGui.Text(Loc.Localize("DalamudSettingsChanged", "(Changed)")); - ImGui.PopStyleColor(); - } - - ImGui.TextColored(ImGuiColors.DalamudGrey, Loc.Localize("DalamudSettingsDevPluginLocationsHint", "Add additional dev plugin load locations.\nThese can be either the directory or DLL path.")); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.Columns(4); - ImGui.SetColumnWidth(0, 18 + (5 * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(1, ImGui.GetWindowContentRegionWidth() - (18 + 16 + 14) - ((5 + 45 + 26) * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(2, 16 + (45 * ImGuiHelpers.GlobalScale)); - ImGui.SetColumnWidth(3, 14 + (26 * ImGuiHelpers.GlobalScale)); - - ImGui.Separator(); - - ImGui.Text("#"); - ImGui.NextColumn(); - ImGui.Text("Path"); - ImGui.NextColumn(); - ImGui.Text("Enabled"); - ImGui.NextColumn(); - ImGui.Text(string.Empty); - ImGui.NextColumn(); - - ImGui.Separator(); - - DevPluginLocationSettings locationToRemove = null; - - var locNumber = 1; - foreach (var devPluginLocationSetting in this.devPluginLocations) - { - var isEnabled = devPluginLocationSetting.IsEnabled; - - ImGui.PushID($"devPluginLocation_{devPluginLocationSetting.Path}"); + if (locationToRemove != null) + { + this.devPluginLocations.Remove(locationToRemove); + this.devPluginLocationsChanged = true; + } ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2)); ImGui.Text(locNumber.ToString()); ImGui.NextColumn(); - ImGui.SetNextItemWidth(-1); - var path = devPluginLocationSetting.Path; - if (ImGui.InputText($"##devPluginLocationInput", ref path, 65535, ImGuiInputTextFlags.EnterReturnsTrue)) + ImGui.InputText("##devPluginLocationInput", ref this.devPluginTempLocation, 300); + ImGui.NextColumn(); + // Enabled button + ImGui.NextColumn(); + if (!string.IsNullOrEmpty(this.devPluginTempLocation) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { - var contains = this.devPluginLocations.Select(loc => loc.Path).Contains(path); - if (devPluginLocationSetting.Path == path) - { - // no change. - } - else if (contains && devPluginLocationSetting.Path != path) + if (this.devPluginLocations.Any(r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase))) { this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); } else { - devPluginLocationSetting.Path = path; - this.devPluginLocationsChanged = path != devPluginLocationSetting.Path; + this.devPluginLocations.Add(new DevPluginLocationSettings + { + Path = this.devPluginTempLocation, + IsEnabled = true, + }); + this.devPluginLocationsChanged = true; + this.devPluginTempLocation = string.Empty; } } - ImGui.NextColumn(); + ImGui.Columns(1); - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 7 - (12 * ImGuiHelpers.GlobalScale)); - ImGui.Checkbox("##devPluginLocationCheck", ref isEnabled); - ImGui.NextColumn(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + if (!string.IsNullOrEmpty(this.devPluginLocationAddError)) { - locationToRemove = devPluginLocationSetting; + ImGui.TextColored(new Vector4(1, 0, 0, 1), this.devPluginLocationAddError); + } + } + + private void DrawSaveCloseButtons() + { + var buttonSave = false; + var buttonClose = false; + + var pluginManager = Service.Get(); + + if (ImGui.Button(Loc.Localize("Save", "Save"))) + buttonSave = true; + + ImGui.SameLine(); + + if (ImGui.Button(Loc.Localize("Close", "Close"))) + buttonClose = true; + + ImGui.SameLine(); + + if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) + buttonSave = buttonClose = true; + + if (buttonSave) + { + this.Save(); + + if (this.thirdRepoListChanged) + { + _ = pluginManager.SetPluginReposFromConfigAsync(true); + this.thirdRepoListChanged = false; + } + + if (this.devPluginLocationsChanged) + { + pluginManager.ScanDevPlugins(); + this.devPluginLocationsChanged = false; + } } - ImGui.PopID(); - - ImGui.NextColumn(); - ImGui.Separator(); - - devPluginLocationSetting.IsEnabled = isEnabled; - - locNumber++; - } - - if (locationToRemove != null) - { - this.devPluginLocations.Remove(locationToRemove); - this.devPluginLocationsChanged = true; - } - - ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() / 2) - 8 - (ImGui.CalcTextSize(locNumber.ToString()).X / 2)); - ImGui.Text(locNumber.ToString()); - ImGui.NextColumn(); - ImGui.SetNextItemWidth(-1); - ImGui.InputText("##devPluginLocationInput", ref this.devPluginTempLocation, 300); - ImGui.NextColumn(); - // Enabled button - ImGui.NextColumn(); - if (!string.IsNullOrEmpty(this.devPluginTempLocation) && ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) - { - if (this.devPluginLocations.Any(r => string.Equals(r.Path, this.devPluginTempLocation, StringComparison.InvariantCultureIgnoreCase))) + if (buttonClose) { - this.devPluginLocationAddError = Loc.Localize("DalamudDevPluginLocationExists", "Location already exists."); - Task.Delay(5000).ContinueWith(t => this.devPluginLocationAddError = string.Empty); + this.IsOpen = false; + } + } + + private void Save() + { + var configuration = Service.Get(); + var localization = Service.Get(); + + localization.SetupWithLangCode(this.languages[this.langIndex]); + configuration.LanguageOverride = this.languages[this.langIndex]; + + configuration.GeneralChatType = this.dalamudMessagesChatType; + + configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash; + configuration.DutyFinderChatMessage = this.doCfChatMessage; + + configuration.GlobalUiScale = this.globalUiScale; + configuration.ToggleUiHide = this.doToggleUiHide; + configuration.ToggleUiHideDuringCutscenes = this.doToggleUiHideDuringCutscenes; + configuration.ToggleUiHideDuringGpose = this.doToggleUiHideDuringGpose; + + configuration.IsDocking = this.doDocking; + configuration.IsGamepadNavigationEnabled = this.doGamepad; + configuration.IsFocusManagementEnabled = this.doFocus; + + // This is applied every frame in InterfaceManager::CheckViewportState() + configuration.IsDisableViewport = !this.doViewport; + + // Apply docking flag + if (!configuration.IsDocking) + { + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable; } else { - this.devPluginLocations.Add(new DevPluginLocationSettings - { - Path = this.devPluginTempLocation, - IsEnabled = true, - }); - this.devPluginLocationsChanged = true; - this.devPluginTempLocation = string.Empty; + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; } - } - ImGui.Columns(1); - - if (!string.IsNullOrEmpty(this.devPluginLocationAddError)) - { - ImGui.TextColored(new Vector4(1, 0, 0, 1), this.devPluginLocationAddError); - } - } - - private void DrawSaveCloseButtons() - { - var buttonSave = false; - var buttonClose = false; - - var pluginManager = Service.Get(); - - if (ImGui.Button(Loc.Localize("Save", "Save"))) - buttonSave = true; - - ImGui.SameLine(); - - if (ImGui.Button(Loc.Localize("Close", "Close"))) - buttonClose = true; - - ImGui.SameLine(); - - if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) - buttonSave = buttonClose = true; - - if (buttonSave) - { - this.Save(); - - if (this.thirdRepoListChanged) + // NOTE (Chiv) Toggle gamepad navigation via setting + if (!configuration.IsGamepadNavigationEnabled) { - _ = pluginManager.SetPluginReposFromConfigAsync(true); - this.thirdRepoListChanged = false; + ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; } - - if (this.devPluginLocationsChanged) + else { - pluginManager.ScanDevPlugins(); - this.devPluginLocationsChanged = false; + ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; } + + configuration.DoPluginTest = this.doPluginTest; + configuration.ThirdRepoList = this.thirdRepoList.Select(x => x.Clone()).ToList(); + configuration.DevPluginLoadLocations = this.devPluginLocations.Select(x => x.Clone()).ToList(); + + configuration.PrintPluginsWelcomeMsg = this.printPluginsWelcomeMsg; + configuration.AutoUpdatePlugins = this.autoUpdatePlugins; + configuration.DoButtonsSystemMenu = this.doButtonsSystemMenu; + configuration.DisableRmtFiltering = this.disableRmtFiltering; + + configuration.Save(); + + _ = Service.Get().ReloadPluginMastersAsync(); } - - if (buttonClose) - { - this.IsOpen = false; - } - } - - private void Save() - { - var configuration = Service.Get(); - var localization = Service.Get(); - - localization.SetupWithLangCode(this.languages[this.langIndex]); - configuration.LanguageOverride = this.languages[this.langIndex]; - - configuration.GeneralChatType = this.dalamudMessagesChatType; - - configuration.DutyFinderTaskbarFlash = this.doCfTaskBarFlash; - configuration.DutyFinderChatMessage = this.doCfChatMessage; - - configuration.GlobalUiScale = this.globalUiScale; - configuration.ToggleUiHide = this.doToggleUiHide; - configuration.ToggleUiHideDuringCutscenes = this.doToggleUiHideDuringCutscenes; - configuration.ToggleUiHideDuringGpose = this.doToggleUiHideDuringGpose; - - configuration.IsDocking = this.doDocking; - configuration.IsGamepadNavigationEnabled = this.doGamepad; - configuration.IsFocusManagementEnabled = this.doFocus; - - // This is applied every frame in InterfaceManager::CheckViewportState() - configuration.IsDisableViewport = !this.doViewport; - - // Apply docking flag - if (!configuration.IsDocking) - { - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.DockingEnable; - } - else - { - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; - } - - // NOTE (Chiv) Toggle gamepad navigation via setting - if (!configuration.IsGamepadNavigationEnabled) - { - ImGui.GetIO().BackendFlags &= ~ImGuiBackendFlags.HasGamepad; - ImGui.GetIO().ConfigFlags &= ~ImGuiConfigFlags.NavEnableSetMousePos; - } - else - { - ImGui.GetIO().BackendFlags |= ImGuiBackendFlags.HasGamepad; - ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.NavEnableSetMousePos; - } - - configuration.DoPluginTest = this.doPluginTest; - configuration.ThirdRepoList = this.thirdRepoList.Select(x => x.Clone()).ToList(); - configuration.DevPluginLoadLocations = this.devPluginLocations.Select(x => x.Clone()).ToList(); - - configuration.PrintPluginsWelcomeMsg = this.printPluginsWelcomeMsg; - configuration.AutoUpdatePlugins = this.autoUpdatePlugins; - configuration.DoButtonsSystemMenu = this.doButtonsSystemMenu; - configuration.DisableRmtFiltering = this.disableRmtFiltering; - - configuration.Save(); - - _ = Service.Get().ReloadPluginMastersAsync(); } } diff --git a/Dalamud/Interface/Internal/Windows/StyleEditor/StyleEditorWindow.cs b/Dalamud/Interface/Internal/Windows/StyleEditor/StyleEditorWindow.cs index 8ad4e98e6..5b2c3d90c 100644 --- a/Dalamud/Interface/Internal/Windows/StyleEditor/StyleEditorWindow.cs +++ b/Dalamud/Interface/Internal/Windows/StyleEditor/StyleEditorWindow.cs @@ -15,387 +15,388 @@ using ImGuiNET; using Lumina.Excel.GeneratedSheets; using Serilog; -namespace Dalamud.Interface.Internal.Windows.StyleEditor; - -/// -/// Window for the Dalamud style editor. -/// -public class StyleEditorWindow : Window +namespace Dalamud.Interface.Internal.Windows.StyleEditor { - private ImGuiColorEditFlags alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf; - - private int currentSel = 0; - private string initialStyle = string.Empty; - private bool didSave = false; - - private string renameText = string.Empty; - private bool renameModalDrawing = false; - /// - /// Initializes a new instance of the class. + /// Window for the Dalamud style editor. /// - public StyleEditorWindow() - : base("Dalamud Style Editor") + public class StyleEditorWindow : Window { - this.IsOpen = true; - this.SizeConstraints = new WindowSizeConstraints + private ImGuiColorEditFlags alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf; + + private int currentSel = 0; + private string initialStyle = string.Empty; + private bool didSave = false; + + private string renameText = string.Empty; + private bool renameModalDrawing = false; + + /// + /// Initializes a new instance of the class. + /// + public StyleEditorWindow() + : base("Dalamud Style Editor") { - MinimumSize = new Vector2(890, 560), - MaximumSize = new Vector2(10000, 10000), - }; - } + this.IsOpen = true; + this.SizeConstraints = new WindowSizeConstraints + { + MinimumSize = new Vector2(890, 560), + MaximumSize = new Vector2(10000, 10000), + }; + } - /// - public override void OnOpen() - { - this.didSave = false; + /// + public override void OnOpen() + { + this.didSave = false; - var config = Service.Get(); - config.SavedStyles ??= new List(); - this.currentSel = config.SavedStyles.FindIndex(x => x.Name == config.ChosenStyle); + var config = Service.Get(); + config.SavedStyles ??= new List(); + this.currentSel = config.SavedStyles.FindIndex(x => x.Name == config.ChosenStyle); - this.initialStyle = config.ChosenStyle; + this.initialStyle = config.ChosenStyle; - base.OnOpen(); - } + base.OnOpen(); + } - /// - public override void OnClose() - { - if (!this.didSave) + /// + public override void OnClose() + { + if (!this.didSave) + { + var config = Service.Get(); + var newStyle = config.SavedStyles.FirstOrDefault(x => x.Name == this.initialStyle); + newStyle?.Apply(); + } + + base.OnClose(); + } + + /// + public override void Draw() { var config = Service.Get(); - var newStyle = config.SavedStyles.FirstOrDefault(x => x.Name == this.initialStyle); - newStyle?.Apply(); - } + var renameModalTitle = Loc.Localize("RenameStyleModalTitle", "Rename Style"); - base.OnClose(); - } + var workStyle = config.SavedStyles[this.currentSel]; + workStyle.BuiltInColors ??= StyleModelV1.DalamudStandard.BuiltInColors; - /// - public override void Draw() - { - var config = Service.Get(); - var renameModalTitle = Loc.Localize("RenameStyleModalTitle", "Rename Style"); + var appliedThisFrame = false; - var workStyle = config.SavedStyles[this.currentSel]; - workStyle.BuiltInColors ??= StyleModelV1.DalamudStandard.BuiltInColors; - - var appliedThisFrame = false; - - var styleAry = config.SavedStyles.Select(x => x.Name).ToArray(); - ImGui.Text(Loc.Localize("StyleEditorChooseStyle", "Choose Style:")); - if (ImGui.Combo("###styleChooserCombo", ref this.currentSel, styleAry, styleAry.Length)) - { - var newStyle = config.SavedStyles[this.currentSel]; - newStyle.Apply(); - appliedThisFrame = true; - } - - if (ImGui.Button(Loc.Localize("StyleEditorAddNew", "Add new style"))) - { - this.SaveStyle(); - - var newStyle = StyleModelV1.DalamudStandard; - newStyle.Name = GetRandomName(); - config.SavedStyles.Add(newStyle); - - this.currentSel = config.SavedStyles.Count - 1; - - newStyle.Apply(); - appliedThisFrame = true; - - config.Save(); - } - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash) && this.currentSel != 0) - { - this.currentSel--; - var newStyle = config.SavedStyles[this.currentSel]; - newStyle.Apply(); - appliedThisFrame = true; - - config.SavedStyles.RemoveAt(this.currentSel + 1); - - config.Save(); - } - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Loc.Localize("StyleEditorDeleteStyle", "Delete current style")); - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Pen) && this.currentSel != 0) - { - var newStyle = config.SavedStyles[this.currentSel]; - this.renameText = newStyle.Name; - - this.renameModalDrawing = true; - ImGui.OpenPopup(renameModalTitle); - } - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Loc.Localize("StyleEditorRenameStyle", "Rename style")); - - ImGui.SameLine(); - - ImGuiHelpers.ScaledDummy(5); - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.FileExport)) - { - var selectedStyle = config.SavedStyles[this.currentSel]; - var newStyle = StyleModelV1.Get(); - newStyle.Name = selectedStyle.Name; - ImGui.SetClipboardText(newStyle.Serialize()); - } - - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Loc.Localize("StyleEditorCopy", "Copy style to clipboard for sharing")); - - ImGui.SameLine(); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.FileImport)) - { - this.SaveStyle(); - - var styleJson = ImGui.GetClipboardText(); - - try + var styleAry = config.SavedStyles.Select(x => x.Name).ToArray(); + ImGui.Text(Loc.Localize("StyleEditorChooseStyle", "Choose Style:")); + if (ImGui.Combo("###styleChooserCombo", ref this.currentSel, styleAry, styleAry.Length)) { - var newStyle = StyleModel.Deserialize(styleJson); - - newStyle.Name ??= GetRandomName(); - - if (config.SavedStyles.Any(x => x.Name == newStyle.Name)) - { - newStyle.Name = $"{newStyle.Name} ({GetRandomName()} Mix)"; - } - - config.SavedStyles.Add(newStyle); + var newStyle = config.SavedStyles[this.currentSel]; newStyle.Apply(); appliedThisFrame = true; + } + + if (ImGui.Button(Loc.Localize("StyleEditorAddNew", "Add new style"))) + { + this.SaveStyle(); + + var newStyle = StyleModelV1.DalamudStandard; + newStyle.Name = GetRandomName(); + config.SavedStyles.Add(newStyle); this.currentSel = config.SavedStyles.Count - 1; + newStyle.Apply(); + appliedThisFrame = true; + config.Save(); } - catch (Exception ex) + + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash) && this.currentSel != 0) { - Log.Error(ex, "Could not import style"); - } - } + this.currentSel--; + var newStyle = config.SavedStyles[this.currentSel]; + newStyle.Apply(); + appliedThisFrame = true; - if (ImGui.IsItemHovered()) - ImGui.SetTooltip(Loc.Localize("StyleEditorImport", "Import style from clipboard")); + config.SavedStyles.RemoveAt(this.currentSel + 1); - ImGui.Separator(); - - ImGui.PushItemWidth(ImGui.GetWindowWidth() * 0.50f); - - if (this.currentSel < 2) - { - ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("StyleEditorNotAllowed", "You cannot edit built-in styles. Please add a new style first.")); - } - else if (appliedThisFrame) - { - ImGui.Text(Loc.Localize("StyleEditorApplying", "Applying style...")); - } - else if (ImGui.BeginTabBar("StyleEditorTabs")) - { - var style = ImGui.GetStyle(); - - if (ImGui.BeginTabItem(Loc.Localize("StyleEditorVariables", "Variables"))) - { - ImGui.BeginChild($"ScrollingVars", ImGuiHelpers.ScaledVector2(0, -32), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); - - ImGui.SliderFloat2("WindowPadding", ref style.WindowPadding, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("FramePadding", ref style.FramePadding, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("CellPadding", ref style.CellPadding, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("ItemSpacing", ref style.ItemSpacing, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("ItemInnerSpacing", ref style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); - ImGui.SliderFloat2("TouchExtraPadding", ref style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); - ImGui.SliderFloat("IndentSpacing", ref style.IndentSpacing, 0.0f, 30.0f, "%.0f"); - ImGui.SliderFloat("ScrollbarSize", ref style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); - ImGui.SliderFloat("GrabMinSize", ref style.GrabMinSize, 1.0f, 20.0f, "%.0f"); - ImGui.Text("Borders"); - ImGui.SliderFloat("WindowBorderSize", ref style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.SliderFloat("ChildBorderSize", ref style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.SliderFloat("PopupBorderSize", ref style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.SliderFloat("FrameBorderSize", ref style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.SliderFloat("TabBorderSize", ref style.TabBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui.Text("Rounding"); - ImGui.SliderFloat("WindowRounding", ref style.WindowRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("ChildRounding", ref style.ChildRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("FrameRounding", ref style.FrameRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("PopupRounding", ref style.PopupRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("ScrollbarRounding", ref style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("GrabRounding", ref style.GrabRounding, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("LogSliderDeadzone", ref style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); - ImGui.SliderFloat("TabRounding", ref style.TabRounding, 0.0f, 12.0f, "%.0f"); - ImGui.Text("Alignment"); - ImGui.SliderFloat2("WindowTitleAlign", ref style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); - var windowMenuButtonPosition = (int)style.WindowMenuButtonPosition + 1; - if (ImGui.Combo("WindowMenuButtonPosition", ref windowMenuButtonPosition, "None\0Left\0Right\0")) - style.WindowMenuButtonPosition = (ImGuiDir)(windowMenuButtonPosition - 1); - ImGui.SliderFloat2("ButtonTextAlign", ref style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui.SameLine(); - ImGuiComponents.HelpMarker("Alignment applies when a button is larger than its text content."); - ImGui.SliderFloat2("SelectableTextAlign", ref style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); - ImGui.SameLine(); - ImGuiComponents.HelpMarker("Alignment applies when a selectable is larger than its text content."); - ImGui.SliderFloat2("DisplaySafeAreaPadding", ref style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); - ImGui.SameLine(); - ImGuiComponents.HelpMarker( - "Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); - ImGui.EndTabItem(); - - ImGui.EndChild(); - - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem(Loc.Localize("StyleEditorColors", "Colors"))) - { - ImGui.BeginChild("ScrollingColors", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); - - ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); - - if (ImGui.RadioButton("Opaque", this.alphaFlags == ImGuiColorEditFlags.None)) - this.alphaFlags = ImGuiColorEditFlags.None; - ImGui.SameLine(); - if (ImGui.RadioButton("Alpha", this.alphaFlags == ImGuiColorEditFlags.AlphaPreview)) - this.alphaFlags = ImGuiColorEditFlags.AlphaPreview; - ImGui.SameLine(); - if (ImGui.RadioButton("Both", this.alphaFlags == ImGuiColorEditFlags.AlphaPreviewHalf)) - this.alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf; - ImGui.SameLine(); - - ImGuiComponents.HelpMarker( - "In the color list:\n" + - "Left-click on color square to open color picker,\n" + - "Right-click to open edit options menu."); - - foreach (var imGuiCol in Enum.GetValues()) - { - if (imGuiCol == ImGuiCol.COUNT) - continue; - - ImGui.PushID(imGuiCol.ToString()); - - ImGui.ColorEdit4("##color", ref style.Colors[(int)imGuiCol], ImGuiColorEditFlags.AlphaBar | this.alphaFlags); - - ImGui.SameLine(0.0f, style.ItemInnerSpacing.X); - ImGui.TextUnformatted(imGuiCol.ToString()); - - ImGui.PopID(); - } - - ImGui.Separator(); - - foreach (var property in typeof(DalamudColors).GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - ImGui.PushID(property.Name); - - var colorVal = property.GetValue(workStyle.BuiltInColors); - if (colorVal == null) - { - colorVal = property.GetValue(StyleModelV1.DalamudStandard.BuiltInColors); - property.SetValue(workStyle.BuiltInColors, colorVal); - } - - var color = (Vector4)colorVal; - - if (ImGui.ColorEdit4("##color", ref color, ImGuiColorEditFlags.AlphaBar | this.alphaFlags)) - { - property.SetValue(workStyle.BuiltInColors, color); - workStyle.BuiltInColors?.Apply(); - } - - ImGui.SameLine(0.0f, style.ItemInnerSpacing.X); - ImGui.TextUnformatted(property.Name); - - ImGui.PopID(); - } - - ImGui.EndChild(); - - ImGui.EndTabItem(); - } - - ImGui.EndTabBar(); - } - - ImGui.PopItemWidth(); - - ImGui.Separator(); - - if (ImGui.Button(Loc.Localize("Close", "Close"))) - { - this.IsOpen = false; - } - - ImGui.SameLine(); - - if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) - { - this.SaveStyle(); - - config.ChosenStyle = config.SavedStyles[this.currentSel].Name; - Log.Verbose("ChosenStyle = {ChosenStyle}", config.ChosenStyle); - - this.didSave = true; - - this.IsOpen = false; - } - - if (ImGui.BeginPopupModal(renameModalTitle, ref this.renameModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) - { - ImGui.Text(Loc.Localize("StyleEditorEnterName", "Please enter the new name for this style.")); - ImGui.Spacing(); - - ImGui.InputText("###renameModalInput", ref this.renameText, 255); - - const float buttonWidth = 120f; - ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); - - if (ImGui.Button("OK", new Vector2(buttonWidth, 40))) - { - config.SavedStyles[this.currentSel].Name = this.renameText; config.Save(); - - ImGui.CloseCurrentPopup(); } - ImGui.EndPopup(); + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Loc.Localize("StyleEditorDeleteStyle", "Delete current style")); + + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Pen) && this.currentSel != 0) + { + var newStyle = config.SavedStyles[this.currentSel]; + this.renameText = newStyle.Name; + + this.renameModalDrawing = true; + ImGui.OpenPopup(renameModalTitle); + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Loc.Localize("StyleEditorRenameStyle", "Rename style")); + + ImGui.SameLine(); + + ImGuiHelpers.ScaledDummy(5); + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.FileExport)) + { + var selectedStyle = config.SavedStyles[this.currentSel]; + var newStyle = StyleModelV1.Get(); + newStyle.Name = selectedStyle.Name; + ImGui.SetClipboardText(newStyle.Serialize()); + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Loc.Localize("StyleEditorCopy", "Copy style to clipboard for sharing")); + + ImGui.SameLine(); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.FileImport)) + { + this.SaveStyle(); + + var styleJson = ImGui.GetClipboardText(); + + try + { + var newStyle = StyleModel.Deserialize(styleJson); + + newStyle.Name ??= GetRandomName(); + + if (config.SavedStyles.Any(x => x.Name == newStyle.Name)) + { + newStyle.Name = $"{newStyle.Name} ({GetRandomName()} Mix)"; + } + + config.SavedStyles.Add(newStyle); + newStyle.Apply(); + appliedThisFrame = true; + + this.currentSel = config.SavedStyles.Count - 1; + + config.Save(); + } + catch (Exception ex) + { + Log.Error(ex, "Could not import style"); + } + } + + if (ImGui.IsItemHovered()) + ImGui.SetTooltip(Loc.Localize("StyleEditorImport", "Import style from clipboard")); + + ImGui.Separator(); + + ImGui.PushItemWidth(ImGui.GetWindowWidth() * 0.50f); + + if (this.currentSel < 2) + { + ImGui.TextColored(ImGuiColors.DalamudRed, Loc.Localize("StyleEditorNotAllowed", "You cannot edit built-in styles. Please add a new style first.")); + } + else if (appliedThisFrame) + { + ImGui.Text(Loc.Localize("StyleEditorApplying", "Applying style...")); + } + else if (ImGui.BeginTabBar("StyleEditorTabs")) + { + var style = ImGui.GetStyle(); + + if (ImGui.BeginTabItem(Loc.Localize("StyleEditorVariables", "Variables"))) + { + ImGui.BeginChild($"ScrollingVars", ImGuiHelpers.ScaledVector2(0, -32), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); + + ImGui.SliderFloat2("WindowPadding", ref style.WindowPadding, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("FramePadding", ref style.FramePadding, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("CellPadding", ref style.CellPadding, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("ItemSpacing", ref style.ItemSpacing, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("ItemInnerSpacing", ref style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); + ImGui.SliderFloat2("TouchExtraPadding", ref style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); + ImGui.SliderFloat("IndentSpacing", ref style.IndentSpacing, 0.0f, 30.0f, "%.0f"); + ImGui.SliderFloat("ScrollbarSize", ref style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); + ImGui.SliderFloat("GrabMinSize", ref style.GrabMinSize, 1.0f, 20.0f, "%.0f"); + ImGui.Text("Borders"); + ImGui.SliderFloat("WindowBorderSize", ref style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.SliderFloat("ChildBorderSize", ref style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.SliderFloat("PopupBorderSize", ref style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.SliderFloat("FrameBorderSize", ref style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.SliderFloat("TabBorderSize", ref style.TabBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui.Text("Rounding"); + ImGui.SliderFloat("WindowRounding", ref style.WindowRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("ChildRounding", ref style.ChildRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("FrameRounding", ref style.FrameRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("PopupRounding", ref style.PopupRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("ScrollbarRounding", ref style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("GrabRounding", ref style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("LogSliderDeadzone", ref style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); + ImGui.SliderFloat("TabRounding", ref style.TabRounding, 0.0f, 12.0f, "%.0f"); + ImGui.Text("Alignment"); + ImGui.SliderFloat2("WindowTitleAlign", ref style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); + var windowMenuButtonPosition = (int)style.WindowMenuButtonPosition + 1; + if (ImGui.Combo("WindowMenuButtonPosition", ref windowMenuButtonPosition, "None\0Left\0Right\0")) + style.WindowMenuButtonPosition = (ImGuiDir)(windowMenuButtonPosition - 1); + ImGui.SliderFloat2("ButtonTextAlign", ref style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); + ImGui.SameLine(); + ImGuiComponents.HelpMarker("Alignment applies when a button is larger than its text content."); + ImGui.SliderFloat2("SelectableTextAlign", ref style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); + ImGui.SameLine(); + ImGuiComponents.HelpMarker("Alignment applies when a selectable is larger than its text content."); + ImGui.SliderFloat2("DisplaySafeAreaPadding", ref style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); + ImGui.SameLine(); + ImGuiComponents.HelpMarker( + "Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); + ImGui.EndTabItem(); + + ImGui.EndChild(); + + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem(Loc.Localize("StyleEditorColors", "Colors"))) + { + ImGui.BeginChild("ScrollingColors", ImGuiHelpers.ScaledVector2(0, -30), true, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoBackground); + + ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 5); + + if (ImGui.RadioButton("Opaque", this.alphaFlags == ImGuiColorEditFlags.None)) + this.alphaFlags = ImGuiColorEditFlags.None; + ImGui.SameLine(); + if (ImGui.RadioButton("Alpha", this.alphaFlags == ImGuiColorEditFlags.AlphaPreview)) + this.alphaFlags = ImGuiColorEditFlags.AlphaPreview; + ImGui.SameLine(); + if (ImGui.RadioButton("Both", this.alphaFlags == ImGuiColorEditFlags.AlphaPreviewHalf)) + this.alphaFlags = ImGuiColorEditFlags.AlphaPreviewHalf; + ImGui.SameLine(); + + ImGuiComponents.HelpMarker( + "In the color list:\n" + + "Left-click on color square to open color picker,\n" + + "Right-click to open edit options menu."); + + foreach (var imGuiCol in Enum.GetValues()) + { + if (imGuiCol == ImGuiCol.COUNT) + continue; + + ImGui.PushID(imGuiCol.ToString()); + + ImGui.ColorEdit4("##color", ref style.Colors[(int)imGuiCol], ImGuiColorEditFlags.AlphaBar | this.alphaFlags); + + ImGui.SameLine(0.0f, style.ItemInnerSpacing.X); + ImGui.TextUnformatted(imGuiCol.ToString()); + + ImGui.PopID(); + } + + ImGui.Separator(); + + foreach (var property in typeof(DalamudColors).GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + ImGui.PushID(property.Name); + + var colorVal = property.GetValue(workStyle.BuiltInColors); + if (colorVal == null) + { + colorVal = property.GetValue(StyleModelV1.DalamudStandard.BuiltInColors); + property.SetValue(workStyle.BuiltInColors, colorVal); + } + + var color = (Vector4)colorVal; + + if (ImGui.ColorEdit4("##color", ref color, ImGuiColorEditFlags.AlphaBar | this.alphaFlags)) + { + property.SetValue(workStyle.BuiltInColors, color); + workStyle.BuiltInColors?.Apply(); + } + + ImGui.SameLine(0.0f, style.ItemInnerSpacing.X); + ImGui.TextUnformatted(property.Name); + + ImGui.PopID(); + } + + ImGui.EndChild(); + + ImGui.EndTabItem(); + } + + ImGui.EndTabBar(); + } + + ImGui.PopItemWidth(); + + ImGui.Separator(); + + if (ImGui.Button(Loc.Localize("Close", "Close"))) + { + this.IsOpen = false; + } + + ImGui.SameLine(); + + if (ImGui.Button(Loc.Localize("SaveAndClose", "Save and Close"))) + { + this.SaveStyle(); + + config.ChosenStyle = config.SavedStyles[this.currentSel].Name; + Log.Verbose("ChosenStyle = {ChosenStyle}", config.ChosenStyle); + + this.didSave = true; + + this.IsOpen = false; + } + + if (ImGui.BeginPopupModal(renameModalTitle, ref this.renameModalDrawing, ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoScrollbar)) + { + ImGui.Text(Loc.Localize("StyleEditorEnterName", "Please enter the new name for this style.")); + ImGui.Spacing(); + + ImGui.InputText("###renameModalInput", ref this.renameText, 255); + + const float buttonWidth = 120f; + ImGui.SetCursorPosX((ImGui.GetWindowWidth() - buttonWidth) / 2); + + if (ImGui.Button("OK", new Vector2(buttonWidth, 40))) + { + config.SavedStyles[this.currentSel].Name = this.renameText; + config.Save(); + + ImGui.CloseCurrentPopup(); + } + + ImGui.EndPopup(); + } } - } - private static string GetRandomName() - { - var data = Service.Get(); - var names = data.GetExcelSheet(ClientLanguage.English); - var rng = new Random(); + private static string GetRandomName() + { + var data = Service.Get(); + var names = data.GetExcelSheet(ClientLanguage.English); + var rng = new Random(); - return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString; - } + return names.ElementAt(rng.Next(0, names.Count() - 1)).Singular.RawString; + } - private void SaveStyle() - { - if (this.currentSel < 2) - return; + private void SaveStyle() + { + if (this.currentSel < 2) + return; - var config = Service.Get(); + var config = Service.Get(); - var newStyle = StyleModelV1.Get(); - newStyle.Name = config.SavedStyles[this.currentSel].Name; - config.SavedStyles[this.currentSel] = newStyle; - newStyle.Apply(); + var newStyle = StyleModelV1.Get(); + newStyle.Name = config.SavedStyles[this.currentSel].Name; + config.SavedStyles[this.currentSel] = newStyle; + newStyle.Apply(); - config.Save(); + config.Save(); + } } } diff --git a/Dalamud/Interface/Style/DalamudColors.cs b/Dalamud/Interface/Style/DalamudColors.cs index 0d39e056a..a674ee4b2 100644 --- a/Dalamud/Interface/Style/DalamudColors.cs +++ b/Dalamud/Interface/Style/DalamudColors.cs @@ -1,93 +1,97 @@ -using System.Numerics; +using System.Numerics; using Dalamud.Interface.Colors; using Newtonsoft.Json; -namespace Dalamud.Interface.Style; -#pragma warning disable SA1600 - -public class DalamudColors +namespace Dalamud.Interface.Style { - [JsonProperty("a")] - public Vector4? DalamudRed { get; set; } + #pragma warning disable SA1600 - [JsonProperty("b")] - public Vector4? DalamudGrey { get; set; } - - [JsonProperty("c")] - public Vector4? DalamudGrey2 { get; set; } - - [JsonProperty("d")] - public Vector4? DalamudGrey3 { get; set; } - - [JsonProperty("e")] - public Vector4? DalamudWhite { get; set; } - - [JsonProperty("f")] - public Vector4? DalamudWhite2 { get; set; } - - [JsonProperty("g")] - public Vector4? DalamudOrange { get; set; } - - [JsonProperty("h")] - public Vector4? TankBlue { get; set; } - - [JsonProperty("i")] - public Vector4? HealerGreen { get; set; } - - [JsonProperty("j")] - public Vector4? DPSRed { get; set; } - - public void Apply() - { - if (this.DalamudRed.HasValue) + public class DalamudColors { - ImGuiColors.DalamudRed = this.DalamudRed.Value; + [JsonProperty("a")] + public Vector4? DalamudRed { get; set; } + + [JsonProperty("b")] + public Vector4? DalamudGrey { get; set; } + + [JsonProperty("c")] + public Vector4? DalamudGrey2 { get; set; } + + [JsonProperty("d")] + public Vector4? DalamudGrey3 { get; set; } + + [JsonProperty("e")] + public Vector4? DalamudWhite { get; set; } + + [JsonProperty("f")] + public Vector4? DalamudWhite2 { get; set; } + + [JsonProperty("g")] + public Vector4? DalamudOrange { get; set; } + + [JsonProperty("h")] + public Vector4? TankBlue { get; set; } + + [JsonProperty("i")] + public Vector4? HealerGreen { get; set; } + + [JsonProperty("j")] + public Vector4? DPSRed { get; set; } + + public void Apply() + { + if (this.DalamudRed.HasValue) + { + ImGuiColors.DalamudRed = this.DalamudRed.Value; + } + + if (this.DalamudGrey.HasValue) + { + ImGuiColors.DalamudGrey = this.DalamudGrey.Value; + } + + if (this.DalamudGrey2.HasValue) + { + ImGuiColors.DalamudGrey2 = this.DalamudGrey2.Value; + } + + if (this.DalamudGrey3.HasValue) + { + ImGuiColors.DalamudGrey3 = this.DalamudGrey3.Value; + } + + if (this.DalamudWhite.HasValue) + { + ImGuiColors.DalamudWhite = this.DalamudWhite.Value; + } + + if (this.DalamudWhite2.HasValue) + { + ImGuiColors.DalamudWhite2 = this.DalamudWhite2.Value; + } + + if (this.DalamudOrange.HasValue) + { + ImGuiColors.DalamudOrange = this.DalamudOrange.Value; + } + + if (this.TankBlue.HasValue) + { + ImGuiColors.TankBlue = this.TankBlue.Value; + } + + if (this.HealerGreen.HasValue) + { + ImGuiColors.HealerGreen = this.HealerGreen.Value; + } + + if (this.DPSRed.HasValue) + { + ImGuiColors.DPSRed = this.DPSRed.Value; + } + } } - if (this.DalamudGrey.HasValue) - { - ImGuiColors.DalamudGrey = this.DalamudGrey.Value; - } - - if (this.DalamudGrey2.HasValue) - { - ImGuiColors.DalamudGrey2 = this.DalamudGrey2.Value; - } - - if (this.DalamudGrey3.HasValue) - { - ImGuiColors.DalamudGrey3 = this.DalamudGrey3.Value; - } - - if (this.DalamudWhite.HasValue) - { - ImGuiColors.DalamudWhite = this.DalamudWhite.Value; - } - - if (this.DalamudWhite2.HasValue) - { - ImGuiColors.DalamudWhite2 = this.DalamudWhite2.Value; - } - - if (this.DalamudOrange.HasValue) - { - ImGuiColors.DalamudOrange = this.DalamudOrange.Value; - } - - if (this.TankBlue.HasValue) - { - ImGuiColors.TankBlue = this.TankBlue.Value; - } - - if (this.HealerGreen.HasValue) - { - ImGuiColors.HealerGreen = this.HealerGreen.Value; - } - - if (this.DPSRed.HasValue) - { - ImGuiColors.DPSRed = this.DPSRed.Value; - } - } +#pragma warning restore SA1600 } diff --git a/Dalamud/Interface/Style/StyleModel.cs b/Dalamud/Interface/Style/StyleModel.cs index 8047a9a42..978ef78dc 100644 --- a/Dalamud/Interface/Style/StyleModel.cs +++ b/Dalamud/Interface/Style/StyleModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; @@ -8,120 +8,121 @@ using Dalamud.Utility; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Interface.Style; - -/// -/// Superclass for all versions of the Dalamud style model. -/// -public abstract class StyleModel +namespace Dalamud.Interface.Style { /// - /// Gets or sets the name of the style model. + /// Superclass for all versions of the Dalamud style model. /// - [JsonProperty("name")] - public string Name { get; set; } = "Unknown"; - - /// - /// Gets or sets class representing Dalamud-builtin . - /// - [JsonProperty("dol")] - public DalamudColors? BuiltInColors { get; set; } - - /// - /// Gets or sets version number of this model. - /// - [JsonProperty("ver")] - public int Version { get; set; } - - /// - /// Get a StyleModel based on the current Dalamud style, with the current version. - /// - /// The current style. - public static StyleModel GetFromCurrent() => StyleModelV1.Get(); - - /// - /// Get the current style model, as per configuration. - /// - /// The current style, as per configuration. - public static StyleModel? GetConfiguredStyle() + public abstract class StyleModel { - var configuration = Service.Get(); - return configuration.SavedStyles?.FirstOrDefault(x => x.Name == configuration.ChosenStyle); - } + /// + /// Gets or sets the name of the style model. + /// + [JsonProperty("name")] + public string Name { get; set; } = "Unknown"; - /// - /// Get an enumerable of all saved styles. - /// - /// Enumerable of saved styles. - public static IEnumerable? GetConfiguredStyles() => Service.Get().SavedStyles; + /// + /// Gets or sets class representing Dalamud-builtin . + /// + [JsonProperty("dol")] + public DalamudColors? BuiltInColors { get; set; } - /// - /// Deserialize a style model. - /// - /// The serialized model. - /// The deserialized model. - /// Thrown in case the version of the model is not known. - public static StyleModel? Deserialize(string model) - { - var json = Util.DecompressString(Convert.FromBase64String(model.Substring(3))); + /// + /// Gets or sets version number of this model. + /// + [JsonProperty("ver")] + public int Version { get; set; } - if (model.StartsWith(StyleModelV1.SerializedPrefix)) - return JsonConvert.DeserializeObject(json); + /// + /// Get a StyleModel based on the current Dalamud style, with the current version. + /// + /// The current style. + public static StyleModel GetFromCurrent() => StyleModelV1.Get(); - throw new ArgumentException("Was not a compressed style model."); - } - - /// - /// [TEMPORARY] Transfer old non-polymorphic style models to the new format. - /// - public static void TransferOldModels() - { - var configuration = Service.Get(); - - if (configuration.SavedStylesOld == null) - return; - - configuration.SavedStyles = new List(); - configuration.SavedStyles.AddRange(configuration.SavedStylesOld); - - Log.Information("Transferred {0} styles", configuration.SavedStyles.Count); - - configuration.SavedStylesOld = null; - configuration.Save(); - } - - /// - /// Serialize this style model. - /// - /// Serialized style model as string. - /// Thrown when the version of the style model is unknown. - public string Serialize() - { - string prefix; - switch (this) + /// + /// Get the current style model, as per configuration. + /// + /// The current style, as per configuration. + public static StyleModel? GetConfiguredStyle() { - case StyleModelV1: - prefix = StyleModelV1.SerializedPrefix; - break; - default: - throw new ArgumentOutOfRangeException(); + var configuration = Service.Get(); + return configuration.SavedStyles?.FirstOrDefault(x => x.Name == configuration.ChosenStyle); } - return prefix + Convert.ToBase64String(Util.CompressString(JsonConvert.SerializeObject(this))); + /// + /// Get an enumerable of all saved styles. + /// + /// Enumerable of saved styles. + public static IEnumerable? GetConfiguredStyles() => Service.Get().SavedStyles; + + /// + /// Deserialize a style model. + /// + /// The serialized model. + /// The deserialized model. + /// Thrown in case the version of the model is not known. + public static StyleModel? Deserialize(string model) + { + var json = Util.DecompressString(Convert.FromBase64String(model.Substring(3))); + + if (model.StartsWith(StyleModelV1.SerializedPrefix)) + return JsonConvert.DeserializeObject(json); + + throw new ArgumentException("Was not a compressed style model."); + } + + /// + /// [TEMPORARY] Transfer old non-polymorphic style models to the new format. + /// + public static void TransferOldModels() + { + var configuration = Service.Get(); + + if (configuration.SavedStylesOld == null) + return; + + configuration.SavedStyles = new List(); + configuration.SavedStyles.AddRange(configuration.SavedStylesOld); + + Log.Information("Transferred {0} styles", configuration.SavedStyles.Count); + + configuration.SavedStylesOld = null; + configuration.Save(); + } + + /// + /// Serialize this style model. + /// + /// Serialized style model as string. + /// Thrown when the version of the style model is unknown. + public string Serialize() + { + string prefix; + switch (this) + { + case StyleModelV1: + prefix = StyleModelV1.SerializedPrefix; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return prefix + Convert.ToBase64String(Util.CompressString(JsonConvert.SerializeObject(this))); + } + + /// + /// Apply this style model to ImGui. + /// + public abstract void Apply(); + + /// + /// Push this StyleModel into the ImGui style/color stack. + /// + public abstract void Push(); + + /// + /// Pop this style model from the ImGui style/color stack. + /// + public abstract void Pop(); } - - /// - /// Apply this style model to ImGui. - /// - public abstract void Apply(); - - /// - /// Push this StyleModel into the ImGui style/color stack. - /// - public abstract void Push(); - - /// - /// Pop this style model from the ImGui style/color stack. - /// - public abstract void Pop(); } diff --git a/Dalamud/Interface/Style/StyleModelV1.cs b/Dalamud/Interface/Style/StyleModelV1.cs index 676c68f64..3792df4f4 100644 --- a/Dalamud/Interface/Style/StyleModelV1.cs +++ b/Dalamud/Interface/Style/StyleModelV1.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Numerics; @@ -6,58 +6,58 @@ using Dalamud.Interface.Colors; using ImGuiNET; using Newtonsoft.Json; -namespace Dalamud.Interface.Style; - -/// -/// Version one of the Dalamud style model. -/// -public class StyleModelV1 : StyleModel +namespace Dalamud.Interface.Style { /// - /// Initializes a new instance of the class. + /// Version one of the Dalamud style model. /// - private StyleModelV1() + public class StyleModelV1 : StyleModel { - this.Colors = new Dictionary(); - this.Name = "Unknown"; - } + /// + /// Initializes a new instance of the class. + /// + private StyleModelV1() + { + this.Colors = new Dictionary(); + this.Name = "Unknown"; + } - /// - /// Gets the standard Dalamud look. - /// - public static StyleModelV1 DalamudStandard => new() - { - Name = "Dalamud Standard", + /// + /// Gets the standard Dalamud look. + /// + public static StyleModelV1 DalamudStandard => new() + { + Name = "Dalamud Standard", - Alpha = 1, - WindowPadding = new Vector2(8, 8), - WindowRounding = 4, - WindowBorderSize = 0, - WindowTitleAlign = new Vector2(0, 0.5f), - WindowMenuButtonPosition = ImGuiDir.Right, - ChildRounding = 0, - ChildBorderSize = 1, - PopupRounding = 0, - FramePadding = new Vector2(4, 3), - FrameRounding = 4, - FrameBorderSize = 0, - ItemSpacing = new Vector2(8, 4), - ItemInnerSpacing = new Vector2(4, 4), - CellPadding = new Vector2(4, 2), - TouchExtraPadding = new Vector2(0, 0), - IndentSpacing = 21, - ScrollbarSize = 16, - ScrollbarRounding = 9, - GrabMinSize = 13, - GrabRounding = 3, - LogSliderDeadzone = 4, - TabRounding = 4, - TabBorderSize = 0, - ButtonTextAlign = new Vector2(0.5f, 0.5f), - SelectableTextAlign = new Vector2(0, 0), - DisplaySafeAreaPadding = new Vector2(3, 3), + Alpha = 1, + WindowPadding = new Vector2(8, 8), + WindowRounding = 4, + WindowBorderSize = 0, + WindowTitleAlign = new Vector2(0, 0.5f), + WindowMenuButtonPosition = ImGuiDir.Right, + ChildRounding = 0, + ChildBorderSize = 1, + PopupRounding = 0, + FramePadding = new Vector2(4, 3), + FrameRounding = 4, + FrameBorderSize = 0, + ItemSpacing = new Vector2(8, 4), + ItemInnerSpacing = new Vector2(4, 4), + CellPadding = new Vector2(4, 2), + TouchExtraPadding = new Vector2(0, 0), + IndentSpacing = 21, + ScrollbarSize = 16, + ScrollbarRounding = 9, + GrabMinSize = 13, + GrabRounding = 3, + LogSliderDeadzone = 4, + TabRounding = 4, + TabBorderSize = 0, + ButtonTextAlign = new Vector2(0.5f, 0.5f), + SelectableTextAlign = new Vector2(0, 0), + DisplaySafeAreaPadding = new Vector2(3, 3), - Colors = new Dictionary + Colors = new Dictionary { { "Text", new Vector4(1, 1, 1, 1) }, { "TextDisabled", new Vector4(0.5f, 0.5f, 0.5f, 1) }, @@ -116,57 +116,57 @@ public class StyleModelV1 : StyleModel { "ModalWindowDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.35f) }, }, - BuiltInColors = new DalamudColors + BuiltInColors = new DalamudColors + { + DalamudRed = new Vector4(1f, 0f, 0f, 1f), + DalamudGrey = new Vector4(0.7f, 0.7f, 0.7f, 1f), + DalamudGrey2 = new Vector4(0.7f, 0.7f, 0.7f, 1f), + DalamudGrey3 = new Vector4(0.5f, 0.5f, 0.5f, 1f), + DalamudWhite = new Vector4(1f, 1f, 1f, 1f), + DalamudWhite2 = new Vector4(0.878f, 0.878f, 0.878f, 1f), + DalamudOrange = new Vector4(1f, 0.709f, 0f, 1f), + TankBlue = new Vector4(0f, 0.6f, 1f, 1f), + HealerGreen = new Vector4(0f, 0.8f, 0.1333333f, 1f), + DPSRed = new Vector4(0.7058824f, 0f, 0f, 1f), + }, + }; + + /// + /// Gets the standard Dalamud look. + /// + public static StyleModelV1 DalamudClassic => new() { - DalamudRed = new Vector4(1f, 0f, 0f, 1f), - DalamudGrey = new Vector4(0.7f, 0.7f, 0.7f, 1f), - DalamudGrey2 = new Vector4(0.7f, 0.7f, 0.7f, 1f), - DalamudGrey3 = new Vector4(0.5f, 0.5f, 0.5f, 1f), - DalamudWhite = new Vector4(1f, 1f, 1f, 1f), - DalamudWhite2 = new Vector4(0.878f, 0.878f, 0.878f, 1f), - DalamudOrange = new Vector4(1f, 0.709f, 0f, 1f), - TankBlue = new Vector4(0f, 0.6f, 1f, 1f), - HealerGreen = new Vector4(0f, 0.8f, 0.1333333f, 1f), - DPSRed = new Vector4(0.7058824f, 0f, 0f, 1f), - }, - }; + Name = "Dalamud Classic", - /// - /// Gets the standard Dalamud look. - /// - public static StyleModelV1 DalamudClassic => new() - { - Name = "Dalamud Classic", + Alpha = 1, + WindowPadding = new Vector2(8, 8), + WindowRounding = 4, + WindowBorderSize = 0, + WindowTitleAlign = new Vector2(0, 0.5f), + WindowMenuButtonPosition = ImGuiDir.Right, + ChildRounding = 0, + ChildBorderSize = 1, + PopupRounding = 0, + FramePadding = new Vector2(4, 3), + FrameRounding = 4, + FrameBorderSize = 0, + ItemSpacing = new Vector2(8, 4), + ItemInnerSpacing = new Vector2(4, 4), + CellPadding = new Vector2(4, 2), + TouchExtraPadding = new Vector2(0, 0), + IndentSpacing = 21, + ScrollbarSize = 16, + ScrollbarRounding = 9, + GrabMinSize = 10, + GrabRounding = 3, + LogSliderDeadzone = 4, + TabRounding = 4, + TabBorderSize = 0, + ButtonTextAlign = new Vector2(0.5f, 0.5f), + SelectableTextAlign = new Vector2(0, 0), + DisplaySafeAreaPadding = new Vector2(3, 3), - Alpha = 1, - WindowPadding = new Vector2(8, 8), - WindowRounding = 4, - WindowBorderSize = 0, - WindowTitleAlign = new Vector2(0, 0.5f), - WindowMenuButtonPosition = ImGuiDir.Right, - ChildRounding = 0, - ChildBorderSize = 1, - PopupRounding = 0, - FramePadding = new Vector2(4, 3), - FrameRounding = 4, - FrameBorderSize = 0, - ItemSpacing = new Vector2(8, 4), - ItemInnerSpacing = new Vector2(4, 4), - CellPadding = new Vector2(4, 2), - TouchExtraPadding = new Vector2(0, 0), - IndentSpacing = 21, - ScrollbarSize = 16, - ScrollbarRounding = 9, - GrabMinSize = 10, - GrabRounding = 3, - LogSliderDeadzone = 4, - TabRounding = 4, - TabBorderSize = 0, - ButtonTextAlign = new Vector2(0.5f, 0.5f), - SelectableTextAlign = new Vector2(0, 0), - DisplaySafeAreaPadding = new Vector2(3, 3), - - Colors = new Dictionary + Colors = new Dictionary { { "Text", new Vector4(1f, 1f, 1f, 1f) }, { "TextDisabled", new Vector4(0.5f, 0.5f, 0.5f, 1f) }, @@ -225,240 +225,241 @@ public class StyleModelV1 : StyleModel { "ModalWindowDimBg", new Vector4(0.8f, 0.8f, 0.8f, 0.35f) }, }, - BuiltInColors = new DalamudColors - { - DalamudRed = new Vector4(1f, 0f, 0f, 1f), - DalamudGrey = new Vector4(0.7f, 0.7f, 0.7f, 1f), - DalamudGrey2 = new Vector4(0.7f, 0.7f, 0.7f, 1f), - DalamudGrey3 = new Vector4(0.5f, 0.5f, 0.5f, 1f), - DalamudWhite = new Vector4(1f, 1f, 1f, 1f), - DalamudWhite2 = new Vector4(0.878f, 0.878f, 0.878f, 1f), - DalamudOrange = new Vector4(1f, 0.709f, 0f, 1f), - TankBlue = new Vector4(0f, 0.6f, 1f, 1f), - HealerGreen = new Vector4(0f, 0.8f, 0.1333333f, 1f), - DPSRed = new Vector4(0.7058824f, 0f, 0f, 1f), - }, - }; + BuiltInColors = new DalamudColors + { + DalamudRed = new Vector4(1f, 0f, 0f, 1f), + DalamudGrey = new Vector4(0.7f, 0.7f, 0.7f, 1f), + DalamudGrey2 = new Vector4(0.7f, 0.7f, 0.7f, 1f), + DalamudGrey3 = new Vector4(0.5f, 0.5f, 0.5f, 1f), + DalamudWhite = new Vector4(1f, 1f, 1f, 1f), + DalamudWhite2 = new Vector4(0.878f, 0.878f, 0.878f, 1f), + DalamudOrange = new Vector4(1f, 0.709f, 0f, 1f), + TankBlue = new Vector4(0f, 0.6f, 1f, 1f), + HealerGreen = new Vector4(0f, 0.8f, 0.1333333f, 1f), + DPSRed = new Vector4(0.7058824f, 0f, 0f, 1f), + }, + }; - /// - /// Gets the version prefix for this version. - /// - public static string SerializedPrefix => "DS1"; + /// + /// Gets the version prefix for this version. + /// + public static string SerializedPrefix => "DS1"; #pragma warning disable SA1600 - [JsonProperty("a")] - public float Alpha { get; set; } + [JsonProperty("a")] + public float Alpha { get; set; } - [JsonProperty("b")] - public Vector2 WindowPadding { get; set; } + [JsonProperty("b")] + public Vector2 WindowPadding { get; set; } - [JsonProperty("c")] - public float WindowRounding { get; set; } + [JsonProperty("c")] + public float WindowRounding { get; set; } - [JsonProperty("d")] - public float WindowBorderSize { get; set; } + [JsonProperty("d")] + public float WindowBorderSize { get; set; } - [JsonProperty("e")] - public Vector2 WindowTitleAlign { get; set; } + [JsonProperty("e")] + public Vector2 WindowTitleAlign { get; set; } - [JsonProperty("f")] - public ImGuiDir WindowMenuButtonPosition { get; set; } + [JsonProperty("f")] + public ImGuiDir WindowMenuButtonPosition { get; set; } - [JsonProperty("g")] - public float ChildRounding { get; set; } + [JsonProperty("g")] + public float ChildRounding { get; set; } - [JsonProperty("h")] - public float ChildBorderSize { get; set; } + [JsonProperty("h")] + public float ChildBorderSize { get; set; } - [JsonProperty("i")] - public float PopupRounding { get; set; } + [JsonProperty("i")] + public float PopupRounding { get; set; } - [JsonProperty("j")] - public Vector2 FramePadding { get; set; } + [JsonProperty("j")] + public Vector2 FramePadding { get; set; } - [JsonProperty("k")] - public float FrameRounding { get; set; } + [JsonProperty("k")] + public float FrameRounding { get; set; } - [JsonProperty("l")] - public float FrameBorderSize { get; set; } + [JsonProperty("l")] + public float FrameBorderSize { get; set; } - [JsonProperty("m")] - public Vector2 ItemSpacing { get; set; } + [JsonProperty("m")] + public Vector2 ItemSpacing { get; set; } - [JsonProperty("n")] - public Vector2 ItemInnerSpacing { get; set; } + [JsonProperty("n")] + public Vector2 ItemInnerSpacing { get; set; } - [JsonProperty("o")] - public Vector2 CellPadding { get; set; } + [JsonProperty("o")] + public Vector2 CellPadding { get; set; } - [JsonProperty("p")] - public Vector2 TouchExtraPadding { get; set; } + [JsonProperty("p")] + public Vector2 TouchExtraPadding { get; set; } - [JsonProperty("q")] - public float IndentSpacing { get; set; } + [JsonProperty("q")] + public float IndentSpacing { get; set; } - [JsonProperty("r")] - public float ScrollbarSize { get; set; } + [JsonProperty("r")] + public float ScrollbarSize { get; set; } - [JsonProperty("s")] - public float ScrollbarRounding { get; set; } + [JsonProperty("s")] + public float ScrollbarRounding { get; set; } - [JsonProperty("t")] - public float GrabMinSize { get; set; } + [JsonProperty("t")] + public float GrabMinSize { get; set; } - [JsonProperty("u")] - public float GrabRounding { get; set; } + [JsonProperty("u")] + public float GrabRounding { get; set; } - [JsonProperty("v")] - public float LogSliderDeadzone { get; set; } + [JsonProperty("v")] + public float LogSliderDeadzone { get; set; } - [JsonProperty("w")] - public float TabRounding { get; set; } + [JsonProperty("w")] + public float TabRounding { get; set; } - [JsonProperty("x")] - public float TabBorderSize { get; set; } + [JsonProperty("x")] + public float TabBorderSize { get; set; } - [JsonProperty("y")] - public Vector2 ButtonTextAlign { get; set; } + [JsonProperty("y")] + public Vector2 ButtonTextAlign { get; set; } - [JsonProperty("z")] - public Vector2 SelectableTextAlign { get; set; } + [JsonProperty("z")] + public Vector2 SelectableTextAlign { get; set; } - [JsonProperty("aa")] - public Vector2 DisplaySafeAreaPadding { get; set; } + [JsonProperty("aa")] + public Vector2 DisplaySafeAreaPadding { get; set; } #pragma warning restore SA1600 - /// - /// Gets or sets a dictionary mapping ImGui color names to colors. - /// - [JsonProperty("col")] - public Dictionary Colors { get; set; } + /// + /// Gets or sets a dictionary mapping ImGui color names to colors. + /// + [JsonProperty("col")] + public Dictionary Colors { get; set; } - /// - /// Get a instance via ImGui. - /// - /// The newly created instance. - public static StyleModelV1 Get() - { - var model = new StyleModelV1(); - var style = ImGui.GetStyle(); - - model.Alpha = style.Alpha; - model.WindowPadding = style.WindowPadding; - model.WindowRounding = style.WindowRounding; - model.WindowBorderSize = style.WindowBorderSize; - model.WindowTitleAlign = style.WindowTitleAlign; - model.WindowMenuButtonPosition = style.WindowMenuButtonPosition; - model.ChildRounding = style.ChildRounding; - model.ChildBorderSize = style.ChildBorderSize; - model.PopupRounding = style.PopupRounding; - model.FramePadding = style.FramePadding; - model.FrameRounding = style.FrameRounding; - model.FrameBorderSize = style.FrameBorderSize; - model.ItemSpacing = style.ItemSpacing; - model.ItemInnerSpacing = style.ItemInnerSpacing; - model.CellPadding = style.CellPadding; - model.TouchExtraPadding = style.TouchExtraPadding; - model.IndentSpacing = style.IndentSpacing; - model.ScrollbarSize = style.ScrollbarSize; - model.ScrollbarRounding = style.ScrollbarRounding; - model.GrabMinSize = style.GrabMinSize; - model.GrabRounding = style.GrabRounding; - model.LogSliderDeadzone = style.LogSliderDeadzone; - model.TabRounding = style.TabRounding; - model.TabBorderSize = style.TabBorderSize; - model.ButtonTextAlign = style.ButtonTextAlign; - model.SelectableTextAlign = style.SelectableTextAlign; - model.DisplaySafeAreaPadding = style.DisplaySafeAreaPadding; - - model.Colors = new Dictionary(); - - foreach (var imGuiCol in Enum.GetValues()) + /// + /// Get a instance via ImGui. + /// + /// The newly created instance. + public static StyleModelV1 Get() { - if (imGuiCol == ImGuiCol.COUNT) + var model = new StyleModelV1(); + var style = ImGui.GetStyle(); + + model.Alpha = style.Alpha; + model.WindowPadding = style.WindowPadding; + model.WindowRounding = style.WindowRounding; + model.WindowBorderSize = style.WindowBorderSize; + model.WindowTitleAlign = style.WindowTitleAlign; + model.WindowMenuButtonPosition = style.WindowMenuButtonPosition; + model.ChildRounding = style.ChildRounding; + model.ChildBorderSize = style.ChildBorderSize; + model.PopupRounding = style.PopupRounding; + model.FramePadding = style.FramePadding; + model.FrameRounding = style.FrameRounding; + model.FrameBorderSize = style.FrameBorderSize; + model.ItemSpacing = style.ItemSpacing; + model.ItemInnerSpacing = style.ItemInnerSpacing; + model.CellPadding = style.CellPadding; + model.TouchExtraPadding = style.TouchExtraPadding; + model.IndentSpacing = style.IndentSpacing; + model.ScrollbarSize = style.ScrollbarSize; + model.ScrollbarRounding = style.ScrollbarRounding; + model.GrabMinSize = style.GrabMinSize; + model.GrabRounding = style.GrabRounding; + model.LogSliderDeadzone = style.LogSliderDeadzone; + model.TabRounding = style.TabRounding; + model.TabBorderSize = style.TabBorderSize; + model.ButtonTextAlign = style.ButtonTextAlign; + model.SelectableTextAlign = style.SelectableTextAlign; + model.DisplaySafeAreaPadding = style.DisplaySafeAreaPadding; + + model.Colors = new Dictionary(); + + foreach (var imGuiCol in Enum.GetValues()) { - continue; + if (imGuiCol == ImGuiCol.COUNT) + { + continue; + } + + model.Colors[imGuiCol.ToString()] = style.Colors[(int)imGuiCol]; } - model.Colors[imGuiCol.ToString()] = style.Colors[(int)imGuiCol]; + model.BuiltInColors = new DalamudColors + { + DalamudRed = ImGuiColors.DalamudRed, + DalamudGrey = ImGuiColors.DalamudGrey, + DalamudGrey2 = ImGuiColors.DalamudGrey2, + DalamudGrey3 = ImGuiColors.DalamudGrey3, + DalamudWhite = ImGuiColors.DalamudWhite, + DalamudWhite2 = ImGuiColors.DalamudWhite2, + DalamudOrange = ImGuiColors.DalamudOrange, + TankBlue = ImGuiColors.TankBlue, + HealerGreen = ImGuiColors.HealerGreen, + DPSRed = ImGuiColors.DPSRed, + }; + + return model; } - model.BuiltInColors = new DalamudColors + /// + /// Apply this StyleModel via ImGui. + /// + public override void Apply() { - DalamudRed = ImGuiColors.DalamudRed, - DalamudGrey = ImGuiColors.DalamudGrey, - DalamudGrey2 = ImGuiColors.DalamudGrey2, - DalamudGrey3 = ImGuiColors.DalamudGrey3, - DalamudWhite = ImGuiColors.DalamudWhite, - DalamudWhite2 = ImGuiColors.DalamudWhite2, - DalamudOrange = ImGuiColors.DalamudOrange, - TankBlue = ImGuiColors.TankBlue, - HealerGreen = ImGuiColors.HealerGreen, - DPSRed = ImGuiColors.DPSRed, - }; + var style = ImGui.GetStyle(); - return model; - } + style.Alpha = this.Alpha; + style.WindowPadding = this.WindowPadding; + style.WindowRounding = this.WindowRounding; + style.WindowBorderSize = this.WindowBorderSize; + style.WindowTitleAlign = this.WindowTitleAlign; + style.WindowMenuButtonPosition = this.WindowMenuButtonPosition; + style.ChildRounding = this.ChildRounding; + style.ChildBorderSize = this.ChildBorderSize; + style.PopupRounding = this.PopupRounding; + style.FramePadding = this.FramePadding; + style.FrameRounding = this.FrameRounding; + style.FrameBorderSize = this.FrameBorderSize; + style.ItemSpacing = this.ItemSpacing; + style.ItemInnerSpacing = this.ItemInnerSpacing; + style.CellPadding = this.CellPadding; + style.TouchExtraPadding = this.TouchExtraPadding; + style.IndentSpacing = this.IndentSpacing; + style.ScrollbarSize = this.ScrollbarSize; + style.ScrollbarRounding = this.ScrollbarRounding; + style.GrabMinSize = this.GrabMinSize; + style.GrabRounding = this.GrabRounding; + style.LogSliderDeadzone = this.LogSliderDeadzone; + style.TabRounding = this.TabRounding; + style.TabBorderSize = this.TabBorderSize; + style.ButtonTextAlign = this.ButtonTextAlign; + style.SelectableTextAlign = this.SelectableTextAlign; + style.DisplaySafeAreaPadding = this.DisplaySafeAreaPadding; - /// - /// Apply this StyleModel via ImGui. - /// - public override void Apply() - { - var style = ImGui.GetStyle(); - - style.Alpha = this.Alpha; - style.WindowPadding = this.WindowPadding; - style.WindowRounding = this.WindowRounding; - style.WindowBorderSize = this.WindowBorderSize; - style.WindowTitleAlign = this.WindowTitleAlign; - style.WindowMenuButtonPosition = this.WindowMenuButtonPosition; - style.ChildRounding = this.ChildRounding; - style.ChildBorderSize = this.ChildBorderSize; - style.PopupRounding = this.PopupRounding; - style.FramePadding = this.FramePadding; - style.FrameRounding = this.FrameRounding; - style.FrameBorderSize = this.FrameBorderSize; - style.ItemSpacing = this.ItemSpacing; - style.ItemInnerSpacing = this.ItemInnerSpacing; - style.CellPadding = this.CellPadding; - style.TouchExtraPadding = this.TouchExtraPadding; - style.IndentSpacing = this.IndentSpacing; - style.ScrollbarSize = this.ScrollbarSize; - style.ScrollbarRounding = this.ScrollbarRounding; - style.GrabMinSize = this.GrabMinSize; - style.GrabRounding = this.GrabRounding; - style.LogSliderDeadzone = this.LogSliderDeadzone; - style.TabRounding = this.TabRounding; - style.TabBorderSize = this.TabBorderSize; - style.ButtonTextAlign = this.ButtonTextAlign; - style.SelectableTextAlign = this.SelectableTextAlign; - style.DisplaySafeAreaPadding = this.DisplaySafeAreaPadding; - - foreach (var imGuiCol in Enum.GetValues()) - { - if (imGuiCol == ImGuiCol.COUNT) + foreach (var imGuiCol in Enum.GetValues()) { - continue; + if (imGuiCol == ImGuiCol.COUNT) + { + continue; + } + + style.Colors[(int)imGuiCol] = this.Colors[imGuiCol.ToString()]; } - style.Colors[(int)imGuiCol] = this.Colors[imGuiCol.ToString()]; + this.BuiltInColors?.Apply(); } - this.BuiltInColors?.Apply(); - } + /// + public override void Push() + { + throw new NotImplementedException(); + } - /// - public override void Push() - { - throw new NotImplementedException(); - } - - /// - public override void Pop() - { - throw new NotImplementedException(); + /// + public override void Pop() + { + throw new NotImplementedException(); + } } } diff --git a/Dalamud/Interface/UiBuilder.cs b/Dalamud/Interface/UiBuilder.cs index 3991ed8e5..7811ccd00 100644 --- a/Dalamud/Interface/UiBuilder.cs +++ b/Dalamud/Interface/UiBuilder.cs @@ -13,315 +13,316 @@ using ImGuiScene; using Serilog; using SharpDX.Direct3D11; -namespace Dalamud.Interface; - -/// -/// This class represents the Dalamud UI that is drawn on top of the game. -/// It can be used to draw custom windows and overlays. -/// -public sealed class UiBuilder : IDisposable +namespace Dalamud.Interface { - private readonly Stopwatch stopwatch; - private readonly string namespaceName; - - private bool hasErrorWindow; - /// - /// Initializes a new instance of the class and registers it. - /// You do not have to call this manually. + /// This class represents the Dalamud UI that is drawn on top of the game. + /// It can be used to draw custom windows and overlays. /// - /// The plugin namespace. - internal UiBuilder(string namespaceName) + public sealed class UiBuilder : IDisposable { - this.stopwatch = new Stopwatch(); - this.namespaceName = namespaceName; + private readonly Stopwatch stopwatch; + private readonly string namespaceName; - var interfaceManager = Service.Get(); - interfaceManager.Draw += this.OnDraw; - interfaceManager.BuildFonts += this.OnBuildFonts; - interfaceManager.ResizeBuffers += this.OnResizeBuffers; - } + private bool hasErrorWindow; - /// - /// The event that gets called when Dalamud is ready to draw your windows or overlays. - /// When it is called, you can use static ImGui calls. - /// - public event Action Draw; + /// + /// Initializes a new instance of the class and registers it. + /// You do not have to call this manually. + /// + /// The plugin namespace. + internal UiBuilder(string namespaceName) + { + this.stopwatch = new Stopwatch(); + this.namespaceName = namespaceName; - /// - /// The event that is called when the game's DirectX device is requesting you to resize your buffers. - /// - public event Action ResizeBuffers; + var interfaceManager = Service.Get(); + interfaceManager.Draw += this.OnDraw; + interfaceManager.BuildFonts += this.OnBuildFonts; + interfaceManager.ResizeBuffers += this.OnResizeBuffers; + } - /// - /// Event that is fired when the plugin should open its configuration interface. - /// - public event Action OpenConfigUi; + /// + /// The event that gets called when Dalamud is ready to draw your windows or overlays. + /// When it is called, you can use static ImGui calls. + /// + public event Action Draw; - /// - /// Gets or sets an action that is called any time ImGui fonts need to be rebuilt.
- /// Any ImFontPtr objects that you store can be invalidated when fonts are rebuilt - /// (at any time), so you should both reload your custom fonts and restore those - /// pointers inside this handler.
- /// PLEASE remove this handler inside Dispose, or when you no longer need your fonts! - ///
- public event Action BuildFonts; + /// + /// The event that is called when the game's DirectX device is requesting you to resize your buffers. + /// + public event Action ResizeBuffers; - /// - /// Gets the default Dalamud font based on Noto Sans CJK Medium in 17pt - supporting all game languages and icons. - /// - public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont; + /// + /// Event that is fired when the plugin should open its configuration interface. + /// + public event Action OpenConfigUi; - /// - /// Gets the default Dalamud icon font based on FontAwesome 5 Free solid in 17pt. - /// - public static ImFontPtr IconFont => InterfaceManager.IconFont; + /// + /// Gets or sets an action that is called any time ImGui fonts need to be rebuilt.
+ /// Any ImFontPtr objects that you store can be invalidated when fonts are rebuilt + /// (at any time), so you should both reload your custom fonts and restore those + /// pointers inside this handler.
+ /// PLEASE remove this handler inside Dispose, or when you no longer need your fonts! + ///
+ public event Action BuildFonts; - /// - /// Gets the default Dalamud monospaced font based on Inconsolata Regular in 16pt. - /// - public static ImFontPtr MonoFont => InterfaceManager.MonoFont; + /// + /// Gets the default Dalamud font based on Noto Sans CJK Medium in 17pt - supporting all game languages and icons. + /// + public static ImFontPtr DefaultFont => InterfaceManager.DefaultFont; - /// - /// Gets the game's active Direct3D device. - /// - public Device Device => Service.Get().Device; + /// + /// Gets the default Dalamud icon font based on FontAwesome 5 Free solid in 17pt. + /// + public static ImFontPtr IconFont => InterfaceManager.IconFont; - /// - /// Gets the game's main window handle. - /// - public IntPtr WindowHandlePtr => Service.Get().WindowHandlePtr; + /// + /// Gets the default Dalamud monospaced font based on Inconsolata Regular in 16pt. + /// + public static ImFontPtr MonoFont => InterfaceManager.MonoFont; - /// - /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden. - /// - public bool DisableAutomaticUiHide { get; set; } = false; + /// + /// Gets the game's active Direct3D device. + /// + public Device Device => Service.Get().Device; - /// - /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the user toggles the UI. - /// - public bool DisableUserUiHide { get; set; } = false; + /// + /// Gets the game's main window handle. + /// + public IntPtr WindowHandlePtr => Service.Get().WindowHandlePtr; - /// - /// Gets or sets a value indicating whether this plugin should hide its UI automatically during cutscenes. - /// - public bool DisableCutsceneUiHide { get; set; } = false; + /// + /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the game's UI is hidden. + /// + public bool DisableAutomaticUiHide { get; set; } = false; - /// - /// Gets or sets a value indicating whether this plugin should hide its UI automatically while gpose is active. - /// - public bool DisableGposeUiHide { get; set; } = false; + /// + /// Gets or sets a value indicating whether this plugin should hide its UI automatically when the user toggles the UI. + /// + public bool DisableUserUiHide { get; set; } = false; - /// - /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. - /// - public bool OverrideGameCursor - { - get => Service.Get().OverrideGameCursor; - set => Service.Get().OverrideGameCursor = value; - } + /// + /// Gets or sets a value indicating whether this plugin should hide its UI automatically during cutscenes. + /// + public bool DisableCutsceneUiHide { get; set; } = false; - /// - /// Gets the count of Draw calls made since plugin creation. - /// - public ulong FrameCount { get; private set; } = 0; + /// + /// Gets or sets a value indicating whether this plugin should hide its UI automatically while gpose is active. + /// + public bool DisableGposeUiHide { get; set; } = false; - /// - /// Gets or sets a value indicating whether statistics about UI draw time should be collected. - /// + /// + /// Gets or sets a value indicating whether or not the game's cursor should be overridden with the ImGui cursor. + /// + public bool OverrideGameCursor + { + get => Service.Get().OverrideGameCursor; + set => Service.Get().OverrideGameCursor = value; + } + + /// + /// Gets the count of Draw calls made since plugin creation. + /// + public ulong FrameCount { get; private set; } = 0; + + /// + /// Gets or sets a value indicating whether statistics about UI draw time should be collected. + /// #if DEBUG - internal static bool DoStats { get; set; } = true; + internal static bool DoStats { get; set; } = true; #else internal static bool DoStats { get; set; } = false; #endif - /// - /// Gets a value indicating whether this UiBuilder has a configuration UI registered. - /// - internal bool HasConfigUi => this.OpenConfigUi != null; + /// + /// Gets a value indicating whether this UiBuilder has a configuration UI registered. + /// + internal bool HasConfigUi => this.OpenConfigUi != null; - /// - /// Gets or sets the time this plugin took to draw on the last frame. - /// - internal long LastDrawTime { get; set; } = -1; + /// + /// Gets or sets the time this plugin took to draw on the last frame. + /// + internal long LastDrawTime { get; set; } = -1; - /// - /// Gets or sets the longest amount of time this plugin ever took to draw. - /// - internal long MaxDrawTime { get; set; } = -1; + /// + /// Gets or sets the longest amount of time this plugin ever took to draw. + /// + internal long MaxDrawTime { get; set; } = -1; - /// - /// Gets or sets a history of the last draw times, used to calculate an average. - /// - internal List DrawTimeHistory { get; set; } = new List(); + /// + /// Gets or sets a history of the last draw times, used to calculate an average. + /// + internal List DrawTimeHistory { get; set; } = new List(); - private bool CutsceneActive - { - get + private bool CutsceneActive { - var condition = Service.Get(); - return condition[ConditionFlag.OccupiedInCutSceneEvent] - || condition[ConditionFlag.WatchingCutscene78]; - } - } - - private bool GposeActive - { - get - { - var condition = Service.Get(); - return condition[ConditionFlag.WatchingCutscene]; - } - } - - /// - /// Loads an image from the specified file. - /// - /// The full filepath to the image. - /// A object wrapping the created image. Use inside ImGui.Image(). - public TextureWrap LoadImage(string filePath) - => Service.Get().LoadImage(filePath); - - /// - /// Loads an image from a byte stream, such as a png downloaded into memory. - /// - /// A byte array containing the raw image data. - /// A object wrapping the created image. Use inside ImGui.Image(). - public TextureWrap LoadImage(byte[] imageData) - => Service.Get().LoadImage(imageData); - - /// - /// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use . - /// - /// A byte array containing the raw pixel data. - /// The width of the image contained in . - /// The height of the image contained in . - /// The number of channels (bytes per pixel) of the image contained in . This should usually be 4. - /// A object wrapping the created image. Use inside ImGui.Image(). - public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) - => Service.Get().LoadImageRaw(imageData, width, height, numChannels); - - /// - /// Call this to queue a rebuild of the font atlas.
- /// This will invoke any handlers and ensure that any loaded fonts are - /// ready to be used on the next UI frame. - ///
- public void RebuildFonts() - { - Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName); - Service.Get().RebuildFonts(); - } - - /// - /// Add a notification to the notification queue. - /// - /// The content of the notification. - /// The title of the notification. - /// The type of the notification. - /// The time the notification should be displayed for. - public void AddNotification( - string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = 3000) => - Service.Get().AddNotification(content, title, type, msDelay); - - /// - /// Unregister the UiBuilder. Do not call this in plugin code. - /// - public void Dispose() - { - var interfaceManager = Service.Get(); - - interfaceManager.Draw -= this.OnDraw; - interfaceManager.BuildFonts -= this.OnBuildFonts; - interfaceManager.ResizeBuffers -= this.OnResizeBuffers; - } - - /// - /// Open the registered configuration UI, if it exists. - /// - internal void OpenConfig() - { - this.OpenConfigUi?.Invoke(); - } - - private void OnDraw() - { - var configuration = Service.Get(); - var gameGui = Service.Get(); - var interfaceManager = Service.Get(); - - if ((gameGui.GameUiHidden && configuration.ToggleUiHide && !(this.DisableUserUiHide || this.DisableAutomaticUiHide)) || - (this.CutsceneActive && configuration.ToggleUiHideDuringCutscenes && !(this.DisableCutsceneUiHide || this.DisableAutomaticUiHide)) || - (this.GposeActive && configuration.ToggleUiHideDuringGpose && !(this.DisableGposeUiHide || this.DisableAutomaticUiHide))) - return; - - if (!interfaceManager.FontsReady) - return; - - ImGui.PushID(this.namespaceName); - if (DoStats) - { - this.stopwatch.Restart(); - } - - if (this.hasErrorWindow && ImGui.Begin($"{this.namespaceName} Error", ref this.hasErrorWindow, ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize)) - { - ImGui.Text($"The plugin {this.namespaceName} ran into an error.\nContact the plugin developer for support.\n\nPlease try restarting your game."); - ImGui.Spacing(); - - if (ImGui.Button("OK")) + get { - this.hasErrorWindow = false; + var condition = Service.Get(); + return condition[ConditionFlag.OccupiedInCutSceneEvent] + || condition[ConditionFlag.WatchingCutscene78]; + } + } + + private bool GposeActive + { + get + { + var condition = Service.Get(); + return condition[ConditionFlag.WatchingCutscene]; + } + } + + /// + /// Loads an image from the specified file. + /// + /// The full filepath to the image. + /// A object wrapping the created image. Use inside ImGui.Image(). + public TextureWrap LoadImage(string filePath) + => Service.Get().LoadImage(filePath); + + /// + /// Loads an image from a byte stream, such as a png downloaded into memory. + /// + /// A byte array containing the raw image data. + /// A object wrapping the created image. Use inside ImGui.Image(). + public TextureWrap LoadImage(byte[] imageData) + => Service.Get().LoadImage(imageData); + + /// + /// Loads an image from raw unformatted pixel data, with no type or header information. To load formatted data, use . + /// + /// A byte array containing the raw pixel data. + /// The width of the image contained in . + /// The height of the image contained in . + /// The number of channels (bytes per pixel) of the image contained in . This should usually be 4. + /// A object wrapping the created image. Use inside ImGui.Image(). + public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) + => Service.Get().LoadImageRaw(imageData, width, height, numChannels); + + /// + /// Call this to queue a rebuild of the font atlas.
+ /// This will invoke any handlers and ensure that any loaded fonts are + /// ready to be used on the next UI frame. + ///
+ public void RebuildFonts() + { + Log.Verbose("[FONT] {0} plugin is initiating FONT REBUILD", this.namespaceName); + Service.Get().RebuildFonts(); + } + + /// + /// Add a notification to the notification queue. + /// + /// The content of the notification. + /// The title of the notification. + /// The type of the notification. + /// The time the notification should be displayed for. + public void AddNotification( + string content, string? title = null, NotificationType type = NotificationType.None, uint msDelay = 3000) => + Service.Get().AddNotification(content, title, type, msDelay); + + /// + /// Unregister the UiBuilder. Do not call this in plugin code. + /// + public void Dispose() + { + var interfaceManager = Service.Get(); + + interfaceManager.Draw -= this.OnDraw; + interfaceManager.BuildFonts -= this.OnBuildFonts; + interfaceManager.ResizeBuffers -= this.OnResizeBuffers; + } + + /// + /// Open the registered configuration UI, if it exists. + /// + internal void OpenConfig() + { + this.OpenConfigUi?.Invoke(); + } + + private void OnDraw() + { + var configuration = Service.Get(); + var gameGui = Service.Get(); + var interfaceManager = Service.Get(); + + if ((gameGui.GameUiHidden && configuration.ToggleUiHide && !(this.DisableUserUiHide || this.DisableAutomaticUiHide)) || + (this.CutsceneActive && configuration.ToggleUiHideDuringCutscenes && !(this.DisableCutsceneUiHide || this.DisableAutomaticUiHide)) || + (this.GposeActive && configuration.ToggleUiHideDuringGpose && !(this.DisableGposeUiHide || this.DisableAutomaticUiHide))) + return; + + if (!interfaceManager.FontsReady) + return; + + ImGui.PushID(this.namespaceName); + if (DoStats) + { + this.stopwatch.Restart(); } - ImGui.End(); + if (this.hasErrorWindow && ImGui.Begin($"{this.namespaceName} Error", ref this.hasErrorWindow, ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize)) + { + ImGui.Text($"The plugin {this.namespaceName} ran into an error.\nContact the plugin developer for support.\n\nPlease try restarting your game."); + ImGui.Spacing(); + + if (ImGui.Button("OK")) + { + this.hasErrorWindow = false; + } + + ImGui.End(); + } + + ImGuiManagedAsserts.ImGuiContextSnapshot snapshot = null; + if (this.Draw != null) + { + snapshot = ImGuiManagedAsserts.GetSnapshot(); + } + + try + { + this.Draw?.Invoke(); + } + catch (Exception ex) + { + Log.Error(ex, "[{0}] UiBuilder OnBuildUi caught exception", this.namespaceName); + this.Draw = null; + this.OpenConfigUi = null; + + this.hasErrorWindow = true; + } + + // Only if Draw was successful + if (this.Draw != null) + { + ImGuiManagedAsserts.ReportProblems(this.namespaceName, snapshot); + } + + this.FrameCount++; + + if (DoStats) + { + this.stopwatch.Stop(); + this.LastDrawTime = this.stopwatch.ElapsedTicks; + this.MaxDrawTime = Math.Max(this.LastDrawTime, this.MaxDrawTime); + this.DrawTimeHistory.Add(this.LastDrawTime); + while (this.DrawTimeHistory.Count > 100) this.DrawTimeHistory.RemoveAt(0); + } + + ImGui.PopID(); } - ImGuiManagedAsserts.ImGuiContextSnapshot snapshot = null; - if (this.Draw != null) + private void OnBuildFonts() { - snapshot = ImGuiManagedAsserts.GetSnapshot(); + this.BuildFonts?.Invoke(); } - try + private void OnResizeBuffers() { - this.Draw?.Invoke(); + this.ResizeBuffers?.Invoke(); } - catch (Exception ex) - { - Log.Error(ex, "[{0}] UiBuilder OnBuildUi caught exception", this.namespaceName); - this.Draw = null; - this.OpenConfigUi = null; - - this.hasErrorWindow = true; - } - - // Only if Draw was successful - if (this.Draw != null) - { - ImGuiManagedAsserts.ReportProblems(this.namespaceName, snapshot); - } - - this.FrameCount++; - - if (DoStats) - { - this.stopwatch.Stop(); - this.LastDrawTime = this.stopwatch.ElapsedTicks; - this.MaxDrawTime = Math.Max(this.LastDrawTime, this.MaxDrawTime); - this.DrawTimeHistory.Add(this.LastDrawTime); - while (this.DrawTimeHistory.Count > 100) this.DrawTimeHistory.RemoveAt(0); - } - - ImGui.PopID(); - } - - private void OnBuildFonts() - { - this.BuildFonts?.Invoke(); - } - - private void OnResizeBuffers() - { - this.ResizeBuffers?.Invoke(); } } diff --git a/Dalamud/Interface/Windowing/Window.cs b/Dalamud/Interface/Windowing/Window.cs index 8e1c65d26..f173f8256 100644 --- a/Dalamud/Interface/Windowing/Window.cs +++ b/Dalamud/Interface/Windowing/Window.cs @@ -4,297 +4,298 @@ using Dalamud.Configuration.Internal; using Dalamud.Game.ClientState.Keys; using ImGuiNET; -namespace Dalamud.Interface.Windowing; - -/// -/// Base class you can use to implement an ImGui window for use with the built-in . -/// -public abstract class Window +namespace Dalamud.Interface.Windowing { - private static bool wasEscPressedLastFrame = false; - - private bool internalLastIsOpen = false; - private bool internalIsOpen = false; - /// - /// Initializes a new instance of the class. + /// Base class you can use to implement an ImGui window for use with the built-in . /// - /// The name/ID of this window. - /// If you have multiple windows with the same name, you will need to - /// append an unique ID to it by specifying it after "###" behind the window title. - /// - /// The of this window. - /// Whether or not this window should be limited to the main game window. - protected Window(string name, ImGuiWindowFlags flags = ImGuiWindowFlags.None, bool forceMainWindow = false) + public abstract class Window { - this.WindowName = name; - this.Flags = flags; - this.ForceMainWindow = forceMainWindow; - } + private static bool wasEscPressedLastFrame = false; - /// - /// Gets or sets the namespace of the window. - /// - public string? Namespace { get; set; } + private bool internalLastIsOpen = false; + private bool internalIsOpen = false; - /// - /// Gets or sets the name of the window. - /// If you have multiple windows with the same name, you will need to - /// append an unique ID to it by specifying it after "###" behind the window title. - /// - public string WindowName { get; set; } - - /// - /// Gets a value indicating whether the window is focused. - /// - public bool IsFocused { get; private set; } - - /// - /// Gets or sets a value indicating whether this window is to be closed with a hotkey, like Escape, and keep game addons open in turn if it is closed. - /// - public bool RespectCloseHotkey { get; set; } = true; - - /// - /// Gets or sets the position of this window. - /// - public Vector2? Position { get; set; } - - /// - /// Gets or sets the condition that defines when the position of this window is set. - /// - public ImGuiCond PositionCondition { get; set; } - - /// - /// Gets or sets the size of the window. - /// - public Vector2? Size { get; set; } - - /// - /// Gets or sets the condition that defines when the size of this window is set. - /// - public ImGuiCond SizeCondition { get; set; } - - /// - /// Gets or sets the size constraints of the window. - /// - public WindowSizeConstraints? SizeConstraints { get; set; } - - /// - /// Gets or sets a value indicating whether or not this window is collapsed. - /// - public bool? Collapsed { get; set; } - - /// - /// Gets or sets the condition that defines when the collapsed state of this window is set. - /// - public ImGuiCond CollapsedCondition { get; set; } - - /// - /// Gets or sets the window flags. - /// - public ImGuiWindowFlags Flags { get; set; } - - /// - /// Gets or sets a value indicating whether or not this ImGui window will be forced to stay inside the main game window. - /// - public bool ForceMainWindow { get; set; } - - /// - /// Gets or sets this window's background alpha value. - /// - public float? BgAlpha { get; set; } - - /// - /// Gets or sets a value indicating whether or not this window will stay open. - /// - public bool IsOpen - { - get => this.internalIsOpen; - set => this.internalIsOpen = value; - } - - /// - /// Toggle window is open state. - /// - public void Toggle() - { - this.IsOpen ^= true; - } - - /// - /// Code to be executed before conditionals are applied and the window is drawn. - /// - public virtual void PreDraw() - { - } - - /// - /// Code to be executed after the window is drawn. - /// - public virtual void PostDraw() - { - } - - /// - /// Code to be executed every time the window renders. - /// - /// - /// In this method, implement your drawing code. - /// You do NOT need to ImGui.Begin your window. - /// - public abstract void Draw(); - - /// - /// Code to be executed when the window is opened. - /// - public virtual void OnOpen() - { - } - - /// - /// Code to be executed when the window is closed. - /// - public virtual void OnClose() - { - } - - /// - /// Draw the window via ImGui. - /// - internal void DrawInternal() - { - if (!this.IsOpen) + /// + /// Initializes a new instance of the class. + /// + /// The name/ID of this window. + /// If you have multiple windows with the same name, you will need to + /// append an unique ID to it by specifying it after "###" behind the window title. + /// + /// The of this window. + /// Whether or not this window should be limited to the main game window. + protected Window(string name, ImGuiWindowFlags flags = ImGuiWindowFlags.None, bool forceMainWindow = false) { - if (this.internalIsOpen != this.internalLastIsOpen) - { - this.internalLastIsOpen = this.internalIsOpen; - this.OnClose(); + this.WindowName = name; + this.Flags = flags; + this.ForceMainWindow = forceMainWindow; + } - this.IsFocused = false; + /// + /// Gets or sets the namespace of the window. + /// + public string? Namespace { get; set; } + + /// + /// Gets or sets the name of the window. + /// If you have multiple windows with the same name, you will need to + /// append an unique ID to it by specifying it after "###" behind the window title. + /// + public string WindowName { get; set; } + + /// + /// Gets a value indicating whether the window is focused. + /// + public bool IsFocused { get; private set; } + + /// + /// Gets or sets a value indicating whether this window is to be closed with a hotkey, like Escape, and keep game addons open in turn if it is closed. + /// + public bool RespectCloseHotkey { get; set; } = true; + + /// + /// Gets or sets the position of this window. + /// + public Vector2? Position { get; set; } + + /// + /// Gets or sets the condition that defines when the position of this window is set. + /// + public ImGuiCond PositionCondition { get; set; } + + /// + /// Gets or sets the size of the window. + /// + public Vector2? Size { get; set; } + + /// + /// Gets or sets the condition that defines when the size of this window is set. + /// + public ImGuiCond SizeCondition { get; set; } + + /// + /// Gets or sets the size constraints of the window. + /// + public WindowSizeConstraints? SizeConstraints { get; set; } + + /// + /// Gets or sets a value indicating whether or not this window is collapsed. + /// + public bool? Collapsed { get; set; } + + /// + /// Gets or sets the condition that defines when the collapsed state of this window is set. + /// + public ImGuiCond CollapsedCondition { get; set; } + + /// + /// Gets or sets the window flags. + /// + public ImGuiWindowFlags Flags { get; set; } + + /// + /// Gets or sets a value indicating whether or not this ImGui window will be forced to stay inside the main game window. + /// + public bool ForceMainWindow { get; set; } + + /// + /// Gets or sets this window's background alpha value. + /// + public float? BgAlpha { get; set; } + + /// + /// Gets or sets a value indicating whether or not this window will stay open. + /// + public bool IsOpen + { + get => this.internalIsOpen; + set => this.internalIsOpen = value; + } + + /// + /// Toggle window is open state. + /// + public void Toggle() + { + this.IsOpen ^= true; + } + + /// + /// Code to be executed before conditionals are applied and the window is drawn. + /// + public virtual void PreDraw() + { + } + + /// + /// Code to be executed after the window is drawn. + /// + public virtual void PostDraw() + { + } + + /// + /// Code to be executed every time the window renders. + /// + /// + /// In this method, implement your drawing code. + /// You do NOT need to ImGui.Begin your window. + /// + public abstract void Draw(); + + /// + /// Code to be executed when the window is opened. + /// + public virtual void OnOpen() + { + } + + /// + /// Code to be executed when the window is closed. + /// + public virtual void OnClose() + { + } + + /// + /// Draw the window via ImGui. + /// + internal void DrawInternal() + { + if (!this.IsOpen) + { + if (this.internalIsOpen != this.internalLastIsOpen) + { + this.internalLastIsOpen = this.internalIsOpen; + this.OnClose(); + + this.IsFocused = false; + } + + return; } - return; - } + var hasNamespace = !string.IsNullOrEmpty(this.Namespace); - var hasNamespace = !string.IsNullOrEmpty(this.Namespace); + if (hasNamespace) + ImGui.PushID(this.Namespace); - if (hasNamespace) - ImGui.PushID(this.Namespace); - - this.PreDraw(); - this.ApplyConditionals(); - - if (this.ForceMainWindow) - ImGuiHelpers.ForceNextWindowMainViewport(); - - if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen) - { - this.internalLastIsOpen = this.internalIsOpen; - this.OnOpen(); - } - - var wasFocused = this.IsFocused; - if (wasFocused) - { - var style = ImGui.GetStyle(); - var focusedHeaderColor = style.Colors[(int)ImGuiCol.TitleBgActive]; - ImGui.PushStyleColor(ImGuiCol.TitleBgCollapsed, focusedHeaderColor); - } - - if (ImGui.Begin(this.WindowName, ref this.internalIsOpen, this.Flags)) - { - // Draw the actual window contents - this.Draw(); - } - - if (wasFocused) - { - ImGui.PopStyleColor(); - } - - this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); - - var escapeDown = Service.Get()[VirtualKey.ESCAPE]; - var isAllowed = Service.Get().IsFocusManagementEnabled; - if (escapeDown && this.IsFocused && isAllowed && !wasEscPressedLastFrame && this.RespectCloseHotkey) - { - this.IsOpen = false; - wasEscPressedLastFrame = true; - } - else if (!escapeDown && wasEscPressedLastFrame) - { - wasEscPressedLastFrame = false; - } - - ImGui.End(); - - this.PostDraw(); - - if (hasNamespace) - ImGui.PopID(); - } - - // private void CheckState() - // { - // if (this.internalLastIsOpen != this.internalIsOpen) - // { - // if (this.internalIsOpen) - // { - // this.OnOpen(); - // } - // else - // { - // this.OnClose(); - // } - // } - // } - - private void ApplyConditionals() - { - if (this.Position.HasValue) - { - var pos = this.Position.Value; + this.PreDraw(); + this.ApplyConditionals(); if (this.ForceMainWindow) - pos += ImGuiHelpers.MainViewport.Pos; + ImGuiHelpers.ForceNextWindowMainViewport(); - ImGui.SetNextWindowPos(pos, this.PositionCondition); + if (this.internalLastIsOpen != this.internalIsOpen && this.internalIsOpen) + { + this.internalLastIsOpen = this.internalIsOpen; + this.OnOpen(); + } + + var wasFocused = this.IsFocused; + if (wasFocused) + { + var style = ImGui.GetStyle(); + var focusedHeaderColor = style.Colors[(int)ImGuiCol.TitleBgActive]; + ImGui.PushStyleColor(ImGuiCol.TitleBgCollapsed, focusedHeaderColor); + } + + if (ImGui.Begin(this.WindowName, ref this.internalIsOpen, this.Flags)) + { + // Draw the actual window contents + this.Draw(); + } + + if (wasFocused) + { + ImGui.PopStyleColor(); + } + + this.IsFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows); + + var escapeDown = Service.Get()[VirtualKey.ESCAPE]; + var isAllowed = Service.Get().IsFocusManagementEnabled; + if (escapeDown && this.IsFocused && isAllowed && !wasEscPressedLastFrame && this.RespectCloseHotkey) + { + this.IsOpen = false; + wasEscPressedLastFrame = true; + } + else if (!escapeDown && wasEscPressedLastFrame) + { + wasEscPressedLastFrame = false; + } + + ImGui.End(); + + this.PostDraw(); + + if (hasNamespace) + ImGui.PopID(); } - if (this.Size.HasValue) + // private void CheckState() + // { + // if (this.internalLastIsOpen != this.internalIsOpen) + // { + // if (this.internalIsOpen) + // { + // this.OnOpen(); + // } + // else + // { + // this.OnClose(); + // } + // } + // } + + private void ApplyConditionals() { - ImGui.SetNextWindowSize(this.Size.Value * ImGuiHelpers.GlobalScale, this.SizeCondition); - } + if (this.Position.HasValue) + { + var pos = this.Position.Value; - if (this.Collapsed.HasValue) - { - ImGui.SetNextWindowCollapsed(this.Collapsed.Value, this.CollapsedCondition); - } + if (this.ForceMainWindow) + pos += ImGuiHelpers.MainViewport.Pos; - if (this.SizeConstraints.HasValue) - { - ImGui.SetNextWindowSizeConstraints(this.SizeConstraints.Value.MinimumSize * ImGuiHelpers.GlobalScale, this.SizeConstraints.Value.MaximumSize * ImGuiHelpers.GlobalScale); - } + ImGui.SetNextWindowPos(pos, this.PositionCondition); + } - if (this.BgAlpha.HasValue) - { - ImGui.SetNextWindowBgAlpha(this.BgAlpha.Value); - } - } + if (this.Size.HasValue) + { + ImGui.SetNextWindowSize(this.Size.Value * ImGuiHelpers.GlobalScale, this.SizeCondition); + } - /// - /// Structure detailing the size constraints of a window. - /// - public struct WindowSizeConstraints - { - /// - /// Gets or sets the minimum size of the window. - /// - public Vector2 MinimumSize { get; set; } + if (this.Collapsed.HasValue) + { + ImGui.SetNextWindowCollapsed(this.Collapsed.Value, this.CollapsedCondition); + } + + if (this.SizeConstraints.HasValue) + { + ImGui.SetNextWindowSizeConstraints(this.SizeConstraints.Value.MinimumSize * ImGuiHelpers.GlobalScale, this.SizeConstraints.Value.MaximumSize * ImGuiHelpers.GlobalScale); + } + + if (this.BgAlpha.HasValue) + { + ImGui.SetNextWindowBgAlpha(this.BgAlpha.Value); + } + } /// - /// Gets or sets the maximum size of the window. + /// Structure detailing the size constraints of a window. /// - public Vector2 MaximumSize { get; set; } + public struct WindowSizeConstraints + { + /// + /// Gets or sets the minimum size of the window. + /// + public Vector2 MinimumSize { get; set; } + + /// + /// Gets or sets the maximum size of the window. + /// + public Vector2 MaximumSize { get; set; } + } } } diff --git a/Dalamud/Interface/Windowing/WindowSystem.cs b/Dalamud/Interface/Windowing/WindowSystem.cs index 18739763a..16dfcdf5c 100644 --- a/Dalamud/Interface/Windowing/WindowSystem.cs +++ b/Dalamud/Interface/Windowing/WindowSystem.cs @@ -6,133 +6,134 @@ using Dalamud.Interface.Internal.ManagedAsserts; using ImGuiNET; using Serilog; -namespace Dalamud.Interface.Windowing; - -/// -/// Class running a WindowSystem using implementations to simplify ImGui windowing. -/// -public class WindowSystem +namespace Dalamud.Interface.Windowing { - private static DateTimeOffset lastAnyFocus; - - private readonly List windows = new(); - - private string lastFocusedWindowName = string.Empty; - /// - /// Initializes a new instance of the class. + /// Class running a WindowSystem using implementations to simplify ImGui windowing. /// - /// The name/ID-space of this . - public WindowSystem(string? imNamespace = null) + public class WindowSystem { - this.Namespace = imNamespace; - } + private static DateTimeOffset lastAnyFocus; - /// - /// Gets a value indicating whether any contains any - /// that has focus and is not marked to be excluded from consideration. - /// - public static bool HasAnyWindowSystemFocus { get; internal set; } = false; + private readonly List windows = new(); - /// - /// Gets the name of the currently focused window system that is redirecting normal escape functionality. - /// - public static string FocusedWindowSystemNamespace { get; internal set; } = string.Empty; + private string lastFocusedWindowName = string.Empty; - /// - /// Gets the timespan since the last time any window was focused. - /// - public static TimeSpan TimeSinceLastAnyFocus => DateTimeOffset.Now - lastAnyFocus; - - /// - /// Gets a value indicating whether any window in this has focus and is - /// not marked to be excluded from consideration. - /// - public bool HasAnyFocus { get; private set; } - - /// - /// Gets or sets the name/ID-space of this . - /// - public string? Namespace { get; set; } - - /// - /// Add a window to this . - /// - /// The window to add. - public void AddWindow(Window window) - { - if (this.windows.Any(x => x.WindowName == window.WindowName)) - throw new ArgumentException("A window with this name/ID already exists."); - - this.windows.Add(window); - } - - /// - /// Remove a window from this . - /// - /// The window to remove. - public void RemoveWindow(Window window) - { - if (!this.windows.Contains(window)) - throw new ArgumentException("This window is not registered on this WindowSystem."); - - this.windows.Remove(window); - } - - /// - /// Remove all windows from this . - /// - public void RemoveAllWindows() => this.windows.Clear(); - - /// - /// Draw all registered windows using ImGui. - /// - public void Draw() - { - var hasNamespace = !string.IsNullOrEmpty(this.Namespace); - - if (hasNamespace) - ImGui.PushID(this.Namespace); - - foreach (var window in this.windows) + /// + /// Initializes a new instance of the class. + /// + /// The name/ID-space of this . + public WindowSystem(string? imNamespace = null) { + this.Namespace = imNamespace; + } + + /// + /// Gets a value indicating whether any contains any + /// that has focus and is not marked to be excluded from consideration. + /// + public static bool HasAnyWindowSystemFocus { get; internal set; } = false; + + /// + /// Gets the name of the currently focused window system that is redirecting normal escape functionality. + /// + public static string FocusedWindowSystemNamespace { get; internal set; } = string.Empty; + + /// + /// Gets the timespan since the last time any window was focused. + /// + public static TimeSpan TimeSinceLastAnyFocus => DateTimeOffset.Now - lastAnyFocus; + + /// + /// Gets a value indicating whether any window in this has focus and is + /// not marked to be excluded from consideration. + /// + public bool HasAnyFocus { get; private set; } + + /// + /// Gets or sets the name/ID-space of this . + /// + public string? Namespace { get; set; } + + /// + /// Add a window to this . + /// + /// The window to add. + public void AddWindow(Window window) + { + if (this.windows.Any(x => x.WindowName == window.WindowName)) + throw new ArgumentException("A window with this name/ID already exists."); + + this.windows.Add(window); + } + + /// + /// Remove a window from this . + /// + /// The window to remove. + public void RemoveWindow(Window window) + { + if (!this.windows.Contains(window)) + throw new ArgumentException("This window is not registered on this WindowSystem."); + + this.windows.Remove(window); + } + + /// + /// Remove all windows from this . + /// + public void RemoveAllWindows() => this.windows.Clear(); + + /// + /// Draw all registered windows using ImGui. + /// + public void Draw() + { + var hasNamespace = !string.IsNullOrEmpty(this.Namespace); + + if (hasNamespace) + ImGui.PushID(this.Namespace); + + foreach (var window in this.windows) + { #if DEBUG - // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); + // Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}"); #endif - var snapshot = ImGuiManagedAsserts.GetSnapshot(); + var snapshot = ImGuiManagedAsserts.GetSnapshot(); - window.DrawInternal(); + window.DrawInternal(); - var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName; - ImGuiManagedAsserts.ReportProblems(source, snapshot); - } - - var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused && window.RespectCloseHotkey); - this.HasAnyFocus = focusedWindow != default; - - if (this.HasAnyFocus) - { - if (this.lastFocusedWindowName != focusedWindow.WindowName) - { - Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{focusedWindow.WindowName}\" has focus now"); - this.lastFocusedWindowName = focusedWindow.WindowName; + var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName; + ImGuiManagedAsserts.ReportProblems(source, snapshot); } - HasAnyWindowSystemFocus = true; - FocusedWindowSystemNamespace = this.Namespace; + var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused && window.RespectCloseHotkey); + this.HasAnyFocus = focusedWindow != default; - lastAnyFocus = DateTimeOffset.Now; - } - else - { - if (this.lastFocusedWindowName != string.Empty) + if (this.HasAnyFocus) { - Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{this.lastFocusedWindowName}\" lost focus"); - this.lastFocusedWindowName = string.Empty; - } - } + if (this.lastFocusedWindowName != focusedWindow.WindowName) + { + Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{focusedWindow.WindowName}\" has focus now"); + this.lastFocusedWindowName = focusedWindow.WindowName; + } - if (hasNamespace) - ImGui.PopID(); + HasAnyWindowSystemFocus = true; + FocusedWindowSystemNamespace = this.Namespace; + + lastAnyFocus = DateTimeOffset.Now; + } + else + { + if (this.lastFocusedWindowName != string.Empty) + { + Log.Verbose($"WindowSystem \"{this.Namespace}\" Window \"{this.lastFocusedWindowName}\" lost focus"); + this.lastFocusedWindowName = string.Empty; + } + } + + if (hasNamespace) + ImGui.PopID(); + } } } diff --git a/Dalamud/IoC/Internal/InterfaceVersionAttribute.cs b/Dalamud/IoC/Internal/InterfaceVersionAttribute.cs index db69e17aa..f6e6c1001 100644 --- a/Dalamud/IoC/Internal/InterfaceVersionAttribute.cs +++ b/Dalamud/IoC/Internal/InterfaceVersionAttribute.cs @@ -1,24 +1,25 @@ using System; -namespace Dalamud.IoC.Internal; - -/// -/// This attribute represents the current version of a module that is loaded in the Service Locator. -/// -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] -internal class InterfaceVersionAttribute : Attribute +namespace Dalamud.IoC.Internal { /// - /// Initializes a new instance of the class. + /// This attribute represents the current version of a module that is loaded in the Service Locator. /// - /// The current version. - public InterfaceVersionAttribute(string version) + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] + internal class InterfaceVersionAttribute : Attribute { - this.Version = new(version); - } + /// + /// Initializes a new instance of the class. + /// + /// The current version. + public InterfaceVersionAttribute(string version) + { + this.Version = new(version); + } - /// - /// Gets the service version. - /// - public Version Version { get; } + /// + /// Gets the service version. + /// + public Version Version { get; } + } } diff --git a/Dalamud/IoC/Internal/ObjectInstance.cs b/Dalamud/IoC/Internal/ObjectInstance.cs index 5d232f36b..7475fcfaf 100644 --- a/Dalamud/IoC/Internal/ObjectInstance.cs +++ b/Dalamud/IoC/Internal/ObjectInstance.cs @@ -1,30 +1,31 @@ using System; using System.Reflection; -namespace Dalamud.IoC.Internal; - -/// -/// An object instance registered in the . -/// -internal class ObjectInstance +namespace Dalamud.IoC.Internal { /// - /// Initializes a new instance of the class. + /// An object instance registered in the . /// - /// The underlying instance. - public ObjectInstance(object instance) + internal class ObjectInstance { - this.Instance = new WeakReference(instance); - this.Version = instance.GetType().GetCustomAttribute(); + /// + /// Initializes a new instance of the class. + /// + /// The underlying instance. + public ObjectInstance(object instance) + { + this.Instance = new WeakReference(instance); + this.Version = instance.GetType().GetCustomAttribute(); + } + + /// + /// Gets the current version of the instance, if it exists. + /// + public InterfaceVersionAttribute? Version { get; } + + /// + /// Gets a reference to the underlying instance. + /// + public WeakReference Instance { get; } } - - /// - /// Gets the current version of the instance, if it exists. - /// - public InterfaceVersionAttribute? Version { get; } - - /// - /// Gets a reference to the underlying instance. - /// - public WeakReference Instance { get; } } diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs index 5ee15a1b6..a211107bb 100644 --- a/Dalamud/IoC/Internal/ServiceContainer.cs +++ b/Dalamud/IoC/Internal/ServiceContainer.cs @@ -6,229 +6,230 @@ using System.Runtime.Serialization; using Dalamud.Logging.Internal; -namespace Dalamud.IoC.Internal; - -/// -/// A simple singleton-only IOC container that provides (optional) version-based dependency resolution. -/// -internal class ServiceContainer : IServiceProvider +namespace Dalamud.IoC.Internal { - private static readonly ModuleLog Log = new("SERVICECONTAINER"); - - private readonly Dictionary instances = new(); - /// - /// Register a singleton object of any type into the current IOC container. + /// A simple singleton-only IOC container that provides (optional) version-based dependency resolution. /// - /// The existing instance to register in the container. - /// The interface to register. - public void RegisterSingleton(T instance) + internal class ServiceContainer : IServiceProvider { - if (instance == null) + private static readonly ModuleLog Log = new("SERVICECONTAINER"); + + private readonly Dictionary instances = new(); + + /// + /// Register a singleton object of any type into the current IOC container. + /// + /// The existing instance to register in the container. + /// The interface to register. + public void RegisterSingleton(T instance) { - throw new ArgumentNullException(nameof(instance)); - } - - this.instances[typeof(T)] = new(instance); - } - - /// - /// Create an object. - /// - /// The type of object to create. - /// Scoped objects to be included in the constructor. - /// The created object. - public object? Create(Type objectType, params object[] scopedObjects) - { - var ctor = this.FindApplicableCtor(objectType, scopedObjects); - if (ctor == null) - { - Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName); - return null; - } - - // validate dependency versions (if they exist) - var parameters = ctor.GetParameters().Select(p => - { - var parameterType = p.ParameterType; - var requiredVersion = p.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; - return (parameterType, requiredVersion); - }); - - var versionCheck = parameters.All(p => CheckInterfaceVersion(p.requiredVersion, p.parameterType)); - - if (!versionCheck) - { - Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); - return null; - } - - var resolvedParams = parameters - .Select(p => + if (instance == null) { - var service = this.GetService(p.parameterType, scopedObjects); + throw new ArgumentNullException(nameof(instance)); + } + + this.instances[typeof(T)] = new(instance); + } + + /// + /// Create an object. + /// + /// The type of object to create. + /// Scoped objects to be included in the constructor. + /// The created object. + public object? Create(Type objectType, params object[] scopedObjects) + { + var ctor = this.FindApplicableCtor(objectType, scopedObjects); + if (ctor == null) + { + Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName); + return null; + } + + // validate dependency versions (if they exist) + var parameters = ctor.GetParameters().Select(p => + { + var parameterType = p.ParameterType; + var requiredVersion = p.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; + return (parameterType, requiredVersion); + }); + + var versionCheck = parameters.All(p => CheckInterfaceVersion(p.requiredVersion, p.parameterType)); + + if (!versionCheck) + { + Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); + return null; + } + + var resolvedParams = parameters + .Select(p => + { + var service = this.GetService(p.parameterType, scopedObjects); + + if (service == null) + { + Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName); + } + + return service; + }) + .ToArray(); + + var hasNull = resolvedParams.Any(p => p == null); + if (hasNull) + { + Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName); + return null; + } + + var instance = FormatterServices.GetUninitializedObject(objectType); + + if (!this.InjectProperties(instance, scopedObjects)) + { + Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName); + return null; + } + + ctor.Invoke(instance, resolvedParams); + + return instance; + } + + /// + /// Inject interfaces into public or static properties on the provided object. + /// The properties have to be marked with the . + /// The properties can be marked with the to lock down versions. + /// + /// The object instance. + /// Scoped objects. + /// Whether or not the injection was successful. + public bool InjectProperties(object instance, params object[] scopedObjects) + { + var objectType = instance.GetType(); + + var props = objectType.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | + BindingFlags.NonPublic).Where(x => x.GetCustomAttributes(typeof(PluginServiceAttribute)).Any()).Select( + propertyInfo => + { + var requiredVersion = propertyInfo.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; + return (propertyInfo, requiredVersion); + }).ToArray(); + + var versionCheck = props.All(x => CheckInterfaceVersion(x.requiredVersion, x.propertyInfo.PropertyType)); + + if (!versionCheck) + { + Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); + return false; + } + + foreach (var prop in props) + { + var service = this.GetService(prop.propertyInfo.PropertyType, scopedObjects); if (service == null) { - Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName); + Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName); + return false; } - return service; - }) - .ToArray(); + prop.propertyInfo.SetValue(instance, service); + } - var hasNull = resolvedParams.Any(p => p == null); - if (hasNull) - { - Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName); - return null; + return true; } - var instance = FormatterServices.GetUninitializedObject(objectType); + /// + object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType); - if (!this.InjectProperties(instance, scopedObjects)) + private static bool CheckInterfaceVersion(RequiredVersionAttribute? requiredVersion, Type parameterType) { - Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName); - return null; - } + // if there's no required version, ignore it + if (requiredVersion == null) + return true; - ctor.Invoke(instance, resolvedParams); + // if there's no requested version, ignore it + var declVersion = parameterType.GetCustomAttribute(); + if (declVersion == null) + return true; - return instance; - } + if (declVersion.Version == requiredVersion.Version) + return true; - /// - /// Inject interfaces into public or static properties on the provided object. - /// The properties have to be marked with the . - /// The properties can be marked with the to lock down versions. - /// - /// The object instance. - /// Scoped objects. - /// Whether or not the injection was successful. - public bool InjectProperties(object instance, params object[] scopedObjects) - { - var objectType = instance.GetType(); + Log.Error( + "Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}", + requiredVersion.Version, + declVersion.Version, + parameterType.FullName); - var props = objectType.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | - BindingFlags.NonPublic).Where(x => x.GetCustomAttributes(typeof(PluginServiceAttribute)).Any()).Select( - propertyInfo => - { - var requiredVersion = propertyInfo.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; - return (propertyInfo, requiredVersion); - }).ToArray(); - - var versionCheck = props.All(x => CheckInterfaceVersion(x.requiredVersion, x.propertyInfo.PropertyType)); - - if (!versionCheck) - { - Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); return false; } - foreach (var prop in props) + private object? GetService(Type serviceType, object[] scopedObjects) { - var service = this.GetService(prop.propertyInfo.PropertyType, scopedObjects); - - if (service == null) + var singletonService = this.GetService(serviceType); + if (singletonService != null) { - Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName); - return false; + return singletonService; } - prop.propertyInfo.SetValue(instance, service); + // resolve dependency from scoped objects + var scoped = scopedObjects.FirstOrDefault(o => o.GetType() == serviceType); + if (scoped == default) + { + return null; + } + + return scoped; } - return true; - } - - /// - object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType); - - private static bool CheckInterfaceVersion(RequiredVersionAttribute? requiredVersion, Type parameterType) - { - // if there's no required version, ignore it - if (requiredVersion == null) - return true; - - // if there's no requested version, ignore it - var declVersion = parameterType.GetCustomAttribute(); - if (declVersion == null) - return true; - - if (declVersion.Version == requiredVersion.Version) - return true; - - Log.Error( - "Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}", - requiredVersion.Version, - declVersion.Version, - parameterType.FullName); - - return false; - } - - private object? GetService(Type serviceType, object[] scopedObjects) - { - var singletonService = this.GetService(serviceType); - if (singletonService != null) + private object? GetService(Type serviceType) { - return singletonService; - } + var hasInstance = this.instances.TryGetValue(serviceType, out var service); + if (hasInstance && service.Instance.IsAlive) + { + return service.Instance.Target; + } - // resolve dependency from scoped objects - var scoped = scopedObjects.FirstOrDefault(o => o.GetType() == serviceType); - if (scoped == default) - { return null; } - return scoped; - } - - private object? GetService(Type serviceType) - { - var hasInstance = this.instances.TryGetValue(serviceType, out var service); - if (hasInstance && service.Instance.IsAlive) + private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects) { - return service.Instance.Target; - } + // get a list of all the available types: scoped and singleton + var types = scopedObjects + .Select(o => o.GetType()) + .Union(this.instances.Keys) + .ToArray(); - return null; - } - - private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects) - { - // get a list of all the available types: scoped and singleton - var types = scopedObjects - .Select(o => o.GetType()) - .Union(this.instances.Keys) - .ToArray(); - - var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); - foreach (var ctor in ctors) - { - if (this.ValidateCtor(ctor, types)) + var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); + foreach (var ctor in ctors) { - return ctor; + if (this.ValidateCtor(ctor, types)) + { + return ctor; + } } + + return null; } - return null; - } - - private bool ValidateCtor(ConstructorInfo ctor, Type[] types) - { - var parameters = ctor.GetParameters(); - foreach (var parameter in parameters) + private bool ValidateCtor(ConstructorInfo ctor, Type[] types) { - var contains = types.Contains(parameter.ParameterType); - if (!contains) + var parameters = ctor.GetParameters(); + foreach (var parameter in parameters) { - Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName); - return false; + var contains = types.Contains(parameter.ParameterType); + if (!contains) + { + Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName); + return false; + } } - } - return true; + return true; + } } } diff --git a/Dalamud/IoC/PluginInterfaceAttribute.cs b/Dalamud/IoC/PluginInterfaceAttribute.cs index 1711a5e84..98d330117 100644 --- a/Dalamud/IoC/PluginInterfaceAttribute.cs +++ b/Dalamud/IoC/PluginInterfaceAttribute.cs @@ -1,11 +1,12 @@ using System; -namespace Dalamud.IoC; - -/// -/// This attribute indicates whether the decorated class should be exposed to plugins via IoC. -/// -[AttributeUsage(AttributeTargets.Class)] -public class PluginInterfaceAttribute : Attribute +namespace Dalamud.IoC { + /// + /// This attribute indicates whether the decorated class should be exposed to plugins via IoC. + /// + [AttributeUsage(AttributeTargets.Class)] + public class PluginInterfaceAttribute : Attribute + { + } } diff --git a/Dalamud/IoC/PluginServiceAttribute.cs b/Dalamud/IoC/PluginServiceAttribute.cs index 341592cf1..80baceb55 100644 --- a/Dalamud/IoC/PluginServiceAttribute.cs +++ b/Dalamud/IoC/PluginServiceAttribute.cs @@ -1,11 +1,12 @@ -using System; +using System; -namespace Dalamud.IoC; - -/// -/// This attribute indicates whether an applicable service should be injected into the plugin. -/// -[AttributeUsage(AttributeTargets.Property)] -public class PluginServiceAttribute : Attribute +namespace Dalamud.IoC { + /// + /// This attribute indicates whether an applicable service should be injected into the plugin. + /// + [AttributeUsage(AttributeTargets.Property)] + public class PluginServiceAttribute : Attribute + { + } } diff --git a/Dalamud/IoC/RequiredVersionAttribute.cs b/Dalamud/IoC/RequiredVersionAttribute.cs index 97aca56bb..a456ef783 100644 --- a/Dalamud/IoC/RequiredVersionAttribute.cs +++ b/Dalamud/IoC/RequiredVersionAttribute.cs @@ -1,24 +1,25 @@ using System; -namespace Dalamud.IoC; - -/// -/// This attribute indicates the version of a service module that is required for the plugin to load. -/// -[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] -public class RequiredVersionAttribute : Attribute +namespace Dalamud.IoC { /// - /// Initializes a new instance of the class. + /// This attribute indicates the version of a service module that is required for the plugin to load. /// - /// The required version. - public RequiredVersionAttribute(string version) + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public class RequiredVersionAttribute : Attribute { - this.Version = new(version); - } + /// + /// Initializes a new instance of the class. + /// + /// The required version. + public RequiredVersionAttribute(string version) + { + this.Version = new(version); + } - /// - /// Gets the required version. - /// - public Version Version { get; } + /// + /// Gets the required version. + /// + public Version Version { get; } + } } diff --git a/Dalamud/Localization.cs b/Dalamud/Localization.cs index 3188d5296..e8bb28578 100644 --- a/Dalamud/Localization.cs +++ b/Dalamud/Localization.cs @@ -7,144 +7,145 @@ using System.Reflection; using CheapLoc; using Serilog; -namespace Dalamud; - -/// -/// Class handling localization. -/// -public class Localization +namespace Dalamud { /// - /// Array of language codes which have a valid translation in Dalamud. + /// Class handling localization. /// - public static readonly string[] ApplicableLangCodes = { "de", "ja", "fr", "it", "es", "ko", "no", "ru" }; - - private const string FallbackLangCode = "en"; - - private readonly string locResourceDirectory; - private readonly string locResourcePrefix; - private readonly bool useEmbedded; - private readonly Assembly assembly; - - /// - /// Initializes a new instance of the class. - /// - /// The working directory to load language files from. - /// The prefix on the loc resource file name (e.g. dalamud_). - /// Use embedded loc resource files. - public Localization(string locResourceDirectory, string locResourcePrefix = "", bool useEmbedded = false) + public class Localization { - this.locResourceDirectory = locResourceDirectory; - this.locResourcePrefix = locResourcePrefix; - this.useEmbedded = useEmbedded; - this.assembly = Assembly.GetCallingAssembly(); - } + /// + /// Array of language codes which have a valid translation in Dalamud. + /// + public static readonly string[] ApplicableLangCodes = { "de", "ja", "fr", "it", "es", "ko", "no", "ru" }; - /// - /// Delegate for the event that occurs when the language is changed. - /// - /// The language code of the new language. - public delegate void LocalizationChangedDelegate(string langCode); + private const string FallbackLangCode = "en"; - /// - /// Event that occurs when the language is changed. - /// - public event LocalizationChangedDelegate LocalizationChanged; + private readonly string locResourceDirectory; + private readonly string locResourcePrefix; + private readonly bool useEmbedded; + private readonly Assembly assembly; - /// - /// Search the set-up localization data for the provided assembly for the given string key and return it. - /// If the key is not present, the fallback is shown. - /// The fallback is also required to create the string files to be localized. - /// - /// The string key to be returned. - /// The fallback string, usually your source language. - /// The localized string, fallback or string key if not found. - // TODO: This breaks loc export, since it's being called without string args. Fix in CheapLoc. - public static string Localize(string key, string fallBack) - { - return Loc.Localize(key, fallBack, Assembly.GetCallingAssembly()); - } - - /// - /// Set up the UI language with the users' local UI culture. - /// - public void SetupWithUiCulture() - { - try + /// + /// Initializes a new instance of the class. + /// + /// The working directory to load language files from. + /// The prefix on the loc resource file name (e.g. dalamud_). + /// Use embedded loc resource files. + public Localization(string locResourceDirectory, string locResourcePrefix = "", bool useEmbedded = false) { - var currentUiLang = CultureInfo.CurrentUICulture; - Log.Information("Trying to set up Loc for culture {0}", currentUiLang.TwoLetterISOLanguageName); + this.locResourceDirectory = locResourceDirectory; + this.locResourcePrefix = locResourcePrefix; + this.useEmbedded = useEmbedded; + this.assembly = Assembly.GetCallingAssembly(); + } - if (ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) + /// + /// Delegate for the event that occurs when the language is changed. + /// + /// The language code of the new language. + public delegate void LocalizationChangedDelegate(string langCode); + + /// + /// Event that occurs when the language is changed. + /// + public event LocalizationChangedDelegate LocalizationChanged; + + /// + /// Search the set-up localization data for the provided assembly for the given string key and return it. + /// If the key is not present, the fallback is shown. + /// The fallback is also required to create the string files to be localized. + /// + /// The string key to be returned. + /// The fallback string, usually your source language. + /// The localized string, fallback or string key if not found. + // TODO: This breaks loc export, since it's being called without string args. Fix in CheapLoc. + public static string Localize(string key, string fallBack) + { + return Loc.Localize(key, fallBack, Assembly.GetCallingAssembly()); + } + + /// + /// Set up the UI language with the users' local UI culture. + /// + public void SetupWithUiCulture() + { + try { - this.SetupWithLangCode(currentUiLang.TwoLetterISOLanguageName); + var currentUiLang = CultureInfo.CurrentUICulture; + Log.Information("Trying to set up Loc for culture {0}", currentUiLang.TwoLetterISOLanguageName); + + if (ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) + { + this.SetupWithLangCode(currentUiLang.TwoLetterISOLanguageName); + } + else + { + this.SetupWithFallbacks(); + } } - else + catch (Exception ex) { + Log.Error(ex, "Could not get language information. Setting up fallbacks."); this.SetupWithFallbacks(); } } - catch (Exception ex) - { - Log.Error(ex, "Could not get language information. Setting up fallbacks."); - this.SetupWithFallbacks(); - } - } - /// - /// Set up the UI language with "fallbacks"(original English text). - /// - public void SetupWithFallbacks() - { - this.LocalizationChanged?.Invoke(FallbackLangCode); - Loc.SetupWithFallbacks(this.assembly); - } - - /// - /// Set up the UI language with the provided language code. - /// - /// The language code to set up the UI language with. - public void SetupWithLangCode(string langCode) - { - if (langCode.ToLower() == FallbackLangCode) + /// + /// Set up the UI language with "fallbacks"(original English text). + /// + public void SetupWithFallbacks() { - this.SetupWithFallbacks(); - return; + this.LocalizationChanged?.Invoke(FallbackLangCode); + Loc.SetupWithFallbacks(this.assembly); } - this.LocalizationChanged?.Invoke(langCode); - - try + /// + /// Set up the UI language with the provided language code. + /// + /// The language code to set up the UI language with. + public void SetupWithLangCode(string langCode) { - Loc.Setup(this.ReadLocData(langCode), this.assembly); - } - catch (Exception ex) - { - Log.Error(ex, "Could not load loc {0}. Setting up fallbacks.", langCode); - this.SetupWithFallbacks(); - } - } + if (langCode.ToLower() == FallbackLangCode) + { + this.SetupWithFallbacks(); + return; + } - /// - /// Saves localizable JSON data in the current working directory for the provided assembly. - /// - public void ExportLocalizable() - { - Loc.ExportLocalizableForAssembly(this.assembly); - } + this.LocalizationChanged?.Invoke(langCode); - private string ReadLocData(string langCode) - { - if (this.useEmbedded) - { - var resourceStream = this.assembly.GetManifestResourceStream($"{this.locResourceDirectory}{this.locResourcePrefix}{langCode}.json"); - if (resourceStream == null) - return null; - - using var reader = new StreamReader(resourceStream); - return reader.ReadToEnd(); + try + { + Loc.Setup(this.ReadLocData(langCode), this.assembly); + } + catch (Exception ex) + { + Log.Error(ex, "Could not load loc {0}. Setting up fallbacks.", langCode); + this.SetupWithFallbacks(); + } } - return File.ReadAllText(Path.Combine(this.locResourceDirectory, $"{this.locResourcePrefix}{langCode}.json")); + /// + /// Saves localizable JSON data in the current working directory for the provided assembly. + /// + public void ExportLocalizable() + { + Loc.ExportLocalizableForAssembly(this.assembly); + } + + private string ReadLocData(string langCode) + { + if (this.useEmbedded) + { + var resourceStream = this.assembly.GetManifestResourceStream($"{this.locResourceDirectory}{this.locResourcePrefix}{langCode}.json"); + if (resourceStream == null) + return null; + + using var reader = new StreamReader(resourceStream); + return reader.ReadToEnd(); + } + + return File.ReadAllText(Path.Combine(this.locResourceDirectory, $"{this.locResourcePrefix}{langCode}.json")); + } } } diff --git a/Dalamud/Logging/Internal/ModuleLog.cs b/Dalamud/Logging/Internal/ModuleLog.cs index fa763ba13..9ec951cad 100644 --- a/Dalamud/Logging/Internal/ModuleLog.cs +++ b/Dalamud/Logging/Internal/ModuleLog.cs @@ -1,123 +1,124 @@ using System; -namespace Dalamud.Logging.Internal; - -/// -/// Class offering various methods to allow for logging in Dalamud modules. -/// -internal class ModuleLog +namespace Dalamud.Logging.Internal { - private readonly string moduleName; - /// - /// Initializes a new instance of the class. - /// This class can be used to prefix logging messages with a Dalamud module name prefix. For example, "[PLUGINR] ...". + /// Class offering various methods to allow for logging in Dalamud modules. /// - /// The module name. - public ModuleLog(string moduleName) + internal class ModuleLog { - this.moduleName = moduleName; + private readonly string moduleName; + + /// + /// Initializes a new instance of the class. + /// This class can be used to prefix logging messages with a Dalamud module name prefix. For example, "[PLUGINR] ...". + /// + /// The module name. + public ModuleLog(string moduleName) + { + this.moduleName = moduleName; + } + + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Verbose(string messageTemplate, params object[] values) + => Serilog.Log.Verbose($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Verbose(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Verbose(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Debug(string messageTemplate, params object[] values) + => Serilog.Log.Debug($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Debug(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Debug(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated information message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Information(string messageTemplate, params object[] values) + => Serilog.Log.Information($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated information message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Information(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Information(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Warning(string messageTemplate, params object[] values) + => Serilog.Log.Warning($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Warning(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Warning(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated error message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Error(string messageTemplate, params object[] values) + => Serilog.Log.Error($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated error message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Error(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Error(exception, $"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public void Fatal(string messageTemplate, params object[] values) + => Serilog.Log.Fatal($"[{this.moduleName}] {messageTemplate}", values); + + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public void Fatal(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Fatal(exception, $"[{this.moduleName}] {messageTemplate}", values); } - - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Verbose(string messageTemplate, params object[] values) - => Serilog.Log.Verbose($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Verbose(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Verbose(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Debug(string messageTemplate, params object[] values) - => Serilog.Log.Debug($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Debug(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Debug(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated information message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Information(string messageTemplate, params object[] values) - => Serilog.Log.Information($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated information message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Information(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Information(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Warning(string messageTemplate, params object[] values) - => Serilog.Log.Warning($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Warning(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Warning(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated error message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Error(string messageTemplate, params object[] values) - => Serilog.Log.Error($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated error message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Error(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Error(exception, $"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public void Fatal(string messageTemplate, params object[] values) - => Serilog.Log.Fatal($"[{this.moduleName}] {messageTemplate}", values); - - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public void Fatal(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Fatal(exception, $"[{this.moduleName}] {messageTemplate}", values); } diff --git a/Dalamud/Logging/Internal/SerilogEventSink.cs b/Dalamud/Logging/Internal/SerilogEventSink.cs index 4c1aa3803..eb7c23d10 100644 --- a/Dalamud/Logging/Internal/SerilogEventSink.cs +++ b/Dalamud/Logging/Internal/SerilogEventSink.cs @@ -3,48 +3,49 @@ using System; using Serilog.Core; using Serilog.Events; -namespace Dalamud.Logging.Internal; - -/// -/// Serilog event sink. -/// -internal class SerilogEventSink : ILogEventSink +namespace Dalamud.Logging.Internal { - private static SerilogEventSink instance; - private readonly IFormatProvider formatProvider; - /// - /// Initializes a new instance of the class. + /// Serilog event sink. /// - /// Logging format provider. - private SerilogEventSink(IFormatProvider formatProvider) + internal class SerilogEventSink : ILogEventSink { - this.formatProvider = formatProvider; - } + private static SerilogEventSink instance; + private readonly IFormatProvider formatProvider; - /// - /// Event on a log line being emitted. - /// - public event EventHandler<(string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception)>? LogLine; - - /// - /// Gets the default instance. - /// - public static SerilogEventSink Instance => instance ??= new SerilogEventSink(null); - - /// - /// Emit a log event. - /// - /// Log event to be emitted. - public void Emit(LogEvent logEvent) - { - var message = logEvent.RenderMessage(this.formatProvider); - - if (logEvent.Exception != null) + /// + /// Initializes a new instance of the class. + /// + /// Logging format provider. + private SerilogEventSink(IFormatProvider formatProvider) { - message += "\n" + logEvent.Exception; + this.formatProvider = formatProvider; } - this.LogLine?.Invoke(this, (message, logEvent.Level, logEvent.Timestamp, logEvent.Exception)); + /// + /// Event on a log line being emitted. + /// + public event EventHandler<(string Line, LogEventLevel Level, DateTimeOffset TimeStamp, Exception? Exception)>? LogLine; + + /// + /// Gets the default instance. + /// + public static SerilogEventSink Instance => instance ??= new SerilogEventSink(null); + + /// + /// Emit a log event. + /// + /// Log event to be emitted. + public void Emit(LogEvent logEvent) + { + var message = logEvent.RenderMessage(this.formatProvider); + + if (logEvent.Exception != null) + { + message += "\n" + logEvent.Exception; + } + + this.LogLine?.Invoke(this, (message, logEvent.Level, logEvent.Timestamp, logEvent.Exception)); + } } } diff --git a/Dalamud/Logging/Internal/TaskTracker.cs b/Dalamud/Logging/Internal/TaskTracker.cs index 4d5625dd0..3888d55db 100644 --- a/Dalamud/Logging/Internal/TaskTracker.cs +++ b/Dalamud/Logging/Internal/TaskTracker.cs @@ -8,222 +8,223 @@ using System.Threading.Tasks; using Dalamud.Game; using Serilog; -namespace Dalamud.Logging.Internal; - -/// -/// Class responsible for tracking asynchronous tasks. -/// -internal class TaskTracker : IDisposable +namespace Dalamud.Logging.Internal { - private static readonly ModuleLog Log = new("TT"); - private static readonly List TrackedTasksInternal = new(); - private static readonly ConcurrentQueue NewlyCreatedTasks = new(); - private static bool clearRequested = false; - - private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook; - /// - /// Initializes a new instance of the class. + /// Class responsible for tracking asynchronous tasks. /// - public TaskTracker() + internal class TaskTracker : IDisposable { - this.ApplyPatch(); + private static readonly ModuleLog Log = new("TT"); + private static readonly List TrackedTasksInternal = new(); + private static readonly ConcurrentQueue NewlyCreatedTasks = new(); + private static bool clearRequested = false; - var framework = Service.Get(); - framework.Update += this.FrameworkOnUpdate; - } + private MonoMod.RuntimeDetour.Hook? scheduleAndStartHook; - /// - /// Gets a read-only list of tracked tasks. - /// - public static IReadOnlyList Tasks => TrackedTasksInternal.ToArray(); - - /// - /// Clear the list of tracked tasks. - /// - public static void Clear() => clearRequested = true; - - /// - /// Update the tracked data. - /// - public static void UpdateData() - { - if (clearRequested) + /// + /// Initializes a new instance of the class. + /// + public TaskTracker() { - TrackedTasksInternal.Clear(); - clearRequested = false; + this.ApplyPatch(); + + var framework = Service.Get(); + framework.Update += this.FrameworkOnUpdate; } - var i = 0; - var pruned = 0; - // Prune old tasks. Technically a memory "leak" that grows infinitely otherwise. - while (i < TrackedTasksInternal.Count) + /// + /// Gets a read-only list of tracked tasks. + /// + public static IReadOnlyList Tasks => TrackedTasksInternal.ToArray(); + + /// + /// Clear the list of tracked tasks. + /// + public static void Clear() => clearRequested = true; + + /// + /// Update the tracked data. + /// + public static void UpdateData() { - var taskInfo = TrackedTasksInternal[i]; - if (taskInfo.IsCompleted && !taskInfo.IsBeingViewed && TrackedTasksInternal.Count > 1000) + if (clearRequested) { - TrackedTasksInternal.RemoveAt(i); - pruned++; - continue; + TrackedTasksInternal.Clear(); + clearRequested = false; } - i++; - } - - // if (pruned > 0) - // Log.Debug($"Pruned {pruned} tasks"); - - // Consume from a queue to prevent iteration errors - while (NewlyCreatedTasks.TryDequeue(out var newTask)) - { - TrackedTasksInternal.Add(newTask); - } - - // Update each task - for (i = 0; i < TrackedTasksInternal.Count; i++) - { - var taskInfo = TrackedTasksInternal[i]; - if (taskInfo.Task == null) - continue; - - taskInfo.IsCompleted = taskInfo.Task.IsCompleted; - taskInfo.IsFaulted = taskInfo.Task.IsFaulted; - taskInfo.IsCanceled = taskInfo.Task.IsCanceled; - taskInfo.IsCompletedSuccessfully = taskInfo.Task.IsCompletedSuccessfully; - taskInfo.Status = taskInfo.Task.Status; - - if (taskInfo.IsCompleted || taskInfo.IsFaulted || taskInfo.IsCanceled || - taskInfo.IsCompletedSuccessfully) + var i = 0; + var pruned = 0; + // Prune old tasks. Technically a memory "leak" that grows infinitely otherwise. + while (i < TrackedTasksInternal.Count) { - taskInfo.Exception = taskInfo.Task.Exception; + var taskInfo = TrackedTasksInternal[i]; + if (taskInfo.IsCompleted && !taskInfo.IsBeingViewed && TrackedTasksInternal.Count > 1000) + { + TrackedTasksInternal.RemoveAt(i); + pruned++; + continue; + } - taskInfo.Task = null; - taskInfo.FinishTime = DateTime.Now; + i++; + } + + // if (pruned > 0) + // Log.Debug($"Pruned {pruned} tasks"); + + // Consume from a queue to prevent iteration errors + while (NewlyCreatedTasks.TryDequeue(out var newTask)) + { + TrackedTasksInternal.Add(newTask); + } + + // Update each task + for (i = 0; i < TrackedTasksInternal.Count; i++) + { + var taskInfo = TrackedTasksInternal[i]; + if (taskInfo.Task == null) + continue; + + taskInfo.IsCompleted = taskInfo.Task.IsCompleted; + taskInfo.IsFaulted = taskInfo.Task.IsFaulted; + taskInfo.IsCanceled = taskInfo.Task.IsCanceled; + taskInfo.IsCompletedSuccessfully = taskInfo.Task.IsCompletedSuccessfully; + taskInfo.Status = taskInfo.Task.Status; + + if (taskInfo.IsCompleted || taskInfo.IsFaulted || taskInfo.IsCanceled || + taskInfo.IsCompletedSuccessfully) + { + taskInfo.Exception = taskInfo.Task.Exception; + + taskInfo.Task = null; + taskInfo.FinishTime = DateTime.Now; + } } } - } - /// - public void Dispose() - { - this.scheduleAndStartHook?.Dispose(); - - var framework = Service.Get(); - framework.Update -= this.FrameworkOnUpdate; - } - - private static bool AddToActiveTasksHook(Func orig, Task self) - { - orig(self); - - var trace = new StackTrace(); - NewlyCreatedTasks.Enqueue(new TaskInfo + /// + public void Dispose() { - Task = self, - Id = self.Id, - StackTrace = trace, - }); + this.scheduleAndStartHook?.Dispose(); - return true; - } - - private void FrameworkOnUpdate(Framework framework) - { - UpdateData(); - } - - private void ApplyPatch() - { - var targetType = typeof(Task); - - var debugField = targetType.GetField("s_asyncDebuggingEnabled", BindingFlags.Static | BindingFlags.NonPublic); - debugField.SetValue(null, true); - - Log.Information("s_asyncDebuggingEnabled: {0}", debugField.GetValue(null)); - - var targetMethod = targetType.GetMethod("AddToActiveTasks", BindingFlags.Static | BindingFlags.NonPublic); - var patchMethod = typeof(TaskTracker).GetMethod(nameof(AddToActiveTasksHook), BindingFlags.NonPublic | BindingFlags.Static); - - if (targetMethod == null) - { - Log.Error("AddToActiveTasks TargetMethod null!"); - return; + var framework = Service.Get(); + framework.Update -= this.FrameworkOnUpdate; } - if (patchMethod == null) + private static bool AddToActiveTasksHook(Func orig, Task self) { - Log.Error("AddToActiveTasks PatchMethod null!"); - return; + orig(self); + + var trace = new StackTrace(); + NewlyCreatedTasks.Enqueue(new TaskInfo + { + Task = self, + Id = self.Id, + StackTrace = trace, + }); + + return true; } - this.scheduleAndStartHook = new MonoMod.RuntimeDetour.Hook(targetMethod, patchMethod); + private void FrameworkOnUpdate(Framework framework) + { + UpdateData(); + } - Log.Information("AddToActiveTasks Hooked!"); - } + private void ApplyPatch() + { + var targetType = typeof(Task); - /// - /// Class representing a tracked task. - /// - internal class TaskInfo - { - /// - /// Gets or sets the tracked task. - /// - public Task? Task { get; set; } + var debugField = targetType.GetField("s_asyncDebuggingEnabled", BindingFlags.Static | BindingFlags.NonPublic); + debugField.SetValue(null, true); + + Log.Information("s_asyncDebuggingEnabled: {0}", debugField.GetValue(null)); + + var targetMethod = targetType.GetMethod("AddToActiveTasks", BindingFlags.Static | BindingFlags.NonPublic); + var patchMethod = typeof(TaskTracker).GetMethod(nameof(AddToActiveTasksHook), BindingFlags.NonPublic | BindingFlags.Static); + + if (targetMethod == null) + { + Log.Error("AddToActiveTasks TargetMethod null!"); + return; + } + + if (patchMethod == null) + { + Log.Error("AddToActiveTasks PatchMethod null!"); + return; + } + + this.scheduleAndStartHook = new MonoMod.RuntimeDetour.Hook(targetMethod, patchMethod); + + Log.Information("AddToActiveTasks Hooked!"); + } /// - /// Gets or sets the ID of the task. + /// Class representing a tracked task. /// - public int Id { get; set; } + internal class TaskInfo + { + /// + /// Gets or sets the tracked task. + /// + public Task? Task { get; set; } - /// - /// Gets or sets the stack trace of where the task was started. - /// - public StackTrace? StackTrace { get; set; } + /// + /// Gets or sets the ID of the task. + /// + public int Id { get; set; } - /// - /// Gets or sets a value indicating whether or not the task was completed. - /// - public bool IsCompleted { get; set; } + /// + /// Gets or sets the stack trace of where the task was started. + /// + public StackTrace? StackTrace { get; set; } - /// - /// Gets or sets a value indicating whether or not the task faulted. - /// - public bool IsFaulted { get; set; } + /// + /// Gets or sets a value indicating whether or not the task was completed. + /// + public bool IsCompleted { get; set; } - /// - /// Gets or sets a value indicating whether or not the task was canceled. - /// - public bool IsCanceled { get; set; } + /// + /// Gets or sets a value indicating whether or not the task faulted. + /// + public bool IsFaulted { get; set; } - /// - /// Gets or sets a value indicating whether or not the task was completed successfully. - /// - public bool IsCompletedSuccessfully { get; set; } + /// + /// Gets or sets a value indicating whether or not the task was canceled. + /// + public bool IsCanceled { get; set; } - /// - /// Gets or sets a value indicating whether this task is being viewed. - /// - public bool IsBeingViewed { get; set; } + /// + /// Gets or sets a value indicating whether or not the task was completed successfully. + /// + public bool IsCompletedSuccessfully { get; set; } - /// - /// Gets or sets the status of the task. - /// - public TaskStatus Status { get; set; } + /// + /// Gets or sets a value indicating whether this task is being viewed. + /// + public bool IsBeingViewed { get; set; } - /// - /// Gets the start time of the task. - /// - public DateTime StartTime { get; } = DateTime.Now; + /// + /// Gets or sets the status of the task. + /// + public TaskStatus Status { get; set; } - /// - /// Gets or sets the end time of the task. - /// - public DateTime FinishTime { get; set; } + /// + /// Gets the start time of the task. + /// + public DateTime StartTime { get; } = DateTime.Now; - /// - /// Gets or sets the exception that occurred within the task. - /// - public AggregateException? Exception { get; set; } + /// + /// Gets or sets the end time of the task. + /// + public DateTime FinishTime { get; set; } + + /// + /// Gets or sets the exception that occurred within the task. + /// + public AggregateException? Exception { get; set; } + } } } diff --git a/Dalamud/Logging/PluginLog.cs b/Dalamud/Logging/PluginLog.cs index 4b652f5c5..a21363854 100644 --- a/Dalamud/Logging/PluginLog.cs +++ b/Dalamud/Logging/PluginLog.cs @@ -1,239 +1,240 @@ using System; using System.Reflection; -namespace Dalamud.Logging; - -/// -/// Class offering various static methods to allow for logging in plugins. -/// -public static class PluginLog +namespace Dalamud.Logging { - #region "Log" prefixed Serilog style methods - /// - /// Log a templated message to the in-game debug log. + /// Class offering various static methods to allow for logging in plugins. /// - /// The message template. - /// Values to log. - public static void Log(string messageTemplate, params object[] values) - => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + public static class PluginLog + { + #region "Log" prefixed Serilog style methods - /// - /// Log a templated message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Log(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Log(string messageTemplate, params object[] values) + => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogVerbose(string messageTemplate, params object[] values) - => Serilog.Log.Verbose($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Log(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogVerbose(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Verbose(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogVerbose(string messageTemplate, params object[] values) + => Serilog.Log.Verbose($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogDebug(string messageTemplate, params object[] values) - => Serilog.Log.Debug($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogVerbose(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Verbose(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogDebug(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Debug(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogDebug(string messageTemplate, params object[] values) + => Serilog.Log.Debug($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated information message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogInformation(string messageTemplate, params object[] values) - => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogDebug(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Debug(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated information message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogInformation(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated information message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogInformation(string messageTemplate, params object[] values) + => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogWarning(string messageTemplate, params object[] values) - => Serilog.Log.Warning($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated information message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogInformation(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogWarning(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Warning(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogWarning(string messageTemplate, params object[] values) + => Serilog.Log.Warning($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated error message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogError(string messageTemplate, params object[] values) - => Serilog.Log.Error($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogWarning(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Warning(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated error message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogError(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Error(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated error message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogError(string messageTemplate, params object[] values) + => Serilog.Log.Error($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void LogFatal(string messageTemplate, params object[] values) - => Serilog.Log.Fatal($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated error message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogError(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Error(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void LogFatal(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Fatal(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void LogFatal(string messageTemplate, params object[] values) + => Serilog.Log.Fatal($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - #endregion + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void LogFatal(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Fatal(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - #region Serilog style methods + #endregion - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Verbose(string messageTemplate, params object[] values) - => Serilog.Log.Verbose($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + #region Serilog style methods - /// - /// Log a templated verbose message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Verbose(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Verbose(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Verbose(string messageTemplate, params object[] values) + => Serilog.Log.Verbose($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Debug(string messageTemplate, params object[] values) - => Serilog.Log.Debug($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated verbose message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Verbose(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Verbose(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated debug message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Debug(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Debug(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Debug(string messageTemplate, params object[] values) + => Serilog.Log.Debug($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated information message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Information(string messageTemplate, params object[] values) - => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated debug message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Debug(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Debug(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated information message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Information(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated information message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Information(string messageTemplate, params object[] values) + => Serilog.Log.Information($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Warning(string messageTemplate, params object[] values) - => Serilog.Log.Warning($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated information message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Information(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Information(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated warning message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Warning(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Warning(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Warning(string messageTemplate, params object[] values) + => Serilog.Log.Warning($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated error message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Error(string messageTemplate, params object[] values) - => Serilog.Log.Error($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated warning message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Warning(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Warning(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated error message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Error(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Error(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated error message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Error(string messageTemplate, params object[] values) + => Serilog.Log.Error($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The message template. - /// Values to log. - public static void Fatal(string messageTemplate, params object[] values) - => Serilog.Log.Fatal($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated error message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Error(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Error(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - /// - /// Log a templated fatal message to the in-game debug log. - /// - /// The exception that caused the error. - /// The message template. - /// Values to log. - public static void Fatal(Exception exception, string messageTemplate, params object[] values) - => Serilog.Log.Fatal(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The message template. + /// Values to log. + public static void Fatal(string messageTemplate, params object[] values) + => Serilog.Log.Fatal($"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); - #endregion + /// + /// Log a templated fatal message to the in-game debug log. + /// + /// The exception that caused the error. + /// The message template. + /// Values to log. + public static void Fatal(Exception exception, string messageTemplate, params object[] values) + => Serilog.Log.Fatal(exception, $"[{Assembly.GetCallingAssembly().GetName().Name}] {messageTemplate}", values); + + #endregion + } } diff --git a/Dalamud/Memory/Exceptions/MemoryAllocationException.cs b/Dalamud/Memory/Exceptions/MemoryAllocationException.cs index 61f124bad..e289c8782 100644 --- a/Dalamud/Memory/Exceptions/MemoryAllocationException.cs +++ b/Dalamud/Memory/Exceptions/MemoryAllocationException.cs @@ -1,46 +1,47 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions; - -/// -/// An exception thrown when VirtualAlloc fails. -/// -public class MemoryAllocationException : MemoryException +namespace Dalamud.Memory.Exceptions { /// - /// Initializes a new instance of the class. + /// An exception thrown when VirtualAlloc fails. /// - public MemoryAllocationException() + public class MemoryAllocationException : MemoryException { - } + /// + /// Initializes a new instance of the class. + /// + public MemoryAllocationException() + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryAllocationException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryAllocationException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryAllocationException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryAllocationException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryAllocationException(SerializationInfo info, StreamingContext context) - : base(info, context) - { + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryAllocationException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } } diff --git a/Dalamud/Memory/Exceptions/MemoryException.cs b/Dalamud/Memory/Exceptions/MemoryException.cs index 6cb1b887c..810e76404 100644 --- a/Dalamud/Memory/Exceptions/MemoryException.cs +++ b/Dalamud/Memory/Exceptions/MemoryException.cs @@ -1,46 +1,47 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions; - -/// -/// The base exception when thrown from Dalamud.Memory. -/// -public abstract class MemoryException : Exception +namespace Dalamud.Memory.Exceptions { /// - /// Initializes a new instance of the class. + /// The base exception when thrown from Dalamud.Memory. /// - public MemoryException() + public abstract class MemoryException : Exception { - } + /// + /// Initializes a new instance of the class. + /// + public MemoryException() + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryException(SerializationInfo info, StreamingContext context) - : base(info, context) - { + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } } diff --git a/Dalamud/Memory/Exceptions/MemoryPermissionException.cs b/Dalamud/Memory/Exceptions/MemoryPermissionException.cs index b4dddfc5f..e94ccc0ce 100644 --- a/Dalamud/Memory/Exceptions/MemoryPermissionException.cs +++ b/Dalamud/Memory/Exceptions/MemoryPermissionException.cs @@ -1,46 +1,47 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions; - -/// -/// An exception thrown when VirtualProtect fails. -/// -public class MemoryPermissionException : MemoryException +namespace Dalamud.Memory.Exceptions { /// - /// Initializes a new instance of the class. + /// An exception thrown when VirtualProtect fails. /// - public MemoryPermissionException() + public class MemoryPermissionException : MemoryException { - } + /// + /// Initializes a new instance of the class. + /// + public MemoryPermissionException() + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryPermissionException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryPermissionException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryPermissionException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryPermissionException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryPermissionException(SerializationInfo info, StreamingContext context) - : base(info, context) - { + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryPermissionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } } diff --git a/Dalamud/Memory/Exceptions/MemoryReadException.cs b/Dalamud/Memory/Exceptions/MemoryReadException.cs index ee02c5473..7f3f4b1f2 100644 --- a/Dalamud/Memory/Exceptions/MemoryReadException.cs +++ b/Dalamud/Memory/Exceptions/MemoryReadException.cs @@ -1,46 +1,47 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions; - -/// -/// An exception thrown when ReadProcessMemory fails. -/// -public class MemoryReadException : MemoryException +namespace Dalamud.Memory.Exceptions { /// - /// Initializes a new instance of the class. + /// An exception thrown when ReadProcessMemory fails. /// - public MemoryReadException() + public class MemoryReadException : MemoryException { - } + /// + /// Initializes a new instance of the class. + /// + public MemoryReadException() + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryReadException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryReadException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryReadException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryReadException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryReadException(SerializationInfo info, StreamingContext context) - : base(info, context) - { + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryReadException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } } diff --git a/Dalamud/Memory/Exceptions/MemoryWriteException.cs b/Dalamud/Memory/Exceptions/MemoryWriteException.cs index edbf06fdc..5aadcee53 100644 --- a/Dalamud/Memory/Exceptions/MemoryWriteException.cs +++ b/Dalamud/Memory/Exceptions/MemoryWriteException.cs @@ -1,46 +1,47 @@ using System; using System.Runtime.Serialization; -namespace Dalamud.Memory.Exceptions; - -/// -/// An exception thrown when WriteProcessMemory fails. -/// -public class MemoryWriteException : MemoryException +namespace Dalamud.Memory.Exceptions { /// - /// Initializes a new instance of the class. + /// An exception thrown when WriteProcessMemory fails. /// - public MemoryWriteException() + public class MemoryWriteException : MemoryException { - } + /// + /// Initializes a new instance of the class. + /// + public MemoryWriteException() + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public MemoryWriteException(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MemoryWriteException(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. - public MemoryWriteException(string message, Exception innerException) - : base(message, innerException) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. + public MemoryWriteException(string message, Exception innerException) + : base(message, innerException) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The object that holds the serialized data about the exception being thrown. - /// The object that contains contextual information about the source or destination. - protected MemoryWriteException(SerializationInfo info, StreamingContext context) - : base(info, context) - { + /// + /// Initializes a new instance of the class. + /// + /// The object that holds the serialized data about the exception being thrown. + /// The object that contains contextual information about the source or destination. + protected MemoryWriteException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } } } diff --git a/Dalamud/Memory/MemoryHelper.cs b/Dalamud/Memory/MemoryHelper.cs index d2dffc59f..0a4b840ef 100644 --- a/Dalamud/Memory/MemoryHelper.cs +++ b/Dalamud/Memory/MemoryHelper.cs @@ -11,646 +11,647 @@ using static Dalamud.NativeFunctions; // Heavily inspired from Reloaded (https://github.com/Reloaded-Project/Reloaded.Memory) -namespace Dalamud.Memory; - -/// -/// A simple class that provides read/write access to arbitrary memory. -/// -public static unsafe class MemoryHelper +namespace Dalamud.Memory { - #region Read - /// - /// Reads a generic type from a specified memory address. + /// A simple class that provides read/write access to arbitrary memory. /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The read in struct. - public static T Read(IntPtr memoryAddress) where T : unmanaged - => Read(memoryAddress, false); - - /// - /// Reads a generic type from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// Set this to true to enable struct marshalling. - /// The read in struct. - public static T Read(IntPtr memoryAddress, bool marshal) + public static unsafe class MemoryHelper { - return marshal - ? Marshal.PtrToStructure(memoryAddress) - : Unsafe.Read((void*)memoryAddress); - } + #region Read - /// - /// Reads a byte array from a specified memory address. - /// - /// The memory address to read from. - /// The amount of bytes to read starting from the memoryAddress. - /// The read in byte array. - public static byte[] ReadRaw(IntPtr memoryAddress, int length) - { - var value = new byte[length]; - Marshal.Copy(memoryAddress, value, 0, value.Length); - return value; - } + /// + /// Reads a generic type from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The read in struct. + public static T Read(IntPtr memoryAddress) where T : unmanaged + => Read(memoryAddress, false); - /// - /// Reads a generic type array from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The amount of array items to read. - /// The read in struct array. - public static T[] Read(IntPtr memoryAddress, int arrayLength) where T : unmanaged - => Read(memoryAddress, arrayLength, false); - - /// - /// Reads a generic type array from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The amount of array items to read. - /// Set this to true to enable struct marshalling. - /// The read in struct array. - public static T[] Read(IntPtr memoryAddress, int arrayLength, bool marshal) - { - var structSize = SizeOf(marshal); - var value = new T[arrayLength]; - - for (var i = 0; i < arrayLength; i++) + /// + /// Reads a generic type from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// Set this to true to enable struct marshalling. + /// The read in struct. + public static T Read(IntPtr memoryAddress, bool marshal) { - var address = memoryAddress + (structSize * i); - Read(address, out T result, marshal); - value[i] = result; + return marshal + ? Marshal.PtrToStructure(memoryAddress) + : Unsafe.Read((void*)memoryAddress); } - return value; - } - - /// - /// Reads a null-terminated byte array from a specified memory address. - /// - /// The memory address to read from. - /// The read in byte array. - public static unsafe byte[] ReadRawNullTerminated(IntPtr memoryAddress) - { - var byteCount = 0; - while (*(byte*)(memoryAddress + byteCount) != 0x00) + /// + /// Reads a byte array from a specified memory address. + /// + /// The memory address to read from. + /// The amount of bytes to read starting from the memoryAddress. + /// The read in byte array. + public static byte[] ReadRaw(IntPtr memoryAddress, int length) { - byteCount++; + var value = new byte[length]; + Marshal.Copy(memoryAddress, value, 0, value.Length); + return value; } - return ReadRaw(memoryAddress, byteCount); - } + /// + /// Reads a generic type array from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The amount of array items to read. + /// The read in struct array. + public static T[] Read(IntPtr memoryAddress, int arrayLength) where T : unmanaged + => Read(memoryAddress, arrayLength, false); - #endregion - - #region Read(out) - - /// - /// Reads a generic type from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// Local variable to receive the read in struct. - public static void Read(IntPtr memoryAddress, out T value) where T : unmanaged - => value = Read(memoryAddress); - - /// - /// Reads a generic type from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// Local variable to receive the read in struct. - /// Set this to true to enable struct marshalling. - public static void Read(IntPtr memoryAddress, out T value, bool marshal) - => value = Read(memoryAddress, marshal); - - /// - /// Reads raw data from a specified memory address. - /// - /// The memory address to read from. - /// The amount of bytes to read starting from the memoryAddress. - /// Local variable to receive the read in bytes. - public static void ReadRaw(IntPtr memoryAddress, int length, out byte[] value) - => value = ReadRaw(memoryAddress, length); - - /// - /// Reads a generic type array from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The amount of array items to read. - /// The read in struct array. - public static void Read(IntPtr memoryAddress, int arrayLength, out T[] value) where T : unmanaged - => value = Read(memoryAddress, arrayLength); - - /// - /// Reads a generic type array from a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The amount of array items to read. - /// Set this to true to enable struct marshalling. - /// The read in struct array. - public static void Read(IntPtr memoryAddress, int arrayLength, bool marshal, out T[] value) - => value = Read(memoryAddress, arrayLength, marshal); - - #endregion - - #region ReadString - - /// - /// Read a UTF-8 encoded string from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The read in string. - public static string ReadStringNullTerminated(IntPtr memoryAddress) - => ReadStringNullTerminated(memoryAddress, Encoding.UTF8); - - /// - /// Read a string with the given encoding from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The encoding to use to decode the string. - /// The read in string. - public static string ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding) - { - var buffer = ReadRawNullTerminated(memoryAddress); - return encoding.GetString(buffer); - } - - /// - /// Read a UTF-8 encoded string from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The maximum length of the string. - /// The read in string. - public static string ReadString(IntPtr memoryAddress, int maxLength) - => ReadString(memoryAddress, Encoding.UTF8, maxLength); - - /// - /// Read a string with the given encoding from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The encoding to use to decode the string. - /// The maximum length of the string. - /// The read in string. - public static string ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength) - { - if (maxLength <= 0) - return string.Empty; - - ReadRaw(memoryAddress, maxLength, out var buffer); - - var data = encoding.GetString(buffer); - var eosPos = data.IndexOf('\0'); - return eosPos >= 0 ? data.Substring(0, eosPos) : data; - } - - /// - /// Read a null-terminated SeString from a specified memory address. - /// - /// The memory address to read from. - /// The read in string. - public static SeString ReadSeStringNullTerminated(IntPtr memoryAddress) - { - var buffer = ReadRawNullTerminated(memoryAddress); - return SeString.Parse(buffer); - } - - /// - /// Read an SeString from a specified memory address. - /// - /// The memory address to read from. - /// The maximum length of the string. - /// The read in string. - public static SeString ReadSeString(IntPtr memoryAddress, int maxLength) - { - ReadRaw(memoryAddress, maxLength, out var buffer); - - var eos = Array.IndexOf(buffer, (byte)0); - if (eos < 0) + /// + /// Reads a generic type array from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The amount of array items to read. + /// Set this to true to enable struct marshalling. + /// The read in struct array. + public static T[] Read(IntPtr memoryAddress, int arrayLength, bool marshal) { + var structSize = SizeOf(marshal); + var value = new T[arrayLength]; + + for (var i = 0; i < arrayLength; i++) + { + var address = memoryAddress + (structSize * i); + Read(address, out T result, marshal); + value[i] = result; + } + + return value; + } + + /// + /// Reads a null-terminated byte array from a specified memory address. + /// + /// The memory address to read from. + /// The read in byte array. + public static unsafe byte[] ReadRawNullTerminated(IntPtr memoryAddress) + { + var byteCount = 0; + while (*(byte*)(memoryAddress + byteCount) != 0x00) + { + byteCount++; + } + + return ReadRaw(memoryAddress, byteCount); + } + + #endregion + + #region Read(out) + + /// + /// Reads a generic type from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// Local variable to receive the read in struct. + public static void Read(IntPtr memoryAddress, out T value) where T : unmanaged + => value = Read(memoryAddress); + + /// + /// Reads a generic type from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// Local variable to receive the read in struct. + /// Set this to true to enable struct marshalling. + public static void Read(IntPtr memoryAddress, out T value, bool marshal) + => value = Read(memoryAddress, marshal); + + /// + /// Reads raw data from a specified memory address. + /// + /// The memory address to read from. + /// The amount of bytes to read starting from the memoryAddress. + /// Local variable to receive the read in bytes. + public static void ReadRaw(IntPtr memoryAddress, int length, out byte[] value) + => value = ReadRaw(memoryAddress, length); + + /// + /// Reads a generic type array from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The amount of array items to read. + /// The read in struct array. + public static void Read(IntPtr memoryAddress, int arrayLength, out T[] value) where T : unmanaged + => value = Read(memoryAddress, arrayLength); + + /// + /// Reads a generic type array from a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The amount of array items to read. + /// Set this to true to enable struct marshalling. + /// The read in struct array. + public static void Read(IntPtr memoryAddress, int arrayLength, bool marshal, out T[] value) + => value = Read(memoryAddress, arrayLength, marshal); + + #endregion + + #region ReadString + + /// + /// Read a UTF-8 encoded string from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The read in string. + public static string ReadStringNullTerminated(IntPtr memoryAddress) + => ReadStringNullTerminated(memoryAddress, Encoding.UTF8); + + /// + /// Read a string with the given encoding from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The encoding to use to decode the string. + /// The read in string. + public static string ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding) + { + var buffer = ReadRawNullTerminated(memoryAddress); + return encoding.GetString(buffer); + } + + /// + /// Read a UTF-8 encoded string from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The maximum length of the string. + /// The read in string. + public static string ReadString(IntPtr memoryAddress, int maxLength) + => ReadString(memoryAddress, Encoding.UTF8, maxLength); + + /// + /// Read a string with the given encoding from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The encoding to use to decode the string. + /// The maximum length of the string. + /// The read in string. + public static string ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength) + { + if (maxLength <= 0) + return string.Empty; + + ReadRaw(memoryAddress, maxLength, out var buffer); + + var data = encoding.GetString(buffer); + var eosPos = data.IndexOf('\0'); + return eosPos >= 0 ? data.Substring(0, eosPos) : data; + } + + /// + /// Read a null-terminated SeString from a specified memory address. + /// + /// The memory address to read from. + /// The read in string. + public static SeString ReadSeStringNullTerminated(IntPtr memoryAddress) + { + var buffer = ReadRawNullTerminated(memoryAddress); return SeString.Parse(buffer); } - else + + /// + /// Read an SeString from a specified memory address. + /// + /// The memory address to read from. + /// The maximum length of the string. + /// The read in string. + public static SeString ReadSeString(IntPtr memoryAddress, int maxLength) { - var newBuffer = new byte[eos]; - Buffer.BlockCopy(buffer, 0, newBuffer, 0, eos); - return SeString.Parse(newBuffer); + ReadRaw(memoryAddress, maxLength, out var buffer); + + var eos = Array.IndexOf(buffer, (byte)0); + if (eos < 0) + { + return SeString.Parse(buffer); + } + else + { + var newBuffer = new byte[eos]; + Buffer.BlockCopy(buffer, 0, newBuffer, 0, eos); + return SeString.Parse(newBuffer); + } } - } - /// - /// Read an SeString from a specified Utf8String structure. - /// - /// The memory address to read from. - /// The read in string. - public static unsafe SeString ReadSeString(Utf8String* utf8String) - { - if (utf8String == null) - return string.Empty; - - var ptr = utf8String->StringPtr; - if (ptr == null) - return string.Empty; - - var len = Math.Max(utf8String->BufUsed, utf8String->StringLength); - - return ReadSeString((IntPtr)ptr, (int)len); - } - - #endregion - - #region ReadString(out) - - /// - /// Read a UTF-8 encoded string from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The read in string. - public static void ReadStringNullTerminated(IntPtr memoryAddress, out string value) - => value = ReadStringNullTerminated(memoryAddress); - - /// - /// Read a string with the given encoding from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The encoding to use to decode the string. - /// The read in string. - public static void ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding, out string value) - => value = ReadStringNullTerminated(memoryAddress, encoding); - - /// - /// Read a UTF-8 encoded string from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The read in string. - /// The maximum length of the string. - public static void ReadString(IntPtr memoryAddress, out string value, int maxLength) - => value = ReadString(memoryAddress, maxLength); - - /// - /// Read a string with the given encoding from a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to decode or the applicable helper method. - /// - /// The memory address to read from. - /// The encoding to use to decode the string. - /// The maximum length of the string. - /// The read in string. - public static void ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength, out string value) - => value = ReadString(memoryAddress, encoding, maxLength); - - /// - /// Read a null-terminated SeString from a specified memory address. - /// - /// The memory address to read from. - /// The read in SeString. - public static void ReadSeStringNullTerminated(IntPtr memoryAddress, out SeString value) - => value = ReadSeStringNullTerminated(memoryAddress); - - /// - /// Read an SeString from a specified memory address. - /// - /// The memory address to read from. - /// The maximum length of the string. - /// The read in SeString. - public static void ReadSeString(IntPtr memoryAddress, int maxLength, out SeString value) - => value = ReadSeString(memoryAddress, maxLength); - - /// - /// Read an SeString from a specified Utf8String structure. - /// - /// The memory address to read from. - /// The read in string. - public static unsafe void ReadSeString(Utf8String* utf8String, out SeString value) - => value = ReadSeString(utf8String); - - #endregion - - #region Write - - /// - /// Writes a generic type to a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The item to write to the address. - public static void Write(IntPtr memoryAddress, T item) where T : unmanaged - => Write(memoryAddress, item); - - /// - /// Writes a generic type to a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to read from. - /// The item to write to the address. - /// Set this to true to enable struct marshalling. - public static void Write(IntPtr memoryAddress, T item, bool marshal) - { - if (marshal) - Marshal.StructureToPtr(item, memoryAddress, false); - else - Unsafe.Write((void*)memoryAddress, item); - } - - /// - /// Writes raw data to a specified memory address. - /// - /// The memory address to read from. - /// The bytes to write to memoryAddress. - public static void WriteRaw(IntPtr memoryAddress, byte[] data) - { - Marshal.Copy(data, 0, memoryAddress, data.Length); - } - - /// - /// Writes a generic type array to a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to write to. - /// The array of items to write to the address. - public static void Write(IntPtr memoryAddress, T[] items) where T : unmanaged - => Write(memoryAddress, items, false); - - /// - /// Writes a generic type array to a specified memory address. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address to write to. - /// The array of items to write to the address. - /// Set this to true to enable struct marshalling. - public static void Write(IntPtr memoryAddress, T[] items, bool marshal) - { - var structSize = SizeOf(marshal); - - for (var i = 0; i < items.Length; i++) + /// + /// Read an SeString from a specified Utf8String structure. + /// + /// The memory address to read from. + /// The read in string. + public static unsafe SeString ReadSeString(Utf8String* utf8String) { - var address = memoryAddress + (structSize * i); - Write(address, items[i], marshal); + if (utf8String == null) + return string.Empty; + + var ptr = utf8String->StringPtr; + if (ptr == null) + return string.Empty; + + var len = Math.Max(utf8String->BufUsed, utf8String->StringLength); + + return ReadSeString((IntPtr)ptr, (int)len); } + + #endregion + + #region ReadString(out) + + /// + /// Read a UTF-8 encoded string from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The read in string. + public static void ReadStringNullTerminated(IntPtr memoryAddress, out string value) + => value = ReadStringNullTerminated(memoryAddress); + + /// + /// Read a string with the given encoding from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The encoding to use to decode the string. + /// The read in string. + public static void ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding, out string value) + => value = ReadStringNullTerminated(memoryAddress, encoding); + + /// + /// Read a UTF-8 encoded string from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The read in string. + /// The maximum length of the string. + public static void ReadString(IntPtr memoryAddress, out string value, int maxLength) + => value = ReadString(memoryAddress, maxLength); + + /// + /// Read a string with the given encoding from a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to decode or the applicable helper method. + /// + /// The memory address to read from. + /// The encoding to use to decode the string. + /// The maximum length of the string. + /// The read in string. + public static void ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength, out string value) + => value = ReadString(memoryAddress, encoding, maxLength); + + /// + /// Read a null-terminated SeString from a specified memory address. + /// + /// The memory address to read from. + /// The read in SeString. + public static void ReadSeStringNullTerminated(IntPtr memoryAddress, out SeString value) + => value = ReadSeStringNullTerminated(memoryAddress); + + /// + /// Read an SeString from a specified memory address. + /// + /// The memory address to read from. + /// The maximum length of the string. + /// The read in SeString. + public static void ReadSeString(IntPtr memoryAddress, int maxLength, out SeString value) + => value = ReadSeString(memoryAddress, maxLength); + + /// + /// Read an SeString from a specified Utf8String structure. + /// + /// The memory address to read from. + /// The read in string. + public static unsafe void ReadSeString(Utf8String* utf8String, out SeString value) + => value = ReadSeString(utf8String); + + #endregion + + #region Write + + /// + /// Writes a generic type to a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The item to write to the address. + public static void Write(IntPtr memoryAddress, T item) where T : unmanaged + => Write(memoryAddress, item); + + /// + /// Writes a generic type to a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to read from. + /// The item to write to the address. + /// Set this to true to enable struct marshalling. + public static void Write(IntPtr memoryAddress, T item, bool marshal) + { + if (marshal) + Marshal.StructureToPtr(item, memoryAddress, false); + else + Unsafe.Write((void*)memoryAddress, item); + } + + /// + /// Writes raw data to a specified memory address. + /// + /// The memory address to read from. + /// The bytes to write to memoryAddress. + public static void WriteRaw(IntPtr memoryAddress, byte[] data) + { + Marshal.Copy(data, 0, memoryAddress, data.Length); + } + + /// + /// Writes a generic type array to a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to write to. + /// The array of items to write to the address. + public static void Write(IntPtr memoryAddress, T[] items) where T : unmanaged + => Write(memoryAddress, items, false); + + /// + /// Writes a generic type array to a specified memory address. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address to write to. + /// The array of items to write to the address. + /// Set this to true to enable struct marshalling. + public static void Write(IntPtr memoryAddress, T[] items, bool marshal) + { + var structSize = SizeOf(marshal); + + for (var i = 0; i < items.Length; i++) + { + var address = memoryAddress + (structSize * i); + Write(address, items[i], marshal); + } + } + + #endregion + + #region WriteString + + /// + /// Write a UTF-8 encoded string to a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to encode or the applicable helper method. + /// + /// The memory address to write to. + /// The string to write. + public static void WriteString(IntPtr memoryAddress, string value) + => WriteString(memoryAddress, value, Encoding.UTF8); + + /// + /// Write a string with the given encoding to a specified memory address. + /// + /// + /// Attention! If this is an SeString, use the to encode or the applicable helper method. + /// + /// The memory address to write to. + /// The string to write. + /// The encoding to use. + public static void WriteString(IntPtr memoryAddress, string value, Encoding encoding) + { + if (string.IsNullOrEmpty(value)) + return; + + var bytes = encoding.GetBytes(value + '\0'); + + WriteRaw(memoryAddress, bytes); + } + + /// + /// Write an SeString to a specified memory address. + /// + /// The memory address to write to. + /// The SeString to write. + public static void WriteSeString(IntPtr memoryAddress, SeString value) + { + if (value is null) + return; + + WriteRaw(memoryAddress, value.Encode()); + } + + #endregion + + #region ApiWrappers + + /// + /// Allocates fixed size of memory inside the target memory source via Windows API calls. + /// Returns the address of newly allocated memory. + /// + /// Amount of bytes to be allocated. + /// Address to the newly allocated memory. + public static IntPtr Allocate(int length) + { + var address = VirtualAlloc( + IntPtr.Zero, + (UIntPtr)length, + AllocationType.Commit | AllocationType.Reserve, + MemoryProtection.ExecuteReadWrite); + + if (address == IntPtr.Zero) + throw new MemoryAllocationException($"Unable to allocate {length} bytes."); + + return address; + } + + /// + /// Allocates fixed size of memory inside the target memory source via Windows API calls. + /// Returns the address of newly allocated memory. + /// + /// Amount of bytes to be allocated. + /// Address to the newly allocated memory. + public static void Allocate(int length, out IntPtr memoryAddress) + => memoryAddress = Allocate(length); + + /// + /// Frees memory previously allocated with Allocate via Windows API calls. + /// + /// The address of the memory to free. + /// True if the operation is successful. + public static bool Free(IntPtr memoryAddress) + { + return VirtualFree(memoryAddress, UIntPtr.Zero, AllocationType.Release); + } + + /// + /// Changes the page permissions for a specified combination of address and length via Windows API calls. + /// + /// The memory address for which to change page permissions for. + /// The region size for which to change permissions for. + /// The new permissions to set. + /// The old page permissions. + public static MemoryProtection ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions) + { + var result = VirtualProtect(memoryAddress, (UIntPtr)length, newPermissions, out var oldPermissions); + + if (!result) + throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (result={result})"); + + var last = Marshal.GetLastWin32Error(); + if (last > 0) + throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (error={last})"); + + return oldPermissions; + } + + /// + /// Changes the page permissions for a specified combination of address and length via Windows API calls. + /// + /// The memory address for which to change page permissions for. + /// The region size for which to change permissions for. + /// The new permissions to set. + /// The old page permissions. + public static void ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions, out MemoryProtection oldPermissions) + => oldPermissions = ChangePermission(memoryAddress, length, newPermissions); + + /// + /// Changes the page permissions for a specified combination of address and element from which to deduce size via Windows API calls. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The memory address for which to change page permissions for. + /// The struct element from which the region size to change permissions for will be calculated. + /// The new permissions to set. + /// Set to true to calculate the size of the struct after marshalling instead of before. + /// The old page permissions. + public static MemoryProtection ChangePermission(IntPtr memoryAddress, ref T baseElement, MemoryProtection newPermissions, bool marshal) + => ChangePermission(memoryAddress, SizeOf(marshal), newPermissions); + + /// + /// Reads raw data from a specified memory address via Windows API calls. + /// This is noticably slower than Unsafe or Marshal. + /// + /// The memory address to read from. + /// The amount of bytes to read starting from the memoryAddress. + /// The read in bytes. + public static byte[] ReadProcessMemory(IntPtr memoryAddress, int length) + { + var value = new byte[length]; + ReadProcessMemory(memoryAddress, ref value); + return value; + } + + /// + /// Reads raw data from a specified memory address via Windows API calls. + /// This is noticably slower than Unsafe or Marshal. + /// + /// The memory address to read from. + /// The amount of bytes to read starting from the memoryAddress. + /// The read in bytes. + public static void ReadProcessMemory(IntPtr memoryAddress, int length, out byte[] value) + => value = ReadProcessMemory(memoryAddress, length); + + /// + /// Reads raw data from a specified memory address via Windows API calls. + /// This is noticably slower than Unsafe or Marshal. + /// + /// The memory address to read from. + /// The read in bytes. + public static void ReadProcessMemory(IntPtr memoryAddress, ref byte[] value) + { + var length = value.Length; + var result = NativeFunctions.ReadProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, value, length, out _); + + if (!result) + throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); + + var last = Marshal.GetLastWin32Error(); + if (last > 0) + throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); + } + + /// + /// Writes raw data to a specified memory address via Windows API calls. + /// This is noticably slower than Unsafe or Marshal. + /// + /// The memory address to write to. + /// The bytes to write to memoryAddress. + public static void WriteProcessMemory(IntPtr memoryAddress, byte[] data) + { + var length = data.Length; + var result = NativeFunctions.WriteProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, data, length, out _); + + if (!result) + throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); + + var last = Marshal.GetLastWin32Error(); + if (last > 0) + throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); + } + + #endregion + + #region Sizing + + /// + /// Returns the size of a specific primitive or struct type. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The size of the primitive or struct. + public static int SizeOf() + => SizeOf(false); + + /// + /// Returns the size of a specific primitive or struct type. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// If set to true; will return the size of an element after marshalling. + /// The size of the primitive or struct. + public static int SizeOf(bool marshal) + => marshal ? Marshal.SizeOf() : Unsafe.SizeOf(); + + /// + /// Returns the size of a specific primitive or struct type. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The number of array elements present. + /// The size of the primitive or struct array. + public static int SizeOf(int elementCount) where T : unmanaged + => SizeOf() * elementCount; + + /// + /// Returns the size of a specific primitive or struct type. + /// + /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. + /// The number of array elements present. + /// If set to true; will return the size of an element after marshalling. + /// The size of the primitive or struct array. + public static int SizeOf(int elementCount, bool marshal) + => SizeOf(marshal) * elementCount; + + #endregion } - - #endregion - - #region WriteString - - /// - /// Write a UTF-8 encoded string to a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to encode or the applicable helper method. - /// - /// The memory address to write to. - /// The string to write. - public static void WriteString(IntPtr memoryAddress, string value) - => WriteString(memoryAddress, value, Encoding.UTF8); - - /// - /// Write a string with the given encoding to a specified memory address. - /// - /// - /// Attention! If this is an SeString, use the to encode or the applicable helper method. - /// - /// The memory address to write to. - /// The string to write. - /// The encoding to use. - public static void WriteString(IntPtr memoryAddress, string value, Encoding encoding) - { - if (string.IsNullOrEmpty(value)) - return; - - var bytes = encoding.GetBytes(value + '\0'); - - WriteRaw(memoryAddress, bytes); - } - - /// - /// Write an SeString to a specified memory address. - /// - /// The memory address to write to. - /// The SeString to write. - public static void WriteSeString(IntPtr memoryAddress, SeString value) - { - if (value is null) - return; - - WriteRaw(memoryAddress, value.Encode()); - } - - #endregion - - #region ApiWrappers - - /// - /// Allocates fixed size of memory inside the target memory source via Windows API calls. - /// Returns the address of newly allocated memory. - /// - /// Amount of bytes to be allocated. - /// Address to the newly allocated memory. - public static IntPtr Allocate(int length) - { - var address = VirtualAlloc( - IntPtr.Zero, - (UIntPtr)length, - AllocationType.Commit | AllocationType.Reserve, - MemoryProtection.ExecuteReadWrite); - - if (address == IntPtr.Zero) - throw new MemoryAllocationException($"Unable to allocate {length} bytes."); - - return address; - } - - /// - /// Allocates fixed size of memory inside the target memory source via Windows API calls. - /// Returns the address of newly allocated memory. - /// - /// Amount of bytes to be allocated. - /// Address to the newly allocated memory. - public static void Allocate(int length, out IntPtr memoryAddress) - => memoryAddress = Allocate(length); - - /// - /// Frees memory previously allocated with Allocate via Windows API calls. - /// - /// The address of the memory to free. - /// True if the operation is successful. - public static bool Free(IntPtr memoryAddress) - { - return VirtualFree(memoryAddress, UIntPtr.Zero, AllocationType.Release); - } - - /// - /// Changes the page permissions for a specified combination of address and length via Windows API calls. - /// - /// The memory address for which to change page permissions for. - /// The region size for which to change permissions for. - /// The new permissions to set. - /// The old page permissions. - public static MemoryProtection ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions) - { - var result = VirtualProtect(memoryAddress, (UIntPtr)length, newPermissions, out var oldPermissions); - - if (!result) - throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (result={result})"); - - var last = Marshal.GetLastWin32Error(); - if (last > 0) - throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (error={last})"); - - return oldPermissions; - } - - /// - /// Changes the page permissions for a specified combination of address and length via Windows API calls. - /// - /// The memory address for which to change page permissions for. - /// The region size for which to change permissions for. - /// The new permissions to set. - /// The old page permissions. - public static void ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions, out MemoryProtection oldPermissions) - => oldPermissions = ChangePermission(memoryAddress, length, newPermissions); - - /// - /// Changes the page permissions for a specified combination of address and element from which to deduce size via Windows API calls. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The memory address for which to change page permissions for. - /// The struct element from which the region size to change permissions for will be calculated. - /// The new permissions to set. - /// Set to true to calculate the size of the struct after marshalling instead of before. - /// The old page permissions. - public static MemoryProtection ChangePermission(IntPtr memoryAddress, ref T baseElement, MemoryProtection newPermissions, bool marshal) - => ChangePermission(memoryAddress, SizeOf(marshal), newPermissions); - - /// - /// Reads raw data from a specified memory address via Windows API calls. - /// This is noticably slower than Unsafe or Marshal. - /// - /// The memory address to read from. - /// The amount of bytes to read starting from the memoryAddress. - /// The read in bytes. - public static byte[] ReadProcessMemory(IntPtr memoryAddress, int length) - { - var value = new byte[length]; - ReadProcessMemory(memoryAddress, ref value); - return value; - } - - /// - /// Reads raw data from a specified memory address via Windows API calls. - /// This is noticably slower than Unsafe or Marshal. - /// - /// The memory address to read from. - /// The amount of bytes to read starting from the memoryAddress. - /// The read in bytes. - public static void ReadProcessMemory(IntPtr memoryAddress, int length, out byte[] value) - => value = ReadProcessMemory(memoryAddress, length); - - /// - /// Reads raw data from a specified memory address via Windows API calls. - /// This is noticably slower than Unsafe or Marshal. - /// - /// The memory address to read from. - /// The read in bytes. - public static void ReadProcessMemory(IntPtr memoryAddress, ref byte[] value) - { - var length = value.Length; - var result = NativeFunctions.ReadProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, value, length, out _); - - if (!result) - throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); - - var last = Marshal.GetLastWin32Error(); - if (last > 0) - throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); - } - - /// - /// Writes raw data to a specified memory address via Windows API calls. - /// This is noticably slower than Unsafe or Marshal. - /// - /// The memory address to write to. - /// The bytes to write to memoryAddress. - public static void WriteProcessMemory(IntPtr memoryAddress, byte[] data) - { - var length = data.Length; - var result = NativeFunctions.WriteProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, data, length, out _); - - if (!result) - throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); - - var last = Marshal.GetLastWin32Error(); - if (last > 0) - throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (error={last})"); - } - - #endregion - - #region Sizing - - /// - /// Returns the size of a specific primitive or struct type. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The size of the primitive or struct. - public static int SizeOf() - => SizeOf(false); - - /// - /// Returns the size of a specific primitive or struct type. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// If set to true; will return the size of an element after marshalling. - /// The size of the primitive or struct. - public static int SizeOf(bool marshal) - => marshal ? Marshal.SizeOf() : Unsafe.SizeOf(); - - /// - /// Returns the size of a specific primitive or struct type. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The number of array elements present. - /// The size of the primitive or struct array. - public static int SizeOf(int elementCount) where T : unmanaged - => SizeOf() * elementCount; - - /// - /// Returns the size of a specific primitive or struct type. - /// - /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. - /// The number of array elements present. - /// If set to true; will return the size of an element after marshalling. - /// The size of the primitive or struct array. - public static int SizeOf(int elementCount, bool marshal) - => SizeOf(marshal) * elementCount; - - #endregion } diff --git a/Dalamud/Memory/MemoryProtection.cs b/Dalamud/Memory/MemoryProtection.cs index 019b656e8..289c5024d 100644 --- a/Dalamud/Memory/MemoryProtection.cs +++ b/Dalamud/Memory/MemoryProtection.cs @@ -2,115 +2,116 @@ using System; // This is a copy from NativeFunctions.MemoryProtection -namespace Dalamud.Memory; - -/// -/// PAGE_* from memoryapi. -/// -[Flags] -public enum MemoryProtection +namespace Dalamud.Memory { - // Copied from NativeFunctions to expose to the user. - /// - /// Enables execute access to the committed region of pages. An attempt to write to the committed region results - /// in an access violation. This flag is not supported by the CreateFileMapping function. + /// PAGE_* from memoryapi. /// - Execute = 0x10, + [Flags] + public enum MemoryProtection + { + // Copied from NativeFunctions to expose to the user. - /// - /// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region - /// results in an access violation. - /// - ExecuteRead = 0x20, + /// + /// Enables execute access to the committed region of pages. An attempt to write to the committed region results + /// in an access violation. This flag is not supported by the CreateFileMapping function. + /// + Execute = 0x10, - /// - /// Enables execute, read-only, or read/write access to the committed region of pages. - /// - ExecuteReadWrite = 0x40, + /// + /// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region + /// results in an access violation. + /// + ExecuteRead = 0x20, - /// - /// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to - /// write to a committed copy-on-write page results in a private copy of the page being made for the process. The - /// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not - /// supported by the VirtualAlloc or VirtualAllocEx functions. - /// - ExecuteWriteCopy = 0x80, + /// + /// Enables execute, read-only, or read/write access to the committed region of pages. + /// + ExecuteReadWrite = 0x40, - /// - /// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed - /// region results in an access violation. This flag is not supported by the CreateFileMapping function. - /// - NoAccess = 0x01, + /// + /// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to + /// write to a committed copy-on-write page results in a private copy of the page being made for the process. The + /// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not + /// supported by the VirtualAlloc or VirtualAllocEx functions. + /// + ExecuteWriteCopy = 0x80, - /// - /// Enables read-only access to the committed region of pages. An attempt to write to the committed region results - /// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed - /// region results in an access violation. - /// - ReadOnly = 0x02, + /// + /// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed + /// region results in an access violation. This flag is not supported by the CreateFileMapping function. + /// + NoAccess = 0x01, - /// - /// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, - /// attempting to execute code in the committed region results in an access violation. - /// - ReadWrite = 0x04, + /// + /// Enables read-only access to the committed region of pages. An attempt to write to the committed region results + /// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed + /// region results in an access violation. + /// + ReadOnly = 0x02, - /// - /// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to - /// a committed copy-on-write page results in a private copy of the page being made for the process. The private - /// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is - /// enabled, attempting to execute code in the committed region results in an access violation. This flag is not - /// supported by the VirtualAlloc or VirtualAllocEx functions. - /// - WriteCopy = 0x08, + /// + /// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, + /// attempting to execute code in the committed region results in an access violation. + /// + ReadWrite = 0x04, - /// - /// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like - /// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations - /// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable - /// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect - /// or CreateFileMapping functions. - /// - TargetsInvalid = 0x40000000, + /// + /// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to + /// a committed copy-on-write page results in a private copy of the page being made for the process. The private + /// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is + /// enabled, attempting to execute code in the committed region results in an access violation. This flag is not + /// supported by the VirtualAlloc or VirtualAllocEx functions. + /// + WriteCopy = 0x08, - /// - /// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect. - /// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information - /// will be maintained while the page protection changes. This flag is only valid when the protection changes to - /// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. - /// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call - /// targets for CFG. - /// - TargetsNoUpdate = TargetsInvalid, + /// + /// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like + /// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations + /// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable + /// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect + /// or CreateFileMapping functions. + /// + TargetsInvalid = 0x40000000, - /// - /// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a - /// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time - /// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn - /// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a - /// system service, the service typically returns a failure status indicator. This value cannot be used with - /// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function. - /// - Guard = 0x100, + /// + /// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect. + /// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information + /// will be maintained while the page protection changes. This flag is only valid when the protection changes to + /// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. + /// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call + /// targets for CFG. + /// + TargetsNoUpdate = TargetsInvalid, - /// - /// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required - /// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an - /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS, - /// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the - /// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared - /// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function. - /// - NoCache = 0x200, + /// + /// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a + /// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time + /// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn + /// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a + /// system service, the service typically returns a failure status indicator. This value cannot be used with + /// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function. + /// + Guard = 0x100, - /// - /// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required - /// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an - /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS, - /// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory - /// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access - /// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function. - /// - WriteCombine = 0x400, + /// + /// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required + /// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an + /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS, + /// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the + /// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared + /// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function. + /// + NoCache = 0x200, + + /// + /// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required + /// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an + /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS, + /// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory + /// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access + /// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function. + /// + WriteCombine = 0x400, + } } diff --git a/Dalamud/NativeFunctions.cs b/Dalamud/NativeFunctions.cs index c75db66a7..d11c39464 100644 --- a/Dalamud/NativeFunctions.cs +++ b/Dalamud/NativeFunctions.cs @@ -4,1927 +4,1928 @@ using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; -namespace Dalamud; - -/// -/// Native user32 functions. -/// -internal static partial class NativeFunctions +namespace Dalamud { /// - /// FLASHW_* from winuser. + /// Native user32 functions. /// - public enum FlashWindow : uint + internal static partial class NativeFunctions { /// - /// Stop flashing. The system restores the window to its original state. + /// FLASHW_* from winuser. /// - Stop = 0, + public enum FlashWindow : uint + { + /// + /// Stop flashing. The system restores the window to its original state. + /// + Stop = 0, + + /// + /// Flash the window caption. + /// + Caption = 1, + + /// + /// Flash the taskbar button. + /// + Tray = 2, + + /// + /// Flash both the window caption and taskbar button. + /// This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. + /// + All = 3, + + /// + /// Flash continuously, until the FLASHW_STOP flag is set. + /// + Timer = 4, + + /// + /// Flash continuously until the window comes to the foreground. + /// + TimerNoFG = 12, + } /// - /// Flash the window caption. + /// IDC_* from winuser. /// - Caption = 1, + public enum CursorType + { + /// + /// Standard arrow and small hourglass. + /// + AppStarting = 32650, + + /// + /// Standard arrow. + /// + Arrow = 32512, + + /// + /// Crosshair. + /// + Cross = 32515, + + /// + /// Hand. + /// + Hand = 32649, + + /// + /// Arrow and question mark. + /// + Help = 32651, + + /// + /// I-beam. + /// + IBeam = 32513, + + /// + /// Obsolete for applications marked version 4.0 or later. + /// + Icon = 32641, + + /// + /// Slashed circle. + /// + No = 32648, + + /// + /// Obsolete for applications marked version 4.0 or later.Use IDC_SIZEALL. + /// + Size = 32640, + + /// + /// Four-pointed arrow pointing north, south, east, and west. + /// + SizeAll = 32646, + + /// + /// Double-pointed arrow pointing northeast and southwest. + /// + SizeNeSw = 32643, + + /// + /// Double-pointed arrow pointing north and south. + /// + SizeNS = 32645, + + /// + /// Double-pointed arrow pointing northwest and southeast. + /// + SizeNwSe = 32642, + + /// + /// Double-pointed arrow pointing west and east. + /// + SizeWE = 32644, + + /// + /// Vertical arrow. + /// + UpArrow = 32516, + + /// + /// Hourglass. + /// + Wait = 32514, + } /// - /// Flash the taskbar button. + /// MB_* from winuser. /// - Tray = 2, + public enum MessageBoxType : uint + { + /// + /// The default value for any of the various subtypes. + /// + DefaultValue = 0x0, + + // To indicate the buttons displayed in the message box, specify one of the following values. + + /// + /// The message box contains three push buttons: Abort, Retry, and Ignore. + /// + AbortRetryIgnore = 0x2, + + /// + /// The message box contains three push buttons: Cancel, Try Again, Continue. Use this message box type instead + /// of MB_ABORTRETRYIGNORE. + /// + CancelTryContinue = 0x6, + + /// + /// Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends + /// a WM_HELP message to the owner. + /// + Help = 0x4000, + + /// + /// The message box contains one push button: OK. This is the default. + /// + Ok = DefaultValue, + + /// + /// The message box contains two push buttons: OK and Cancel. + /// + OkCancel = 0x1, + + /// + /// The message box contains two push buttons: Retry and Cancel. + /// + RetryCancel = 0x5, + + /// + /// The message box contains two push buttons: Yes and No. + /// + YesNo = 0x4, + + /// + /// The message box contains three push buttons: Yes, No, and Cancel. + /// + YesNoCancel = 0x3, + + // To display an icon in the message box, specify one of the following values. + + /// + /// An exclamation-point icon appears in the message box. + /// + IconExclamation = 0x30, + + /// + /// An exclamation-point icon appears in the message box. + /// + IconWarning = IconExclamation, + + /// + /// An icon consisting of a lowercase letter i in a circle appears in the message box. + /// + IconInformation = 0x40, + + /// + /// An icon consisting of a lowercase letter i in a circle appears in the message box. + /// + IconAsterisk = IconInformation, + + /// + /// A question-mark icon appears in the message box. + /// The question-mark message icon is no longer recommended because it does not clearly represent a specific type + /// of message and because the phrasing of a message as a question could apply to any message type. In addition, + /// users can confuse the message symbol question mark with Help information. Therefore, do not use this question + /// mark message symbol in your message boxes. The system continues to support its inclusion only for backward + /// compatibility. + /// + IconQuestion = 0x20, + + /// + /// A stop-sign icon appears in the message box. + /// + IconStop = 0x10, + + /// + /// A stop-sign icon appears in the message box. + /// + IconError = IconStop, + + /// + /// A stop-sign icon appears in the message box. + /// + IconHand = IconStop, + + // To indicate the default button, specify one of the following values. + + /// + /// The first button is the default button. + /// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified. + /// + DefButton1 = DefaultValue, + + /// + /// The second button is the default button. + /// + DefButton2 = 0x100, + + /// + /// The third button is the default button. + /// + DefButton3 = 0x200, + + /// + /// The fourth button is the default button. + /// + DefButton4 = 0x300, + + // To indicate the modality of the dialog box, specify one of the following values. + + /// + /// The user must respond to the message box before continuing work in the window identified by the hWnd parameter. + /// However, the user can move to the windows of other threads and work in those windows. Depending on the hierarchy + /// of windows in the application, the user may be able to move to other windows within the thread. All child windows + /// of the parent of the message box are automatically disabled, but pop-up windows are not. MB_APPLMODAL is the + /// default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is specified. + /// + ApplModal = DefaultValue, + + /// + /// Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST style. + /// Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate + /// attention (for example, running out of memory). This flag has no effect on the user's ability to interact with + /// windows other than those associated with hWnd. + /// + SystemModal = 0x1000, + + /// + /// Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the + /// hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle + /// available but still needs to prevent input to other windows in the calling thread without suspending other threads. + /// + TaskModal = 0x2000, + + // To specify other options, use one or more of the following values. + + /// + /// Same as desktop of the interactive window station. For more information, see Window Stations. If the current + /// input desktop is not the default desktop, MessageBox does not return until the user switches to the default + /// desktop. + /// + DefaultDesktopOnly = 0x20000, + + /// + /// The text is right-justified. + /// + Right = 0x80000, + + /// + /// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems. + /// + RtlReading = 0x100000, + + /// + /// The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function + /// for the message box. + /// + SetForeground = 0x10000, + + /// + /// The message box is created with the WS_EX_TOPMOST window style. + /// + Topmost = 0x40000, + + /// + /// The caller is a service notifying the user of an event. The function displays a message box on the current active + /// desktop, even if there is no user logged on to the computer. + /// + ServiceNotification = 0x200000, + } /// - /// Flash both the window caption and taskbar button. - /// This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. + /// GWL_* from winuser. /// - All = 3, + public enum WindowLongType + { + /// + /// Sets a new extended window style. + /// + ExStyle = -20, + + /// + /// Sets a new application instance handle. + /// + HInstance = -6, + + /// + /// Sets a new identifier of the child window.The window cannot be a top-level window. + /// + Id = -12, + + /// + /// Sets a new window style. + /// + Style = -16, + + /// + /// Sets the user data associated with the window. This data is intended for use by the application that created the window. Its value is initially zero. + /// + UserData = -21, + + /// + /// Sets a new address for the window procedure. + /// + WndProc = -4, + + // The following values are also available when the hWnd parameter identifies a dialog box. + + // /// + // /// Sets the new pointer to the dialog box procedure. + // /// + // DWLP_DLGPROC = DWLP_MSGRESULT + sizeof(LRESULT), + + /// + /// Sets the return value of a message processed in the dialog box procedure. + /// + MsgResult = 0, + + // /// + // /// Sets new extra information that is private to the application, such as handles or pointers. + // /// + // DWLP_USER = DWLP_DLGPROC + sizeof(DLGPROC), + } /// - /// Flash continuously, until the FLASHW_STOP flag is set. + /// WM_* from winuser. + /// These are spread throughout multiple files, find the documentation manually if you need it. + /// https://gist.github.com/amgine/2395987. /// - Timer = 4, + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "No documentation available.")] + public enum WindowsMessage + { + WM_NULL = 0x0000, + WM_CREATE = 0x0001, + WM_DESTROY = 0x0002, + WM_MOVE = 0x0003, + WM_SIZE = 0x0005, + WM_ACTIVATE = 0x0006, + WM_SETFOCUS = 0x0007, + WM_KILLFOCUS = 0x0008, + WM_ENABLE = 0x000A, + WM_SETREDRAW = 0x000B, + WM_SETTEXT = 0x000C, + WM_GETTEXT = 0x000D, + WM_GETTEXTLENGTH = 0x000E, + WM_PAINT = 0x000F, + WM_CLOSE = 0x0010, + WM_QUERYENDSESSION = 0x0011, + WM_QUERYOPEN = 0x0013, + WM_ENDSESSION = 0x0016, + WM_QUIT = 0x0012, + WM_ERASEBKGND = 0x0014, + WM_SYSCOLORCHANGE = 0x0015, + WM_SHOWWINDOW = 0x0018, + WM_WININICHANGE = 0x001A, + WM_SETTINGCHANGE = WM_WININICHANGE, + WM_DEVMODECHANGE = 0x001B, + WM_ACTIVATEAPP = 0x001C, + WM_FONTCHANGE = 0x001D, + WM_TIMECHANGE = 0x001E, + WM_CANCELMODE = 0x001F, + WM_SETCURSOR = 0x0020, + WM_MOUSEACTIVATE = 0x0021, + WM_CHILDACTIVATE = 0x0022, + WM_QUEUESYNC = 0x0023, + WM_GETMINMAXINFO = 0x0024, + WM_PAINTICON = 0x0026, + WM_ICONERASEBKGND = 0x0027, + WM_NEXTDLGCTL = 0x0028, + WM_SPOOLERSTATUS = 0x002A, + WM_DRAWITEM = 0x002B, + WM_MEASUREITEM = 0x002C, + WM_DELETEITEM = 0x002D, + WM_VKEYTOITEM = 0x002E, + WM_CHARTOITEM = 0x002F, + WM_SETFONT = 0x0030, + WM_GETFONT = 0x0031, + WM_SETHOTKEY = 0x0032, + WM_GETHOTKEY = 0x0033, + WM_QUERYDRAGICON = 0x0037, + WM_COMPAREITEM = 0x0039, + WM_GETOBJECT = 0x003D, + WM_COMPACTING = 0x0041, + WM_COMMNOTIFY = 0x0044, + WM_WINDOWPOSCHANGING = 0x0046, + WM_WINDOWPOSCHANGED = 0x0047, + WM_POWER = 0x0048, + WM_COPYDATA = 0x004A, + WM_CANCELJOURNAL = 0x004B, + WM_NOTIFY = 0x004E, + WM_INPUTLANGCHANGEREQUEST = 0x0050, + WM_INPUTLANGCHANGE = 0x0051, + WM_TCARD = 0x0052, + WM_HELP = 0x0053, + WM_USERCHANGED = 0x0054, + WM_NOTIFYFORMAT = 0x0055, + WM_CONTEXTMENU = 0x007B, + WM_STYLECHANGING = 0x007C, + WM_STYLECHANGED = 0x007D, + WM_DISPLAYCHANGE = 0x007E, + WM_GETICON = 0x007F, + WM_SETICON = 0x0080, + WM_NCCREATE = 0x0081, + WM_NCDESTROY = 0x0082, + WM_NCCALCSIZE = 0x0083, + WM_NCHITTEST = 0x0084, + WM_NCPAINT = 0x0085, + WM_NCACTIVATE = 0x0086, + WM_GETDLGCODE = 0x0087, + WM_SYNCPAINT = 0x0088, + + WM_NCMOUSEMOVE = 0x00A0, + WM_NCLBUTTONDOWN = 0x00A1, + WM_NCLBUTTONUP = 0x00A2, + WM_NCLBUTTONDBLCLK = 0x00A3, + WM_NCRBUTTONDOWN = 0x00A4, + WM_NCRBUTTONUP = 0x00A5, + WM_NCRBUTTONDBLCLK = 0x00A6, + WM_NCMBUTTONDOWN = 0x00A7, + WM_NCMBUTTONUP = 0x00A8, + WM_NCMBUTTONDBLCLK = 0x00A9, + WM_NCXBUTTONDOWN = 0x00AB, + WM_NCXBUTTONUP = 0x00AC, + WM_NCXBUTTONDBLCLK = 0x00AD, + + WM_INPUT_DEVICE_CHANGE = 0x00FE, + WM_INPUT = 0x00FF, + + WM_KEYFIRST = 0x0100, + WM_KEYDOWN = WM_KEYFIRST, + WM_KEYUP = 0x0101, + WM_CHAR = 0x0102, + WM_DEADCHAR = 0x0103, + WM_SYSKEYDOWN = 0x0104, + WM_SYSKEYUP = 0x0105, + WM_SYSCHAR = 0x0106, + WM_SYSDEADCHAR = 0x0107, + WM_UNICHAR = 0x0109, + WM_KEYLAST = WM_UNICHAR, + + WM_IME_STARTCOMPOSITION = 0x010D, + WM_IME_ENDCOMPOSITION = 0x010E, + WM_IME_COMPOSITION = 0x010F, + WM_IME_KEYLAST = WM_IME_COMPOSITION, + + WM_INITDIALOG = 0x0110, + WM_COMMAND = 0x0111, + WM_SYSCOMMAND = 0x0112, + WM_TIMER = 0x0113, + WM_HSCROLL = 0x0114, + WM_VSCROLL = 0x0115, + WM_INITMENU = 0x0116, + WM_INITMENUPOPUP = 0x0117, + WM_MENUSELECT = 0x011F, + WM_MENUCHAR = 0x0120, + WM_ENTERIDLE = 0x0121, + WM_MENURBUTTONUP = 0x0122, + WM_MENUDRAG = 0x0123, + WM_MENUGETOBJECT = 0x0124, + WM_UNINITMENUPOPUP = 0x0125, + WM_MENUCOMMAND = 0x0126, + + WM_CHANGEUISTATE = 0x0127, + WM_UPDATEUISTATE = 0x0128, + WM_QUERYUISTATE = 0x0129, + + WM_CTLCOLORMSGBOX = 0x0132, + WM_CTLCOLOREDIT = 0x0133, + WM_CTLCOLORLISTBOX = 0x0134, + WM_CTLCOLORBTN = 0x0135, + WM_CTLCOLORDLG = 0x0136, + WM_CTLCOLORSCROLLBAR = 0x0137, + WM_CTLCOLORSTATIC = 0x0138, + MN_GETHMENU = 0x01E1, + + WM_MOUSEFIRST = 0x0200, + WM_MOUSEMOVE = WM_MOUSEFIRST, + WM_LBUTTONDOWN = 0x0201, + WM_LBUTTONUP = 0x0202, + WM_LBUTTONDBLCLK = 0x0203, + WM_RBUTTONDOWN = 0x0204, + WM_RBUTTONUP = 0x0205, + WM_RBUTTONDBLCLK = 0x0206, + WM_MBUTTONDOWN = 0x0207, + WM_MBUTTONUP = 0x0208, + WM_MBUTTONDBLCLK = 0x0209, + WM_MOUSEWHEEL = 0x020A, + WM_XBUTTONDOWN = 0x020B, + WM_XBUTTONUP = 0x020C, + WM_XBUTTONDBLCLK = 0x020D, + WM_MOUSEHWHEEL = 0x020E, + + WM_PARENTNOTIFY = 0x0210, + WM_ENTERMENULOOP = 0x0211, + WM_EXITMENULOOP = 0x0212, + + WM_NEXTMENU = 0x0213, + WM_SIZING = 0x0214, + WM_CAPTURECHANGED = 0x0215, + WM_MOVING = 0x0216, + + WM_POWERBROADCAST = 0x0218, + + WM_DEVICECHANGE = 0x0219, + + WM_MDICREATE = 0x0220, + WM_MDIDESTROY = 0x0221, + WM_MDIACTIVATE = 0x0222, + WM_MDIRESTORE = 0x0223, + WM_MDINEXT = 0x0224, + WM_MDIMAXIMIZE = 0x0225, + WM_MDITILE = 0x0226, + WM_MDICASCADE = 0x0227, + WM_MDIICONARRANGE = 0x0228, + WM_MDIGETACTIVE = 0x0229, + + WM_MDISETMENU = 0x0230, + WM_ENTERSIZEMOVE = 0x0231, + WM_EXITSIZEMOVE = 0x0232, + WM_DROPFILES = 0x0233, + WM_MDIREFRESHMENU = 0x0234, + + WM_IME_SETCONTEXT = 0x0281, + WM_IME_NOTIFY = 0x0282, + WM_IME_CONTROL = 0x0283, + WM_IME_COMPOSITIONFULL = 0x0284, + WM_IME_SELECT = 0x0285, + WM_IME_CHAR = 0x0286, + WM_IME_REQUEST = 0x0288, + WM_IME_KEYDOWN = 0x0290, + WM_IME_KEYUP = 0x0291, + + WM_MOUSEHOVER = 0x02A1, + WM_MOUSELEAVE = 0x02A3, + WM_NCMOUSEHOVER = 0x02A0, + WM_NCMOUSELEAVE = 0x02A2, + + WM_WTSSESSION_CHANGE = 0x02B1, + + WM_TABLET_FIRST = 0x02c0, + WM_TABLET_LAST = 0x02df, + + WM_CUT = 0x0300, + WM_COPY = 0x0301, + WM_PASTE = 0x0302, + WM_CLEAR = 0x0303, + WM_UNDO = 0x0304, + WM_RENDERFORMAT = 0x0305, + WM_RENDERALLFORMATS = 0x0306, + WM_DESTROYCLIPBOARD = 0x0307, + WM_DRAWCLIPBOARD = 0x0308, + WM_PAINTCLIPBOARD = 0x0309, + WM_VSCROLLCLIPBOARD = 0x030A, + WM_SIZECLIPBOARD = 0x030B, + WM_ASKCBFORMATNAME = 0x030C, + WM_CHANGECBCHAIN = 0x030D, + WM_HSCROLLCLIPBOARD = 0x030E, + WM_QUERYNEWPALETTE = 0x030F, + WM_PALETTEISCHANGING = 0x0310, + WM_PALETTECHANGED = 0x0311, + WM_HOTKEY = 0x0312, + + WM_PRINT = 0x0317, + WM_PRINTCLIENT = 0x0318, + + WM_APPCOMMAND = 0x0319, + + WM_THEMECHANGED = 0x031A, + + WM_CLIPBOARDUPDATE = 0x031D, + + WM_DWMCOMPOSITIONCHANGED = 0x031E, + WM_DWMNCRENDERINGCHANGED = 0x031F, + WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320, + WM_DWMWINDOWMAXIMIZEDCHANGE = 0x0321, + + WM_GETTITLEBARINFOEX = 0x033F, + + WM_HANDHELDFIRST = 0x0358, + WM_HANDHELDLAST = 0x035F, + + WM_AFXFIRST = 0x0360, + WM_AFXLAST = 0x037F, + + WM_PENWINFIRST = 0x0380, + WM_PENWINLAST = 0x038F, + + WM_APP = 0x8000, + + WM_USER = 0x0400, + + WM_REFLECT = WM_USER + 0x1C00, + } /// - /// Flash continuously until the window comes to the foreground. + /// Returns true if the current application has focus, false otherwise. /// - TimerNoFG = 12, + /// + /// If the current application is focused. + /// + public static bool ApplicationIsActivated() + { + var activatedHandle = GetForegroundWindow(); + if (activatedHandle == IntPtr.Zero) + return false; // No window is currently activated + + _ = GetWindowThreadProcessId(activatedHandle, out var activeProcId); + if (Marshal.GetLastWin32Error() != 0) + return false; + + return activeProcId == Environment.ProcessId; + } + + /// + /// Passes message information to the specified window procedure. + /// + /// + /// The previous window procedure. If this value is obtained by calling the GetWindowLong function with the nIndex parameter set to + /// GWL_WNDPROC or DWL_DLGPROC, it is actually either the address of a window or dialog box procedure, or a special internal value + /// meaningful only to CallWindowProc. + /// + /// + /// A handle to the window procedure to receive the message. + /// + /// + /// The message. + /// + /// + /// Additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. + /// + /// + /// More additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. + /// + /// + /// Use the CallWindowProc function for window subclassing. Usually, all windows with the same class share one window procedure. A + /// subclass is a window or set of windows with the same class whose messages are intercepted and processed by another window procedure + /// (or procedures) before being passed to the window procedure of the class. + /// The SetWindowLong function creates the subclass by changing the window procedure associated with a particular window, causing the + /// system to call the new window procedure instead of the previous one.An application must pass any messages not processed by the new + /// window procedure to the previous window procedure by calling CallWindowProc.This allows the application to create a chain of window + /// procedures. + /// + [DllImport("user32.dll")] + public static extern long CallWindowProcW(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, ulong wParam, long lParam); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-flashwindowex. + /// Flashes the specified window. It does not change the active state of the window. + /// + /// + /// A pointer to a FLASHWINFO structure. + /// + /// + /// The return value specifies the window's state before the call to the FlashWindowEx function. If the window caption + /// was drawn as active before the call, the return value is nonzero. Otherwise, the return value is zero. + /// + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FlashWindowEx(ref FlashWindowInfo pwfi); + + /// + /// Retrieves a handle to the foreground window (the window with which the user is currently working). The system assigns + /// a slightly higher priority to the thread that creates the foreground window than it does to other threads. + /// + /// + /// The return value is a handle to the foreground window. The foreground window can be NULL in certain circumstances, + /// such as when a window is losing activation. + /// + [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] + public static extern IntPtr GetForegroundWindow(); + + /// + /// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the + /// process that created the window. + /// + /// + /// A handle to the window. + /// + /// + /// A pointer to a variable that receives the process identifier. If this parameter is not NULL, GetWindowThreadProcessId + /// copies the identifier of the process to the variable; otherwise, it does not. + /// + /// + /// The return value is the identifier of the thread that created the window. + /// + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); + + /// + /// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message, + /// such as status or error information. The message box returns an integer value that indicates which button the user + /// clicked. + /// + /// + /// A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no + /// owner window. + /// + /// + /// The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage + /// return and/or linefeed character between each line. + /// + /// + /// The dialog box title. If this parameter is NULL, the default title is Error. + /// + /// The contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups + /// of flags. + /// + /// + /// If a message box has a Cancel button, the function returns the IDCANCEL value if either the ESC key is pressed or + /// the Cancel button is selected. If the message box has no Cancel button, pressing ESC will no effect - unless an + /// MB_OK button is present. If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK. + /// If the function fails, the return value is zero.To get extended error information, call GetLastError. If the function + /// succeeds, the return value is one of the ID* enum values. + /// + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, MessageBoxType type); + + /// + /// Changes an attribute of the specified window. The function also sets a value at the specified offset in the extra window memory. + /// + /// + /// A handle to the window and, indirectly, the class to which the window belongs. The SetWindowLongPtr function fails if the + /// process that owns the window specified by the hWnd parameter is at a higher process privilege in the UIPI hierarchy than the + /// process the calling thread resides in. + /// + /// + /// The zero-based offset to the value to be set. Valid values are in the range zero through the number of bytes of extra window + /// memory, minus the size of a LONG_PTR. To set any other value, specify one of the values. + /// + /// + /// The replacement value. + /// + /// + /// If the function succeeds, the return value is the previous value of the specified offset. If the function fails, the return + /// value is zero.To get extended error information, call GetLastError. If the previous value is zero and the function succeeds, + /// the return value is zero, but the function does not clear the last error information. To determine success or failure, clear + /// the last error information by calling SetLastError with 0, then call SetWindowLongPtr.Function failure will be indicated by + /// a return value of zero and a GetLastError result that is nonzero. + /// + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, WindowLongType nIndex, IntPtr dwNewLong); + + /// + /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-flashwinfo. + /// Contains the flash status for a window and the number of times the system should flash the window. + /// + [StructLayout(LayoutKind.Sequential)] + public struct FlashWindowInfo + { + /// + /// The size of the structure, in bytes. + /// + public uint Size; + + /// + /// A handle to the window to be flashed. The window can be either opened or minimized. + /// + public IntPtr Hwnd; + + /// + /// The flash status. This parameter can be one or more of the FlashWindow enum values. + /// + public FlashWindow Flags; + + /// + /// The number of times to flash the window. + /// + public uint Count; + + /// + /// The rate at which the window is to be flashed, in milliseconds. If dwTimeout is zero, the function uses the + /// default cursor blink rate. + /// + public uint Timeout; + } } /// - /// IDC_* from winuser. + /// Native imm32 functions. /// - public enum CursorType + internal static partial class NativeFunctions { /// - /// Standard arrow and small hourglass. + /// GCS_* from imm32. + /// These values are used with ImmGetCompositionString and WM_IME_COMPOSITION. /// - AppStarting = 32650, + [Flags] + public enum IMEComposition + { + /// + /// Retrieve or update the attribute of the composition string. + /// + CompAttr = 0x0010, + + /// + /// Retrieve or update clause information of the composition string. + /// + CompClause = 0x0020, + + /// + /// Retrieve or update the attributes of the reading string of the current composition. + /// + CompReadAttr = 0x0002, + + /// + /// Retrieve or update the clause information of the reading string of the composition string. + /// + CompReadClause = 0x0004, + + /// + /// Retrieve or update the reading string of the current composition. + /// + CompReadStr = 0x0001, + + /// + /// Retrieve or update the current composition string. + /// + CompStr = 0x0008, + + /// + /// Retrieve or update the cursor position in composition string. + /// + CursorPos = 0x0080, + + /// + /// Retrieve or update the starting position of any changes in composition string. + /// + DeltaStart = 0x0100, + + /// + /// Retrieve or update clause information of the result string. + /// + ResultClause = 0x1000, + + /// + /// Retrieve or update clause information of the reading string. + /// + ResultReadClause = 0x0400, + + /// + /// Retrieve or update the reading string. + /// + ResultReadStr = 0x0200, + + /// + /// Retrieve or update the string of the composition result. + /// + ResultStr = 0x0800, + } /// - /// Standard arrow. + /// IMN_* from imm32. + /// Input Method Manager Commands, this enum is not exhaustive. /// - Arrow = 32512, + public enum IMECommand + { + /// + /// Notifies the application when an IME is about to change the content of the candidate window. + /// + ChangeCandidate = 0x0003, + + /// + /// Notifies an application when an IME is about to close the candidates window. + /// + CloseCandidate = 0x0004, + + /// + /// Notifies an application when an IME is about to open the candidate window. + /// + OpenCandidate = 0x0005, + + /// + /// Notifies an application when the conversion mode of the input context is updated. + /// + SetConversionMode = 0x0006, + } /// - /// Crosshair. + /// Returns the input context associated with the specified window. /// - Cross = 32515, + /// Unnamed parameter 1. + /// + /// Returns the handle to the input context. + /// + [DllImport("imm32.dll")] + public static extern IntPtr ImmGetContext(IntPtr hWnd); /// - /// Hand. + /// Retrieves information about the composition string. /// - Hand = 32649, + /// + /// Unnamed parameter 1. + /// + /// + /// Unnamed parameter 2. + /// + /// + /// Pointer to a buffer in which the function retrieves the composition string information. + /// + /// + /// Size, in bytes, of the output buffer, even if the output is a Unicode string. The application sets this parameter to 0 + /// if the function is to return the size of the required output buffer. + /// + /// + /// Returns the number of bytes copied to the output buffer. If dwBufLen is set to 0, the function returns the buffer size, + /// in bytes, required to receive all requested information, excluding the terminating null character. The return value is + /// always the size, in bytes, even if the requested data is a Unicode string. + /// This function returns one of the following negative error codes if it does not succeed: + /// - IMM_ERROR_NODATA.Composition data is not ready in the input context. + /// - IMM_ERROR_GENERAL.General error detected by IME. + /// + [DllImport("imm32.dll")] + public static extern long ImmGetCompositionStringW(IntPtr hImc, IMEComposition arg2, IntPtr lpBuf, uint dwBufLen); /// - /// Arrow and question mark. + /// Retrieves a candidate list. /// - Help = 32651, + /// + /// Unnamed parameter 1. + /// + /// + /// Zero-based index of the candidate list. + /// + /// + /// Pointer to a CANDIDATELIST structure in which the function retrieves the candidate list. + /// + /// + /// Size, in bytes, of the buffer to receive the candidate list. The application can specify 0 for this parameter if the + /// function is to return the required size of the output buffer only. + /// + /// + /// Returns the number of bytes copied to the candidate list buffer if successful. If the application has supplied 0 for + /// the dwBufLen parameter, the function returns the size required for the candidate list buffer. The function returns 0 + /// if it does not succeed. + /// + [DllImport("imm32.dll")] + public static extern long ImmGetCandidateListW(IntPtr hImc, uint deIndex, IntPtr lpCandList, uint dwBufLen); /// - /// I-beam. + /// Sets the position of the composition window. /// - IBeam = 32513, + /// + /// Unnamed parameter 1. + /// + /// + /// Pointer to a COMPOSITIONFORM structure that contains the new position and other related information about + /// the composition window. + /// + /// + /// Returns a nonzero value if successful, or 0 otherwise. + /// + [DllImport("imm32.dll", CharSet = CharSet.Auto)] + public static extern bool ImmSetCompositionWindow(IntPtr hImc, ref CompositionForm frm); /// - /// Obsolete for applications marked version 4.0 or later. + /// Releases the input context and unlocks the memory associated in the input context. An application must call this + /// function for each call to the ImmGetContext function. /// - Icon = 32641, + /// + /// Unnamed parameter 1. + /// + /// + /// Unnamed parameter 2. + /// + /// + /// Returns a nonzero value if successful, or 0 otherwise. + /// + [DllImport("imm32.dll", CharSet = CharSet.Auto)] + public static extern bool ImmReleaseContext(IntPtr hwnd, IntPtr hImc); /// - /// Slashed circle. + /// Contains information about a candidate list. /// - No = 32648, + public struct CandidateList + { + /// + /// Size, in bytes, of the structure, the offset array, and all candidate strings. + /// + public int Size; + + /// + /// Candidate style values. This member can have one or more of the IME_CAND_* values. + /// + public int Style; + + /// + /// Number of candidate strings. + /// + public int Count; + + /// + /// Index of the selected candidate string. + /// + public int Selection; + + /// + /// Index of the first candidate string in the candidate window. This varies as the user presses the PAGE UP and PAGE DOWN keys. + /// + public int PageStart; + + /// + /// Number of candidate strings to be shown in one page in the candidate window. The user can move to the next page by pressing IME-defined keys, such as the PAGE UP or PAGE DOWN key. If this number is 0, an application can define a proper value by itself. + /// + public int PageSize; + + // /// + // /// Offset to the start of the first candidate string, relative to the start of this structure. The offsets + // /// for subsequent strings immediately follow this member, forming an array of 32-bit offsets. + // /// + // public IntPtr Offset; // manually handle + } /// - /// Obsolete for applications marked version 4.0 or later.Use IDC_SIZEALL. + /// Contains style and position information for a composition window. /// - Size = 32640, + [StructLayout(LayoutKind.Sequential)] + public struct CompositionForm + { + /// + /// Position style. This member can be one of the CFS_* values. + /// + public int Style; + + /// + /// A POINT structure containing the coordinates of the upper left corner of the composition window. + /// + public Point CurrentPos; + + /// + /// A RECT structure containing the coordinates of the upper left and lower right corners of the composition window. + /// + public Rect Area; + } /// - /// Four-pointed arrow pointing north, south, east, and west. + /// Contains coordinates for a point. /// - SizeAll = 32646, + [StructLayout(LayoutKind.Sequential)] + public struct Point + { + /// + /// The X position. + /// + public int X; + + /// + /// The Y position. + /// + public int Y; + } /// - /// Double-pointed arrow pointing northeast and southwest. + /// Contains dimensions for a rectangle. /// - SizeNeSw = 32643, + [StructLayout(LayoutKind.Sequential)] + public struct Rect + { + /// + /// The left position. + /// + public int Left; - /// - /// Double-pointed arrow pointing north and south. - /// - SizeNS = 32645, + /// + /// The top position. + /// + public int Top; - /// - /// Double-pointed arrow pointing northwest and southeast. - /// - SizeNwSe = 32642, + /// + /// The right position. + /// + public int Right; - /// - /// Double-pointed arrow pointing west and east. - /// - SizeWE = 32644, - - /// - /// Vertical arrow. - /// - UpArrow = 32516, - - /// - /// Hourglass. - /// - Wait = 32514, + /// + /// The bottom position. + /// + public int Bottom; + } } /// - /// MB_* from winuser. + /// Native kernel32 functions. /// - public enum MessageBoxType : uint + internal static partial class NativeFunctions { /// - /// The default value for any of the various subtypes. + /// MEM_* from memoryapi. /// - DefaultValue = 0x0, + [Flags] + public enum AllocationType + { + /// + /// To coalesce two adjacent placeholders, specify MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS. When you coalesce + /// placeholders, lpAddress and dwSize must exactly match those of the placeholder. + /// + CoalescePlaceholders = 0x1, - // To indicate the buttons displayed in the message box, specify one of the following values. + /// + /// Frees an allocation back to a placeholder (after you've replaced a placeholder with a private allocation using + /// VirtualAlloc2 or Virtual2AllocFromApp). To split a placeholder into two placeholders, specify + /// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER. + /// + PreservePlaceholder = 0x2, + + /// + /// Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved + /// memory pages. The function also guarantees that when the caller later initially accesses the memory, the contents + /// will be zero. Actual physical pages are not allocated unless/until the virtual addresses are actually accessed. + /// To reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Attempting to commit + /// a specific address range by specifying MEM_COMMIT without MEM_RESERVE and a non-NULL lpAddress fails unless the + /// entire range has already been reserved. The resulting error code is ERROR_INVALID_ADDRESS. An attempt to commit + /// a page that is already committed does not cause the function to fail. This means that you can commit pages without + /// first determining the current commitment state of each page. If lpAddress specifies an address within an enclave, + /// flAllocationType must be MEM_COMMIT. + /// + Commit = 0x1000, + + /// + /// Reserves a range of the process's virtual address space without allocating any actual physical storage in memory + /// or in the paging file on disk. You commit reserved pages by calling VirtualAllocEx again with MEM_COMMIT. To + /// reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Other memory allocation + /// functions, such as malloc and LocalAlloc, cannot use reserved memory until it has been released. + /// + Reserve = 0x2000, + + /// + /// Decommits the specified region of committed pages. After the operation, the pages are in the reserved state. + /// The function does not fail if you attempt to decommit an uncommitted page. This means that you can decommit + /// a range of pages without first determining the current commitment state. The MEM_DECOMMIT value is not supported + /// when the lpAddress parameter provides the base address for an enclave. + /// + Decommit = 0x4000, + + /// + /// Releases the specified region of pages, or placeholder (for a placeholder, the address space is released and + /// available for other allocations). After this operation, the pages are in the free state. If you specify this + /// value, dwSize must be 0 (zero), and lpAddress must point to the base address returned by the VirtualAlloc function + /// when the region is reserved. The function fails if either of these conditions is not met. If any pages in the + /// region are committed currently, the function first decommits, and then releases them. The function does not + /// fail if you attempt to release pages that are in different states, some reserved and some committed. This means + /// that you can release a range of pages without first determining the current commitment state. + /// + Release = 0x8000, + + /// + /// Indicates that data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages + /// should not be read from or written to the paging file. However, the memory block will be used again later, so + /// it should not be decommitted. This value cannot be used with any other value. Using this value does not guarantee + /// that the range operated on with MEM_RESET will contain zeros. If you want the range to contain zeros, decommit + /// the memory and then recommit it. When you use MEM_RESET, the VirtualAllocEx function ignores the value of fProtect. + /// However, you must still set fProtect to a valid protection value, such as PAGE_NOACCESS. VirtualAllocEx returns + /// an error if you use MEM_RESET and the range of memory is mapped to a file. A shared view is only acceptable + /// if it is mapped to a paging file. + /// + Reset = 0x80000, + + /// + /// MEM_RESET_UNDO should only be called on an address range to which MEM_RESET was successfully applied earlier. + /// It indicates that the data in the specified memory range specified by lpAddress and dwSize is of interest to + /// the caller and attempts to reverse the effects of MEM_RESET. If the function succeeds, that means all data in + /// the specified address range is intact. If the function fails, at least some of the data in the address range + /// has been replaced with zeroes. This value cannot be used with any other value. If MEM_RESET_UNDO is called on + /// an address range which was not MEM_RESET earlier, the behavior is undefined. When you specify MEM_RESET, the + /// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid + /// protection value, such as PAGE_NOACCESS. + /// + ResetUndo = 0x1000000, + + /// + /// Reserves an address range that can be used to map Address Windowing Extensions (AWE) pages. This value must + /// be used with MEM_RESERVE and no other values. + /// + Physical = 0x400000, + + /// + /// Allocates memory at the highest possible address. This can be slower than regular allocations, especially when + /// there are many allocations. + /// + TopDown = 0x100000, + + /// + /// Causes the system to track pages that are written to in the allocated region. If you specify this value, you + /// must also specify MEM_RESERVE. To retrieve the addresses of the pages that have been written to since the region + /// was allocated or the write-tracking state was reset, call the GetWriteWatch function. To reset the write-tracking + /// state, call GetWriteWatch or ResetWriteWatch. The write-tracking feature remains enabled for the memory region + /// until the region is freed. + /// + WriteWatch = 0x200000, + + /// + /// Allocates memory using large page support. The size and alignment must be a multiple of the large-page minimum. + /// To obtain this value, use the GetLargePageMinimum function. If you specify this value, you must also specify + /// MEM_RESERVE and MEM_COMMIT. + /// + LargePages = 0x20000000, + } /// - /// The message box contains three push buttons: Abort, Retry, and Ignore. + /// SEM_* from errhandlingapi. /// - AbortRetryIgnore = 0x2, + [Flags] + public enum ErrorModes : uint + { + /// + /// Use the system default, which is to display all error dialog boxes. + /// + SystemDefault = 0x0, + + /// + /// The system does not display the critical-error-handler message box. Instead, the system sends the error to the + /// calling process. Best practice is that all applications call the process-wide SetErrorMode function with a parameter + /// of SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application. + /// + FailCriticalErrors = 0x0001, + + /// + /// The system automatically fixes memory alignment faults and makes them invisible to the application. It does + /// this for the calling process and any descendant processes. This feature is only supported by certain processor + /// architectures. For more information, see the Remarks section. After this value is set for a process, subsequent + /// attempts to clear the value are ignored. + /// + NoAlignmentFaultExcept = 0x0004, + + /// + /// The system does not display the Windows Error Reporting dialog. + /// + NoGpFaultErrorBox = 0x0002, + + /// + /// The OpenFile function does not display a message box when it fails to find a file. Instead, the error is returned + /// to the caller. This error mode overrides the OF_PROMPT flag. + /// + NoOpenFileErrorBox = 0x8000, + } /// - /// The message box contains three push buttons: Cancel, Try Again, Continue. Use this message box type instead - /// of MB_ABORTRETRYIGNORE. + /// PAGE_* from memoryapi. /// - CancelTryContinue = 0x6, + [Flags] + public enum MemoryProtection + { + /// + /// Enables execute access to the committed region of pages. An attempt to write to the committed region results + /// in an access violation. This flag is not supported by the CreateFileMapping function. + /// + Execute = 0x10, + + /// + /// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region + /// results in an access violation. + /// + ExecuteRead = 0x20, + + /// + /// Enables execute, read-only, or read/write access to the committed region of pages. + /// + ExecuteReadWrite = 0x40, + + /// + /// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to + /// write to a committed copy-on-write page results in a private copy of the page being made for the process. The + /// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not + /// supported by the VirtualAlloc or VirtualAllocEx functions. + /// + ExecuteWriteCopy = 0x80, + + /// + /// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed + /// region results in an access violation. This flag is not supported by the CreateFileMapping function. + /// + NoAccess = 0x01, + + /// + /// Enables read-only access to the committed region of pages. An attempt to write to the committed region results + /// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed + /// region results in an access violation. + /// + ReadOnly = 0x02, + + /// + /// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, + /// attempting to execute code in the committed region results in an access violation. + /// + ReadWrite = 0x04, + + /// + /// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to + /// a committed copy-on-write page results in a private copy of the page being made for the process. The private + /// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is + /// enabled, attempting to execute code in the committed region results in an access violation. This flag is not + /// supported by the VirtualAlloc or VirtualAllocEx functions. + /// + WriteCopy = 0x08, + + /// + /// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like + /// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations + /// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable + /// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect + /// or CreateFileMapping functions. + /// + TargetsInvalid = 0x40000000, + + /// + /// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect. + /// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information + /// will be maintained while the page protection changes. This flag is only valid when the protection changes to + /// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. + /// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call + /// targets for CFG. + /// + TargetsNoUpdate = TargetsInvalid, + + /// + /// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a + /// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time + /// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn + /// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a + /// system service, the service typically returns a failure status indicator. This value cannot be used with + /// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function. + /// + Guard = 0x100, + + /// + /// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required + /// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an + /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS, + /// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the + /// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared + /// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function. + /// + NoCache = 0x200, + + /// + /// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required + /// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an + /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS, + /// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory + /// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access + /// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function. + /// + WriteCombine = 0x400, + } /// - /// Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends - /// a WM_HELP message to the owner. + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary. + /// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference + /// count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer + /// valid. /// - Help = 0x4000, + /// + /// A handle to the loaded library module. The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function + /// returns this handle. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended + /// error information, call the GetLastError function. + /// + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FreeLibrary(IntPtr hModule); /// - /// The message box contains one push button: OK. This is the default. + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew. + /// Retrieves the fully qualified path for the file that contains the specified module. The module must have been loaded + /// by the current process. To locate the file for a module that was loaded by another process, use the GetModuleFileNameEx + /// function. /// - Ok = DefaultValue, + /// + /// A handle to the loaded module whose path is being requested. If this parameter is NULL, GetModuleFileName retrieves + /// the path of the executable file of the current process. The GetModuleFileName function does not retrieve the path + /// for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag. For more information, see LoadLibraryEx. + /// + /// + /// A pointer to a buffer that receives the fully qualified path of the module. If the length of the path is less than + /// the size that the nSize parameter specifies, the function succeeds and the path is returned as a null-terminated + /// string. If the length of the path exceeds the size that the nSize parameter specifies, the function succeeds and + /// the string is truncated to nSize characters including the terminating null character. + /// + /// + /// The size of the lpFilename buffer, in TCHARs. + /// + /// + /// If the function succeeds, the return value is the length of the string that is copied to the buffer, in characters, + /// not including the terminating null character. If the buffer is too small to hold the module name, the string is + /// truncated to nSize characters including the terminating null character, the function returns nSize, and the function + /// sets the last error to ERROR_INSUFFICIENT_BUFFER. If nSize is zero, the return value is zero and the last error + /// code is ERROR_SUCCESS. If the function fails, the return value is 0 (zero). To get extended error information, call + /// GetLastError. + /// + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [PreserveSig] + public static extern uint GetModuleFileNameW( + [In] IntPtr hModule, + [Out] StringBuilder lpFilename, + [In][MarshalAs(UnmanagedType.U4)] int nSize); /// - /// The message box contains two push buttons: OK and Cancel. + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew. + /// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To + /// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function. /// - OkCancel = 0x1, + /// + /// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default + /// library extension .dll is appended. The file name string can include a trailing point character (.) to indicate + /// that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure + /// to use backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules + /// currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns + /// a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve + /// handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. + /// + /// + /// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return + /// value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + public static extern IntPtr GetModuleHandleW(string lpModuleName); /// - /// The message box contains two push buttons: Retry and Cancel. + /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). /// - RetryCancel = 0x5, + /// + /// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, LoadPackagedLibrary, + /// or GetModuleHandle function returns this handle. The GetProcAddress function does not retrieve addresses from modules + /// that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. + /// + /// + /// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be + /// in the low-order word; the high-order word must be zero. + /// + /// + /// If the function succeeds, the return value is the address of the exported function or variable. If the function + /// fails, the return value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + [SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "Ansi only")] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); /// - /// The message box contains two push buttons: Yes and No. + /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw. + /// Loads the specified module into the address space of the calling process. The specified module may cause other modules + /// to be loaded. For additional load options, use the LoadLibraryEx function. /// - YesNo = 0x4, + /// + /// The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file). + /// The name specified is the file name of the module and is not related to the name stored in the library module itself, + /// as specified by the LIBRARY keyword in the module-definition (.def) file. If the string specifies a full path, the + /// function searches only that path for the module. If the string specifies a relative path or a module name without + /// a path, the function uses a standard search strategy to find the module; for more information, see the Remarks. + /// If the function cannot find the module, the function fails.When specifying a path, be sure to use backslashes (\), + /// not forward slashes(/). For more information about paths, see Naming a File or Directory. If the string specifies + /// a module name without a path and the file name extension is omitted, the function appends the default library extension + /// .dll to the module name. To prevent the function from appending .dll to the module name, include a trailing point + /// character (.) in the module name string. + /// + /// + /// If the function succeeds, the return value is a handle to the module. If the function fails, the return value is + /// NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr LoadLibraryW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName); /// - /// The message box contains three push buttons: Yes, No, and Cancel. + /// ReadProcessMemory copies the data in the specified address range from the address space of the specified process + /// into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can + /// call the function. The entire area to be read must be accessible, and if it is not accessible, the function fails. /// - YesNoCancel = 0x3, - - // To display an icon in the message box, specify one of the following values. + /// + /// A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process. + /// + /// + /// A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the + /// system verifies that all data in the base address and memory of the specified size is accessible for read access, + /// and if it is not accessible the function fails. + /// + /// + /// A pointer to a buffer that receives the contents from the address space of the specified process. + /// + /// + /// The number of bytes to be read from the specified process. + /// + /// + /// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead + /// is NULL, the parameter is ignored. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get + /// extended error information, call GetLastError. The function fails if the requested read operation crosses into an + /// area of the process that is inaccessible. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + IntPtr lpBuffer, + int dwSize, + out IntPtr lpNumberOfBytesRead); /// - /// An exclamation-point icon appears in the message box. + /// ReadProcessMemory copies the data in the specified address range from the address space of the specified process + /// into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can + /// call the function. The entire area to be read must be accessible, and if it is not accessible, the function fails. /// - IconExclamation = 0x30, + /// + /// A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process. + /// + /// + /// A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the + /// system verifies that all data in the base address and memory of the specified size is accessible for read access, + /// and if it is not accessible the function fails. + /// + /// + /// A pointer to a buffer that receives the contents from the address space of the specified process. + /// + /// + /// The number of bytes to be read from the specified process. + /// + /// + /// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead + /// is NULL, the parameter is ignored. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get + /// extended error information, call GetLastError. The function fails if the requested read operation crosses into an + /// area of the process that is inaccessible. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool ReadProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + byte[] lpBuffer, + int dwSize, + out IntPtr lpNumberOfBytesRead); /// - /// An exclamation-point icon appears in the message box. + /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-seterrormode. + /// Controls whether the system will handle the specified types of serious errors or whether the process will handle + /// them. /// - IconWarning = IconExclamation, + /// + /// The process error mode. This parameter can be one or more of the ErrorMode enum values. + /// + /// + /// The return value is the previous state of the error-mode bit flags. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern ErrorModes SetErrorMode(ErrorModes uMode); /// - /// An icon consisting of a lowercase letter i in a circle appears in the message box. + /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setunhandledexceptionfilter. + /// Enables an application to supersede the top-level exception handler of each thread of a process. After calling this + /// function, if an exception occurs in a process that is not being debugged, and the exception makes it to the unhandled + /// exception filter, that filter will call the exception filter function specified by the lpTopLevelExceptionFilter + /// parameter. /// - IconInformation = 0x40, + /// + /// A pointer to a top-level exception filter function that will be called whenever the UnhandledExceptionFilter function + /// gets control, and the process is not being debugged. A value of NULL for this parameter specifies default handling + /// within UnhandledExceptionFilter. The filter function has syntax similar to that of UnhandledExceptionFilter: It + /// takes a single parameter of type LPEXCEPTION_POINTERS, has a WINAPI calling convention, and returns a value of type + /// LONG. The filter function should return one of the EXCEPTION_* enum values. + /// + /// + /// The SetUnhandledExceptionFilter function returns the address of the previous exception filter established with the + /// function. A NULL return value means that there is no current top-level exception handler. + /// + [DllImport("kernel32.dll")] + public static extern IntPtr SetUnhandledExceptionFilter(IntPtr lpTopLevelExceptionFilter); /// - /// An icon consisting of a lowercase letter i in a circle appears in the message box. + /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc. + /// Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process. + /// Memory allocated by this function is automatically initialized to zero. To allocate memory in the address space + /// of another process, use the VirtualAllocEx function. /// - IconAsterisk = IconInformation, + /// + /// The starting address of the region to allocate. If the memory is being reserved, the specified address is rounded + /// down to the nearest multiple of the allocation granularity. If the memory is already reserved and is being committed, + /// the address is rounded down to the next page boundary. To determine the size of a page and the allocation granularity + /// on the host computer, use the GetSystemInfo function. If this parameter is NULL, the system determines where to + /// allocate the region. If this address is within an enclave that you have not initialized by calling InitializeEnclave, + /// VirtualAlloc allocates a page of zeros for the enclave at that address. The page must be previously uncommitted, + /// and will not be measured with the EEXTEND instruction of the Intel Software Guard Extensions programming model. + /// If the address in within an enclave that you initialized, then the allocation operation fails with the + /// ERROR_INVALID_ADDRESS error. + /// + /// + /// The size of the region, in bytes. If the lpAddress parameter is NULL, this value is rounded up to the next page + /// boundary. Otherwise, the allocated pages include all pages containing one or more bytes in the range from lpAddress + /// to lpAddress+dwSize. This means that a 2-byte range straddling a page boundary causes both pages to be included + /// in the allocated region. + /// + /// + /// The type of memory allocation. This parameter must contain one of the MEM_* enum values. + /// + /// + /// The memory protection for the region of pages to be allocated. If the pages are being committed, you can specify + /// any one of the memory protection constants. + /// + /// + /// If the function succeeds, the return value is the base address of the allocated region of pages. If the function + /// fails, the return value is NULL.To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + UIntPtr dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect); + + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + UIntPtr dwSize, + AllocationType flAllocationType, + Memory.MemoryProtection flProtect); /// - /// A question-mark icon appears in the message box. - /// The question-mark message icon is no longer recommended because it does not clearly represent a specific type - /// of message and because the phrasing of a message as a question could apply to any message type. In addition, - /// users can confuse the message symbol question mark with Help information. Therefore, do not use this question - /// mark message symbol in your message boxes. The system continues to support its inclusion only for backward - /// compatibility. + /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree. + /// Releases, decommits, or releases and decommits a region of pages within the virtual address space of the calling + /// process. + /// process. /// - IconQuestion = 0x20, + /// + /// A pointer to the base address of the region of pages to be freed. If the dwFreeType parameter is MEM_RELEASE, this + /// parameter must be the base address returned by the VirtualAlloc function when the region of pages is reserved. + /// + /// + /// The size of the region of memory to be freed, in bytes. If the dwFreeType parameter is MEM_RELEASE, this parameter + /// must be 0 (zero). The function frees the entire region that is reserved in the initial allocation call to VirtualAlloc. + /// If the dwFreeType parameter is MEM_DECOMMIT, the function decommits all memory pages that contain one or more bytes + /// in the range from the lpAddress parameter to (lpAddress+dwSize). This means, for example, that a 2-byte region of + /// memory that straddles a page boundary causes both pages to be decommitted.If lpAddress is the base address returned + /// by VirtualAlloc and dwSize is 0 (zero), the function decommits the entire region that is allocated by VirtualAlloc. + /// After that, the entire region is in the reserved state. + /// + /// + /// The type of free operation. This parameter must be one of the MEM_* enum values. + /// + /// + /// If the function succeeds, the return value is a nonzero value. If the function fails, the return value is 0 (zero). + /// To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern bool VirtualFree( + IntPtr lpAddress, + UIntPtr dwSize, + AllocationType dwFreeType); /// - /// A stop-sign icon appears in the message box. + /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect. + /// Changes the protection on a region of committed pages in the virtual address space of the calling process. /// - IconStop = 0x10, + /// + /// The address of the starting page of the region of pages whose access protection attributes are to be changed. All + /// pages in the specified region must be within the same reserved region allocated when calling the VirtualAlloc or + /// VirtualAllocEx function using MEM_RESERVE. The pages cannot span adjacent reserved regions that were allocated by + /// separate calls to VirtualAlloc or VirtualAllocEx using MEM_RESERVE. + /// + /// + /// The size of the region whose access protection attributes are to be changed, in bytes. The region of affected pages + /// includes all pages containing one or more bytes in the range from the lpAddress parameter to (lpAddress+dwSize). + /// This means that a 2-byte range straddling a page boundary causes the protection attributes of both pages to be changed. + /// + /// + /// The memory protection option. This parameter can be one of the memory protection constants. For mapped views, this + /// value must be compatible with the access protection specified when the view was mapped (see MapViewOfFile, + /// MapViewOfFileEx, and MapViewOfFileExNuma). + /// + /// + /// A pointer to a variable that receives the previous access protection value of the first page in the specified region + /// of pages. If this parameter is NULL or does not point to a valid variable, the function fails. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. + /// To get extended error information, call GetLastError. + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern bool VirtualProtect( + IntPtr lpAddress, + UIntPtr dwSize, + MemoryProtection flNewProtection, + out MemoryProtection lpflOldProtect); + + /// + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + public static extern bool VirtualProtect( + IntPtr lpAddress, + UIntPtr dwSize, + Memory.MemoryProtection flNewProtection, + out Memory.MemoryProtection lpflOldProtect); /// - /// A stop-sign icon appears in the message box. + /// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or + /// the operation fails. /// - IconError = IconStop, + /// + /// A handle to the process memory to be modified. The handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access + /// to the process. + /// + /// + /// A pointer to the base address in the specified process to which data is written. Before data transfer occurs, the + /// system verifies that all data in the base address and memory of the specified size is accessible for write access, + /// and if it is not accessible, the function fails. + /// + /// + /// A pointer to the buffer that contains data to be written in the address space of the specified process. + /// + /// + /// The number of bytes to be written to the specified process. + /// + /// + /// A pointer to a variable that receives the number of bytes transferred into the specified process. This parameter + /// is optional. If lpNumberOfBytesWritten is NULL, the parameter is ignored. + /// + /// + /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get + /// extended error information, call GetLastError.The function fails if the requested write operation crosses into an + /// area of the process that is inaccessible. + /// + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool WriteProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + byte[] lpBuffer, + int dwSize, + out IntPtr lpNumberOfBytesWritten); /// - /// A stop-sign icon appears in the message box. + /// Get a handle to the current process. /// - IconHand = IconStop, - - // To indicate the default button, specify one of the following values. + /// Handle to the process. + [DllImport("kernel32.dll")] + public static extern IntPtr GetCurrentProcess(); /// - /// The first button is the default button. - /// MB_DEFBUTTON1 is the default unless MB_DEFBUTTON2, MB_DEFBUTTON3, or MB_DEFBUTTON4 is specified. + /// Get the current process ID. /// - DefButton1 = DefaultValue, + /// The process ID. + [DllImport("kernel32.dll")] + public static extern uint GetCurrentProcessId(); /// - /// The second button is the default button. + /// Get the current thread ID. /// - DefButton2 = 0x100, - - /// - /// The third button is the default button. - /// - DefButton3 = 0x200, - - /// - /// The fourth button is the default button. - /// - DefButton4 = 0x300, - - // To indicate the modality of the dialog box, specify one of the following values. - - /// - /// The user must respond to the message box before continuing work in the window identified by the hWnd parameter. - /// However, the user can move to the windows of other threads and work in those windows. Depending on the hierarchy - /// of windows in the application, the user may be able to move to other windows within the thread. All child windows - /// of the parent of the message box are automatically disabled, but pop-up windows are not. MB_APPLMODAL is the - /// default if neither MB_SYSTEMMODAL nor MB_TASKMODAL is specified. - /// - ApplModal = DefaultValue, - - /// - /// Same as MB_APPLMODAL except that the message box has the WS_EX_TOPMOST style. - /// Use system-modal message boxes to notify the user of serious, potentially damaging errors that require immediate - /// attention (for example, running out of memory). This flag has no effect on the user's ability to interact with - /// windows other than those associated with hWnd. - /// - SystemModal = 0x1000, - - /// - /// Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the - /// hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle - /// available but still needs to prevent input to other windows in the calling thread without suspending other threads. - /// - TaskModal = 0x2000, - - // To specify other options, use one or more of the following values. - - /// - /// Same as desktop of the interactive window station. For more information, see Window Stations. If the current - /// input desktop is not the default desktop, MessageBox does not return until the user switches to the default - /// desktop. - /// - DefaultDesktopOnly = 0x20000, - - /// - /// The text is right-justified. - /// - Right = 0x80000, - - /// - /// Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems. - /// - RtlReading = 0x100000, - - /// - /// The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function - /// for the message box. - /// - SetForeground = 0x10000, - - /// - /// The message box is created with the WS_EX_TOPMOST window style. - /// - Topmost = 0x40000, - - /// - /// The caller is a service notifying the user of an event. The function displays a message box on the current active - /// desktop, even if there is no user logged on to the computer. - /// - ServiceNotification = 0x200000, + /// The thread ID. + [DllImport("kernel32.dll")] + public static extern uint GetCurrentThreadId(); } /// - /// GWL_* from winuser. + /// Native dbghelp functions. /// - public enum WindowLongType + internal static partial class NativeFunctions { /// - /// Sets a new extended window style. + /// Type of minidump to create. /// - ExStyle = -20, + public enum MiniDumpType : int + { + /// + /// Normal minidump. + /// + MiniDumpNormal, + + /// + /// Minidump with data segments. + /// + MiniDumpWithDataSegs, + + /// + /// Minidump with full memory. + /// + MiniDumpWithFullMemory, + } /// - /// Sets a new application instance handle. + /// Initializes the symbol handler for a process. /// - HInstance = -6, + /// + /// A handle that identifies the caller. + /// This value should be unique and nonzero, but need not be a process handle. + /// However, if you do use a process handle, be sure to use the correct handle. + /// If the application is a debugger, use the process handle for the process being debugged. + /// Do not use the handle returned by GetCurrentProcess when debugging another process, because calling functions like SymLoadModuleEx can have unexpected results. + /// This parameter cannot be NULL. + /// + /// The path, or series of paths separated by a semicolon (;), that is used to search for symbol files. + /// If this parameter is NULL, the library attempts to form a symbol path from the following sources: + /// - The current working directory of the application + /// - The _NT_SYMBOL_PATH environment variable + /// - The _NT_ALTERNATE_SYMBOL_PATH environment variable + /// Note that the search path can also be set using the SymSetSearchPath function. + /// + /// + /// If this value is , enumerates the loaded modules for the process and effectively calls the SymLoadModule64 function for each module. + /// + /// Whether or not the function succeeded. + [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern bool SymInitialize(IntPtr hProcess, string userSearchPath, bool fInvadeProcess); /// - /// Sets a new identifier of the child window.The window cannot be a top-level window. + /// Deallocates all resources associated with the process handle. /// - Id = -12, + /// A handle to the process that was originally passed to the function. + /// Whether or not the function succeeded. + [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern bool SymCleanup(IntPtr hProcess); /// - /// Sets a new window style. + /// Creates a minidump. /// - Style = -16, + /// Target process handle. + /// Target process ID. + /// Output file handle. + /// Type of dump to take. + /// Exception information. + /// User information. + /// Callback. + /// Whether or not the minidump succeeded. + [DllImport("dbghelp.dll")] + public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, IntPtr hFile, int dumpType, ref MinidumpExceptionInformation exceptionInfo, IntPtr userStreamParam, IntPtr callback); /// - /// Sets the user data associated with the window. This data is intended for use by the application that created the window. Its value is initially zero. + /// Structure describing minidump exception information. /// - UserData = -21, + [StructLayout(LayoutKind.Sequential, Pack = 4)] + public struct MinidumpExceptionInformation + { + /// + /// ID of the thread that caused the exception. + /// + public uint ThreadId; - /// - /// Sets a new address for the window procedure. - /// - WndProc = -4, + /// + /// Pointer to the exception record. + /// + public IntPtr ExceptionPointers; - // The following values are also available when the hWnd parameter identifies a dialog box. - - // /// - // /// Sets the new pointer to the dialog box procedure. - // /// - // DWLP_DLGPROC = DWLP_MSGRESULT + sizeof(LRESULT), - - /// - /// Sets the return value of a message processed in the dialog box procedure. - /// - MsgResult = 0, - - // /// - // /// Sets new extra information that is private to the application, such as handles or pointers. - // /// - // DWLP_USER = DWLP_DLGPROC + sizeof(DLGPROC), + /// + /// ClientPointers field. + /// + public int ClientPointers; + } } /// - /// WM_* from winuser. - /// These are spread throughout multiple files, find the documentation manually if you need it. - /// https://gist.github.com/amgine/2395987. + /// Native ws2_32 functions. /// - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "No documentation available.")] - public enum WindowsMessage - { - WM_NULL = 0x0000, - WM_CREATE = 0x0001, - WM_DESTROY = 0x0002, - WM_MOVE = 0x0003, - WM_SIZE = 0x0005, - WM_ACTIVATE = 0x0006, - WM_SETFOCUS = 0x0007, - WM_KILLFOCUS = 0x0008, - WM_ENABLE = 0x000A, - WM_SETREDRAW = 0x000B, - WM_SETTEXT = 0x000C, - WM_GETTEXT = 0x000D, - WM_GETTEXTLENGTH = 0x000E, - WM_PAINT = 0x000F, - WM_CLOSE = 0x0010, - WM_QUERYENDSESSION = 0x0011, - WM_QUERYOPEN = 0x0013, - WM_ENDSESSION = 0x0016, - WM_QUIT = 0x0012, - WM_ERASEBKGND = 0x0014, - WM_SYSCOLORCHANGE = 0x0015, - WM_SHOWWINDOW = 0x0018, - WM_WININICHANGE = 0x001A, - WM_SETTINGCHANGE = WM_WININICHANGE, - WM_DEVMODECHANGE = 0x001B, - WM_ACTIVATEAPP = 0x001C, - WM_FONTCHANGE = 0x001D, - WM_TIMECHANGE = 0x001E, - WM_CANCELMODE = 0x001F, - WM_SETCURSOR = 0x0020, - WM_MOUSEACTIVATE = 0x0021, - WM_CHILDACTIVATE = 0x0022, - WM_QUEUESYNC = 0x0023, - WM_GETMINMAXINFO = 0x0024, - WM_PAINTICON = 0x0026, - WM_ICONERASEBKGND = 0x0027, - WM_NEXTDLGCTL = 0x0028, - WM_SPOOLERSTATUS = 0x002A, - WM_DRAWITEM = 0x002B, - WM_MEASUREITEM = 0x002C, - WM_DELETEITEM = 0x002D, - WM_VKEYTOITEM = 0x002E, - WM_CHARTOITEM = 0x002F, - WM_SETFONT = 0x0030, - WM_GETFONT = 0x0031, - WM_SETHOTKEY = 0x0032, - WM_GETHOTKEY = 0x0033, - WM_QUERYDRAGICON = 0x0037, - WM_COMPAREITEM = 0x0039, - WM_GETOBJECT = 0x003D, - WM_COMPACTING = 0x0041, - WM_COMMNOTIFY = 0x0044, - WM_WINDOWPOSCHANGING = 0x0046, - WM_WINDOWPOSCHANGED = 0x0047, - WM_POWER = 0x0048, - WM_COPYDATA = 0x004A, - WM_CANCELJOURNAL = 0x004B, - WM_NOTIFY = 0x004E, - WM_INPUTLANGCHANGEREQUEST = 0x0050, - WM_INPUTLANGCHANGE = 0x0051, - WM_TCARD = 0x0052, - WM_HELP = 0x0053, - WM_USERCHANGED = 0x0054, - WM_NOTIFYFORMAT = 0x0055, - WM_CONTEXTMENU = 0x007B, - WM_STYLECHANGING = 0x007C, - WM_STYLECHANGED = 0x007D, - WM_DISPLAYCHANGE = 0x007E, - WM_GETICON = 0x007F, - WM_SETICON = 0x0080, - WM_NCCREATE = 0x0081, - WM_NCDESTROY = 0x0082, - WM_NCCALCSIZE = 0x0083, - WM_NCHITTEST = 0x0084, - WM_NCPAINT = 0x0085, - WM_NCACTIVATE = 0x0086, - WM_GETDLGCODE = 0x0087, - WM_SYNCPAINT = 0x0088, - - WM_NCMOUSEMOVE = 0x00A0, - WM_NCLBUTTONDOWN = 0x00A1, - WM_NCLBUTTONUP = 0x00A2, - WM_NCLBUTTONDBLCLK = 0x00A3, - WM_NCRBUTTONDOWN = 0x00A4, - WM_NCRBUTTONUP = 0x00A5, - WM_NCRBUTTONDBLCLK = 0x00A6, - WM_NCMBUTTONDOWN = 0x00A7, - WM_NCMBUTTONUP = 0x00A8, - WM_NCMBUTTONDBLCLK = 0x00A9, - WM_NCXBUTTONDOWN = 0x00AB, - WM_NCXBUTTONUP = 0x00AC, - WM_NCXBUTTONDBLCLK = 0x00AD, - - WM_INPUT_DEVICE_CHANGE = 0x00FE, - WM_INPUT = 0x00FF, - - WM_KEYFIRST = 0x0100, - WM_KEYDOWN = WM_KEYFIRST, - WM_KEYUP = 0x0101, - WM_CHAR = 0x0102, - WM_DEADCHAR = 0x0103, - WM_SYSKEYDOWN = 0x0104, - WM_SYSKEYUP = 0x0105, - WM_SYSCHAR = 0x0106, - WM_SYSDEADCHAR = 0x0107, - WM_UNICHAR = 0x0109, - WM_KEYLAST = WM_UNICHAR, - - WM_IME_STARTCOMPOSITION = 0x010D, - WM_IME_ENDCOMPOSITION = 0x010E, - WM_IME_COMPOSITION = 0x010F, - WM_IME_KEYLAST = WM_IME_COMPOSITION, - - WM_INITDIALOG = 0x0110, - WM_COMMAND = 0x0111, - WM_SYSCOMMAND = 0x0112, - WM_TIMER = 0x0113, - WM_HSCROLL = 0x0114, - WM_VSCROLL = 0x0115, - WM_INITMENU = 0x0116, - WM_INITMENUPOPUP = 0x0117, - WM_MENUSELECT = 0x011F, - WM_MENUCHAR = 0x0120, - WM_ENTERIDLE = 0x0121, - WM_MENURBUTTONUP = 0x0122, - WM_MENUDRAG = 0x0123, - WM_MENUGETOBJECT = 0x0124, - WM_UNINITMENUPOPUP = 0x0125, - WM_MENUCOMMAND = 0x0126, - - WM_CHANGEUISTATE = 0x0127, - WM_UPDATEUISTATE = 0x0128, - WM_QUERYUISTATE = 0x0129, - - WM_CTLCOLORMSGBOX = 0x0132, - WM_CTLCOLOREDIT = 0x0133, - WM_CTLCOLORLISTBOX = 0x0134, - WM_CTLCOLORBTN = 0x0135, - WM_CTLCOLORDLG = 0x0136, - WM_CTLCOLORSCROLLBAR = 0x0137, - WM_CTLCOLORSTATIC = 0x0138, - MN_GETHMENU = 0x01E1, - - WM_MOUSEFIRST = 0x0200, - WM_MOUSEMOVE = WM_MOUSEFIRST, - WM_LBUTTONDOWN = 0x0201, - WM_LBUTTONUP = 0x0202, - WM_LBUTTONDBLCLK = 0x0203, - WM_RBUTTONDOWN = 0x0204, - WM_RBUTTONUP = 0x0205, - WM_RBUTTONDBLCLK = 0x0206, - WM_MBUTTONDOWN = 0x0207, - WM_MBUTTONUP = 0x0208, - WM_MBUTTONDBLCLK = 0x0209, - WM_MOUSEWHEEL = 0x020A, - WM_XBUTTONDOWN = 0x020B, - WM_XBUTTONUP = 0x020C, - WM_XBUTTONDBLCLK = 0x020D, - WM_MOUSEHWHEEL = 0x020E, - - WM_PARENTNOTIFY = 0x0210, - WM_ENTERMENULOOP = 0x0211, - WM_EXITMENULOOP = 0x0212, - - WM_NEXTMENU = 0x0213, - WM_SIZING = 0x0214, - WM_CAPTURECHANGED = 0x0215, - WM_MOVING = 0x0216, - - WM_POWERBROADCAST = 0x0218, - - WM_DEVICECHANGE = 0x0219, - - WM_MDICREATE = 0x0220, - WM_MDIDESTROY = 0x0221, - WM_MDIACTIVATE = 0x0222, - WM_MDIRESTORE = 0x0223, - WM_MDINEXT = 0x0224, - WM_MDIMAXIMIZE = 0x0225, - WM_MDITILE = 0x0226, - WM_MDICASCADE = 0x0227, - WM_MDIICONARRANGE = 0x0228, - WM_MDIGETACTIVE = 0x0229, - - WM_MDISETMENU = 0x0230, - WM_ENTERSIZEMOVE = 0x0231, - WM_EXITSIZEMOVE = 0x0232, - WM_DROPFILES = 0x0233, - WM_MDIREFRESHMENU = 0x0234, - - WM_IME_SETCONTEXT = 0x0281, - WM_IME_NOTIFY = 0x0282, - WM_IME_CONTROL = 0x0283, - WM_IME_COMPOSITIONFULL = 0x0284, - WM_IME_SELECT = 0x0285, - WM_IME_CHAR = 0x0286, - WM_IME_REQUEST = 0x0288, - WM_IME_KEYDOWN = 0x0290, - WM_IME_KEYUP = 0x0291, - - WM_MOUSEHOVER = 0x02A1, - WM_MOUSELEAVE = 0x02A3, - WM_NCMOUSEHOVER = 0x02A0, - WM_NCMOUSELEAVE = 0x02A2, - - WM_WTSSESSION_CHANGE = 0x02B1, - - WM_TABLET_FIRST = 0x02c0, - WM_TABLET_LAST = 0x02df, - - WM_CUT = 0x0300, - WM_COPY = 0x0301, - WM_PASTE = 0x0302, - WM_CLEAR = 0x0303, - WM_UNDO = 0x0304, - WM_RENDERFORMAT = 0x0305, - WM_RENDERALLFORMATS = 0x0306, - WM_DESTROYCLIPBOARD = 0x0307, - WM_DRAWCLIPBOARD = 0x0308, - WM_PAINTCLIPBOARD = 0x0309, - WM_VSCROLLCLIPBOARD = 0x030A, - WM_SIZECLIPBOARD = 0x030B, - WM_ASKCBFORMATNAME = 0x030C, - WM_CHANGECBCHAIN = 0x030D, - WM_HSCROLLCLIPBOARD = 0x030E, - WM_QUERYNEWPALETTE = 0x030F, - WM_PALETTEISCHANGING = 0x0310, - WM_PALETTECHANGED = 0x0311, - WM_HOTKEY = 0x0312, - - WM_PRINT = 0x0317, - WM_PRINTCLIENT = 0x0318, - - WM_APPCOMMAND = 0x0319, - - WM_THEMECHANGED = 0x031A, - - WM_CLIPBOARDUPDATE = 0x031D, - - WM_DWMCOMPOSITIONCHANGED = 0x031E, - WM_DWMNCRENDERINGCHANGED = 0x031F, - WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320, - WM_DWMWINDOWMAXIMIZEDCHANGE = 0x0321, - - WM_GETTITLEBARINFOEX = 0x033F, - - WM_HANDHELDFIRST = 0x0358, - WM_HANDHELDLAST = 0x035F, - - WM_AFXFIRST = 0x0360, - WM_AFXLAST = 0x037F, - - WM_PENWINFIRST = 0x0380, - WM_PENWINLAST = 0x038F, - - WM_APP = 0x8000, - - WM_USER = 0x0400, - - WM_REFLECT = WM_USER + 0x1C00, - } - - /// - /// Returns true if the current application has focus, false otherwise. - /// - /// - /// If the current application is focused. - /// - public static bool ApplicationIsActivated() - { - var activatedHandle = GetForegroundWindow(); - if (activatedHandle == IntPtr.Zero) - return false; // No window is currently activated - - _ = GetWindowThreadProcessId(activatedHandle, out var activeProcId); - if (Marshal.GetLastWin32Error() != 0) - return false; - - return activeProcId == Environment.ProcessId; - } - - /// - /// Passes message information to the specified window procedure. - /// - /// - /// The previous window procedure. If this value is obtained by calling the GetWindowLong function with the nIndex parameter set to - /// GWL_WNDPROC or DWL_DLGPROC, it is actually either the address of a window or dialog box procedure, or a special internal value - /// meaningful only to CallWindowProc. - /// - /// - /// A handle to the window procedure to receive the message. - /// - /// - /// The message. - /// - /// - /// Additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. - /// - /// - /// More additional message-specific information. The contents of this parameter depend on the value of the Msg parameter. - /// - /// - /// Use the CallWindowProc function for window subclassing. Usually, all windows with the same class share one window procedure. A - /// subclass is a window or set of windows with the same class whose messages are intercepted and processed by another window procedure - /// (or procedures) before being passed to the window procedure of the class. - /// The SetWindowLong function creates the subclass by changing the window procedure associated with a particular window, causing the - /// system to call the new window procedure instead of the previous one.An application must pass any messages not processed by the new - /// window procedure to the previous window procedure by calling CallWindowProc.This allows the application to create a chain of window - /// procedures. - /// - [DllImport("user32.dll")] - public static extern long CallWindowProcW(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, ulong wParam, long lParam); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-flashwindowex. - /// Flashes the specified window. It does not change the active state of the window. - /// - /// - /// A pointer to a FLASHWINFO structure. - /// - /// - /// The return value specifies the window's state before the call to the FlashWindowEx function. If the window caption - /// was drawn as active before the call, the return value is nonzero. Otherwise, the return value is zero. - /// - [DllImport("user32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool FlashWindowEx(ref FlashWindowInfo pwfi); - - /// - /// Retrieves a handle to the foreground window (the window with which the user is currently working). The system assigns - /// a slightly higher priority to the thread that creates the foreground window than it does to other threads. - /// - /// - /// The return value is a handle to the foreground window. The foreground window can be NULL in certain circumstances, - /// such as when a window is losing activation. - /// - [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] - public static extern IntPtr GetForegroundWindow(); - - /// - /// Retrieves the identifier of the thread that created the specified window and, optionally, the identifier of the - /// process that created the window. - /// - /// - /// A handle to the window. - /// - /// - /// A pointer to a variable that receives the process identifier. If this parameter is not NULL, GetWindowThreadProcessId - /// copies the identifier of the process to the variable; otherwise, it does not. - /// - /// - /// The return value is the identifier of the thread that created the window. - /// - [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] - public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId); - - /// - /// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message, - /// such as status or error information. The message box returns an integer value that indicates which button the user - /// clicked. - /// - /// - /// A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no - /// owner window. - /// - /// - /// The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage - /// return and/or linefeed character between each line. - /// - /// - /// The dialog box title. If this parameter is NULL, the default title is Error. - /// - /// The contents and behavior of the dialog box. This parameter can be a combination of flags from the following groups - /// of flags. - /// - /// - /// If a message box has a Cancel button, the function returns the IDCANCEL value if either the ESC key is pressed or - /// the Cancel button is selected. If the message box has no Cancel button, pressing ESC will no effect - unless an - /// MB_OK button is present. If an MB_OK button is displayed and the user presses ESC, the return value will be IDOK. - /// If the function fails, the return value is zero.To get extended error information, call GetLastError. If the function - /// succeeds, the return value is one of the ID* enum values. - /// - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, MessageBoxType type); - - /// - /// Changes an attribute of the specified window. The function also sets a value at the specified offset in the extra window memory. - /// - /// - /// A handle to the window and, indirectly, the class to which the window belongs. The SetWindowLongPtr function fails if the - /// process that owns the window specified by the hWnd parameter is at a higher process privilege in the UIPI hierarchy than the - /// process the calling thread resides in. - /// - /// - /// The zero-based offset to the value to be set. Valid values are in the range zero through the number of bytes of extra window - /// memory, minus the size of a LONG_PTR. To set any other value, specify one of the values. - /// - /// - /// The replacement value. - /// - /// - /// If the function succeeds, the return value is the previous value of the specified offset. If the function fails, the return - /// value is zero.To get extended error information, call GetLastError. If the previous value is zero and the function succeeds, - /// the return value is zero, but the function does not clear the last error information. To determine success or failure, clear - /// the last error information by calling SetLastError with 0, then call SetWindowLongPtr.Function failure will be indicated by - /// a return value of zero and a GetLastError result that is nonzero. - /// - [DllImport("user32.dll", SetLastError = true)] - public static extern IntPtr SetWindowLongPtrW(IntPtr hWnd, WindowLongType nIndex, IntPtr dwNewLong); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-flashwinfo. - /// Contains the flash status for a window and the number of times the system should flash the window. - /// - [StructLayout(LayoutKind.Sequential)] - public struct FlashWindowInfo + internal static partial class NativeFunctions { /// - /// The size of the structure, in bytes. + /// See https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-setsockopt. + /// The setsockopt function sets a socket option. /// - public uint Size; - - /// - /// A handle to the window to be flashed. The window can be either opened or minimized. - /// - public IntPtr Hwnd; - - /// - /// The flash status. This parameter can be one or more of the FlashWindow enum values. - /// - public FlashWindow Flags; - - /// - /// The number of times to flash the window. - /// - public uint Count; - - /// - /// The rate at which the window is to be flashed, in milliseconds. If dwTimeout is zero, the function uses the - /// default cursor blink rate. - /// - public uint Timeout; + /// + /// A descriptor that identifies a socket. + /// + /// + /// The level at which the option is defined (for example, SOL_SOCKET). + /// + /// + /// The socket option for which the value is to be set (for example, SO_BROADCAST). The optname parameter must be a + /// socket option defined within the specified level, or behavior is undefined. + /// + /// + /// A pointer to the buffer in which the value for the requested option is specified. + /// + /// + /// The size, in bytes, of the buffer pointed to by the optval parameter. + /// + /// + /// If no error occurs, setsockopt returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error + /// code can be retrieved by calling WSAGetLastError. + /// + [DllImport("ws2_32.dll", CallingConvention = CallingConvention.Winapi, EntryPoint = "setsockopt")] + public static extern int SetSockOpt(IntPtr socket, SocketOptionLevel level, SocketOptionName optName, ref IntPtr optVal, int optLen); } } - -/// -/// Native imm32 functions. -/// -internal static partial class NativeFunctions -{ - /// - /// GCS_* from imm32. - /// These values are used with ImmGetCompositionString and WM_IME_COMPOSITION. - /// - [Flags] - public enum IMEComposition - { - /// - /// Retrieve or update the attribute of the composition string. - /// - CompAttr = 0x0010, - - /// - /// Retrieve or update clause information of the composition string. - /// - CompClause = 0x0020, - - /// - /// Retrieve or update the attributes of the reading string of the current composition. - /// - CompReadAttr = 0x0002, - - /// - /// Retrieve or update the clause information of the reading string of the composition string. - /// - CompReadClause = 0x0004, - - /// - /// Retrieve or update the reading string of the current composition. - /// - CompReadStr = 0x0001, - - /// - /// Retrieve or update the current composition string. - /// - CompStr = 0x0008, - - /// - /// Retrieve or update the cursor position in composition string. - /// - CursorPos = 0x0080, - - /// - /// Retrieve or update the starting position of any changes in composition string. - /// - DeltaStart = 0x0100, - - /// - /// Retrieve or update clause information of the result string. - /// - ResultClause = 0x1000, - - /// - /// Retrieve or update clause information of the reading string. - /// - ResultReadClause = 0x0400, - - /// - /// Retrieve or update the reading string. - /// - ResultReadStr = 0x0200, - - /// - /// Retrieve or update the string of the composition result. - /// - ResultStr = 0x0800, - } - - /// - /// IMN_* from imm32. - /// Input Method Manager Commands, this enum is not exhaustive. - /// - public enum IMECommand - { - /// - /// Notifies the application when an IME is about to change the content of the candidate window. - /// - ChangeCandidate = 0x0003, - - /// - /// Notifies an application when an IME is about to close the candidates window. - /// - CloseCandidate = 0x0004, - - /// - /// Notifies an application when an IME is about to open the candidate window. - /// - OpenCandidate = 0x0005, - - /// - /// Notifies an application when the conversion mode of the input context is updated. - /// - SetConversionMode = 0x0006, - } - - /// - /// Returns the input context associated with the specified window. - /// - /// Unnamed parameter 1. - /// - /// Returns the handle to the input context. - /// - [DllImport("imm32.dll")] - public static extern IntPtr ImmGetContext(IntPtr hWnd); - - /// - /// Retrieves information about the composition string. - /// - /// - /// Unnamed parameter 1. - /// - /// - /// Unnamed parameter 2. - /// - /// - /// Pointer to a buffer in which the function retrieves the composition string information. - /// - /// - /// Size, in bytes, of the output buffer, even if the output is a Unicode string. The application sets this parameter to 0 - /// if the function is to return the size of the required output buffer. - /// - /// - /// Returns the number of bytes copied to the output buffer. If dwBufLen is set to 0, the function returns the buffer size, - /// in bytes, required to receive all requested information, excluding the terminating null character. The return value is - /// always the size, in bytes, even if the requested data is a Unicode string. - /// This function returns one of the following negative error codes if it does not succeed: - /// - IMM_ERROR_NODATA.Composition data is not ready in the input context. - /// - IMM_ERROR_GENERAL.General error detected by IME. - /// - [DllImport("imm32.dll")] - public static extern long ImmGetCompositionStringW(IntPtr hImc, IMEComposition arg2, IntPtr lpBuf, uint dwBufLen); - - /// - /// Retrieves a candidate list. - /// - /// - /// Unnamed parameter 1. - /// - /// - /// Zero-based index of the candidate list. - /// - /// - /// Pointer to a CANDIDATELIST structure in which the function retrieves the candidate list. - /// - /// - /// Size, in bytes, of the buffer to receive the candidate list. The application can specify 0 for this parameter if the - /// function is to return the required size of the output buffer only. - /// - /// - /// Returns the number of bytes copied to the candidate list buffer if successful. If the application has supplied 0 for - /// the dwBufLen parameter, the function returns the size required for the candidate list buffer. The function returns 0 - /// if it does not succeed. - /// - [DllImport("imm32.dll")] - public static extern long ImmGetCandidateListW(IntPtr hImc, uint deIndex, IntPtr lpCandList, uint dwBufLen); - - /// - /// Sets the position of the composition window. - /// - /// - /// Unnamed parameter 1. - /// - /// - /// Pointer to a COMPOSITIONFORM structure that contains the new position and other related information about - /// the composition window. - /// - /// - /// Returns a nonzero value if successful, or 0 otherwise. - /// - [DllImport("imm32.dll", CharSet = CharSet.Auto)] - public static extern bool ImmSetCompositionWindow(IntPtr hImc, ref CompositionForm frm); - - /// - /// Releases the input context and unlocks the memory associated in the input context. An application must call this - /// function for each call to the ImmGetContext function. - /// - /// - /// Unnamed parameter 1. - /// - /// - /// Unnamed parameter 2. - /// - /// - /// Returns a nonzero value if successful, or 0 otherwise. - /// - [DllImport("imm32.dll", CharSet = CharSet.Auto)] - public static extern bool ImmReleaseContext(IntPtr hwnd, IntPtr hImc); - - /// - /// Contains information about a candidate list. - /// - public struct CandidateList - { - /// - /// Size, in bytes, of the structure, the offset array, and all candidate strings. - /// - public int Size; - - /// - /// Candidate style values. This member can have one or more of the IME_CAND_* values. - /// - public int Style; - - /// - /// Number of candidate strings. - /// - public int Count; - - /// - /// Index of the selected candidate string. - /// - public int Selection; - - /// - /// Index of the first candidate string in the candidate window. This varies as the user presses the PAGE UP and PAGE DOWN keys. - /// - public int PageStart; - - /// - /// Number of candidate strings to be shown in one page in the candidate window. The user can move to the next page by pressing IME-defined keys, such as the PAGE UP or PAGE DOWN key. If this number is 0, an application can define a proper value by itself. - /// - public int PageSize; - - // /// - // /// Offset to the start of the first candidate string, relative to the start of this structure. The offsets - // /// for subsequent strings immediately follow this member, forming an array of 32-bit offsets. - // /// - // public IntPtr Offset; // manually handle - } - - /// - /// Contains style and position information for a composition window. - /// - [StructLayout(LayoutKind.Sequential)] - public struct CompositionForm - { - /// - /// Position style. This member can be one of the CFS_* values. - /// - public int Style; - - /// - /// A POINT structure containing the coordinates of the upper left corner of the composition window. - /// - public Point CurrentPos; - - /// - /// A RECT structure containing the coordinates of the upper left and lower right corners of the composition window. - /// - public Rect Area; - } - - /// - /// Contains coordinates for a point. - /// - [StructLayout(LayoutKind.Sequential)] - public struct Point - { - /// - /// The X position. - /// - public int X; - - /// - /// The Y position. - /// - public int Y; - } - - /// - /// Contains dimensions for a rectangle. - /// - [StructLayout(LayoutKind.Sequential)] - public struct Rect - { - /// - /// The left position. - /// - public int Left; - - /// - /// The top position. - /// - public int Top; - - /// - /// The right position. - /// - public int Right; - - /// - /// The bottom position. - /// - public int Bottom; - } -} - -/// -/// Native kernel32 functions. -/// -internal static partial class NativeFunctions -{ - /// - /// MEM_* from memoryapi. - /// - [Flags] - public enum AllocationType - { - /// - /// To coalesce two adjacent placeholders, specify MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS. When you coalesce - /// placeholders, lpAddress and dwSize must exactly match those of the placeholder. - /// - CoalescePlaceholders = 0x1, - - /// - /// Frees an allocation back to a placeholder (after you've replaced a placeholder with a private allocation using - /// VirtualAlloc2 or Virtual2AllocFromApp). To split a placeholder into two placeholders, specify - /// MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER. - /// - PreservePlaceholder = 0x2, - - /// - /// Allocates memory charges (from the overall size of memory and the paging files on disk) for the specified reserved - /// memory pages. The function also guarantees that when the caller later initially accesses the memory, the contents - /// will be zero. Actual physical pages are not allocated unless/until the virtual addresses are actually accessed. - /// To reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Attempting to commit - /// a specific address range by specifying MEM_COMMIT without MEM_RESERVE and a non-NULL lpAddress fails unless the - /// entire range has already been reserved. The resulting error code is ERROR_INVALID_ADDRESS. An attempt to commit - /// a page that is already committed does not cause the function to fail. This means that you can commit pages without - /// first determining the current commitment state of each page. If lpAddress specifies an address within an enclave, - /// flAllocationType must be MEM_COMMIT. - /// - Commit = 0x1000, - - /// - /// Reserves a range of the process's virtual address space without allocating any actual physical storage in memory - /// or in the paging file on disk. You commit reserved pages by calling VirtualAllocEx again with MEM_COMMIT. To - /// reserve and commit pages in one step, call VirtualAllocEx with MEM_COMMIT | MEM_RESERVE. Other memory allocation - /// functions, such as malloc and LocalAlloc, cannot use reserved memory until it has been released. - /// - Reserve = 0x2000, - - /// - /// Decommits the specified region of committed pages. After the operation, the pages are in the reserved state. - /// The function does not fail if you attempt to decommit an uncommitted page. This means that you can decommit - /// a range of pages without first determining the current commitment state. The MEM_DECOMMIT value is not supported - /// when the lpAddress parameter provides the base address for an enclave. - /// - Decommit = 0x4000, - - /// - /// Releases the specified region of pages, or placeholder (for a placeholder, the address space is released and - /// available for other allocations). After this operation, the pages are in the free state. If you specify this - /// value, dwSize must be 0 (zero), and lpAddress must point to the base address returned by the VirtualAlloc function - /// when the region is reserved. The function fails if either of these conditions is not met. If any pages in the - /// region are committed currently, the function first decommits, and then releases them. The function does not - /// fail if you attempt to release pages that are in different states, some reserved and some committed. This means - /// that you can release a range of pages without first determining the current commitment state. - /// - Release = 0x8000, - - /// - /// Indicates that data in the memory range specified by lpAddress and dwSize is no longer of interest. The pages - /// should not be read from or written to the paging file. However, the memory block will be used again later, so - /// it should not be decommitted. This value cannot be used with any other value. Using this value does not guarantee - /// that the range operated on with MEM_RESET will contain zeros. If you want the range to contain zeros, decommit - /// the memory and then recommit it. When you use MEM_RESET, the VirtualAllocEx function ignores the value of fProtect. - /// However, you must still set fProtect to a valid protection value, such as PAGE_NOACCESS. VirtualAllocEx returns - /// an error if you use MEM_RESET and the range of memory is mapped to a file. A shared view is only acceptable - /// if it is mapped to a paging file. - /// - Reset = 0x80000, - - /// - /// MEM_RESET_UNDO should only be called on an address range to which MEM_RESET was successfully applied earlier. - /// It indicates that the data in the specified memory range specified by lpAddress and dwSize is of interest to - /// the caller and attempts to reverse the effects of MEM_RESET. If the function succeeds, that means all data in - /// the specified address range is intact. If the function fails, at least some of the data in the address range - /// has been replaced with zeroes. This value cannot be used with any other value. If MEM_RESET_UNDO is called on - /// an address range which was not MEM_RESET earlier, the behavior is undefined. When you specify MEM_RESET, the - /// VirtualAllocEx function ignores the value of flProtect. However, you must still set flProtect to a valid - /// protection value, such as PAGE_NOACCESS. - /// - ResetUndo = 0x1000000, - - /// - /// Reserves an address range that can be used to map Address Windowing Extensions (AWE) pages. This value must - /// be used with MEM_RESERVE and no other values. - /// - Physical = 0x400000, - - /// - /// Allocates memory at the highest possible address. This can be slower than regular allocations, especially when - /// there are many allocations. - /// - TopDown = 0x100000, - - /// - /// Causes the system to track pages that are written to in the allocated region. If you specify this value, you - /// must also specify MEM_RESERVE. To retrieve the addresses of the pages that have been written to since the region - /// was allocated or the write-tracking state was reset, call the GetWriteWatch function. To reset the write-tracking - /// state, call GetWriteWatch or ResetWriteWatch. The write-tracking feature remains enabled for the memory region - /// until the region is freed. - /// - WriteWatch = 0x200000, - - /// - /// Allocates memory using large page support. The size and alignment must be a multiple of the large-page minimum. - /// To obtain this value, use the GetLargePageMinimum function. If you specify this value, you must also specify - /// MEM_RESERVE and MEM_COMMIT. - /// - LargePages = 0x20000000, - } - - /// - /// SEM_* from errhandlingapi. - /// - [Flags] - public enum ErrorModes : uint - { - /// - /// Use the system default, which is to display all error dialog boxes. - /// - SystemDefault = 0x0, - - /// - /// The system does not display the critical-error-handler message box. Instead, the system sends the error to the - /// calling process. Best practice is that all applications call the process-wide SetErrorMode function with a parameter - /// of SEM_FAILCRITICALERRORS at startup. This is to prevent error mode dialogs from hanging the application. - /// - FailCriticalErrors = 0x0001, - - /// - /// The system automatically fixes memory alignment faults and makes them invisible to the application. It does - /// this for the calling process and any descendant processes. This feature is only supported by certain processor - /// architectures. For more information, see the Remarks section. After this value is set for a process, subsequent - /// attempts to clear the value are ignored. - /// - NoAlignmentFaultExcept = 0x0004, - - /// - /// The system does not display the Windows Error Reporting dialog. - /// - NoGpFaultErrorBox = 0x0002, - - /// - /// The OpenFile function does not display a message box when it fails to find a file. Instead, the error is returned - /// to the caller. This error mode overrides the OF_PROMPT flag. - /// - NoOpenFileErrorBox = 0x8000, - } - - /// - /// PAGE_* from memoryapi. - /// - [Flags] - public enum MemoryProtection - { - /// - /// Enables execute access to the committed region of pages. An attempt to write to the committed region results - /// in an access violation. This flag is not supported by the CreateFileMapping function. - /// - Execute = 0x10, - - /// - /// Enables execute or read-only access to the committed region of pages. An attempt to write to the committed region - /// results in an access violation. - /// - ExecuteRead = 0x20, - - /// - /// Enables execute, read-only, or read/write access to the committed region of pages. - /// - ExecuteReadWrite = 0x40, - - /// - /// Enables execute, read-only, or copy-on-write access to a mapped view of a file mapping object. An attempt to - /// write to a committed copy-on-write page results in a private copy of the page being made for the process. The - /// private page is marked as PAGE_EXECUTE_READWRITE, and the change is written to the new page. This flag is not - /// supported by the VirtualAlloc or VirtualAllocEx functions. - /// - ExecuteWriteCopy = 0x80, - - /// - /// Disables all access to the committed region of pages. An attempt to read from, write to, or execute the committed - /// region results in an access violation. This flag is not supported by the CreateFileMapping function. - /// - NoAccess = 0x01, - - /// - /// Enables read-only access to the committed region of pages. An attempt to write to the committed region results - /// in an access violation. If Data Execution Prevention is enabled, an attempt to execute code in the committed - /// region results in an access violation. - /// - ReadOnly = 0x02, - - /// - /// Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, - /// attempting to execute code in the committed region results in an access violation. - /// - ReadWrite = 0x04, - - /// - /// Enables read-only or copy-on-write access to a mapped view of a file mapping object. An attempt to write to - /// a committed copy-on-write page results in a private copy of the page being made for the process. The private - /// page is marked as PAGE_READWRITE, and the change is written to the new page. If Data Execution Prevention is - /// enabled, attempting to execute code in the committed region results in an access violation. This flag is not - /// supported by the VirtualAlloc or VirtualAllocEx functions. - /// - WriteCopy = 0x08, - - /// - /// Sets all locations in the pages as invalid targets for CFG. Used along with any execute page protection like - /// PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. Any indirect call to locations - /// in those pages will fail CFG checks and the process will be terminated. The default behavior for executable - /// pages allocated is to be marked valid call targets for CFG. This flag is not supported by the VirtualProtect - /// or CreateFileMapping functions. - /// - TargetsInvalid = 0x40000000, - - /// - /// Pages in the region will not have their CFG information updated while the protection changes for VirtualProtect. - /// For example, if the pages in the region was allocated using PAGE_TARGETS_INVALID, then the invalid information - /// will be maintained while the page protection changes. This flag is only valid when the protection changes to - /// an executable type like PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE and PAGE_EXECUTE_WRITECOPY. - /// The default behavior for VirtualProtect protection change to executable is to mark all locations as valid call - /// targets for CFG. - /// - TargetsNoUpdate = TargetsInvalid, - - /// - /// Pages in the region become guard pages. Any attempt to access a guard page causes the system to raise a - /// STATUS_GUARD_PAGE_VIOLATION exception and turn off the guard page status. Guard pages thus act as a one-time - /// access alarm. For more information, see Creating Guard Pages. When an access attempt leads the system to turn - /// off guard page status, the underlying page protection takes over. If a guard page exception occurs during a - /// system service, the service typically returns a failure status indicator. This value cannot be used with - /// PAGE_NOACCESS. This flag is not supported by the CreateFileMapping function. - /// - Guard = 0x100, - - /// - /// Sets all pages to be non-cachable. Applications should not use this attribute except when explicitly required - /// for a device. Using the interlocked functions with memory that is mapped with SEC_NOCACHE can result in an - /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_NOCACHE flag cannot be used with the PAGE_GUARD, PAGE_NOACCESS, - /// or PAGE_WRITECOMBINE flags. The PAGE_NOCACHE flag can be used only when allocating private memory with the - /// VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable non-cached memory access for shared - /// memory, specify the SEC_NOCACHE flag when calling the CreateFileMapping function. - /// - NoCache = 0x200, - - /// - /// Sets all pages to be write-combined. Applications should not use this attribute except when explicitly required - /// for a device. Using the interlocked functions with memory that is mapped as write-combined can result in an - /// EXCEPTION_ILLEGAL_INSTRUCTION exception. The PAGE_WRITECOMBINE flag cannot be specified with the PAGE_NOACCESS, - /// PAGE_GUARD, and PAGE_NOCACHE flags. The PAGE_WRITECOMBINE flag can be used only when allocating private memory - /// with the VirtualAlloc, VirtualAllocEx, or VirtualAllocExNuma functions. To enable write-combined memory access - /// for shared memory, specify the SEC_WRITECOMBINE flag when calling the CreateFileMapping function. - /// - WriteCombine = 0x400, - } - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary. - /// Frees the loaded dynamic-link library (DLL) module and, if necessary, decrements its reference count. When the reference - /// count reaches zero, the module is unloaded from the address space of the calling process and the handle is no longer - /// valid. - /// - /// - /// A handle to the loaded library module. The LoadLibrary, LoadLibraryEx, GetModuleHandle, or GetModuleHandleEx function - /// returns this handle. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. To get extended - /// error information, call the GetLastError function. - /// - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool FreeLibrary(IntPtr hModule); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew. - /// Retrieves the fully qualified path for the file that contains the specified module. The module must have been loaded - /// by the current process. To locate the file for a module that was loaded by another process, use the GetModuleFileNameEx - /// function. - /// - /// - /// A handle to the loaded module whose path is being requested. If this parameter is NULL, GetModuleFileName retrieves - /// the path of the executable file of the current process. The GetModuleFileName function does not retrieve the path - /// for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag. For more information, see LoadLibraryEx. - /// - /// - /// A pointer to a buffer that receives the fully qualified path of the module. If the length of the path is less than - /// the size that the nSize parameter specifies, the function succeeds and the path is returned as a null-terminated - /// string. If the length of the path exceeds the size that the nSize parameter specifies, the function succeeds and - /// the string is truncated to nSize characters including the terminating null character. - /// - /// - /// The size of the lpFilename buffer, in TCHARs. - /// - /// - /// If the function succeeds, the return value is the length of the string that is copied to the buffer, in characters, - /// not including the terminating null character. If the buffer is too small to hold the module name, the string is - /// truncated to nSize characters including the terminating null character, the function returns nSize, and the function - /// sets the last error to ERROR_INSUFFICIENT_BUFFER. If nSize is zero, the return value is zero and the last error - /// code is ERROR_SUCCESS. If the function fails, the return value is 0 (zero). To get extended error information, call - /// GetLastError. - /// - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - [PreserveSig] - public static extern uint GetModuleFileNameW( - [In] IntPtr hModule, - [Out] StringBuilder lpFilename, - [In][MarshalAs(UnmanagedType.U4)] int nSize); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew. - /// Retrieves a module handle for the specified module. The module must have been loaded by the calling process. To - /// avoid the race conditions described in the Remarks section, use the GetModuleHandleEx function. - /// - /// - /// The name of the loaded module (either a .dll or .exe file). If the file name extension is omitted, the default - /// library extension .dll is appended. The file name string can include a trailing point character (.) to indicate - /// that the module name has no extension. The string does not have to specify a path. When specifying a path, be sure - /// to use backslashes (\), not forward slashes (/). The name is compared (case independently) to the names of modules - /// currently mapped into the address space of the calling process. If this parameter is NULL, GetModuleHandle returns - /// a handle to the file used to create the calling process (.exe file). The GetModuleHandle function does not retrieve - /// handles for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. - /// - /// - /// If the function succeeds, the return value is a handle to the specified module. If the function fails, the return - /// value is NULL.To get extended error information, call GetLastError. - /// - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] - public static extern IntPtr GetModuleHandleW(string lpModuleName); - - /// - /// Retrieves the address of an exported function or variable from the specified dynamic-link library (DLL). - /// - /// - /// A handle to the DLL module that contains the function or variable. The LoadLibrary, LoadLibraryEx, LoadPackagedLibrary, - /// or GetModuleHandle function returns this handle. The GetProcAddress function does not retrieve addresses from modules - /// that were loaded using the LOAD_LIBRARY_AS_DATAFILE flag.For more information, see LoadLibraryEx. - /// - /// - /// The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be - /// in the low-order word; the high-order word must be zero. - /// - /// - /// If the function succeeds, the return value is the address of the exported function or variable. If the function - /// fails, the return value is NULL.To get extended error information, call GetLastError. - /// - [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] - [SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "Ansi only")] - public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw. - /// Loads the specified module into the address space of the calling process. The specified module may cause other modules - /// to be loaded. For additional load options, use the LoadLibraryEx function. - /// - /// - /// The name of the module. This can be either a library module (a .dll file) or an executable module (an .exe file). - /// The name specified is the file name of the module and is not related to the name stored in the library module itself, - /// as specified by the LIBRARY keyword in the module-definition (.def) file. If the string specifies a full path, the - /// function searches only that path for the module. If the string specifies a relative path or a module name without - /// a path, the function uses a standard search strategy to find the module; for more information, see the Remarks. - /// If the function cannot find the module, the function fails.When specifying a path, be sure to use backslashes (\), - /// not forward slashes(/). For more information about paths, see Naming a File or Directory. If the string specifies - /// a module name without a path and the file name extension is omitted, the function appends the default library extension - /// .dll to the module name. To prevent the function from appending .dll to the module name, include a trailing point - /// character (.) in the module name string. - /// - /// - /// If the function succeeds, the return value is a handle to the module. If the function fails, the return value is - /// NULL.To get extended error information, call GetLastError. - /// - [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern IntPtr LoadLibraryW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName); - - /// - /// ReadProcessMemory copies the data in the specified address range from the address space of the specified process - /// into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can - /// call the function. The entire area to be read must be accessible, and if it is not accessible, the function fails. - /// - /// - /// A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process. - /// - /// - /// A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the - /// system verifies that all data in the base address and memory of the specified size is accessible for read access, - /// and if it is not accessible the function fails. - /// - /// - /// A pointer to a buffer that receives the contents from the address space of the specified process. - /// - /// - /// The number of bytes to be read from the specified process. - /// - /// - /// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead - /// is NULL, the parameter is ignored. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get - /// extended error information, call GetLastError. The function fails if the requested read operation crosses into an - /// area of the process that is inaccessible. - /// - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool ReadProcessMemory( - IntPtr hProcess, - IntPtr lpBaseAddress, - IntPtr lpBuffer, - int dwSize, - out IntPtr lpNumberOfBytesRead); - - /// - /// ReadProcessMemory copies the data in the specified address range from the address space of the specified process - /// into the specified buffer of the current process. Any process that has a handle with PROCESS_VM_READ access can - /// call the function. The entire area to be read must be accessible, and if it is not accessible, the function fails. - /// - /// - /// A handle to the process with memory that is being read. The handle must have PROCESS_VM_READ access to the process. - /// - /// - /// A pointer to the base address in the specified process from which to read. Before any data transfer occurs, the - /// system verifies that all data in the base address and memory of the specified size is accessible for read access, - /// and if it is not accessible the function fails. - /// - /// - /// A pointer to a buffer that receives the contents from the address space of the specified process. - /// - /// - /// The number of bytes to be read from the specified process. - /// - /// - /// A pointer to a variable that receives the number of bytes transferred into the specified buffer. If lpNumberOfBytesRead - /// is NULL, the parameter is ignored. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get - /// extended error information, call GetLastError. The function fails if the requested read operation crosses into an - /// area of the process that is inaccessible. - /// - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool ReadProcessMemory( - IntPtr hProcess, - IntPtr lpBaseAddress, - byte[] lpBuffer, - int dwSize, - out IntPtr lpNumberOfBytesRead); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-seterrormode. - /// Controls whether the system will handle the specified types of serious errors or whether the process will handle - /// them. - /// - /// - /// The process error mode. This parameter can be one or more of the ErrorMode enum values. - /// - /// - /// The return value is the previous state of the error-mode bit flags. - /// - [DllImport("kernel32.dll", SetLastError = true)] - public static extern ErrorModes SetErrorMode(ErrorModes uMode); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-setunhandledexceptionfilter. - /// Enables an application to supersede the top-level exception handler of each thread of a process. After calling this - /// function, if an exception occurs in a process that is not being debugged, and the exception makes it to the unhandled - /// exception filter, that filter will call the exception filter function specified by the lpTopLevelExceptionFilter - /// parameter. - /// - /// - /// A pointer to a top-level exception filter function that will be called whenever the UnhandledExceptionFilter function - /// gets control, and the process is not being debugged. A value of NULL for this parameter specifies default handling - /// within UnhandledExceptionFilter. The filter function has syntax similar to that of UnhandledExceptionFilter: It - /// takes a single parameter of type LPEXCEPTION_POINTERS, has a WINAPI calling convention, and returns a value of type - /// LONG. The filter function should return one of the EXCEPTION_* enum values. - /// - /// - /// The SetUnhandledExceptionFilter function returns the address of the previous exception filter established with the - /// function. A NULL return value means that there is no current top-level exception handler. - /// - [DllImport("kernel32.dll")] - public static extern IntPtr SetUnhandledExceptionFilter(IntPtr lpTopLevelExceptionFilter); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc. - /// Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process. - /// Memory allocated by this function is automatically initialized to zero. To allocate memory in the address space - /// of another process, use the VirtualAllocEx function. - /// - /// - /// The starting address of the region to allocate. If the memory is being reserved, the specified address is rounded - /// down to the nearest multiple of the allocation granularity. If the memory is already reserved and is being committed, - /// the address is rounded down to the next page boundary. To determine the size of a page and the allocation granularity - /// on the host computer, use the GetSystemInfo function. If this parameter is NULL, the system determines where to - /// allocate the region. If this address is within an enclave that you have not initialized by calling InitializeEnclave, - /// VirtualAlloc allocates a page of zeros for the enclave at that address. The page must be previously uncommitted, - /// and will not be measured with the EEXTEND instruction of the Intel Software Guard Extensions programming model. - /// If the address in within an enclave that you initialized, then the allocation operation fails with the - /// ERROR_INVALID_ADDRESS error. - /// - /// - /// The size of the region, in bytes. If the lpAddress parameter is NULL, this value is rounded up to the next page - /// boundary. Otherwise, the allocated pages include all pages containing one or more bytes in the range from lpAddress - /// to lpAddress+dwSize. This means that a 2-byte range straddling a page boundary causes both pages to be included - /// in the allocated region. - /// - /// - /// The type of memory allocation. This parameter must contain one of the MEM_* enum values. - /// - /// - /// The memory protection for the region of pages to be allocated. If the pages are being committed, you can specify - /// any one of the memory protection constants. - /// - /// - /// If the function succeeds, the return value is the base address of the allocated region of pages. If the function - /// fails, the return value is NULL.To get extended error information, call GetLastError. - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern IntPtr VirtualAlloc( - IntPtr lpAddress, - UIntPtr dwSize, - AllocationType flAllocationType, - MemoryProtection flProtect); - - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern IntPtr VirtualAlloc( - IntPtr lpAddress, - UIntPtr dwSize, - AllocationType flAllocationType, - Memory.MemoryProtection flProtect); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree. - /// Releases, decommits, or releases and decommits a region of pages within the virtual address space of the calling - /// process. - /// process. - /// - /// - /// A pointer to the base address of the region of pages to be freed. If the dwFreeType parameter is MEM_RELEASE, this - /// parameter must be the base address returned by the VirtualAlloc function when the region of pages is reserved. - /// - /// - /// The size of the region of memory to be freed, in bytes. If the dwFreeType parameter is MEM_RELEASE, this parameter - /// must be 0 (zero). The function frees the entire region that is reserved in the initial allocation call to VirtualAlloc. - /// If the dwFreeType parameter is MEM_DECOMMIT, the function decommits all memory pages that contain one or more bytes - /// in the range from the lpAddress parameter to (lpAddress+dwSize). This means, for example, that a 2-byte region of - /// memory that straddles a page boundary causes both pages to be decommitted.If lpAddress is the base address returned - /// by VirtualAlloc and dwSize is 0 (zero), the function decommits the entire region that is allocated by VirtualAlloc. - /// After that, the entire region is in the reserved state. - /// - /// - /// The type of free operation. This parameter must be one of the MEM_* enum values. - /// - /// - /// If the function succeeds, the return value is a nonzero value. If the function fails, the return value is 0 (zero). - /// To get extended error information, call GetLastError. - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern bool VirtualFree( - IntPtr lpAddress, - UIntPtr dwSize, - AllocationType dwFreeType); - - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect. - /// Changes the protection on a region of committed pages in the virtual address space of the calling process. - /// - /// - /// The address of the starting page of the region of pages whose access protection attributes are to be changed. All - /// pages in the specified region must be within the same reserved region allocated when calling the VirtualAlloc or - /// VirtualAllocEx function using MEM_RESERVE. The pages cannot span adjacent reserved regions that were allocated by - /// separate calls to VirtualAlloc or VirtualAllocEx using MEM_RESERVE. - /// - /// - /// The size of the region whose access protection attributes are to be changed, in bytes. The region of affected pages - /// includes all pages containing one or more bytes in the range from the lpAddress parameter to (lpAddress+dwSize). - /// This means that a 2-byte range straddling a page boundary causes the protection attributes of both pages to be changed. - /// - /// - /// The memory protection option. This parameter can be one of the memory protection constants. For mapped views, this - /// value must be compatible with the access protection specified when the view was mapped (see MapViewOfFile, - /// MapViewOfFileEx, and MapViewOfFileExNuma). - /// - /// - /// A pointer to a variable that receives the previous access protection value of the first page in the specified region - /// of pages. If this parameter is NULL or does not point to a valid variable, the function fails. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero. - /// To get extended error information, call GetLastError. - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern bool VirtualProtect( - IntPtr lpAddress, - UIntPtr dwSize, - MemoryProtection flNewProtection, - out MemoryProtection lpflOldProtect); - - /// - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - public static extern bool VirtualProtect( - IntPtr lpAddress, - UIntPtr dwSize, - Memory.MemoryProtection flNewProtection, - out Memory.MemoryProtection lpflOldProtect); - - /// - /// Writes data to an area of memory in a specified process. The entire area to be written to must be accessible or - /// the operation fails. - /// - /// - /// A handle to the process memory to be modified. The handle must have PROCESS_VM_WRITE and PROCESS_VM_OPERATION access - /// to the process. - /// - /// - /// A pointer to the base address in the specified process to which data is written. Before data transfer occurs, the - /// system verifies that all data in the base address and memory of the specified size is accessible for write access, - /// and if it is not accessible, the function fails. - /// - /// - /// A pointer to the buffer that contains data to be written in the address space of the specified process. - /// - /// - /// The number of bytes to be written to the specified process. - /// - /// - /// A pointer to a variable that receives the number of bytes transferred into the specified process. This parameter - /// is optional. If lpNumberOfBytesWritten is NULL, the parameter is ignored. - /// - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is 0 (zero). To get - /// extended error information, call GetLastError.The function fails if the requested write operation crosses into an - /// area of the process that is inaccessible. - /// - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool WriteProcessMemory( - IntPtr hProcess, - IntPtr lpBaseAddress, - byte[] lpBuffer, - int dwSize, - out IntPtr lpNumberOfBytesWritten); - - /// - /// Get a handle to the current process. - /// - /// Handle to the process. - [DllImport("kernel32.dll")] - public static extern IntPtr GetCurrentProcess(); - - /// - /// Get the current process ID. - /// - /// The process ID. - [DllImport("kernel32.dll")] - public static extern uint GetCurrentProcessId(); - - /// - /// Get the current thread ID. - /// - /// The thread ID. - [DllImport("kernel32.dll")] - public static extern uint GetCurrentThreadId(); -} - -/// -/// Native dbghelp functions. -/// -internal static partial class NativeFunctions -{ - /// - /// Type of minidump to create. - /// - public enum MiniDumpType : int - { - /// - /// Normal minidump. - /// - MiniDumpNormal, - - /// - /// Minidump with data segments. - /// - MiniDumpWithDataSegs, - - /// - /// Minidump with full memory. - /// - MiniDumpWithFullMemory, - } - - /// - /// Initializes the symbol handler for a process. - /// - /// - /// A handle that identifies the caller. - /// This value should be unique and nonzero, but need not be a process handle. - /// However, if you do use a process handle, be sure to use the correct handle. - /// If the application is a debugger, use the process handle for the process being debugged. - /// Do not use the handle returned by GetCurrentProcess when debugging another process, because calling functions like SymLoadModuleEx can have unexpected results. - /// This parameter cannot be NULL. - /// - /// The path, or series of paths separated by a semicolon (;), that is used to search for symbol files. - /// If this parameter is NULL, the library attempts to form a symbol path from the following sources: - /// - The current working directory of the application - /// - The _NT_SYMBOL_PATH environment variable - /// - The _NT_ALTERNATE_SYMBOL_PATH environment variable - /// Note that the search path can also be set using the SymSetSearchPath function. - /// - /// - /// If this value is , enumerates the loaded modules for the process and effectively calls the SymLoadModule64 function for each module. - /// - /// Whether or not the function succeeded. - [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Auto)] - public static extern bool SymInitialize(IntPtr hProcess, string userSearchPath, bool fInvadeProcess); - - /// - /// Deallocates all resources associated with the process handle. - /// - /// A handle to the process that was originally passed to the function. - /// Whether or not the function succeeded. - [DllImport("dbghelp.dll", SetLastError = true, CharSet = CharSet.Auto)] - public static extern bool SymCleanup(IntPtr hProcess); - - /// - /// Creates a minidump. - /// - /// Target process handle. - /// Target process ID. - /// Output file handle. - /// Type of dump to take. - /// Exception information. - /// User information. - /// Callback. - /// Whether or not the minidump succeeded. - [DllImport("dbghelp.dll")] - public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, IntPtr hFile, int dumpType, ref MinidumpExceptionInformation exceptionInfo, IntPtr userStreamParam, IntPtr callback); - - /// - /// Structure describing minidump exception information. - /// - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct MinidumpExceptionInformation - { - /// - /// ID of the thread that caused the exception. - /// - public uint ThreadId; - - /// - /// Pointer to the exception record. - /// - public IntPtr ExceptionPointers; - - /// - /// ClientPointers field. - /// - public int ClientPointers; - } -} - -/// -/// Native ws2_32 functions. -/// -internal static partial class NativeFunctions -{ - /// - /// See https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-setsockopt. - /// The setsockopt function sets a socket option. - /// - /// - /// A descriptor that identifies a socket. - /// - /// - /// The level at which the option is defined (for example, SOL_SOCKET). - /// - /// - /// The socket option for which the value is to be set (for example, SO_BROADCAST). The optname parameter must be a - /// socket option defined within the specified level, or behavior is undefined. - /// - /// - /// A pointer to the buffer in which the value for the requested option is specified. - /// - /// - /// The size, in bytes, of the buffer pointed to by the optval parameter. - /// - /// - /// If no error occurs, setsockopt returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error - /// code can be retrieved by calling WSAGetLastError. - /// - [DllImport("ws2_32.dll", CallingConvention = CallingConvention.Winapi, EntryPoint = "setsockopt")] - public static extern int SetSockOpt(IntPtr socket, SocketOptionLevel level, SocketOptionName optName, ref IntPtr optVal, int optLen); -} diff --git a/Dalamud/Plugin/DalamudPluginInterface.cs b/Dalamud/Plugin/DalamudPluginInterface.cs index 35c006952..8eaf1a74c 100644 --- a/Dalamud/Plugin/DalamudPluginInterface.cs +++ b/Dalamud/Plugin/DalamudPluginInterface.cs @@ -20,385 +20,386 @@ using Dalamud.Plugin.Ipc; using Dalamud.Plugin.Ipc.Exceptions; using Dalamud.Plugin.Ipc.Internal; -namespace Dalamud.Plugin; - -/// -/// This class acts as an interface to various objects needed to interact with Dalamud and the game. -/// -public sealed class DalamudPluginInterface : IDisposable +namespace Dalamud.Plugin { - private readonly string pluginName; - private readonly PluginConfigurations configs; - /// - /// Initializes a new instance of the class. - /// Set up the interface and populate all fields needed. + /// This class acts as an interface to various objects needed to interact with Dalamud and the game. /// - /// The internal name of the plugin. - /// Location of the assembly. - /// The reason the plugin was loaded. - /// A value indicating whether this is a dev plugin. - internal DalamudPluginInterface(string pluginName, FileInfo assemblyLocation, PluginLoadReason reason, bool isDev) + public sealed class DalamudPluginInterface : IDisposable { - var configuration = Service.Get(); - var dataManager = Service.Get(); - var localization = Service.Get(); + private readonly string pluginName; + private readonly PluginConfigurations configs; - this.UiBuilder = new UiBuilder(pluginName); - - this.pluginName = pluginName; - this.AssemblyLocation = assemblyLocation; - this.configs = Service.Get().PluginConfigs; - this.Reason = reason; - this.IsDev = isDev; - - this.LoadTime = DateTime.Now; - this.LoadTimeUTC = DateTime.UtcNow; - - this.GeneralChatType = configuration.GeneralChatType; - this.Sanitizer = new Sanitizer(dataManager.Language); - if (configuration.LanguageOverride != null) + /// + /// Initializes a new instance of the class. + /// Set up the interface and populate all fields needed. + /// + /// The internal name of the plugin. + /// Location of the assembly. + /// The reason the plugin was loaded. + /// A value indicating whether this is a dev plugin. + internal DalamudPluginInterface(string pluginName, FileInfo assemblyLocation, PluginLoadReason reason, bool isDev) { - this.UiLanguage = configuration.LanguageOverride; - } - else - { - var currentUiLang = CultureInfo.CurrentUICulture; - if (Localization.ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) - this.UiLanguage = currentUiLang.TwoLetterISOLanguageName; + var configuration = Service.Get(); + var dataManager = Service.Get(); + var localization = Service.Get(); + + this.UiBuilder = new UiBuilder(pluginName); + + this.pluginName = pluginName; + this.AssemblyLocation = assemblyLocation; + this.configs = Service.Get().PluginConfigs; + this.Reason = reason; + this.IsDev = isDev; + + this.LoadTime = DateTime.Now; + this.LoadTimeUTC = DateTime.UtcNow; + + this.GeneralChatType = configuration.GeneralChatType; + this.Sanitizer = new Sanitizer(dataManager.Language); + if (configuration.LanguageOverride != null) + { + this.UiLanguage = configuration.LanguageOverride; + } else - this.UiLanguage = "en"; + { + var currentUiLang = CultureInfo.CurrentUICulture; + if (Localization.ApplicableLangCodes.Any(langCode => currentUiLang.TwoLetterISOLanguageName == langCode)) + this.UiLanguage = currentUiLang.TwoLetterISOLanguageName; + else + this.UiLanguage = "en"; + } + + localization.LocalizationChanged += this.OnLocalizationChanged; + configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved; } - localization.LocalizationChanged += this.OnLocalizationChanged; - configuration.DalamudConfigurationSaved += this.OnDalamudConfigurationSaved; - } + /// + /// Delegate for localization change with two-letter iso lang code. + /// + /// The new language code. + public delegate void LanguageChangedDelegate(string langCode); - /// - /// Delegate for localization change with two-letter iso lang code. - /// - /// The new language code. - public delegate void LanguageChangedDelegate(string langCode); + /// + /// Event that gets fired when loc is changed + /// + public event LanguageChangedDelegate LanguageChanged; - /// - /// Event that gets fired when loc is changed - /// - public event LanguageChangedDelegate LanguageChanged; + /// + /// Gets the reason this plugin was loaded. + /// + public PluginLoadReason Reason { get; } - /// - /// Gets the reason this plugin was loaded. - /// - public PluginLoadReason Reason { get; } + /// + /// Gets a value indicating whether this is a dev plugin. + /// + public bool IsDev { get; } - /// - /// Gets a value indicating whether this is a dev plugin. - /// - public bool IsDev { get; } + /// + /// Gets the time that this plugin was loaded. + /// + public DateTime LoadTime { get; } - /// - /// Gets the time that this plugin was loaded. - /// - public DateTime LoadTime { get; } + /// + /// Gets the UTC time that this plugin was loaded. + /// + public DateTime LoadTimeUTC { get; } - /// - /// Gets the UTC time that this plugin was loaded. - /// - public DateTime LoadTimeUTC { get; } + /// + /// Gets the timespan delta from when this plugin was loaded. + /// + public TimeSpan LoadTimeDelta => DateTime.Now - this.LoadTime; - /// - /// Gets the timespan delta from when this plugin was loaded. - /// - public TimeSpan LoadTimeDelta => DateTime.Now - this.LoadTime; + /// + /// Gets the directory Dalamud assets are stored in. + /// + public DirectoryInfo DalamudAssetDirectory => Service.Get().AssetDirectory; - /// - /// Gets the directory Dalamud assets are stored in. - /// - public DirectoryInfo DalamudAssetDirectory => Service.Get().AssetDirectory; + /// + /// Gets the location of your plugin assembly. + /// + public FileInfo AssemblyLocation { get; } - /// - /// Gets the location of your plugin assembly. - /// - public FileInfo AssemblyLocation { get; } + /// + /// Gets the directory your plugin configurations are stored in. + /// + public DirectoryInfo ConfigDirectory => new(this.GetPluginConfigDirectory()); - /// - /// Gets the directory your plugin configurations are stored in. - /// - public DirectoryInfo ConfigDirectory => new(this.GetPluginConfigDirectory()); + /// + /// Gets the config file of your plugin. + /// + public FileInfo ConfigFile => this.configs.GetConfigFile(this.pluginName); - /// - /// Gets the config file of your plugin. - /// - public FileInfo ConfigFile => this.configs.GetConfigFile(this.pluginName); + /// + /// Gets the instance which allows you to draw UI into the game via ImGui draw calls. + /// + public UiBuilder UiBuilder { get; private set; } - /// - /// Gets the instance which allows you to draw UI into the game via ImGui draw calls. - /// - public UiBuilder UiBuilder { get; private set; } - - /// - /// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds. - /// + /// + /// Gets a value indicating whether Dalamud is running in Debug mode or the /xldev menu is open. This can occur on release builds. + /// #if DEBUG - public bool IsDebugging => true; + public bool IsDebugging => true; #else public bool IsDebugging => Service.Get().IsDevMenuOpen; #endif - /// - /// Gets the current UI language in two-letter iso format. - /// - public string UiLanguage { get; private set; } + /// + /// Gets the current UI language in two-letter iso format. + /// + public string UiLanguage { get; private set; } - /// - /// Gets serializer class with functions to remove special characters from strings. - /// - public ISanitizer Sanitizer { get; } + /// + /// Gets serializer class with functions to remove special characters from strings. + /// + public ISanitizer Sanitizer { get; } - /// - /// Gets the chat type used by default for plugin messages. - /// - public XivChatType GeneralChatType { get; private set; } + /// + /// Gets the chat type used by default for plugin messages. + /// + public XivChatType GeneralChatType { get; private set; } - /// - /// Gets a list of installed plugin names. - /// - public List PluginNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.Name).ToList(); + /// + /// Gets a list of installed plugin names. + /// + public List PluginNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.Name).ToList(); - /// - /// Gets a list of installed plugin internal names. - /// - public List PluginInternalNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.InternalName).ToList(); + /// + /// Gets a list of installed plugin internal names. + /// + public List PluginInternalNames => Service.Get().InstalledPlugins.Select(p => p.Manifest.InternalName).ToList(); - #region IPC + #region IPC - /// - /// Gets an IPC provider. - /// - /// The return type for funcs. Use object if this is unused. - /// The name of the IPC registration. - /// An IPC provider. - /// This is thrown when the requested types do not match the previously registered types are different. - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + /// Gets an IPC provider. + /// + /// The return type for funcs. Use object if this is unused. + /// The name of the IPC registration. + /// An IPC provider. + /// This is thrown when the requested types do not match the previously registered types are different. + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - public ICallGateProvider GetIpcProvider(string name) - => new CallGatePubSub(name); + /// + public ICallGateProvider GetIpcProvider(string name) + => new CallGatePubSub(name); - /// - /// Gets an IPC subscriber. - /// - /// The return type for funcs. Use object if this is unused. - /// The name of the IPC registration. - /// An IPC subscriber. - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + /// Gets an IPC subscriber. + /// + /// The return type for funcs. Use object if this is unused. + /// The name of the IPC registration. + /// An IPC subscriber. + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - /// - public ICallGateSubscriber GetIpcSubscriber(string name) - => new CallGatePubSub(name); + /// + public ICallGateSubscriber GetIpcSubscriber(string name) + => new CallGatePubSub(name); - #endregion + #endregion - #region Configuration + #region Configuration - /// - /// Save a plugin configuration(inheriting IPluginConfiguration). - /// - /// The current configuration. - public void SavePluginConfig(IPluginConfiguration? currentConfig) - { - if (currentConfig == null) - return; - - this.configs.Save(currentConfig, this.pluginName); - } - - /// - /// Get a previously saved plugin configuration or null if none was saved before. - /// - /// A previously saved config or null if none was saved before. - public IPluginConfiguration? GetPluginConfig() - { - // This is done to support json deserialization of plugin configurations - // even after running an in-game update of plugins, where the assembly version - // changes. - // Eventually it might make sense to have a separate method on this class - // T GetPluginConfig() where T : IPluginConfiguration - // that can invoke LoadForType() directly instead of via reflection - // This is here for now to support the current plugin API - foreach (var type in Assembly.GetCallingAssembly().GetTypes()) + /// + /// Save a plugin configuration(inheriting IPluginConfiguration). + /// + /// The current configuration. + public void SavePluginConfig(IPluginConfiguration? currentConfig) { - if (type.IsAssignableTo(typeof(IPluginConfiguration))) - { - var mi = this.configs.GetType().GetMethod("LoadForType"); - var fn = mi.MakeGenericMethod(type); - return (IPluginConfiguration)fn.Invoke(this.configs, new object[] { this.pluginName }); - } + if (currentConfig == null) + return; + + this.configs.Save(currentConfig, this.pluginName); } - // this shouldn't be a thing, I think, but just in case - return this.configs.Load(this.pluginName); - } + /// + /// Get a previously saved plugin configuration or null if none was saved before. + /// + /// A previously saved config or null if none was saved before. + public IPluginConfiguration? GetPluginConfig() + { + // This is done to support json deserialization of plugin configurations + // even after running an in-game update of plugins, where the assembly version + // changes. + // Eventually it might make sense to have a separate method on this class + // T GetPluginConfig() where T : IPluginConfiguration + // that can invoke LoadForType() directly instead of via reflection + // This is here for now to support the current plugin API + foreach (var type in Assembly.GetCallingAssembly().GetTypes()) + { + if (type.IsAssignableTo(typeof(IPluginConfiguration))) + { + var mi = this.configs.GetType().GetMethod("LoadForType"); + var fn = mi.MakeGenericMethod(type); + return (IPluginConfiguration)fn.Invoke(this.configs, new object[] { this.pluginName }); + } + } - /// - /// Get the config directory. - /// - /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName. - public string GetPluginConfigDirectory() => this.configs.GetDirectory(this.pluginName); + // this shouldn't be a thing, I think, but just in case + return this.configs.Load(this.pluginName); + } - /// - /// Get the loc directory. - /// - /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc. - public string GetPluginLocDirectory() => this.configs.GetDirectory(Path.Combine(this.pluginName, "loc")); + /// + /// Get the config directory. + /// + /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName. + public string GetPluginConfigDirectory() => this.configs.GetDirectory(this.pluginName); - #endregion + /// + /// Get the loc directory. + /// + /// directory with path of AppData/XIVLauncher/pluginConfig/PluginInternalName/loc. + public string GetPluginLocDirectory() => this.configs.GetDirectory(Path.Combine(this.pluginName, "loc")); - #region Chat Links + #endregion - /// - /// Register a chat link handler. - /// - /// The ID of the command. - /// The action to be executed. - /// Returns an SeString payload for the link. - public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction) - { - return Service.Get().AddChatLinkHandler(this.pluginName, commandId, commandAction); - } + #region Chat Links - /// - /// Remove a chat link handler. - /// - /// The ID of the command. - public void RemoveChatLinkHandler(uint commandId) - { - Service.Get().RemoveChatLinkHandler(this.pluginName, commandId); - } + /// + /// Register a chat link handler. + /// + /// The ID of the command. + /// The action to be executed. + /// Returns an SeString payload for the link. + public DalamudLinkPayload AddChatLinkHandler(uint commandId, Action commandAction) + { + return Service.Get().AddChatLinkHandler(this.pluginName, commandId, commandAction); + } - /// - /// Removes all chat link handlers registered by the plugin. - /// - public void RemoveChatLinkHandler() - { - Service.Get().RemoveChatLinkHandler(this.pluginName); - } - #endregion + /// + /// Remove a chat link handler. + /// + /// The ID of the command. + public void RemoveChatLinkHandler(uint commandId) + { + Service.Get().RemoveChatLinkHandler(this.pluginName, commandId); + } - #region Dependency Injection + /// + /// Removes all chat link handlers registered by the plugin. + /// + public void RemoveChatLinkHandler() + { + Service.Get().RemoveChatLinkHandler(this.pluginName); + } + #endregion - /// - /// Create a new object of the provided type using its default constructor, then inject objects and properties. - /// - /// Objects to inject additionally. - /// The type to create. - /// The created and initialized type. - public T? Create(params object[] scopedObjects) where T : class - { - var svcContainer = Service.Get(); + #region Dependency Injection - var realScopedObjects = new object[scopedObjects.Length + 1]; - realScopedObjects[0] = this; - Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); + /// + /// Create a new object of the provided type using its default constructor, then inject objects and properties. + /// + /// Objects to inject additionally. + /// The type to create. + /// The created and initialized type. + public T? Create(params object[] scopedObjects) where T : class + { + var svcContainer = Service.Get(); - return svcContainer.Create(typeof(T), realScopedObjects) as T; - } + var realScopedObjects = new object[scopedObjects.Length + 1]; + realScopedObjects[0] = this; + Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); - /// - /// Inject services into properties on the provided object instance. - /// - /// The instance to inject services into. - /// Objects to inject additionally. - /// Whether or not the injection succeeded. - public bool Inject(object instance, params object[] scopedObjects) - { - var svcContainer = Service.Get(); + return svcContainer.Create(typeof(T), realScopedObjects) as T; + } - var realScopedObjects = new object[scopedObjects.Length + 1]; - realScopedObjects[0] = this; - Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); + /// + /// Inject services into properties on the provided object instance. + /// + /// The instance to inject services into. + /// Objects to inject additionally. + /// Whether or not the injection succeeded. + public bool Inject(object instance, params object[] scopedObjects) + { + var svcContainer = Service.Get(); - return svcContainer.InjectProperties(instance, realScopedObjects); - } + var realScopedObjects = new object[scopedObjects.Length + 1]; + realScopedObjects[0] = this; + Array.Copy(scopedObjects, 0, realScopedObjects, 1, scopedObjects.Length); - #endregion + return svcContainer.InjectProperties(instance, realScopedObjects); + } - /// - /// Unregister your plugin and dispose all references. - /// - public void Dispose() - { - this.UiBuilder.Dispose(); - Service.Get().RemoveChatLinkHandler(this.pluginName); - Service.Get().LocalizationChanged -= this.OnLocalizationChanged; - Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; - } + #endregion - private void OnLocalizationChanged(string langCode) - { - this.UiLanguage = langCode; - this.LanguageChanged?.Invoke(langCode); - } + /// + /// Unregister your plugin and dispose all references. + /// + public void Dispose() + { + this.UiBuilder.Dispose(); + Service.Get().RemoveChatLinkHandler(this.pluginName); + Service.Get().LocalizationChanged -= this.OnLocalizationChanged; + Service.Get().DalamudConfigurationSaved -= this.OnDalamudConfigurationSaved; + } - private void OnDalamudConfigurationSaved(DalamudConfiguration dalamudConfiguration) - { - this.GeneralChatType = dalamudConfiguration.GeneralChatType; + private void OnLocalizationChanged(string langCode) + { + this.UiLanguage = langCode; + this.LanguageChanged?.Invoke(langCode); + } + + private void OnDalamudConfigurationSaved(DalamudConfiguration dalamudConfiguration) + { + this.GeneralChatType = dalamudConfiguration.GeneralChatType; + } } } diff --git a/Dalamud/Plugin/IDalamudPlugin.cs b/Dalamud/Plugin/IDalamudPlugin.cs index c752df3d6..51d67328d 100644 --- a/Dalamud/Plugin/IDalamudPlugin.cs +++ b/Dalamud/Plugin/IDalamudPlugin.cs @@ -1,14 +1,15 @@ using System; -namespace Dalamud.Plugin; - -/// -/// This interface represents a basic Dalamud plugin. All plugins have to implement this interface. -/// -public interface IDalamudPlugin : IDisposable +namespace Dalamud.Plugin { /// - /// Gets the name of the plugin. + /// This interface represents a basic Dalamud plugin. All plugins have to implement this interface. /// - string Name { get; } + public interface IDalamudPlugin : IDisposable + { + /// + /// Gets the name of the plugin. + /// + string Name { get; } + } } diff --git a/Dalamud/Plugin/Internal/Exceptions/BannedPluginException.cs b/Dalamud/Plugin/Internal/Exceptions/BannedPluginException.cs index 851e5be33..e4418b6d3 100644 --- a/Dalamud/Plugin/Internal/Exceptions/BannedPluginException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/BannedPluginException.cs @@ -1,21 +1,22 @@ -namespace Dalamud.Plugin.Internal.Exceptions; - -/// -/// This represents a banned plugin that attempted an operation. -/// -internal class BannedPluginException : PluginException +namespace Dalamud.Plugin.Internal.Exceptions { /// - /// Initializes a new instance of the class. + /// This represents a banned plugin that attempted an operation. /// - /// The message describing the invalid operation. - public BannedPluginException(string message) + internal class BannedPluginException : PluginException { - this.Message = message; - } + /// + /// Initializes a new instance of the class. + /// + /// The message describing the invalid operation. + public BannedPluginException(string message) + { + this.Message = message; + } - /// - /// Gets the message describing the invalid operation. - /// - public override string Message { get; } + /// + /// Gets the message describing the invalid operation. + /// + public override string Message { get; } + } } diff --git a/Dalamud/Plugin/Internal/Exceptions/DuplicatePluginException.cs b/Dalamud/Plugin/Internal/Exceptions/DuplicatePluginException.cs index 79f855537..093f97e69 100644 --- a/Dalamud/Plugin/Internal/Exceptions/DuplicatePluginException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/DuplicatePluginException.cs @@ -1,25 +1,26 @@ -namespace Dalamud.Plugin.Internal.Exceptions; - -/// -/// This exception that is thrown when a plugin is instructed to load while another plugin with the same -/// assembly name is already present and loaded. -/// -internal class DuplicatePluginException : PluginException +namespace Dalamud.Plugin.Internal.Exceptions { /// - /// Initializes a new instance of the class. + /// This exception that is thrown when a plugin is instructed to load while another plugin with the same + /// assembly name is already present and loaded. /// - /// Name of the conflicting assembly. - public DuplicatePluginException(string assemblyName) + internal class DuplicatePluginException : PluginException { - this.AssemblyName = assemblyName; + /// + /// Initializes a new instance of the class. + /// + /// Name of the conflicting assembly. + public DuplicatePluginException(string assemblyName) + { + this.AssemblyName = assemblyName; + } + + /// + /// Gets the name of the conflicting assembly. + /// + public string AssemblyName { get; init; } + + /// + public override string Message => $"A plugin with the same assembly name of {this.AssemblyName} is already loaded"; } - - /// - /// Gets the name of the conflicting assembly. - /// - public string AssemblyName { get; init; } - - /// - public override string Message => $"A plugin with the same assembly name of {this.AssemblyName} is already loaded"; } diff --git a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs index cdb0bf304..6b5c8920a 100644 --- a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginException.cs @@ -1,24 +1,25 @@ using System; using System.IO; -namespace Dalamud.Plugin.Internal.Exceptions; - -/// -/// This exception represents a file that does not implement IDalamudPlugin. -/// -internal class InvalidPluginException : PluginException +namespace Dalamud.Plugin.Internal.Exceptions { /// - /// Initializes a new instance of the class. + /// This exception represents a file that does not implement IDalamudPlugin. /// - /// The invalid file. - public InvalidPluginException(FileInfo dllFile) + internal class InvalidPluginException : PluginException { - this.DllFile = dllFile; - } + /// + /// Initializes a new instance of the class. + /// + /// The invalid file. + public InvalidPluginException(FileInfo dllFile) + { + this.DllFile = dllFile; + } - /// - /// Gets the invalid file. - /// - public FileInfo DllFile { get; init; } + /// + /// Gets the invalid file. + /// + public FileInfo DllFile { get; init; } + } } diff --git a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs index 978f12b69..a80d6d51d 100644 --- a/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/InvalidPluginOperationException.cs @@ -1,23 +1,24 @@ using System; -namespace Dalamud.Plugin.Internal.Exceptions; - -/// -/// This represents an invalid plugin operation. -/// -internal class InvalidPluginOperationException : PluginException +namespace Dalamud.Plugin.Internal.Exceptions { /// - /// Initializes a new instance of the class. + /// This represents an invalid plugin operation. /// - /// The message describing the invalid operation. - public InvalidPluginOperationException(string message) + internal class InvalidPluginOperationException : PluginException { - this.Message = message; - } + /// + /// Initializes a new instance of the class. + /// + /// The message describing the invalid operation. + public InvalidPluginOperationException(string message) + { + this.Message = message; + } - /// - /// Gets the message describing the invalid operation. - /// - public override string Message { get; } + /// + /// Gets the message describing the invalid operation. + /// + public override string Message { get; } + } } diff --git a/Dalamud/Plugin/Internal/Exceptions/PluginException.cs b/Dalamud/Plugin/Internal/Exceptions/PluginException.cs index 292be5431..e4b17b686 100644 --- a/Dalamud/Plugin/Internal/Exceptions/PluginException.cs +++ b/Dalamud/Plugin/Internal/Exceptions/PluginException.cs @@ -1,10 +1,11 @@ using System; -namespace Dalamud.Plugin.Internal.Exceptions; - -/// -/// This represents the base Dalamud plugin exception. -/// -internal abstract class PluginException : Exception +namespace Dalamud.Plugin.Internal.Exceptions { + /// + /// This represents the base Dalamud plugin exception. + /// + internal abstract class PluginException : Exception + { + } } diff --git a/Dalamud/Plugin/Internal/LocalDevPlugin.cs b/Dalamud/Plugin/Internal/LocalDevPlugin.cs index 23860e70a..3615c01a9 100644 --- a/Dalamud/Plugin/Internal/LocalDevPlugin.cs +++ b/Dalamud/Plugin/Internal/LocalDevPlugin.cs @@ -8,156 +8,157 @@ using Dalamud.Interface.Internal.Notifications; using Dalamud.Logging.Internal; using Dalamud.Plugin.Internal.Types; -namespace Dalamud.Plugin.Internal; - -/// -/// This class represents a dev plugin and all facets of its lifecycle. -/// The DLL on disk, dependencies, loaded assembly, etc. -/// -internal class LocalDevPlugin : LocalPlugin, IDisposable +namespace Dalamud.Plugin.Internal { - private static readonly ModuleLog Log = new("PLUGIN"); - - // Ref to Dalamud.Configuration.DevPluginSettings - private readonly DevPluginSettings devSettings; - - private FileSystemWatcher fileWatcher; - private CancellationTokenSource fileWatcherTokenSource; - private int reloadCounter; - /// - /// Initializes a new instance of the class. + /// This class represents a dev plugin and all facets of its lifecycle. + /// The DLL on disk, dependencies, loaded assembly, etc. /// - /// Path to the DLL file. - /// The plugin manifest. - public LocalDevPlugin(FileInfo dllFile, LocalPluginManifest? manifest) - : base(dllFile, manifest) + internal class LocalDevPlugin : LocalPlugin, IDisposable { - var configuration = Service.Get(); + private static readonly ModuleLog Log = new("PLUGIN"); - if (!configuration.DevPluginSettings.TryGetValue(dllFile.FullName, out this.devSettings)) + // Ref to Dalamud.Configuration.DevPluginSettings + private readonly DevPluginSettings devSettings; + + private FileSystemWatcher fileWatcher; + private CancellationTokenSource fileWatcherTokenSource; + private int reloadCounter; + + /// + /// Initializes a new instance of the class. + /// + /// Path to the DLL file. + /// The plugin manifest. + public LocalDevPlugin(FileInfo dllFile, LocalPluginManifest? manifest) + : base(dllFile, manifest) { - configuration.DevPluginSettings[dllFile.FullName] = this.devSettings = new DevPluginSettings(); - configuration.Save(); - } + var configuration = Service.Get(); - if (this.AutomaticReload) - { - this.EnableReloading(); - } - } + if (!configuration.DevPluginSettings.TryGetValue(dllFile.FullName, out this.devSettings)) + { + configuration.DevPluginSettings[dllFile.FullName] = this.devSettings = new DevPluginSettings(); + configuration.Save(); + } - /// - /// Gets or sets a value indicating whether this dev plugin should start on boot. - /// - public bool StartOnBoot - { - get => this.devSettings.StartOnBoot; - set => this.devSettings.StartOnBoot = value; - } - - /// - /// Gets or sets a value indicating whether this dev plugin should reload on change. - /// - public bool AutomaticReload - { - get => this.devSettings.AutomaticReloading; - set - { - this.devSettings.AutomaticReloading = value; - - if (this.devSettings.AutomaticReloading) + if (this.AutomaticReload) { this.EnableReloading(); } - else + } + + /// + /// Gets or sets a value indicating whether this dev plugin should start on boot. + /// + public bool StartOnBoot + { + get => this.devSettings.StartOnBoot; + set => this.devSettings.StartOnBoot = value; + } + + /// + /// Gets or sets a value indicating whether this dev plugin should reload on change. + /// + public bool AutomaticReload + { + get => this.devSettings.AutomaticReloading; + set { - this.DisableReloading(); + this.devSettings.AutomaticReloading = value; + + if (this.devSettings.AutomaticReloading) + { + this.EnableReloading(); + } + else + { + this.DisableReloading(); + } } } - } - /// - public new void Dispose() - { - if (this.fileWatcher != null) + /// + public new void Dispose() { - this.fileWatcher.Changed -= this.OnFileChanged; - this.fileWatcherTokenSource.Cancel(); - this.fileWatcher.Dispose(); - } - - base.Dispose(); - } - - /// - /// Configure this plugin for automatic reloading and enable it. - /// - public void EnableReloading() - { - if (this.fileWatcher == null) - { - this.fileWatcherTokenSource = new(); - this.fileWatcher = new FileSystemWatcher(this.DllFile.DirectoryName); - this.fileWatcher.Changed += this.OnFileChanged; - this.fileWatcher.Filter = this.DllFile.Name; - this.fileWatcher.NotifyFilter = NotifyFilters.LastWrite; - this.fileWatcher.EnableRaisingEvents = true; - } - } - - /// - /// Disable automatic reloading for this plugin. - /// - public void DisableReloading() - { - if (this.fileWatcher != null) - { - this.fileWatcherTokenSource.Cancel(); - this.fileWatcher.Changed -= this.OnFileChanged; - this.fileWatcher.Dispose(); - this.fileWatcher = null; - } - } - - private void OnFileChanged(object sender, FileSystemEventArgs args) - { - var current = Interlocked.Increment(ref this.reloadCounter); - - Task.Delay(500).ContinueWith( - task => + if (this.fileWatcher != null) { - if (this.fileWatcherTokenSource.IsCancellationRequested) - { - Log.Debug($"Skipping reload of {this.Name}, file watcher was cancelled."); - return; - } + this.fileWatcher.Changed -= this.OnFileChanged; + this.fileWatcherTokenSource.Cancel(); + this.fileWatcher.Dispose(); + } - if (current != this.reloadCounter) - { - Log.Debug($"Skipping reload of {this.Name}, file has changed again."); - return; - } + base.Dispose(); + } - if (this.State != PluginState.Loaded) - { - Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded}."); - return; - } + /// + /// Configure this plugin for automatic reloading and enable it. + /// + public void EnableReloading() + { + if (this.fileWatcher == null) + { + this.fileWatcherTokenSource = new(); + this.fileWatcher = new FileSystemWatcher(this.DllFile.DirectoryName); + this.fileWatcher.Changed += this.OnFileChanged; + this.fileWatcher.Filter = this.DllFile.Name; + this.fileWatcher.NotifyFilter = NotifyFilters.LastWrite; + this.fileWatcher.EnableRaisingEvents = true; + } + } - var notificationManager = Service.Get(); + /// + /// Disable automatic reloading for this plugin. + /// + public void DisableReloading() + { + if (this.fileWatcher != null) + { + this.fileWatcherTokenSource.Cancel(); + this.fileWatcher.Changed -= this.OnFileChanged; + this.fileWatcher.Dispose(); + this.fileWatcher = null; + } + } - try + private void OnFileChanged(object sender, FileSystemEventArgs args) + { + var current = Interlocked.Increment(ref this.reloadCounter); + + Task.Delay(500).ContinueWith( + task => { - this.Reload(); - notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success); - } - catch (Exception ex) - { - Log.Error(ex, "DevPlugin reload failed."); - notificationManager.AddNotification($"The DevPlugin '{this.Name} could not be reloaded.", "Plugin reload failed!", NotificationType.Error); - } - }, - this.fileWatcherTokenSource.Token); + if (this.fileWatcherTokenSource.IsCancellationRequested) + { + Log.Debug($"Skipping reload of {this.Name}, file watcher was cancelled."); + return; + } + + if (current != this.reloadCounter) + { + Log.Debug($"Skipping reload of {this.Name}, file has changed again."); + return; + } + + if (this.State != PluginState.Loaded) + { + Log.Debug($"Skipping reload of {this.Name}, state ({this.State}) is not {PluginState.Loaded}."); + return; + } + + var notificationManager = Service.Get(); + + try + { + this.Reload(); + notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success); + } + catch (Exception ex) + { + Log.Error(ex, "DevPlugin reload failed."); + notificationManager.AddNotification($"The DevPlugin '{this.Name} could not be reloaded.", "Plugin reload failed!", NotificationType.Error); + } + }, + this.fileWatcherTokenSource.Token); + } } } diff --git a/Dalamud/Plugin/Internal/LocalPlugin.cs b/Dalamud/Plugin/Internal/LocalPlugin.cs index ba8cb4837..4a17f8369 100644 --- a/Dalamud/Plugin/Internal/LocalPlugin.cs +++ b/Dalamud/Plugin/Internal/LocalPlugin.cs @@ -11,364 +11,201 @@ using Dalamud.Plugin.Internal.Exceptions; using Dalamud.Plugin.Internal.Loader; using Dalamud.Plugin.Internal.Types; -namespace Dalamud.Plugin.Internal; - -/// -/// This class represents a plugin and all facets of its lifecycle. -/// The DLL on disk, dependencies, loaded assembly, etc. -/// -internal class LocalPlugin : IDisposable +namespace Dalamud.Plugin.Internal { - private static readonly ModuleLog Log = new("LOCALPLUGIN"); - - private readonly FileInfo manifestFile; - private readonly FileInfo disabledFile; - private readonly FileInfo testingFile; - - private PluginLoader loader; - private Assembly pluginAssembly; - private Type? pluginType; - private IDalamudPlugin? instance; - /// - /// Initializes a new instance of the class. + /// This class represents a plugin and all facets of its lifecycle. + /// The DLL on disk, dependencies, loaded assembly, etc. /// - /// Path to the DLL file. - /// The plugin manifest. - public LocalPlugin(FileInfo dllFile, LocalPluginManifest? manifest) + internal class LocalPlugin : IDisposable { - if (dllFile.Name == "FFXIVClientStructs.Generators.dll") + private static readonly ModuleLog Log = new("LOCALPLUGIN"); + + private readonly FileInfo manifestFile; + private readonly FileInfo disabledFile; + private readonly FileInfo testingFile; + + private PluginLoader loader; + private Assembly pluginAssembly; + private Type? pluginType; + private IDalamudPlugin? instance; + + /// + /// Initializes a new instance of the class. + /// + /// Path to the DLL file. + /// The plugin manifest. + public LocalPlugin(FileInfo dllFile, LocalPluginManifest? manifest) { - // Could this be done another way? Sure. It is an extremely common source - // of errors in the log through, and should never be loaded as a plugin. - Log.Error($"Not a plugin: {dllFile.FullName}"); - throw new InvalidPluginException(dllFile); - } - - this.DllFile = dllFile; - this.State = PluginState.Unloaded; - - this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig); - - try - { - this.pluginAssembly = this.loader.LoadDefaultAssembly(); - } - catch (Exception ex) - { - this.pluginAssembly = null; - this.pluginType = null; - this.loader.Dispose(); - - Log.Error(ex, $"Not a plugin: {this.DllFile.FullName}"); - throw new InvalidPluginException(this.DllFile); - } - - try - { - this.pluginType = this.pluginAssembly.GetTypes().FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin))); - } - catch (ReflectionTypeLoadException ex) - { - Log.Error(ex, $"Could not load one or more types when searching for IDalamudPlugin: {this.DllFile.FullName}"); - // Something blew up when parsing types, but we still want to look for IDalamudPlugin. Let Load() handle the error. - this.pluginType = ex.Types.FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin))); - } - - if (this.pluginType == default) - { - this.pluginAssembly = null; - this.pluginType = null; - this.loader.Dispose(); - - Log.Error($"Nothing inherits from IDalamudPlugin: {this.DllFile.FullName}"); - throw new InvalidPluginException(this.DllFile); - } - - var assemblyVersion = this.pluginAssembly.GetName().Version; - - // Although it is conditionally used here, we need to set the initial value regardless. - this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile); - - // If the parameter manifest was null - if (manifest == null) - { - this.Manifest = new LocalPluginManifest() + if (dllFile.Name == "FFXIVClientStructs.Generators.dll") { - Author = "developer", - Name = Path.GetFileNameWithoutExtension(this.DllFile.Name), - InternalName = Path.GetFileNameWithoutExtension(this.DllFile.Name), - AssemblyVersion = assemblyVersion, - Description = string.Empty, - ApplicableVersion = GameVersion.Any, - DalamudApiLevel = PluginManager.DalamudApiLevel, - IsHide = false, - }; + // Could this be done another way? Sure. It is an extremely common source + // of errors in the log through, and should never be loaded as a plugin. + Log.Error($"Not a plugin: {dllFile.FullName}"); + throw new InvalidPluginException(dllFile); + } - // Save the manifest to disk so there won't be any problems later. - // We'll update the name property after it can be retrieved from the instance. - this.Manifest.Save(this.manifestFile); - } - else - { - this.Manifest = manifest; - } + this.DllFile = dllFile; + this.State = PluginState.Unloaded; - // This converts from the ".disabled" file feature to the manifest instead. - this.disabledFile = LocalPluginManifest.GetDisabledFile(this.DllFile); - if (this.disabledFile.Exists) - { - this.Manifest.Disabled = true; - this.disabledFile.Delete(); - } + this.loader = PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig); - // This converts from the ".testing" file feature to the manifest instead. - this.testingFile = LocalPluginManifest.GetTestingFile(this.DllFile); - if (this.testingFile.Exists) - { - this.Manifest.Testing = true; - this.testingFile.Delete(); - } - - var pluginManager = Service.Get(); - this.IsBanned = pluginManager.IsManifestBanned(this.Manifest); - this.BanReason = pluginManager.GetBanReason(this.Manifest); - - this.SaveManifest(); - } - - /// - /// Gets the associated with this plugin. - /// - public DalamudPluginInterface DalamudInterface { get; private set; } - - /// - /// Gets the path to the plugin DLL. - /// - public FileInfo DllFile { get; } - - /// - /// Gets the plugin manifest, if one exists. - /// - public LocalPluginManifest Manifest { get; private set; } - - /// - /// Gets or sets the current state of the plugin. - /// - public PluginState State { get; protected set; } = PluginState.Unloaded; - - /// - /// Gets the AssemblyName plugin, populated during . - /// - /// Plugin type. - public AssemblyName? AssemblyName { get; private set; } = null; - - /// - /// Gets the plugin name, directly from the plugin or if it is not loaded from the manifest. - /// - public string Name => this.instance?.Name ?? this.Manifest.Name ?? this.DllFile.Name; - - /// - /// Gets an optional reason, if the plugin is banned. - /// - public string BanReason { get; } - - /// - /// Gets a value indicating whether the plugin is loaded and running. - /// - public bool IsLoaded => this.State == PluginState.Loaded; - - /// - /// Gets a value indicating whether the plugin is disabled. - /// - public bool IsDisabled => this.Manifest.Disabled; - - /// - /// Gets a value indicating whether this plugin's API level is out of date. - /// - public bool IsOutdated => this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel; - - /// - /// Gets a value indicating whether the plugin is for testing use only. - /// - public bool IsTesting => this.Manifest.IsTestingExclusive || this.Manifest.Testing; - - /// - /// Gets a value indicating whether this plugin has been banned. - /// - public bool IsBanned { get; } - - /// - /// Gets a value indicating whether this plugin is dev plugin. - /// - public bool IsDev => this is LocalDevPlugin; - - /// - public void Dispose() - { - this.instance?.Dispose(); - this.instance = null; - - this.DalamudInterface?.Dispose(); - this.DalamudInterface = null; - - this.pluginType = null; - this.pluginAssembly = null; - - this.loader?.Dispose(); - } - - /// - /// Load this plugin. - /// - /// The reason why this plugin is being loaded. - /// Load while reloading. - public void Load(PluginLoadReason reason, bool reloading = false) - { - var startInfo = Service.Get(); - var configuration = Service.Get(); - var pluginManager = Service.Get(); - - // Allowed: Unloaded - switch (this.State) - { - case PluginState.InProgress: - throw new InvalidPluginOperationException($"Unable to load {this.Name}, already working"); - case PluginState.Loaded: - throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded"); - case PluginState.LoadError: - throw new InvalidPluginOperationException($"Unable to load {this.Name}, load previously faulted, unload first"); - case PluginState.UnloadError: - throw new InvalidPluginOperationException($"Unable to load {this.Name}, unload previously faulted, restart Dalamud"); - } - - if (pluginManager.IsManifestBanned(this.Manifest)) - throw new BannedPluginException($"Unable to load {this.Name}, banned"); - - if (this.Manifest.ApplicableVersion < startInfo.GameVersion) - throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version"); - - if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels) - throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level"); - - if (this.Manifest.Disabled) - throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled"); - - this.State = PluginState.InProgress; - Log.Information($"Loading {this.DllFile.Name}"); - - if (this.DllFile.DirectoryName != null && File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll"))) - { - Log.Error("==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====", this.Manifest.Author, this.Manifest.InternalName); - Log.Error("YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!"); - Log.Error("You may not be able to load your plugin. \"False\" needs to be set in your csproj."); - Log.Error("If you are using ILMerge, do not merge anything other than your direct dependencies."); - Log.Error("Do not merge FFXIVClientStructs.Generators.dll."); - Log.Error("Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information."); - } - - try - { - this.loader ??= PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig); - - if (reloading) + try { - this.loader.Reload(); + this.pluginAssembly = this.loader.LoadDefaultAssembly(); + } + catch (Exception ex) + { + this.pluginAssembly = null; + this.pluginType = null; + this.loader.Dispose(); - // Reload the manifest in-case there were changes here too. - if (this.IsDev) + Log.Error(ex, $"Not a plugin: {this.DllFile.FullName}"); + throw new InvalidPluginException(this.DllFile); + } + + try + { + this.pluginType = this.pluginAssembly.GetTypes().FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin))); + } + catch (ReflectionTypeLoadException ex) + { + Log.Error(ex, $"Could not load one or more types when searching for IDalamudPlugin: {this.DllFile.FullName}"); + // Something blew up when parsing types, but we still want to look for IDalamudPlugin. Let Load() handle the error. + this.pluginType = ex.Types.FirstOrDefault(type => type.IsAssignableTo(typeof(IDalamudPlugin))); + } + + if (this.pluginType == default) + { + this.pluginAssembly = null; + this.pluginType = null; + this.loader.Dispose(); + + Log.Error($"Nothing inherits from IDalamudPlugin: {this.DllFile.FullName}"); + throw new InvalidPluginException(this.DllFile); + } + + var assemblyVersion = this.pluginAssembly.GetName().Version; + + // Although it is conditionally used here, we need to set the initial value regardless. + this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile); + + // If the parameter manifest was null + if (manifest == null) + { + this.Manifest = new LocalPluginManifest() { - var manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile); - if (manifestFile.Exists) - { - this.Manifest = LocalPluginManifest.Load(manifestFile); - } - } - } + Author = "developer", + Name = Path.GetFileNameWithoutExtension(this.DllFile.Name), + InternalName = Path.GetFileNameWithoutExtension(this.DllFile.Name), + AssemblyVersion = assemblyVersion, + Description = string.Empty, + ApplicableVersion = GameVersion.Any, + DalamudApiLevel = PluginManager.DalamudApiLevel, + IsHide = false, + }; - // Load the assembly - this.pluginAssembly ??= this.loader.LoadDefaultAssembly(); - - this.AssemblyName = this.pluginAssembly.GetName(); - - // Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor. - this.pluginType ??= this.pluginAssembly.GetTypes().First(type => type.IsAssignableTo(typeof(IDalamudPlugin))); - - // Check for any loaded plugins with the same assembly name - var assemblyName = this.pluginAssembly.GetName().Name; - foreach (var otherPlugin in pluginManager.InstalledPlugins) - { - // During hot-reloading, this plugin will be in the plugin list, and the instance will have been disposed - if (otherPlugin == this || otherPlugin.instance == null) - continue; - - var otherPluginAssemblyName = otherPlugin.instance.GetType().Assembly.GetName().Name; - if (otherPluginAssemblyName == assemblyName) - { - this.State = PluginState.Unloaded; - Log.Debug($"Duplicate assembly: {this.Name}"); - - throw new DuplicatePluginException(assemblyName); - } - } - - // Update the location for the Location and CodeBase patches - PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new(this.DllFile); - - this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev); - - var ioc = Service.Get(); - this.instance = ioc.Create(this.pluginType, this.DalamudInterface) as IDalamudPlugin; - if (this.instance == null) - { - this.State = PluginState.LoadError; - this.DalamudInterface.Dispose(); - Log.Error($"Error while loading {this.Name}, failed to bind and call the plugin constructor"); - return; - } - - // In-case the manifest name was a placeholder. Can occur when no manifest was included. - if (this.instance.Name != this.Manifest.Name) - { - this.Manifest.Name = this.instance.Name; + // Save the manifest to disk so there won't be any problems later. + // We'll update the name property after it can be retrieved from the instance. this.Manifest.Save(this.manifestFile); } + else + { + this.Manifest = manifest; + } - this.State = PluginState.Loaded; - Log.Information($"Finished loading {this.DllFile.Name}"); - } - catch (Exception ex) - { - this.State = PluginState.LoadError; - Log.Error(ex, $"Error while loading {this.Name}"); + // This converts from the ".disabled" file feature to the manifest instead. + this.disabledFile = LocalPluginManifest.GetDisabledFile(this.DllFile); + if (this.disabledFile.Exists) + { + this.Manifest.Disabled = true; + this.disabledFile.Delete(); + } - throw; - } - } + // This converts from the ".testing" file feature to the manifest instead. + this.testingFile = LocalPluginManifest.GetTestingFile(this.DllFile); + if (this.testingFile.Exists) + { + this.Manifest.Testing = true; + this.testingFile.Delete(); + } - /// - /// Unload this plugin. This is the same as dispose, but without the "disposed" connotations. This object should stay - /// in the plugin list until it has been actually disposed. - /// - /// Unload while reloading. - public void Unload(bool reloading = false) - { - // Allowed: Loaded, LoadError(we are cleaning this up while we're at it) - switch (this.State) - { - case PluginState.InProgress: - throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already working"); - case PluginState.Unloaded: - throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded"); - case PluginState.UnloadError: - throw new InvalidPluginOperationException($"Unable to unload {this.Name}, unload previously faulted, restart Dalamud"); + var pluginManager = Service.Get(); + this.IsBanned = pluginManager.IsManifestBanned(this.Manifest); + this.BanReason = pluginManager.GetBanReason(this.Manifest); + + this.SaveManifest(); } - try - { - this.State = PluginState.InProgress; - Log.Information($"Unloading {this.DllFile.Name}"); + /// + /// Gets the associated with this plugin. + /// + public DalamudPluginInterface DalamudInterface { get; private set; } + /// + /// Gets the path to the plugin DLL. + /// + public FileInfo DllFile { get; } + + /// + /// Gets the plugin manifest, if one exists. + /// + public LocalPluginManifest Manifest { get; private set; } + + /// + /// Gets or sets the current state of the plugin. + /// + public PluginState State { get; protected set; } = PluginState.Unloaded; + + /// + /// Gets the AssemblyName plugin, populated during . + /// + /// Plugin type. + public AssemblyName? AssemblyName { get; private set; } = null; + + /// + /// Gets the plugin name, directly from the plugin or if it is not loaded from the manifest. + /// + public string Name => this.instance?.Name ?? this.Manifest.Name ?? this.DllFile.Name; + + /// + /// Gets an optional reason, if the plugin is banned. + /// + public string BanReason { get; } + + /// + /// Gets a value indicating whether the plugin is loaded and running. + /// + public bool IsLoaded => this.State == PluginState.Loaded; + + /// + /// Gets a value indicating whether the plugin is disabled. + /// + public bool IsDisabled => this.Manifest.Disabled; + + /// + /// Gets a value indicating whether this plugin's API level is out of date. + /// + public bool IsOutdated => this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel; + + /// + /// Gets a value indicating whether the plugin is for testing use only. + /// + public bool IsTesting => this.Manifest.IsTestingExclusive || this.Manifest.Testing; + + /// + /// Gets a value indicating whether this plugin has been banned. + /// + public bool IsBanned { get; } + + /// + /// Gets a value indicating whether this plugin is dev plugin. + /// + public bool IsDev => this is LocalDevPlugin; + + /// + public void Dispose() + { this.instance?.Dispose(); this.instance = null; @@ -378,83 +215,247 @@ internal class LocalPlugin : IDisposable this.pluginType = null; this.pluginAssembly = null; - if (!reloading) + this.loader?.Dispose(); + } + + /// + /// Load this plugin. + /// + /// The reason why this plugin is being loaded. + /// Load while reloading. + public void Load(PluginLoadReason reason, bool reloading = false) + { + var startInfo = Service.Get(); + var configuration = Service.Get(); + var pluginManager = Service.Get(); + + // Allowed: Unloaded + switch (this.State) { - this.loader?.Dispose(); - this.loader = null; + case PluginState.InProgress: + throw new InvalidPluginOperationException($"Unable to load {this.Name}, already working"); + case PluginState.Loaded: + throw new InvalidPluginOperationException($"Unable to load {this.Name}, already loaded"); + case PluginState.LoadError: + throw new InvalidPluginOperationException($"Unable to load {this.Name}, load previously faulted, unload first"); + case PluginState.UnloadError: + throw new InvalidPluginOperationException($"Unable to load {this.Name}, unload previously faulted, restart Dalamud"); } - this.State = PluginState.Unloaded; - Log.Information($"Finished unloading {this.DllFile.Name}"); + if (pluginManager.IsManifestBanned(this.Manifest)) + throw new BannedPluginException($"Unable to load {this.Name}, banned"); + + if (this.Manifest.ApplicableVersion < startInfo.GameVersion) + throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version"); + + if (this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels) + throw new InvalidPluginOperationException($"Unable to load {this.Name}, incompatible API level"); + + if (this.Manifest.Disabled) + throw new InvalidPluginOperationException($"Unable to load {this.Name}, disabled"); + + this.State = PluginState.InProgress; + Log.Information($"Loading {this.DllFile.Name}"); + + if (this.DllFile.DirectoryName != null && File.Exists(Path.Combine(this.DllFile.DirectoryName, "Dalamud.dll"))) + { + Log.Error("==== IMPORTANT MESSAGE TO {0}, THE DEVELOPER OF {1} ====", this.Manifest.Author, this.Manifest.InternalName); + Log.Error("YOU ARE INCLUDING DALAMUD DEPENDENCIES IN YOUR BUILDS!!!"); + Log.Error("You may not be able to load your plugin. \"False\" needs to be set in your csproj."); + Log.Error("If you are using ILMerge, do not merge anything other than your direct dependencies."); + Log.Error("Do not merge FFXIVClientStructs.Generators.dll."); + Log.Error("Please refer to https://github.com/goatcorp/Dalamud/discussions/603 for more information."); + } + + try + { + this.loader ??= PluginLoader.CreateFromAssemblyFile(this.DllFile.FullName, this.SetupLoaderConfig); + + if (reloading) + { + this.loader.Reload(); + + // Reload the manifest in-case there were changes here too. + if (this.IsDev) + { + var manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile); + if (manifestFile.Exists) + { + this.Manifest = LocalPluginManifest.Load(manifestFile); + } + } + } + + // Load the assembly + this.pluginAssembly ??= this.loader.LoadDefaultAssembly(); + + this.AssemblyName = this.pluginAssembly.GetName(); + + // Find the plugin interface implementation. It is guaranteed to exist after checking in the ctor. + this.pluginType ??= this.pluginAssembly.GetTypes().First(type => type.IsAssignableTo(typeof(IDalamudPlugin))); + + // Check for any loaded plugins with the same assembly name + var assemblyName = this.pluginAssembly.GetName().Name; + foreach (var otherPlugin in pluginManager.InstalledPlugins) + { + // During hot-reloading, this plugin will be in the plugin list, and the instance will have been disposed + if (otherPlugin == this || otherPlugin.instance == null) + continue; + + var otherPluginAssemblyName = otherPlugin.instance.GetType().Assembly.GetName().Name; + if (otherPluginAssemblyName == assemblyName) + { + this.State = PluginState.Unloaded; + Log.Debug($"Duplicate assembly: {this.Name}"); + + throw new DuplicatePluginException(assemblyName); + } + } + + // Update the location for the Location and CodeBase patches + PluginManager.PluginLocations[this.pluginType.Assembly.FullName] = new(this.DllFile); + + this.DalamudInterface = new DalamudPluginInterface(this.pluginAssembly.GetName().Name!, this.DllFile, reason, this.IsDev); + + var ioc = Service.Get(); + this.instance = ioc.Create(this.pluginType, this.DalamudInterface) as IDalamudPlugin; + if (this.instance == null) + { + this.State = PluginState.LoadError; + this.DalamudInterface.Dispose(); + Log.Error($"Error while loading {this.Name}, failed to bind and call the plugin constructor"); + return; + } + + // In-case the manifest name was a placeholder. Can occur when no manifest was included. + if (this.instance.Name != this.Manifest.Name) + { + this.Manifest.Name = this.instance.Name; + this.Manifest.Save(this.manifestFile); + } + + this.State = PluginState.Loaded; + Log.Information($"Finished loading {this.DllFile.Name}"); + } + catch (Exception ex) + { + this.State = PluginState.LoadError; + Log.Error(ex, $"Error while loading {this.Name}"); + + throw; + } } - catch (Exception ex) + + /// + /// Unload this plugin. This is the same as dispose, but without the "disposed" connotations. This object should stay + /// in the plugin list until it has been actually disposed. + /// + /// Unload while reloading. + public void Unload(bool reloading = false) { - this.State = PluginState.UnloadError; - Log.Error(ex, $"Error while unloading {this.Name}"); + // Allowed: Loaded, LoadError(we are cleaning this up while we're at it) + switch (this.State) + { + case PluginState.InProgress: + throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already working"); + case PluginState.Unloaded: + throw new InvalidPluginOperationException($"Unable to unload {this.Name}, already unloaded"); + case PluginState.UnloadError: + throw new InvalidPluginOperationException($"Unable to unload {this.Name}, unload previously faulted, restart Dalamud"); + } - throw; + try + { + this.State = PluginState.InProgress; + Log.Information($"Unloading {this.DllFile.Name}"); + + this.instance?.Dispose(); + this.instance = null; + + this.DalamudInterface?.Dispose(); + this.DalamudInterface = null; + + this.pluginType = null; + this.pluginAssembly = null; + + if (!reloading) + { + this.loader?.Dispose(); + this.loader = null; + } + + this.State = PluginState.Unloaded; + Log.Information($"Finished unloading {this.DllFile.Name}"); + } + catch (Exception ex) + { + this.State = PluginState.UnloadError; + Log.Error(ex, $"Error while unloading {this.Name}"); + + throw; + } } - } - /// - /// Reload this plugin. - /// - public void Reload() - { - this.Unload(true); - this.Load(PluginLoadReason.Reload, true); - } - - /// - /// Revert a disable. Must be unloaded first, does not load. - /// - public void Enable() - { - // Allowed: Unloaded, UnloadError - switch (this.State) + /// + /// Reload this plugin. + /// + public void Reload() { - case PluginState.InProgress: - case PluginState.Loaded: - case PluginState.LoadError: - throw new InvalidPluginOperationException($"Unable to enable {this.Name}, still loaded"); + this.Unload(true); + this.Load(PluginLoadReason.Reload, true); } - if (!this.Manifest.Disabled) - throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled"); - - this.Manifest.Disabled = false; - this.SaveManifest(); - } - - /// - /// Disable this plugin, must be unloaded first. - /// - public void Disable() - { - // Allowed: Unloaded, UnloadError - switch (this.State) + /// + /// Revert a disable. Must be unloaded first, does not load. + /// + public void Enable() { - case PluginState.InProgress: - case PluginState.Loaded: - case PluginState.LoadError: - throw new InvalidPluginOperationException($"Unable to disable {this.Name}, still loaded"); + // Allowed: Unloaded, UnloadError + switch (this.State) + { + case PluginState.InProgress: + case PluginState.Loaded: + case PluginState.LoadError: + throw new InvalidPluginOperationException($"Unable to enable {this.Name}, still loaded"); + } + + if (!this.Manifest.Disabled) + throw new InvalidPluginOperationException($"Unable to enable {this.Name}, not disabled"); + + this.Manifest.Disabled = false; + this.SaveManifest(); } - if (this.Manifest.Disabled) - throw new InvalidPluginOperationException($"Unable to disable {this.Name}, already disabled"); + /// + /// Disable this plugin, must be unloaded first. + /// + public void Disable() + { + // Allowed: Unloaded, UnloadError + switch (this.State) + { + case PluginState.InProgress: + case PluginState.Loaded: + case PluginState.LoadError: + throw new InvalidPluginOperationException($"Unable to disable {this.Name}, still loaded"); + } - this.Manifest.Disabled = true; - this.SaveManifest(); + if (this.Manifest.Disabled) + throw new InvalidPluginOperationException($"Unable to disable {this.Name}, already disabled"); + + this.Manifest.Disabled = true; + this.SaveManifest(); + } + + private void SetupLoaderConfig(LoaderConfig config) + { + config.IsUnloadable = true; + config.LoadInMemory = true; + config.PreferSharedTypes = false; + config.SharedAssemblies.Add(typeof(Lumina.GameData).Assembly.GetName()); + config.SharedAssemblies.Add(typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName()); + } + + private void SaveManifest() => this.Manifest.Save(this.manifestFile); } - - private void SetupLoaderConfig(LoaderConfig config) - { - config.IsUnloadable = true; - config.LoadInMemory = true; - config.PreferSharedTypes = false; - config.SharedAssemblies.Add(typeof(Lumina.GameData).Assembly.GetName()); - config.SharedAssemblies.Add(typeof(Lumina.Excel.ExcelSheetImpl).Assembly.GetName()); - } - - private void SaveManifest() => this.Manifest.Save(this.manifestFile); } diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs index 22e951372..4eb06abaf 100644 --- a/Dalamud/Plugin/Internal/PluginManager.cs +++ b/Dalamud/Plugin/Internal/PluginManager.cs @@ -21,1181 +21,1182 @@ using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal; - -/// -/// Class responsible for loading and unloading plugins. -/// -internal partial class PluginManager : IDisposable +namespace Dalamud.Plugin.Internal { /// - /// The current Dalamud API level, used to handle breaking changes. Only plugins with this level will be loaded. + /// Class responsible for loading and unloading plugins. /// - public const int DalamudApiLevel = 4; - - private static readonly ModuleLog Log = new("PLUGINM"); - - private readonly DirectoryInfo pluginDirectory; - private readonly DirectoryInfo devPluginDirectory; - private readonly BannedPlugin[] bannedPlugins; - - /// - /// Initializes a new instance of the class. - /// - public PluginManager() + internal partial class PluginManager : IDisposable { - var startInfo = Service.Get(); - var configuration = Service.Get(); + /// + /// The current Dalamud API level, used to handle breaking changes. Only plugins with this level will be loaded. + /// + public const int DalamudApiLevel = 4; - this.pluginDirectory = new DirectoryInfo(startInfo.PluginDirectory); - this.devPluginDirectory = new DirectoryInfo(startInfo.DefaultPluginDirectory); + private static readonly ModuleLog Log = new("PLUGINM"); - if (!this.pluginDirectory.Exists) - this.pluginDirectory.Create(); + private readonly DirectoryInfo pluginDirectory; + private readonly DirectoryInfo devPluginDirectory; + private readonly BannedPlugin[] bannedPlugins; - if (!this.devPluginDirectory.Exists) - this.devPluginDirectory.Create(); - - this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || configuration.PluginSafeMode; - if (this.SafeMode) + /// + /// Initializes a new instance of the class. + /// + public PluginManager() { - configuration.PluginSafeMode = false; - configuration.Save(); - } + var startInfo = Service.Get(); + var configuration = Service.Get(); - this.PluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath) ?? string.Empty, "pluginConfigs")); + this.pluginDirectory = new DirectoryInfo(startInfo.PluginDirectory); + this.devPluginDirectory = new DirectoryInfo(startInfo.DefaultPluginDirectory); - var bannedPluginsJson = File.ReadAllText(Path.Combine(startInfo.AssetDirectory, "UIRes", "bannedplugin.json")); - this.bannedPlugins = JsonConvert.DeserializeObject(bannedPluginsJson) ?? Array.Empty(); + if (!this.pluginDirectory.Exists) + this.pluginDirectory.Create(); - this.ApplyPatches(); - } + if (!this.devPluginDirectory.Exists) + this.devPluginDirectory.Create(); - /// - /// An event that fires when the installed plugins have changed. - /// - public event Action OnInstalledPluginsChanged; - - /// - /// An event that fires when the available plugins have changed. - /// - public event Action OnAvailablePluginsChanged; - - /// - /// Gets a list of all loaded plugins. - /// - public ImmutableList InstalledPlugins { get; private set; } = ImmutableList.Create(); - - /// - /// Gets a list of all available plugins. - /// - public ImmutableList AvailablePlugins { get; private set; } = ImmutableList.Create(); - - /// - /// Gets a list of all plugins with an available update. - /// - public ImmutableList UpdatablePlugins { get; private set; } = ImmutableList.Create(); - - /// - /// Gets a list of all plugin repositories. The main repo should always be first. - /// - public List Repos { get; private set; } = new(); - - /// - /// Gets a value indicating whether plugins are not still loading from boot. - /// - public bool PluginsReady { get; private set; } = false; - - /// - /// Gets a value indicating whether all added repos are not in progress. - /// - public bool ReposReady => this.Repos.All(repo => repo.State != PluginRepositoryState.InProgress); - - /// - /// Gets a value indicating whether the plugin manager started in safe mode. - /// - public bool SafeMode { get; init; } - - /// - /// Gets the object used when initializing plugins. - /// - public PluginConfigurations PluginConfigs { get; } - - /// - public void Dispose() - { - foreach (var plugin in this.InstalledPlugins) - { - try + this.SafeMode = EnvironmentConfiguration.DalamudNoPlugins || configuration.PluginSafeMode; + if (this.SafeMode) { - plugin.Dispose(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error disposing {plugin.Name}"); + configuration.PluginSafeMode = false; + configuration.Save(); } + + this.PluginConfigs = new PluginConfigurations(Path.Combine(Path.GetDirectoryName(startInfo.ConfigurationPath) ?? string.Empty, "pluginConfigs")); + + var bannedPluginsJson = File.ReadAllText(Path.Combine(startInfo.AssetDirectory, "UIRes", "bannedplugin.json")); + this.bannedPlugins = JsonConvert.DeserializeObject(bannedPluginsJson) ?? Array.Empty(); + + this.ApplyPatches(); } - this.assemblyLocationMonoHook?.Dispose(); - this.assemblyCodeBaseMonoHook?.Dispose(); - } + /// + /// An event that fires when the installed plugins have changed. + /// + public event Action OnInstalledPluginsChanged; - /// - /// Set the list of repositories to use and downloads their contents. - /// Should be called when the Settings window has been updated or at instantiation. - /// - /// Whether the available plugins changed should be evented after. - /// A representing the asynchronous operation. - public async Task SetPluginReposFromConfigAsync(bool notify) - { - var configuration = Service.Get(); + /// + /// An event that fires when the available plugins have changed. + /// + public event Action OnAvailablePluginsChanged; - var repos = new List() { PluginRepository.MainRepo }; - repos.AddRange(configuration.ThirdRepoList - .Where(repo => repo.IsEnabled) - .Select(repo => new PluginRepository(repo.Url, repo.IsEnabled))); + /// + /// Gets a list of all loaded plugins. + /// + public ImmutableList InstalledPlugins { get; private set; } = ImmutableList.Create(); - this.Repos = repos; - await this.ReloadPluginMastersAsync(notify); - } + /// + /// Gets a list of all available plugins. + /// + public ImmutableList AvailablePlugins { get; private set; } = ImmutableList.Create(); - /// - /// Load all plugins, sorted by priority. Any plugins with no explicit definition file or a negative priority - /// are loaded asynchronously. - /// - /// - /// This should only be called during Dalamud startup. - /// - public void LoadAllPlugins() - { - if (this.SafeMode) + /// + /// Gets a list of all plugins with an available update. + /// + public ImmutableList UpdatablePlugins { get; private set; } = ImmutableList.Create(); + + /// + /// Gets a list of all plugin repositories. The main repo should always be first. + /// + public List Repos { get; private set; } = new(); + + /// + /// Gets a value indicating whether plugins are not still loading from boot. + /// + public bool PluginsReady { get; private set; } = false; + + /// + /// Gets a value indicating whether all added repos are not in progress. + /// + public bool ReposReady => this.Repos.All(repo => repo.State != PluginRepositoryState.InProgress); + + /// + /// Gets a value indicating whether the plugin manager started in safe mode. + /// + public bool SafeMode { get; init; } + + /// + /// Gets the object used when initializing plugins. + /// + public PluginConfigurations PluginConfigs { get; } + + /// + public void Dispose() { - Log.Information("PluginSafeMode was enabled, not loading any plugins."); - return; - } - - var configuration = Service.Get(); - - var pluginDefs = new List(); - var devPluginDefs = new List(); - - if (!this.pluginDirectory.Exists) - this.pluginDirectory.Create(); - - if (!this.devPluginDirectory.Exists) - this.devPluginDirectory.Create(); - - // Add installed plugins. These are expected to be in a specific format so we can look for exactly that. - foreach (var pluginDir in this.pluginDirectory.GetDirectories()) - { - foreach (var versionDir in pluginDir.GetDirectories()) - { - var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - - if (!manifestFile.Exists) - continue; - - var manifest = LocalPluginManifest.Load(manifestFile); - - pluginDefs.Add(new(dllFile, manifest, false)); - } - } - - // devPlugins are more freeform. Look for any dll and hope to get lucky. - var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); - - foreach (var setting in configuration.DevPluginLoadLocations) - { - if (!setting.IsEnabled) - continue; - - if (Directory.Exists(setting.Path)) - { - devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); - } - else if (File.Exists(setting.Path)) - { - devDllFiles.Add(new FileInfo(setting.Path)); - } - } - - foreach (var dllFile in devDllFiles) - { - // Manifests are not required for devPlugins. the Plugin type will handle any null manifests. - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - var manifest = manifestFile.Exists ? LocalPluginManifest.Load(manifestFile) : null; - devPluginDefs.Add(new(dllFile, manifest, true)); - } - - // Sort for load order - unloaded definitions have default priority of 0 - pluginDefs.Sort(PluginDef.Sorter); - devPluginDefs.Sort(PluginDef.Sorter); - - // Dev plugins should load first. - pluginDefs.InsertRange(0, devPluginDefs); - - void LoadPlugins(IEnumerable pluginDefs) - { - foreach (var pluginDef in pluginDefs) + foreach (var plugin in this.InstalledPlugins) { try { - this.LoadPlugin(pluginDef.DllFile, pluginDef.Manifest, PluginLoadReason.Boot, pluginDef.IsDev, isBoot: true); + plugin.Dispose(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error disposing {plugin.Name}"); + } + } + + this.assemblyLocationMonoHook?.Dispose(); + this.assemblyCodeBaseMonoHook?.Dispose(); + } + + /// + /// Set the list of repositories to use and downloads their contents. + /// Should be called when the Settings window has been updated or at instantiation. + /// + /// Whether the available plugins changed should be evented after. + /// A representing the asynchronous operation. + public async Task SetPluginReposFromConfigAsync(bool notify) + { + var configuration = Service.Get(); + + var repos = new List() { PluginRepository.MainRepo }; + repos.AddRange(configuration.ThirdRepoList + .Where(repo => repo.IsEnabled) + .Select(repo => new PluginRepository(repo.Url, repo.IsEnabled))); + + this.Repos = repos; + await this.ReloadPluginMastersAsync(notify); + } + + /// + /// Load all plugins, sorted by priority. Any plugins with no explicit definition file or a negative priority + /// are loaded asynchronously. + /// + /// + /// This should only be called during Dalamud startup. + /// + public void LoadAllPlugins() + { + if (this.SafeMode) + { + Log.Information("PluginSafeMode was enabled, not loading any plugins."); + return; + } + + var configuration = Service.Get(); + + var pluginDefs = new List(); + var devPluginDefs = new List(); + + if (!this.pluginDirectory.Exists) + this.pluginDirectory.Create(); + + if (!this.devPluginDirectory.Exists) + this.devPluginDirectory.Create(); + + // Add installed plugins. These are expected to be in a specific format so we can look for exactly that. + foreach (var pluginDir in this.pluginDirectory.GetDirectories()) + { + foreach (var versionDir in pluginDir.GetDirectories()) + { + var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + + if (!manifestFile.Exists) + continue; + + var manifest = LocalPluginManifest.Load(manifestFile); + + pluginDefs.Add(new(dllFile, manifest, false)); + } + } + + // devPlugins are more freeform. Look for any dll and hope to get lucky. + var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); + + foreach (var setting in configuration.DevPluginLoadLocations) + { + if (!setting.IsEnabled) + continue; + + if (Directory.Exists(setting.Path)) + { + devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); + } + else if (File.Exists(setting.Path)) + { + devDllFiles.Add(new FileInfo(setting.Path)); + } + } + + foreach (var dllFile in devDllFiles) + { + // Manifests are not required for devPlugins. the Plugin type will handle any null manifests. + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + var manifest = manifestFile.Exists ? LocalPluginManifest.Load(manifestFile) : null; + devPluginDefs.Add(new(dllFile, manifest, true)); + } + + // Sort for load order - unloaded definitions have default priority of 0 + pluginDefs.Sort(PluginDef.Sorter); + devPluginDefs.Sort(PluginDef.Sorter); + + // Dev plugins should load first. + pluginDefs.InsertRange(0, devPluginDefs); + + void LoadPlugins(IEnumerable pluginDefs) + { + foreach (var pluginDef in pluginDefs) + { + try + { + this.LoadPlugin(pluginDef.DllFile, pluginDef.Manifest, PluginLoadReason.Boot, pluginDef.IsDev, isBoot: true); + } + catch (InvalidPluginException) + { + // Not a plugin + } + catch (Exception) + { + Log.Error("During boot plugin load, an unexpected error occurred"); + } + } + } + + // Load sync plugins + var syncPlugins = pluginDefs.Where(def => def.Manifest?.LoadPriority > 0); + LoadPlugins(syncPlugins); + + var asyncPlugins = pluginDefs.Where(def => def.Manifest == null || def.Manifest.LoadPriority <= 0); + Task.Run(() => LoadPlugins(asyncPlugins)) + .ContinueWith(task => + { + this.PluginsReady = true; + this.NotifyInstalledPluginsChanged(); + }); + } + + /// + /// Reload all loaded plugins. + /// + public void ReloadAllPlugins() + { + var aggregate = new List(); + + foreach (var plugin in this.InstalledPlugins) + { + if (plugin.IsLoaded) + { + try + { + plugin.Reload(); + } + catch (Exception ex) + { + Log.Error(ex, "Error during reload all"); + + aggregate.Add(ex); + } + } + } + + if (aggregate.Any()) + { + throw new AggregateException(aggregate); + } + } + + /// + /// Reload the PluginMaster for each repo, filter, and event that the list has updated. + /// + /// Whether to notify that available plugins have changed afterwards. + /// A representing the asynchronous operation. + public async Task ReloadPluginMastersAsync(bool notify = true) + { + await Task.WhenAll(this.Repos.Select(repo => repo.ReloadPluginMasterAsync())); + + this.RefilterPluginMasters(notify); + } + + /// + /// Apply visibility and eligibility filters to the available plugins, then event that the list has updated. + /// + /// Whether to notify that available plugins have changed afterwards. + public void RefilterPluginMasters(bool notify = true) + { + this.AvailablePlugins = this.Repos + .SelectMany(repo => repo.PluginMaster) + .Where(this.IsManifestEligible) + .Where(this.IsManifestVisible) + .ToImmutableList(); + + if (notify) + { + this.NotifyAvailablePluginsChanged(); + } + } + + /// + /// Scan the devPlugins folder for new DLL files that are not already loaded into the manager. They are not loaded, + /// only shown as disabled in the installed plugins window. This is a modified version of LoadAllPlugins that works + /// a little differently. + /// + public void ScanDevPlugins() + { + if (this.SafeMode) + { + Log.Information("PluginSafeMode was enabled, not scanning any dev plugins."); + return; + } + + var configuration = Service.Get(); + + if (!this.devPluginDirectory.Exists) + this.devPluginDirectory.Create(); + + // devPlugins are more freeform. Look for any dll and hope to get lucky. + var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); + + foreach (var setting in configuration.DevPluginLoadLocations) + { + if (!setting.IsEnabled) + continue; + + if (Directory.Exists(setting.Path)) + { + devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); + } + else if (File.Exists(setting.Path)) + { + devDllFiles.Add(new FileInfo(setting.Path)); + } + } + + var listChanged = false; + + foreach (var dllFile in devDllFiles) + { + // This file is already known to us + if (this.InstalledPlugins.Any(lp => lp.DllFile.FullName == dllFile.FullName)) + continue; + + // Manifests are not required for devPlugins. the Plugin type will handle any null manifests. + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + var manifest = manifestFile.Exists ? LocalPluginManifest.Load(manifestFile) : null; + + try + { + // Add them to the list and let the user decide, nothing is auto-loaded. + this.LoadPlugin(dllFile, manifest, PluginLoadReason.Installer, isDev: true, doNotLoad: true); + listChanged = true; } catch (InvalidPluginException) { // Not a plugin } - catch (Exception) - { - Log.Error("During boot plugin load, an unexpected error occurred"); - } - } - } - - // Load sync plugins - var syncPlugins = pluginDefs.Where(def => def.Manifest?.LoadPriority > 0); - LoadPlugins(syncPlugins); - - var asyncPlugins = pluginDefs.Where(def => def.Manifest == null || def.Manifest.LoadPriority <= 0); - Task.Run(() => LoadPlugins(asyncPlugins)) - .ContinueWith(task => - { - this.PluginsReady = true; - this.NotifyInstalledPluginsChanged(); - }); - } - - /// - /// Reload all loaded plugins. - /// - public void ReloadAllPlugins() - { - var aggregate = new List(); - - foreach (var plugin in this.InstalledPlugins) - { - if (plugin.IsLoaded) - { - try - { - plugin.Reload(); - } catch (Exception ex) { - Log.Error(ex, "Error during reload all"); - - aggregate.Add(ex); + Log.Error(ex, $"During devPlugin scan, an unexpected error occurred"); } } + + if (listChanged) + this.NotifyInstalledPluginsChanged(); } - if (aggregate.Any()) + /// + /// Install a plugin from a repository and load it. + /// + /// The plugin definition. + /// If the testing version should be used. + /// The reason this plugin was loaded. + /// A representing the asynchronous operation. + public async Task InstallPluginAsync(RemotePluginManifest repoManifest, bool useTesting, PluginLoadReason reason) { - throw new AggregateException(aggregate); - } - } + Log.Debug($"Installing plugin {repoManifest.Name} (testing={useTesting})"); - /// - /// Reload the PluginMaster for each repo, filter, and event that the list has updated. - /// - /// Whether to notify that available plugins have changed afterwards. - /// A representing the asynchronous operation. - public async Task ReloadPluginMastersAsync(bool notify = true) - { - await Task.WhenAll(this.Repos.Select(repo => repo.ReloadPluginMasterAsync())); + var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall; + var version = useTesting ? repoManifest.TestingAssemblyVersion : repoManifest.AssemblyVersion; - this.RefilterPluginMasters(notify); - } + var response = await Util.HttpClient.GetAsync(downloadUrl); + response.EnsureSuccessStatusCode(); - /// - /// Apply visibility and eligibility filters to the available plugins, then event that the list has updated. - /// - /// Whether to notify that available plugins have changed afterwards. - public void RefilterPluginMasters(bool notify = true) - { - this.AvailablePlugins = this.Repos - .SelectMany(repo => repo.PluginMaster) - .Where(this.IsManifestEligible) - .Where(this.IsManifestVisible) - .ToImmutableList(); - - if (notify) - { - this.NotifyAvailablePluginsChanged(); - } - } - - /// - /// Scan the devPlugins folder for new DLL files that are not already loaded into the manager. They are not loaded, - /// only shown as disabled in the installed plugins window. This is a modified version of LoadAllPlugins that works - /// a little differently. - /// - public void ScanDevPlugins() - { - if (this.SafeMode) - { - Log.Information("PluginSafeMode was enabled, not scanning any dev plugins."); - return; - } - - var configuration = Service.Get(); - - if (!this.devPluginDirectory.Exists) - this.devPluginDirectory.Create(); - - // devPlugins are more freeform. Look for any dll and hope to get lucky. - var devDllFiles = this.devPluginDirectory.GetFiles("*.dll", SearchOption.AllDirectories).ToList(); - - foreach (var setting in configuration.DevPluginLoadLocations) - { - if (!setting.IsEnabled) - continue; - - if (Directory.Exists(setting.Path)) - { - devDllFiles.AddRange(new DirectoryInfo(setting.Path).GetFiles("*.dll", SearchOption.AllDirectories)); - } - else if (File.Exists(setting.Path)) - { - devDllFiles.Add(new FileInfo(setting.Path)); - } - } - - var listChanged = false; - - foreach (var dllFile in devDllFiles) - { - // This file is already known to us - if (this.InstalledPlugins.Any(lp => lp.DllFile.FullName == dllFile.FullName)) - continue; - - // Manifests are not required for devPlugins. the Plugin type will handle any null manifests. - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - var manifest = manifestFile.Exists ? LocalPluginManifest.Load(manifestFile) : null; + var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName, version.ToString())); try { - // Add them to the list and let the user decide, nothing is auto-loaded. - this.LoadPlugin(dllFile, manifest, PluginLoadReason.Installer, isDev: true, doNotLoad: true); - listChanged = true; + if (outputDir.Exists) + outputDir.Delete(true); + + outputDir.Create(); } - catch (InvalidPluginException) + catch { - // Not a plugin + // ignored, since the plugin may be loaded already } - catch (Exception ex) + + Log.Debug($"Extracting to {outputDir}"); + // This throws an error, even with overwrite=false + // ZipFile.ExtractToDirectory(tempZip.FullName, outputDir.FullName, false); + using (var archive = new ZipArchive(await response.Content.ReadAsStreamAsync())) { - Log.Error(ex, $"During devPlugin scan, an unexpected error occurred"); - } - } - - if (listChanged) - this.NotifyInstalledPluginsChanged(); - } - - /// - /// Install a plugin from a repository and load it. - /// - /// The plugin definition. - /// If the testing version should be used. - /// The reason this plugin was loaded. - /// A representing the asynchronous operation. - public async Task InstallPluginAsync(RemotePluginManifest repoManifest, bool useTesting, PluginLoadReason reason) - { - Log.Debug($"Installing plugin {repoManifest.Name} (testing={useTesting})"); - - var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall; - var version = useTesting ? repoManifest.TestingAssemblyVersion : repoManifest.AssemblyVersion; - - var response = await Util.HttpClient.GetAsync(downloadUrl); - response.EnsureSuccessStatusCode(); - - var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory.FullName, repoManifest.InternalName, version.ToString())); - - try - { - if (outputDir.Exists) - outputDir.Delete(true); - - outputDir.Create(); - } - catch - { - // ignored, since the plugin may be loaded already - } - - Log.Debug($"Extracting to {outputDir}"); - // This throws an error, even with overwrite=false - // ZipFile.ExtractToDirectory(tempZip.FullName, outputDir.FullName, false); - using (var archive = new ZipArchive(await response.Content.ReadAsStreamAsync())) - { - foreach (var zipFile in archive.Entries) - { - var outputFile = new FileInfo(Path.GetFullPath(Path.Combine(outputDir.FullName, zipFile.FullName))); - - if (!outputFile.FullName.StartsWith(outputDir.FullName, StringComparison.OrdinalIgnoreCase)) + foreach (var zipFile in archive.Entries) { - throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); - } + var outputFile = new FileInfo(Path.GetFullPath(Path.Combine(outputDir.FullName, zipFile.FullName))); - if (outputFile.Directory == null) - { - throw new IOException("Output directory invalid."); - } - - if (zipFile.Name.IsNullOrEmpty()) - { - // Assuming Empty for Directory - Log.Verbose($"ZipFile name is null or empty, treating as a directory: {outputFile.Directory.FullName}"); - Directory.CreateDirectory(outputFile.Directory.FullName); - continue; - } - - // Ensure directory is created - Directory.CreateDirectory(outputFile.Directory.FullName); - - try - { - zipFile.ExtractToFile(outputFile.FullName, true); - } - catch (Exception ex) - { - if (outputFile.Extension.EndsWith("dll")) + if (!outputFile.FullName.StartsWith(outputDir.FullName, StringComparison.OrdinalIgnoreCase)) { - throw new IOException($"Could not overwrite {zipFile.Name}: {ex.Message}"); + throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); } - Log.Error($"Could not overwrite {zipFile.Name}: {ex.Message}"); + if (outputFile.Directory == null) + { + throw new IOException("Output directory invalid."); + } + + if (zipFile.Name.IsNullOrEmpty()) + { + // Assuming Empty for Directory + Log.Verbose($"ZipFile name is null or empty, treating as a directory: {outputFile.Directory.FullName}"); + Directory.CreateDirectory(outputFile.Directory.FullName); + continue; + } + + // Ensure directory is created + Directory.CreateDirectory(outputFile.Directory.FullName); + + try + { + zipFile.ExtractToFile(outputFile.FullName, true); + } + catch (Exception ex) + { + if (outputFile.Extension.EndsWith("dll")) + { + throw new IOException($"Could not overwrite {zipFile.Name}: {ex.Message}"); + } + + Log.Error($"Could not overwrite {zipFile.Name}: {ex.Message}"); + } } } - } - var dllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest); - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + var dllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest); + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - // We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use. - File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(repoManifest, Formatting.Indented)); + // We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use. + File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(repoManifest, Formatting.Indented)); - // Reload as a local manifest, add some attributes, and save again. - var manifest = LocalPluginManifest.Load(manifestFile); + // Reload as a local manifest, add some attributes, and save again. + var manifest = LocalPluginManifest.Load(manifestFile); - if (useTesting) - { - manifest.Testing = true; - } - - if (repoManifest.SourceRepo.IsThirdParty) - { - // Only document the url if it came from a third party repo. - manifest.InstalledFromUrl = repoManifest.SourceRepo.PluginMasterUrl; - } - - manifest.Save(manifestFile); - - Log.Information($"Installed plugin {manifest.Name} (testing={useTesting})"); - - var plugin = this.LoadPlugin(dllFile, manifest, reason); - - this.NotifyInstalledPluginsChanged(); - return plugin; - } - - /// - /// Load a plugin. - /// - /// The associated with the main assembly of this plugin. - /// The already loaded definition, if available. - /// The reason this plugin was loaded. - /// If this plugin should support development features. - /// If this plugin is being loaded at boot. - /// Don't load the plugin, just don't do it. - /// The loaded plugin. - public LocalPlugin LoadPlugin(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false) - { - var name = manifest?.Name ?? dllFile.Name; - var loadPlugin = !doNotLoad; - - LocalPlugin plugin; - - if (isDev) - { - Log.Information($"Loading dev plugin {name}"); - var devPlugin = new LocalDevPlugin(dllFile, manifest); - loadPlugin &= !isBoot || devPlugin.StartOnBoot; - - // If we're not loading it, make sure it's disabled - if (!loadPlugin && !devPlugin.IsDisabled) - devPlugin.Disable(); - - plugin = devPlugin; - } - else - { - Log.Information($"Loading plugin {name}"); - plugin = new LocalPlugin(dllFile, manifest); - } - - if (loadPlugin) - { - try + if (useTesting) { - if (plugin.IsDisabled) - plugin.Enable(); - - plugin.Load(reason); + manifest.Testing = true; } - catch (InvalidPluginException) + + if (repoManifest.SourceRepo.IsThirdParty) { - PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); - throw; + // Only document the url if it came from a third party repo. + manifest.InstalledFromUrl = repoManifest.SourceRepo.PluginMasterUrl; } - catch (BannedPluginException) + + manifest.Save(manifestFile); + + Log.Information($"Installed plugin {manifest.Name} (testing={useTesting})"); + + var plugin = this.LoadPlugin(dllFile, manifest, reason); + + this.NotifyInstalledPluginsChanged(); + return plugin; + } + + /// + /// Load a plugin. + /// + /// The associated with the main assembly of this plugin. + /// The already loaded definition, if available. + /// The reason this plugin was loaded. + /// If this plugin should support development features. + /// If this plugin is being loaded at boot. + /// Don't load the plugin, just don't do it. + /// The loaded plugin. + public LocalPlugin LoadPlugin(FileInfo dllFile, LocalPluginManifest manifest, PluginLoadReason reason, bool isDev = false, bool isBoot = false, bool doNotLoad = false) + { + var name = manifest?.Name ?? dllFile.Name; + var loadPlugin = !doNotLoad; + + LocalPlugin plugin; + + if (isDev) { - // Out of date plugins get added so they can be updated. - Log.Information($"Plugin was banned, adding anyways: {dllFile.Name}"); + Log.Information($"Loading dev plugin {name}"); + var devPlugin = new LocalDevPlugin(dllFile, manifest); + loadPlugin &= !isBoot || devPlugin.StartOnBoot; + + // If we're not loading it, make sure it's disabled + if (!loadPlugin && !devPlugin.IsDisabled) + devPlugin.Disable(); + + plugin = devPlugin; } - catch (Exception ex) + else { - if (plugin.IsDev) + Log.Information($"Loading plugin {name}"); + plugin = new LocalPlugin(dllFile, manifest); + } + + if (loadPlugin) + { + try { - // Dev plugins always get added to the list so they can be fiddled with in the UI - Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}"); - plugin.Disable(); // Disable here, otherwise you can't enable+load later + if (plugin.IsDisabled) + plugin.Enable(); + + plugin.Load(reason); } - else if (plugin.IsOutdated) - { - // Out of date plugins get added so they can be updated. - Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}"); - // plugin.Disable(); // Don't disable, or it gets deleted next boot. - } - else + catch (InvalidPluginException) { PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); throw; } - } - } - - this.InstalledPlugins = this.InstalledPlugins.Add(plugin); - return plugin; - } - - /// - /// Remove a plugin. - /// - /// Plugin to remove. - public void RemovePlugin(LocalPlugin plugin) - { - if (plugin.State != PluginState.Unloaded) - throw new InvalidPluginOperationException($"Unable to remove {plugin.Name}, not unloaded"); - - this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); - PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); - - this.NotifyInstalledPluginsChanged(); - this.NotifyAvailablePluginsChanged(); - } - - /// - /// Cleanup disabled plugins. Does not target devPlugins. - /// - public void CleanupPlugins() - { - var configuration = Service.Get(); - var startInfo = Service.Get(); - - foreach (var pluginDir in this.pluginDirectory.GetDirectories()) - { - try - { - var versionDirs = pluginDir.GetDirectories(); - - versionDirs = versionDirs - .OrderByDescending(dir => - { - var isVersion = Version.TryParse(dir.Name, out var version); - - if (!isVersion) - { - Log.Debug($"Not a version, cleaning up {dir.FullName}"); - dir.Delete(true); - } - - return version; - }) - .Where(version => version != null) - .ToArray(); - - if (versionDirs.Length == 0) + catch (BannedPluginException) { - Log.Information($"No versions: cleaning up {pluginDir.FullName}"); - pluginDir.Delete(true); - continue; + // Out of date plugins get added so they can be updated. + Log.Information($"Plugin was banned, adding anyways: {dllFile.Name}"); } - else + catch (Exception ex) { - foreach (var versionDir in versionDirs) + if (plugin.IsDev) { - try - { - var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); - if (!dllFile.Exists) - { - Log.Information($"Missing dll: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - - var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); - if (!manifestFile.Exists) - { - Log.Information($"Missing manifest: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - - var manifest = LocalPluginManifest.Load(manifestFile); - if (manifest.Disabled) - { - Log.Information($"Disabled: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - - if (manifest.DalamudApiLevel < DalamudApiLevel - 1 && !configuration.LoadAllApiLevels) - { - Log.Information($"Lower API: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - - if (manifest.ApplicableVersion < startInfo.GameVersion) - { - Log.Information($"Inapplicable version: cleaning up {versionDir.FullName}"); - versionDir.Delete(true); - continue; - } - } - catch (Exception ex) - { - Log.Error(ex, $"Could not clean up {versionDir.FullName}"); - } + // Dev plugins always get added to the list so they can be fiddled with in the UI + Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}"); + plugin.Disable(); // Disable here, otherwise you can't enable+load later + } + else if (plugin.IsOutdated) + { + // Out of date plugins get added so they can be updated. + Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}"); + // plugin.Disable(); // Don't disable, or it gets deleted next boot. + } + else + { + PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); + throw; } } } - catch (Exception ex) - { - Log.Error(ex, $"Could not clean up {pluginDir.FullName}"); - } - } - } - /// - /// Update all non-dev plugins. - /// - /// Perform a dry run, don't install anything. - /// Success or failure and a list of updated plugin metadata. - public async Task> UpdatePluginsAsync(bool dryRun = false) - { - Log.Information("Starting plugin update"); - - var updatedList = new List(); - - // Prevent collection was modified errors - foreach (var plugin in this.UpdatablePlugins) - { - // Can't update that! - if (plugin.InstalledPlugin.IsDev) - continue; - - var result = await this.UpdateSinglePluginAsync(plugin, false, dryRun); - if (result != null) - updatedList.Add(result); + this.InstalledPlugins = this.InstalledPlugins.Add(plugin); + return plugin; } - this.NotifyInstalledPluginsChanged(); - - Log.Information("Plugin update OK."); - - return updatedList; - } - - /// - /// Update a single plugin, provided a valid . - /// - /// The available plugin update. - /// Whether to notify that installed plugins have changed afterwards. - /// Whether or not to actually perform the update, or just indicate success. - /// The status of the update. - public async Task UpdateSinglePluginAsync(AvailablePluginUpdate metadata, bool notify, bool dryRun) - { - var plugin = metadata.InstalledPlugin; - - var updateStatus = new PluginUpdateStatus + /// + /// Remove a plugin. + /// + /// Plugin to remove. + public void RemovePlugin(LocalPlugin plugin) { - InternalName = plugin.Manifest.InternalName, - Name = plugin.Manifest.Name, - Version = metadata.UseTesting - ? metadata.UpdateManifest.TestingAssemblyVersion - : metadata.UpdateManifest.AssemblyVersion, - }; + if (plugin.State != PluginState.Unloaded) + throw new InvalidPluginOperationException($"Unable to remove {plugin.Name}, not unloaded"); - updateStatus.WasUpdated = true; + this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); + PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty); - if (!dryRun) + this.NotifyInstalledPluginsChanged(); + this.NotifyAvailablePluginsChanged(); + } + + /// + /// Cleanup disabled plugins. Does not target devPlugins. + /// + public void CleanupPlugins() { - // Unload if loaded - if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError) + var configuration = Service.Get(); + var startInfo = Service.Get(); + + foreach (var pluginDir in this.pluginDirectory.GetDirectories()) { try { - plugin.Unload(); - } - catch (Exception ex) - { - Log.Error(ex, "Error during unload (update)"); - updateStatus.WasUpdated = false; - return updateStatus; - } - } + var versionDirs = pluginDir.GetDirectories(); - if (plugin.IsDev) - { - try - { - plugin.DllFile.Delete(); - this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); - } - catch (Exception ex) - { - Log.Error(ex, "Error during delete (update)"); - updateStatus.WasUpdated = false; - return updateStatus; - } - } - else - { - try - { - plugin.Disable(); - this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); - } - catch (Exception ex) - { - Log.Error(ex, "Error during disable (update)"); - updateStatus.WasUpdated = false; - return updateStatus; - } - } + versionDirs = versionDirs + .OrderByDescending(dir => + { + var isVersion = Version.TryParse(dir.Name, out var version); - try - { - await this.InstallPluginAsync(metadata.UpdateManifest, metadata.UseTesting, PluginLoadReason.Update); - } - catch (Exception ex) - { - Log.Error(ex, "Error during install (update)"); - updateStatus.WasUpdated = false; - return updateStatus; + if (!isVersion) + { + Log.Debug($"Not a version, cleaning up {dir.FullName}"); + dir.Delete(true); + } + + return version; + }) + .Where(version => version != null) + .ToArray(); + + if (versionDirs.Length == 0) + { + Log.Information($"No versions: cleaning up {pluginDir.FullName}"); + pluginDir.Delete(true); + continue; + } + else + { + foreach (var versionDir in versionDirs) + { + try + { + var dllFile = new FileInfo(Path.Combine(versionDir.FullName, $"{pluginDir.Name}.dll")); + if (!dllFile.Exists) + { + Log.Information($"Missing dll: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + + var manifestFile = LocalPluginManifest.GetManifestFile(dllFile); + if (!manifestFile.Exists) + { + Log.Information($"Missing manifest: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + + var manifest = LocalPluginManifest.Load(manifestFile); + if (manifest.Disabled) + { + Log.Information($"Disabled: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + + if (manifest.DalamudApiLevel < DalamudApiLevel - 1 && !configuration.LoadAllApiLevels) + { + Log.Information($"Lower API: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + + if (manifest.ApplicableVersion < startInfo.GameVersion) + { + Log.Information($"Inapplicable version: cleaning up {versionDir.FullName}"); + versionDir.Delete(true); + continue; + } + } + catch (Exception ex) + { + Log.Error(ex, $"Could not clean up {versionDir.FullName}"); + } + } + } + } + catch (Exception ex) + { + Log.Error(ex, $"Could not clean up {pluginDir.FullName}"); + } } } - if (notify && updateStatus.WasUpdated) + /// + /// Update all non-dev plugins. + /// + /// Perform a dry run, don't install anything. + /// Success or failure and a list of updated plugin metadata. + public async Task> UpdatePluginsAsync(bool dryRun = false) + { + Log.Information("Starting plugin update"); + + var updatedList = new List(); + + // Prevent collection was modified errors + foreach (var plugin in this.UpdatablePlugins) + { + // Can't update that! + if (plugin.InstalledPlugin.IsDev) + continue; + + var result = await this.UpdateSinglePluginAsync(plugin, false, dryRun); + if (result != null) + updatedList.Add(result); + } + this.NotifyInstalledPluginsChanged(); - return updateStatus; - } + Log.Information("Plugin update OK."); - /// - /// Unload the plugin, delete its configuration, and reload it. - /// - /// The plugin. - /// Throws if the plugin is still loading/unloading. - public void DeleteConfiguration(LocalPlugin plugin) - { - if (plugin.State == PluginState.InProgress) - throw new Exception("Cannot delete configuration for a loading/unloading plugin"); + return updatedList; + } - if (plugin.IsLoaded) - plugin.Unload(); - - // Let's wait so any handles on files in plugin configurations can be closed - Thread.Sleep(500); - - this.PluginConfigs.Delete(plugin.Name); - - Thread.Sleep(500); - - // Let's indicate "installer" here since this is supposed to be a fresh install - plugin.Load(PluginLoadReason.Installer); - } - - /// - /// Print to chat any plugin updates and whether they were successful. - /// - /// The list of updated plugin metadata. - /// The header text to send to chat prior to any update info. - public void PrintUpdatedPlugins(List updateMetadata, string header) - { - var chatGui = Service.Get(); - - if (updateMetadata != null && updateMetadata.Count > 0) + /// + /// Update a single plugin, provided a valid . + /// + /// The available plugin update. + /// Whether to notify that installed plugins have changed afterwards. + /// Whether or not to actually perform the update, or just indicate success. + /// The status of the update. + public async Task UpdateSinglePluginAsync(AvailablePluginUpdate metadata, bool notify, bool dryRun) { - chatGui.Print(header); + var plugin = metadata.InstalledPlugin; - foreach (var metadata in updateMetadata) + var updateStatus = new PluginUpdateStatus { - if (metadata.WasUpdated) + InternalName = plugin.Manifest.InternalName, + Name = plugin.Manifest.Name, + Version = metadata.UseTesting + ? metadata.UpdateManifest.TestingAssemblyVersion + : metadata.UpdateManifest.AssemblyVersion, + }; + + updateStatus.WasUpdated = true; + + if (!dryRun) + { + // Unload if loaded + if (plugin.State == PluginState.Loaded || plugin.State == PluginState.LoadError) { - chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version)); + try + { + plugin.Unload(); + } + catch (Exception ex) + { + Log.Error(ex, "Error during unload (update)"); + updateStatus.WasUpdated = false; + return updateStatus; + } + } + + if (plugin.IsDev) + { + try + { + plugin.DllFile.Delete(); + this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); + } + catch (Exception ex) + { + Log.Error(ex, "Error during delete (update)"); + updateStatus.WasUpdated = false; + return updateStatus; + } } else { - chatGui.PrintChat(new XivChatEntry + try { - Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version), - Type = XivChatType.Urgent, - }); + plugin.Disable(); + this.InstalledPlugins = this.InstalledPlugins.Remove(plugin); + } + catch (Exception ex) + { + Log.Error(ex, "Error during disable (update)"); + updateStatus.WasUpdated = false; + return updateStatus; + } + } + + try + { + await this.InstallPluginAsync(metadata.UpdateManifest, metadata.UseTesting, PluginLoadReason.Update); + } + catch (Exception ex) + { + Log.Error(ex, "Error during install (update)"); + updateStatus.WasUpdated = false; + return updateStatus; + } + } + + if (notify && updateStatus.WasUpdated) + this.NotifyInstalledPluginsChanged(); + + return updateStatus; + } + + /// + /// Unload the plugin, delete its configuration, and reload it. + /// + /// The plugin. + /// Throws if the plugin is still loading/unloading. + public void DeleteConfiguration(LocalPlugin plugin) + { + if (plugin.State == PluginState.InProgress) + throw new Exception("Cannot delete configuration for a loading/unloading plugin"); + + if (plugin.IsLoaded) + plugin.Unload(); + + // Let's wait so any handles on files in plugin configurations can be closed + Thread.Sleep(500); + + this.PluginConfigs.Delete(plugin.Name); + + Thread.Sleep(500); + + // Let's indicate "installer" here since this is supposed to be a fresh install + plugin.Load(PluginLoadReason.Installer); + } + + /// + /// Print to chat any plugin updates and whether they were successful. + /// + /// The list of updated plugin metadata. + /// The header text to send to chat prior to any update info. + public void PrintUpdatedPlugins(List updateMetadata, string header) + { + var chatGui = Service.Get(); + + if (updateMetadata != null && updateMetadata.Count > 0) + { + chatGui.Print(header); + + foreach (var metadata in updateMetadata) + { + if (metadata.WasUpdated) + { + chatGui.Print(Locs.DalamudPluginUpdateSuccessful(metadata.Name, metadata.Version)); + } + else + { + chatGui.PrintChat(new XivChatEntry + { + Message = Locs.DalamudPluginUpdateFailed(metadata.Name, metadata.Version), + Type = XivChatType.Urgent, + }); + } } } } - } - /// - /// For a given manifest, determine if the testing version should be used over the normal version. - /// The higher of the two versions is calculated after checking other settings. - /// - /// Manifest to check. - /// A value indicating whether testing should be used. - public bool UseTesting(PluginManifest manifest) - { - var configuration = Service.Get(); + /// + /// For a given manifest, determine if the testing version should be used over the normal version. + /// The higher of the two versions is calculated after checking other settings. + /// + /// Manifest to check. + /// A value indicating whether testing should be used. + public bool UseTesting(PluginManifest manifest) + { + var configuration = Service.Get(); - if (!configuration.DoPluginTest) - return false; + if (!configuration.DoPluginTest) + return false; + + if (manifest.IsTestingExclusive) + return true; + + var av = manifest.AssemblyVersion; + var tv = manifest.TestingAssemblyVersion; + var hasAv = av != null; + var hasTv = tv != null; + + if (hasAv && hasTv) + { + return tv > av; + } + else + { + return hasTv; + } + } + + /// + /// Gets a value indicating whether the given repo manifest should be visible to the user. + /// + /// Repo manifest. + /// If the manifest is visible. + public bool IsManifestVisible(RemotePluginManifest manifest) + { + var configuration = Service.Get(); + + // Hidden by user + if (configuration.HiddenPluginInternalName.Contains(manifest.InternalName)) + return false; + + // Hidden by manifest + if (manifest.IsHide) + return false; - if (manifest.IsTestingExclusive) return true; - - var av = manifest.AssemblyVersion; - var tv = manifest.TestingAssemblyVersion; - var hasAv = av != null; - var hasTv = tv != null; - - if (hasAv && hasTv) - { - return tv > av; } - else + + /// + /// Gets a value indicating whether the given manifest is eligible for ANYTHING. These are hard + /// checks that should not allow installation or loading. + /// + /// Plugin manifest. + /// If the manifest is eligible. + public bool IsManifestEligible(PluginManifest manifest) { - return hasTv; + var configuration = Service.Get(); + var startInfo = Service.Get(); + + // Testing exclusive + if (manifest.IsTestingExclusive && !configuration.DoPluginTest) + return false; + + // Applicable version + if (manifest.ApplicableVersion < startInfo.GameVersion) + return false; + + // API level + if (manifest.DalamudApiLevel < DalamudApiLevel && !configuration.LoadAllApiLevels) + return false; + + // Banned + if (this.IsManifestBanned(manifest)) + return false; + + return true; } - } - /// - /// Gets a value indicating whether the given repo manifest should be visible to the user. - /// - /// Repo manifest. - /// If the manifest is visible. - public bool IsManifestVisible(RemotePluginManifest manifest) - { - var configuration = Service.Get(); - - // Hidden by user - if (configuration.HiddenPluginInternalName.Contains(manifest.InternalName)) - return false; - - // Hidden by manifest - if (manifest.IsHide) - return false; - - return true; - } - - /// - /// Gets a value indicating whether the given manifest is eligible for ANYTHING. These are hard - /// checks that should not allow installation or loading. - /// - /// Plugin manifest. - /// If the manifest is eligible. - public bool IsManifestEligible(PluginManifest manifest) - { - var configuration = Service.Get(); - var startInfo = Service.Get(); - - // Testing exclusive - if (manifest.IsTestingExclusive && !configuration.DoPluginTest) - return false; - - // Applicable version - if (manifest.ApplicableVersion < startInfo.GameVersion) - return false; - - // API level - if (manifest.DalamudApiLevel < DalamudApiLevel && !configuration.LoadAllApiLevels) - return false; - - // Banned - if (this.IsManifestBanned(manifest)) - return false; - - return true; - } - - /// - /// Determine if a plugin has been banned by inspecting the manifest. - /// - /// Manifest to inspect. - /// A value indicating whether the plugin/manifest has been banned. - public bool IsManifestBanned(PluginManifest manifest) - { - var configuration = Service.Get(); - return configuration.LoadBannedPlugins || this.bannedPlugins.Any(ban => ban.Name == manifest.InternalName && ban.AssemblyVersion >= manifest.AssemblyVersion); - } - - /// - /// Get the reason of a banned plugin by inspecting the manifest. - /// - /// Manifest to inspect. - /// The reason of the ban, if any. - public string GetBanReason(PluginManifest manifest) - { - return this.bannedPlugins.FirstOrDefault(ban => ban.Name == manifest.InternalName).Reason; - } - - private void DetectAvailablePluginUpdates() - { - var updatablePlugins = new List(); - - foreach (var plugin in this.InstalledPlugins) + /// + /// Determine if a plugin has been banned by inspecting the manifest. + /// + /// Manifest to inspect. + /// A value indicating whether the plugin/manifest has been banned. + public bool IsManifestBanned(PluginManifest manifest) { - var installedVersion = plugin.IsTesting - ? plugin.Manifest.TestingAssemblyVersion - : plugin.Manifest.AssemblyVersion; + var configuration = Service.Get(); + return configuration.LoadBannedPlugins || this.bannedPlugins.Any(ban => ban.Name == manifest.InternalName && ban.AssemblyVersion >= manifest.AssemblyVersion); + } - var updates = this.AvailablePlugins - .Where(remoteManifest => plugin.Manifest.InternalName == remoteManifest.InternalName) - .Select(remoteManifest => - { - var useTesting = this.UseTesting(remoteManifest); - var candidateVersion = useTesting - ? remoteManifest.TestingAssemblyVersion - : remoteManifest.AssemblyVersion; - var isUpdate = candidateVersion > installedVersion; + /// + /// Get the reason of a banned plugin by inspecting the manifest. + /// + /// Manifest to inspect. + /// The reason of the ban, if any. + public string GetBanReason(PluginManifest manifest) + { + return this.bannedPlugins.FirstOrDefault(ban => ban.Name == manifest.InternalName).Reason; + } - return (isUpdate, useTesting, candidateVersion, remoteManifest); - }) - .Where(tpl => tpl.isUpdate) - .ToList(); + private void DetectAvailablePluginUpdates() + { + var updatablePlugins = new List(); - if (updates.Count > 0) + foreach (var plugin in this.InstalledPlugins) { - var update = updates.Aggregate((t1, t2) => t1.candidateVersion > t2.candidateVersion ? t1 : t2); - updatablePlugins.Add(new(plugin, update.remoteManifest, update.useTesting)); - } - } + var installedVersion = plugin.IsTesting + ? plugin.Manifest.TestingAssemblyVersion + : plugin.Manifest.AssemblyVersion; - this.UpdatablePlugins = updatablePlugins.ToImmutableList(); - } + var updates = this.AvailablePlugins + .Where(remoteManifest => plugin.Manifest.InternalName == remoteManifest.InternalName) + .Select(remoteManifest => + { + var useTesting = this.UseTesting(remoteManifest); + var candidateVersion = useTesting + ? remoteManifest.TestingAssemblyVersion + : remoteManifest.AssemblyVersion; + var isUpdate = candidateVersion > installedVersion; - private void NotifyAvailablePluginsChanged() - { - this.DetectAvailablePluginUpdates(); + return (isUpdate, useTesting, candidateVersion, remoteManifest); + }) + .Where(tpl => tpl.isUpdate) + .ToList(); - try - { - this.OnAvailablePluginsChanged?.Invoke(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error notifying {nameof(this.OnAvailablePluginsChanged)}"); - } - } - - private void NotifyInstalledPluginsChanged() - { - this.DetectAvailablePluginUpdates(); - - try - { - this.OnInstalledPluginsChanged?.Invoke(); - } - catch (Exception ex) - { - Log.Error(ex, $"Error notifying {nameof(this.OnInstalledPluginsChanged)}"); - } - } - - private struct BannedPlugin - { - [JsonProperty] - public string Name { get; private set; } - - [JsonProperty] - public Version AssemblyVersion { get; private set; } - - [JsonProperty] - public string Reason { get; private set; } - } - - private struct PluginDef - { - public PluginDef(FileInfo dllFile, LocalPluginManifest manifest, bool isDev) - { - this.DllFile = dllFile; - this.Manifest = manifest; - this.IsDev = isDev; - } - - public FileInfo DllFile { get; init; } - - public LocalPluginManifest Manifest { get; init; } - - public bool IsDev { get; init; } - - public static int Sorter(PluginDef def1, PluginDef def2) - { - var prio1 = def1.Manifest?.LoadPriority ?? 0; - var prio2 = def2.Manifest?.LoadPriority ?? 0; - return prio2.CompareTo(prio1); - } - } - - private static class Locs - { - public static string DalamudPluginUpdateSuccessful(string name, Version version) => Loc.Localize("DalamudPluginUpdateSuccessful", " 》 {0} updated to v{1}.").Format(name, version); - - public static string DalamudPluginUpdateFailed(string name, Version version) => Loc.Localize("DalamudPluginUpdateFailed", " 》 {0} update to v{1} failed.").Format(name, version); - } -} - -/// -/// Class responsible for loading and unloading plugins. -/// This contains the assembly patching functionality to resolve assembly locations. -/// -internal partial class PluginManager -{ - /// - /// A mapping of plugin assembly name to patch data. Used to fill in missing data due to loading - /// plugins via byte[]. - /// - internal static readonly Dictionary PluginLocations = new(); - - private MonoMod.RuntimeDetour.Hook assemblyLocationMonoHook; - private MonoMod.RuntimeDetour.Hook assemblyCodeBaseMonoHook; - - /// - /// Patch method for internal class RuntimeAssembly.Location, also known as Assembly.Location. - /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// The plugin location, or the result from the original method. - private static string AssemblyLocationPatch(Func orig, Assembly self) - { - var result = orig(self); - - if (string.IsNullOrEmpty(result)) - { - foreach (var assemblyName in GetStackFrameAssemblyNames()) - { - if (PluginLocations.TryGetValue(assemblyName, out var data)) + if (updates.Count > 0) { - result = data.Location; - break; + var update = updates.Aggregate((t1, t2) => t1.candidateVersion > t2.candidateVersion ? t1 : t2); + updatablePlugins.Add(new(plugin, update.remoteManifest, update.useTesting)); } } + + this.UpdatablePlugins = updatablePlugins.ToImmutableList(); } - result ??= string.Empty; - - Log.Verbose($"Assembly.Location // {self.FullName} // {result}"); - return result; - } - - /// - /// Patch method for internal class RuntimeAssembly.CodeBase, also known as Assembly.CodeBase. - /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. - /// It should never be called manually. - /// - /// A delegate that acts as the original method. - /// The equivalent of `this`. - /// The plugin code base, or the result from the original method. - private static string AssemblyCodeBasePatch(Func orig, Assembly self) - { - var result = orig(self); - - if (string.IsNullOrEmpty(result)) + private void NotifyAvailablePluginsChanged() { - foreach (var assemblyName in GetStackFrameAssemblyNames()) + this.DetectAvailablePluginUpdates(); + + try { - if (PluginLocations.TryGetValue(assemblyName, out var data)) - { - result = data.CodeBase; - break; - } + this.OnAvailablePluginsChanged?.Invoke(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error notifying {nameof(this.OnAvailablePluginsChanged)}"); } } - result ??= string.Empty; - - Log.Verbose($"Assembly.CodeBase // {self.FullName} // {result}"); - return result; - } - - private static IEnumerable GetStackFrameAssemblyNames() - { - var stackTrace = new StackTrace(); - var stackFrames = stackTrace.GetFrames(); - - foreach (var stackFrame in stackFrames) + private void NotifyInstalledPluginsChanged() { - var methodBase = stackFrame.GetMethod(); - if (methodBase == null) - continue; + this.DetectAvailablePluginUpdates(); - yield return methodBase.Module.Assembly.FullName; + try + { + this.OnInstalledPluginsChanged?.Invoke(); + } + catch (Exception ex) + { + Log.Error(ex, $"Error notifying {nameof(this.OnInstalledPluginsChanged)}"); + } + } + + private struct BannedPlugin + { + [JsonProperty] + public string Name { get; private set; } + + [JsonProperty] + public Version AssemblyVersion { get; private set; } + + [JsonProperty] + public string Reason { get; private set; } + } + + private struct PluginDef + { + public PluginDef(FileInfo dllFile, LocalPluginManifest manifest, bool isDev) + { + this.DllFile = dllFile; + this.Manifest = manifest; + this.IsDev = isDev; + } + + public FileInfo DllFile { get; init; } + + public LocalPluginManifest Manifest { get; init; } + + public bool IsDev { get; init; } + + public static int Sorter(PluginDef def1, PluginDef def2) + { + var prio1 = def1.Manifest?.LoadPriority ?? 0; + var prio2 = def2.Manifest?.LoadPriority ?? 0; + return prio2.CompareTo(prio1); + } + } + + private static class Locs + { + public static string DalamudPluginUpdateSuccessful(string name, Version version) => Loc.Localize("DalamudPluginUpdateSuccessful", " 》 {0} updated to v{1}.").Format(name, version); + + public static string DalamudPluginUpdateFailed(string name, Version version) => Loc.Localize("DalamudPluginUpdateFailed", " 》 {0} update to v{1} failed.").Format(name, version); } } - private void ApplyPatches() + /// + /// Class responsible for loading and unloading plugins. + /// This contains the assembly patching functionality to resolve assembly locations. + /// + internal partial class PluginManager { - var targetType = typeof(PluginManager).Assembly.GetType(); + /// + /// A mapping of plugin assembly name to patch data. Used to fill in missing data due to loading + /// plugins via byte[]. + /// + internal static readonly Dictionary PluginLocations = new(); - var locationTarget = targetType.GetProperty(nameof(Assembly.Location)).GetGetMethod(); - var locationPatch = typeof(PluginManager).GetMethod(nameof(PluginManager.AssemblyLocationPatch), BindingFlags.NonPublic | BindingFlags.Static); - this.assemblyLocationMonoHook = new MonoMod.RuntimeDetour.Hook(locationTarget, locationPatch); + private MonoMod.RuntimeDetour.Hook assemblyLocationMonoHook; + private MonoMod.RuntimeDetour.Hook assemblyCodeBaseMonoHook; + + /// + /// Patch method for internal class RuntimeAssembly.Location, also known as Assembly.Location. + /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. + /// It should never be called manually. + /// + /// A delegate that acts as the original method. + /// The equivalent of `this`. + /// The plugin location, or the result from the original method. + private static string AssemblyLocationPatch(Func orig, Assembly self) + { + var result = orig(self); + + if (string.IsNullOrEmpty(result)) + { + foreach (var assemblyName in GetStackFrameAssemblyNames()) + { + if (PluginLocations.TryGetValue(assemblyName, out var data)) + { + result = data.Location; + break; + } + } + } + + result ??= string.Empty; + + Log.Verbose($"Assembly.Location // {self.FullName} // {result}"); + return result; + } + + /// + /// Patch method for internal class RuntimeAssembly.CodeBase, also known as Assembly.CodeBase. + /// This patch facilitates resolving the assembly location for plugins that are loaded via byte[]. + /// It should never be called manually. + /// + /// A delegate that acts as the original method. + /// The equivalent of `this`. + /// The plugin code base, or the result from the original method. + private static string AssemblyCodeBasePatch(Func orig, Assembly self) + { + var result = orig(self); + + if (string.IsNullOrEmpty(result)) + { + foreach (var assemblyName in GetStackFrameAssemblyNames()) + { + if (PluginLocations.TryGetValue(assemblyName, out var data)) + { + result = data.CodeBase; + break; + } + } + } + + result ??= string.Empty; + + Log.Verbose($"Assembly.CodeBase // {self.FullName} // {result}"); + return result; + } + + private static IEnumerable GetStackFrameAssemblyNames() + { + var stackTrace = new StackTrace(); + var stackFrames = stackTrace.GetFrames(); + + foreach (var stackFrame in stackFrames) + { + var methodBase = stackFrame.GetMethod(); + if (methodBase == null) + continue; + + yield return methodBase.Module.Assembly.FullName; + } + } + + private void ApplyPatches() + { + var targetType = typeof(PluginManager).Assembly.GetType(); + + var locationTarget = targetType.GetProperty(nameof(Assembly.Location)).GetGetMethod(); + var locationPatch = typeof(PluginManager).GetMethod(nameof(PluginManager.AssemblyLocationPatch), BindingFlags.NonPublic | BindingFlags.Static); + this.assemblyLocationMonoHook = new MonoMod.RuntimeDetour.Hook(locationTarget, locationPatch); #pragma warning disable SYSLIB0012 // Type or member is obsolete - var codebaseTarget = targetType.GetProperty(nameof(Assembly.CodeBase)).GetGetMethod(); - var codebasePatch = typeof(PluginManager).GetMethod(nameof(PluginManager.AssemblyCodeBasePatch), BindingFlags.NonPublic | BindingFlags.Static); - this.assemblyCodeBaseMonoHook = new MonoMod.RuntimeDetour.Hook(codebaseTarget, codebasePatch); + var codebaseTarget = targetType.GetProperty(nameof(Assembly.CodeBase)).GetGetMethod(); + var codebasePatch = typeof(PluginManager).GetMethod(nameof(PluginManager.AssemblyCodeBasePatch), BindingFlags.NonPublic | BindingFlags.Static); + this.assemblyCodeBaseMonoHook = new MonoMod.RuntimeDetour.Hook(codebaseTarget, codebasePatch); #pragma warning restore SYSLIB0012 // Type or member is obsolete - } - - internal record PluginPatchData - { - /// - /// Initializes a new instance of the class. - /// - /// DLL file being loaded. - public PluginPatchData(FileInfo dllFile) - { - this.Location = dllFile.FullName; - this.CodeBase = new Uri(dllFile.FullName).AbsoluteUri; } - /// - /// Gets simulated Assembly.Location output. - /// - public string Location { get; } + internal record PluginPatchData + { + /// + /// Initializes a new instance of the class. + /// + /// DLL file being loaded. + public PluginPatchData(FileInfo dllFile) + { + this.Location = dllFile.FullName; + this.CodeBase = new Uri(dllFile.FullName).AbsoluteUri; + } - /// - /// Gets simulated Assembly.CodeBase output. - /// - public string CodeBase { get; } + /// + /// Gets simulated Assembly.Location output. + /// + public string Location { get; } + + /// + /// Gets simulated Assembly.CodeBase output. + /// + public string CodeBase { get; } + } } } diff --git a/Dalamud/Plugin/Internal/PluginRepository.cs b/Dalamud/Plugin/Internal/PluginRepository.cs index 2ef0a4f40..3e4e93fb2 100644 --- a/Dalamud/Plugin/Internal/PluginRepository.cs +++ b/Dalamud/Plugin/Internal/PluginRepository.cs @@ -9,102 +9,103 @@ using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal; - -/// -/// This class represents a single plugin repository. -/// -internal partial class PluginRepository +namespace Dalamud.Plugin.Internal { - // TODO: Change back to master after api4 release - private const string DalamudPluginsMasterUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/pluginmaster.json"; - - private static readonly ModuleLog Log = new("PLUGINR"); - /// - /// Initializes a new instance of the class. + /// This class represents a single plugin repository. /// - /// The plugin master URL. - /// Whether the plugin repo is enabled. - public PluginRepository(string pluginMasterUrl, bool isEnabled) + internal partial class PluginRepository { - this.PluginMasterUrl = pluginMasterUrl; - this.IsThirdParty = pluginMasterUrl != DalamudPluginsMasterUrl; - this.IsEnabled = isEnabled; - } + // TODO: Change back to master after api4 release + private const string DalamudPluginsMasterUrl = "https://raw.githubusercontent.com/goatcorp/DalamudPlugins/api4/pluginmaster.json"; - /// - /// Gets a new instance of the class for the main repo. - /// - public static PluginRepository MainRepo => new(DalamudPluginsMasterUrl, true); + private static readonly ModuleLog Log = new("PLUGINR"); - /// - /// Gets the pluginmaster.json URL. - /// - public string PluginMasterUrl { get; } - - /// - /// Gets a value indicating whether this plugin repository is from a third party. - /// - public bool IsThirdParty { get; } - - /// - /// Gets a value indicating whether this repo is enabled. - /// - public bool IsEnabled { get; } - - /// - /// Gets the plugin master list of available plugins. - /// - public ReadOnlyCollection? PluginMaster { get; private set; } - - /// - /// Gets the initialization state of the plugin repository. - /// - public PluginRepositoryState State { get; private set; } - - /// - /// Reload the plugin master asynchronously in a task. - /// - /// The new state. - public async Task ReloadPluginMasterAsync() - { - this.State = PluginRepositoryState.InProgress; - this.PluginMaster = new List().AsReadOnly(); - - try + /// + /// Initializes a new instance of the class. + /// + /// The plugin master URL. + /// Whether the plugin repo is enabled. + public PluginRepository(string pluginMasterUrl, bool isEnabled) { - Log.Information($"Fetching repo: {this.PluginMasterUrl}"); - - // ?ticks causes a cache invalidation. Get a fresh repo every time. - using var response = await Util.HttpClient.GetAsync(this.PluginMasterUrl + "?" + DateTime.Now.Ticks); - response.EnsureSuccessStatusCode(); - - var data = await response.Content.ReadAsStringAsync(); - var pluginMaster = JsonConvert.DeserializeObject>(data); - - if (pluginMaster == null) - { - throw new Exception("Deserialized PluginMaster was null."); - } - - pluginMaster.Sort((pm1, pm2) => pm1.Name.CompareTo(pm2.Name)); - - // Set the source for each remote manifest. Allows for checking if is 3rd party. - foreach (var manifest in pluginMaster) - { - manifest.SourceRepo = this; - } - - this.PluginMaster = pluginMaster.AsReadOnly(); - - Log.Debug($"Successfully fetched repo: {this.PluginMasterUrl}"); - this.State = PluginRepositoryState.Success; + this.PluginMasterUrl = pluginMasterUrl; + this.IsThirdParty = pluginMasterUrl != DalamudPluginsMasterUrl; + this.IsEnabled = isEnabled; } - catch (Exception ex) + + /// + /// Gets a new instance of the class for the main repo. + /// + public static PluginRepository MainRepo => new(DalamudPluginsMasterUrl, true); + + /// + /// Gets the pluginmaster.json URL. + /// + public string PluginMasterUrl { get; } + + /// + /// Gets a value indicating whether this plugin repository is from a third party. + /// + public bool IsThirdParty { get; } + + /// + /// Gets a value indicating whether this repo is enabled. + /// + public bool IsEnabled { get; } + + /// + /// Gets the plugin master list of available plugins. + /// + public ReadOnlyCollection? PluginMaster { get; private set; } + + /// + /// Gets the initialization state of the plugin repository. + /// + public PluginRepositoryState State { get; private set; } + + /// + /// Reload the plugin master asynchronously in a task. + /// + /// The new state. + public async Task ReloadPluginMasterAsync() { - Log.Error(ex, $"PluginMaster failed: {this.PluginMasterUrl}"); - this.State = PluginRepositoryState.Fail; + this.State = PluginRepositoryState.InProgress; + this.PluginMaster = new List().AsReadOnly(); + + try + { + Log.Information($"Fetching repo: {this.PluginMasterUrl}"); + + // ?ticks causes a cache invalidation. Get a fresh repo every time. + using var response = await Util.HttpClient.GetAsync(this.PluginMasterUrl + "?" + DateTime.Now.Ticks); + response.EnsureSuccessStatusCode(); + + var data = await response.Content.ReadAsStringAsync(); + var pluginMaster = JsonConvert.DeserializeObject>(data); + + if (pluginMaster == null) + { + throw new Exception("Deserialized PluginMaster was null."); + } + + pluginMaster.Sort((pm1, pm2) => pm1.Name.CompareTo(pm2.Name)); + + // Set the source for each remote manifest. Allows for checking if is 3rd party. + foreach (var manifest in pluginMaster) + { + manifest.SourceRepo = this; + } + + this.PluginMaster = pluginMaster.AsReadOnly(); + + Log.Debug($"Successfully fetched repo: {this.PluginMasterUrl}"); + this.State = PluginRepositoryState.Success; + } + catch (Exception ex) + { + Log.Error(ex, $"PluginMaster failed: {this.PluginMasterUrl}"); + this.State = PluginRepositoryState.Fail; + } } } } diff --git a/Dalamud/Plugin/Internal/Types/AvailablePluginUpdate.cs b/Dalamud/Plugin/Internal/Types/AvailablePluginUpdate.cs index 13523a379..32dde337c 100644 --- a/Dalamud/Plugin/Internal/Types/AvailablePluginUpdate.cs +++ b/Dalamud/Plugin/Internal/Types/AvailablePluginUpdate.cs @@ -1,35 +1,36 @@ -namespace Dalamud.Plugin.Internal.Types; - -/// -/// Information about an available plugin update. -/// -internal record AvailablePluginUpdate +namespace Dalamud.Plugin.Internal.Types { /// - /// Initializes a new instance of the class. + /// Information about an available plugin update. /// - /// The installed plugin to update. - /// The manifest to use for the update. - /// If the testing version should be used for the update. - public AvailablePluginUpdate(LocalPlugin installedPlugin, RemotePluginManifest updateManifest, bool useTesting) + internal record AvailablePluginUpdate { - this.InstalledPlugin = installedPlugin; - this.UpdateManifest = updateManifest; - this.UseTesting = useTesting; + /// + /// Initializes a new instance of the class. + /// + /// The installed plugin to update. + /// The manifest to use for the update. + /// If the testing version should be used for the update. + public AvailablePluginUpdate(LocalPlugin installedPlugin, RemotePluginManifest updateManifest, bool useTesting) + { + this.InstalledPlugin = installedPlugin; + this.UpdateManifest = updateManifest; + this.UseTesting = useTesting; + } + + /// + /// Gets the currently installed plugin. + /// + public LocalPlugin InstalledPlugin { get; init; } + + /// + /// Gets the available update manifest. + /// + public RemotePluginManifest UpdateManifest { get; init; } + + /// + /// Gets a value indicating whether the update should use the testing URL. + /// + public bool UseTesting { get; init; } } - - /// - /// Gets the currently installed plugin. - /// - public LocalPlugin InstalledPlugin { get; init; } - - /// - /// Gets the available update manifest. - /// - public RemotePluginManifest UpdateManifest { get; init; } - - /// - /// Gets a value indicating whether the update should use the testing URL. - /// - public bool UseTesting { get; init; } } diff --git a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs index 59a9a6a80..21f0bd8d7 100644 --- a/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/LocalPluginManifest.cs @@ -2,78 +2,79 @@ using System.IO; using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal.Types; - -/// -/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as -/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk. -/// -internal record LocalPluginManifest : PluginManifest +namespace Dalamud.Plugin.Internal.Types { /// - /// Gets or sets a value indicating whether the plugin is disabled and should not be loaded. - /// This value supercedes the ".disabled" file functionality and should not be included in the plugin master. + /// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as + /// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk. /// - public bool Disabled { get; set; } = false; + internal record LocalPluginManifest : PluginManifest + { + /// + /// Gets or sets a value indicating whether the plugin is disabled and should not be loaded. + /// This value supercedes the ".disabled" file functionality and should not be included in the plugin master. + /// + public bool Disabled { get; set; } = false; - /// - /// Gets or sets a value indicating whether the plugin should only be loaded when testing is enabled. - /// This value supercedes the ".testing" file functionality and should not be included in the plugin master. - /// - public bool Testing { get; set; } = false; + /// + /// Gets or sets a value indicating whether the plugin should only be loaded when testing is enabled. + /// This value supercedes the ".testing" file functionality and should not be included in the plugin master. + /// + public bool Testing { get; set; } = false; - /// - /// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was - /// sourced from on the installed plugin view. This should not be included in the plugin master. This value is null - /// when installed from the main repo. - /// - public string InstalledFromUrl { get; set; } + /// + /// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was + /// sourced from on the installed plugin view. This should not be included in the plugin master. This value is null + /// when installed from the main repo. + /// + public string InstalledFromUrl { get; set; } - /// - /// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party - /// repo. Unless the manifest has been manually modified, this is determined by the InstalledFromUrl being null. - /// - public bool IsThirdParty => !string.IsNullOrEmpty(this.InstalledFromUrl); + /// + /// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party + /// repo. Unless the manifest has been manually modified, this is determined by the InstalledFromUrl being null. + /// + public bool IsThirdParty => !string.IsNullOrEmpty(this.InstalledFromUrl); - /// - /// Save a plugin manifest to file. - /// - /// Path to save at. - public void Save(FileInfo manifestFile) => File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented)); + /// + /// Save a plugin manifest to file. + /// + /// Path to save at. + public void Save(FileInfo manifestFile) => File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(this, Formatting.Indented)); - /// - /// Loads a plugin manifest from file. - /// - /// Path to the manifest. - /// A object. - public static LocalPluginManifest Load(FileInfo manifestFile) => JsonConvert.DeserializeObject(File.ReadAllText(manifestFile.FullName)); + /// + /// Loads a plugin manifest from file. + /// + /// Path to the manifest. + /// A object. + public static LocalPluginManifest Load(FileInfo manifestFile) => JsonConvert.DeserializeObject(File.ReadAllText(manifestFile.FullName)); - /// - /// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist. - /// - /// Manifest directory. - /// The manifest. - /// The file. - public static FileInfo GetPluginFile(DirectoryInfo dir, PluginManifest manifest) => new(Path.Combine(dir.FullName, $"{manifest.InternalName}.dll")); + /// + /// A standardized way to get the plugin DLL name that should accompany a manifest file. May not exist. + /// + /// Manifest directory. + /// The manifest. + /// The file. + public static FileInfo GetPluginFile(DirectoryInfo dir, PluginManifest manifest) => new(Path.Combine(dir.FullName, $"{manifest.InternalName}.dll")); - /// - /// A standardized way to get the manifest file that should accompany a plugin DLL. May not exist. - /// - /// The plugin DLL. - /// The file. - public static FileInfo GetManifestFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, Path.GetFileNameWithoutExtension(dllFile.Name) + ".json")); + /// + /// A standardized way to get the manifest file that should accompany a plugin DLL. May not exist. + /// + /// The plugin DLL. + /// The file. + public static FileInfo GetManifestFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, Path.GetFileNameWithoutExtension(dllFile.Name) + ".json")); - /// - /// A standardized way to get the obsolete .disabled file that should accompany a plugin DLL. May not exist. - /// - /// The plugin DLL. - /// The .disabled file. - public static FileInfo GetDisabledFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".disabled")); + /// + /// A standardized way to get the obsolete .disabled file that should accompany a plugin DLL. May not exist. + /// + /// The plugin DLL. + /// The .disabled file. + public static FileInfo GetDisabledFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".disabled")); - /// - /// A standardized way to get the obsolete .testing file that should accompany a plugin DLL. May not exist. - /// - /// The plugin DLL. - /// The .testing file. - public static FileInfo GetTestingFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".testing")); + /// + /// A standardized way to get the obsolete .testing file that should accompany a plugin DLL. May not exist. + /// + /// The plugin DLL. + /// The .testing file. + public static FileInfo GetTestingFile(FileInfo dllFile) => new(Path.Combine(dllFile.DirectoryName, ".testing")); + } } diff --git a/Dalamud/Plugin/Internal/Types/PluginManifest.cs b/Dalamud/Plugin/Internal/Types/PluginManifest.cs index e9f2eadf5..ddacb66de 100644 --- a/Dalamud/Plugin/Internal/Types/PluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/PluginManifest.cs @@ -4,161 +4,162 @@ using System.Collections.Generic; using Dalamud.Game; using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal.Types; - -/// -/// Information about a plugin, packaged in a json file with the DLL. -/// -internal record PluginManifest +namespace Dalamud.Plugin.Internal.Types { /// - /// Gets the author/s of the plugin. + /// Information about a plugin, packaged in a json file with the DLL. /// - [JsonProperty] - public string Author { get; init; } + internal record PluginManifest + { + /// + /// Gets the author/s of the plugin. + /// + [JsonProperty] + public string Author { get; init; } - /// - /// Gets or sets the public name of the plugin. - /// - [JsonProperty] - public string Name { get; set; } + /// + /// Gets or sets the public name of the plugin. + /// + [JsonProperty] + public string Name { get; set; } - /// - /// Gets a punchline of the plugins functions. - /// - [JsonProperty] - public string? Punchline { get; init; } + /// + /// Gets a punchline of the plugins functions. + /// + [JsonProperty] + public string? Punchline { get; init; } - /// - /// Gets a description of the plugins functions. - /// - [JsonProperty] - public string? Description { get; init; } + /// + /// Gets a description of the plugins functions. + /// + [JsonProperty] + public string? Description { get; init; } - /// - /// Gets a changelog. - /// - [JsonProperty] - public string? Changelog { get; init; } + /// + /// Gets a changelog. + /// + [JsonProperty] + public string? Changelog { get; init; } - /// - /// Gets a list of tags defined on the plugin. - /// - [JsonProperty] - public List? Tags { get; init; } + /// + /// Gets a list of tags defined on the plugin. + /// + [JsonProperty] + public List? Tags { get; init; } - /// - /// Gets a list of category tags defined on the plugin. - /// - [JsonProperty] - public List? CategoryTags { get; init; } + /// + /// Gets a list of category tags defined on the plugin. + /// + [JsonProperty] + public List? CategoryTags { get; init; } - /// - /// Gets a value indicating whether or not the plugin is hidden in the plugin installer. - /// This value comes from the plugin master and is in addition to the list of hidden names kept by Dalamud. - /// - [JsonProperty] - public bool IsHide { get; init; } + /// + /// Gets a value indicating whether or not the plugin is hidden in the plugin installer. + /// This value comes from the plugin master and is in addition to the list of hidden names kept by Dalamud. + /// + [JsonProperty] + public bool IsHide { get; init; } - /// - /// Gets the internal name of the plugin, which should match the assembly name of the plugin. - /// - [JsonProperty] - public string InternalName { get; init; } + /// + /// Gets the internal name of the plugin, which should match the assembly name of the plugin. + /// + [JsonProperty] + public string InternalName { get; init; } - /// - /// Gets the current assembly version of the plugin. - /// - [JsonProperty] - public Version AssemblyVersion { get; init; } + /// + /// Gets the current assembly version of the plugin. + /// + [JsonProperty] + public Version AssemblyVersion { get; init; } - /// - /// Gets the current testing assembly version of the plugin. - /// - [JsonProperty] - public Version? TestingAssemblyVersion { get; init; } + /// + /// Gets the current testing assembly version of the plugin. + /// + [JsonProperty] + public Version? TestingAssemblyVersion { get; init; } - /// - /// Gets a value indicating whether the is not null. - /// - [JsonIgnore] - public bool HasAssemblyVersion => this.AssemblyVersion != null; + /// + /// Gets a value indicating whether the is not null. + /// + [JsonIgnore] + public bool HasAssemblyVersion => this.AssemblyVersion != null; - /// - /// Gets a value indicating whether the is not null. - /// - [JsonIgnore] - public bool HasTestingAssemblyVersion => this.TestingAssemblyVersion != null; + /// + /// Gets a value indicating whether the is not null. + /// + [JsonIgnore] + public bool HasTestingAssemblyVersion => this.TestingAssemblyVersion != null; - /// - /// Gets a value indicating whether the plugin is only available for testing. - /// - [JsonProperty] - public bool IsTestingExclusive { get; init; } + /// + /// Gets a value indicating whether the plugin is only available for testing. + /// + [JsonProperty] + public bool IsTestingExclusive { get; init; } - /// - /// Gets an URL to the website or source code of the plugin. - /// - [JsonProperty] - public string? RepoUrl { get; init; } + /// + /// Gets an URL to the website or source code of the plugin. + /// + [JsonProperty] + public string? RepoUrl { get; init; } - /// - /// Gets the version of the game this plugin works with. - /// - [JsonProperty] - [JsonConverter(typeof(GameVersionConverter))] - public GameVersion? ApplicableVersion { get; init; } = GameVersion.Any; + /// + /// Gets the version of the game this plugin works with. + /// + [JsonProperty] + [JsonConverter(typeof(GameVersionConverter))] + public GameVersion? ApplicableVersion { get; init; } = GameVersion.Any; - /// - /// Gets the API level of this plugin. For the current API level, please see - /// for the currently used API level. - /// - [JsonProperty] - public int DalamudApiLevel { get; init; } = PluginManager.DalamudApiLevel; + /// + /// Gets the API level of this plugin. For the current API level, please see + /// for the currently used API level. + /// + [JsonProperty] + public int DalamudApiLevel { get; init; } = PluginManager.DalamudApiLevel; - /// - /// Gets the number of downloads this plugin has. - /// - [JsonProperty] - public long DownloadCount { get; init; } + /// + /// Gets the number of downloads this plugin has. + /// + [JsonProperty] + public long DownloadCount { get; init; } - /// - /// Gets the last time this plugin was updated. - /// - [JsonProperty] - public long LastUpdate { get; init; } + /// + /// Gets the last time this plugin was updated. + /// + [JsonProperty] + public long LastUpdate { get; init; } - /// - /// Gets the download link used to install the plugin. - /// - [JsonProperty] - public string DownloadLinkInstall { get; init; } + /// + /// Gets the download link used to install the plugin. + /// + [JsonProperty] + public string DownloadLinkInstall { get; init; } - /// - /// Gets the download link used to update the plugin. - /// - [JsonProperty] - public string DownloadLinkUpdate { get; init; } + /// + /// Gets the download link used to update the plugin. + /// + [JsonProperty] + public string DownloadLinkUpdate { get; init; } - /// - /// Gets the download link used to get testing versions of the plugin. - /// - [JsonProperty] - public string DownloadLinkTesting { get; init; } + /// + /// Gets the download link used to get testing versions of the plugin. + /// + [JsonProperty] + public string DownloadLinkTesting { get; init; } - /// - /// Gets the load priority for this plugin. Higher values means higher priority. 0 is default priority. - /// - [JsonProperty] - public int LoadPriority { get; init; } + /// + /// Gets the load priority for this plugin. Higher values means higher priority. 0 is default priority. + /// + [JsonProperty] + public int LoadPriority { get; init; } - /// - /// Gets a list of screenshot image URLs to show in the plugin installer. - /// - public List? ImageUrls { get; init; } + /// + /// Gets a list of screenshot image URLs to show in the plugin installer. + /// + public List? ImageUrls { get; init; } - /// - /// Gets an URL for the plugin's icon. - /// - public string? IconUrl { get; init; } + /// + /// Gets an URL for the plugin's icon. + /// + public string? IconUrl { get; init; } + } } diff --git a/Dalamud/Plugin/Internal/Types/PluginRepositoryState.cs b/Dalamud/Plugin/Internal/Types/PluginRepositoryState.cs index 2909ff981..46aa2c351 100644 --- a/Dalamud/Plugin/Internal/Types/PluginRepositoryState.cs +++ b/Dalamud/Plugin/Internal/Types/PluginRepositoryState.cs @@ -1,27 +1,28 @@ -namespace Dalamud.Plugin.Internal.Types; - -/// -/// Values representing plugin repository state. -/// -internal enum PluginRepositoryState +namespace Dalamud.Plugin.Internal.Types { /// - /// State is unknown. + /// Values representing plugin repository state. /// - Unknown, + internal enum PluginRepositoryState + { + /// + /// State is unknown. + /// + Unknown, - /// - /// Currently loading. - /// - InProgress, + /// + /// Currently loading. + /// + InProgress, - /// - /// Load was successful. - /// - Success, + /// + /// Load was successful. + /// + Success, - /// - /// Load failed. - /// - Fail, + /// + /// Load failed. + /// + Fail, + } } diff --git a/Dalamud/Plugin/Internal/Types/PluginState.cs b/Dalamud/Plugin/Internal/Types/PluginState.cs index da5fcf977..f32543b39 100644 --- a/Dalamud/Plugin/Internal/Types/PluginState.cs +++ b/Dalamud/Plugin/Internal/Types/PluginState.cs @@ -1,32 +1,33 @@ -namespace Dalamud.Plugin.Internal.Types; - -/// -/// Values representing plugin load state. -/// -internal enum PluginState +namespace Dalamud.Plugin.Internal.Types { /// - /// Plugin is defined, but unloaded. + /// Values representing plugin load state. /// - Unloaded, + internal enum PluginState + { + /// + /// Plugin is defined, but unloaded. + /// + Unloaded, - /// - /// Plugin has thrown an error during unload. - /// - UnloadError, + /// + /// Plugin has thrown an error during unload. + /// + UnloadError, - /// - /// Currently loading. - /// - InProgress, + /// + /// Currently loading. + /// + InProgress, - /// - /// Load is successful. - /// - Loaded, + /// + /// Load is successful. + /// + Loaded, - /// - /// Plugin has thrown an error during loading. - /// - LoadError, + /// + /// Plugin has thrown an error during loading. + /// + LoadError, + } } diff --git a/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs b/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs index f71c39cab..f0394b9b7 100644 --- a/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs +++ b/Dalamud/Plugin/Internal/Types/PluginUpdateStatus.cs @@ -1,29 +1,30 @@ using System; -namespace Dalamud.Plugin.Internal.Types; - -/// -/// Plugin update status. -/// -internal class PluginUpdateStatus +namespace Dalamud.Plugin.Internal.Types { /// - /// Gets or sets the plugin internal name. + /// Plugin update status. /// - public string InternalName { get; set; } + internal class PluginUpdateStatus + { + /// + /// Gets or sets the plugin internal name. + /// + public string InternalName { get; set; } - /// - /// Gets or sets the plugin name. - /// - public string Name { get; set; } + /// + /// Gets or sets the plugin name. + /// + public string Name { get; set; } - /// - /// Gets or sets the plugin version. - /// - public Version Version { get; set; } + /// + /// Gets or sets the plugin version. + /// + public Version Version { get; set; } - /// - /// Gets or sets a value indicating whether the plugin was updated. - /// - public bool WasUpdated { get; set; } + /// + /// Gets or sets a value indicating whether the plugin was updated. + /// + public bool WasUpdated { get; set; } + } } diff --git a/Dalamud/Plugin/Internal/Types/RemotePluginManifest.cs b/Dalamud/Plugin/Internal/Types/RemotePluginManifest.cs index 973276227..cbb989159 100644 --- a/Dalamud/Plugin/Internal/Types/RemotePluginManifest.cs +++ b/Dalamud/Plugin/Internal/Types/RemotePluginManifest.cs @@ -1,17 +1,18 @@ using Newtonsoft.Json; -namespace Dalamud.Plugin.Internal.Types; - -/// -/// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as -/// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk. -/// -internal record RemotePluginManifest : PluginManifest +namespace Dalamud.Plugin.Internal.Types { /// - /// Gets or sets the plugin repository this manifest came from. Used in reporting which third party repo a manifest - /// may have come from in the plugins available view. This functionality should not be included in the plugin master. + /// Information about a plugin, packaged in a json file with the DLL. This variant includes additional information such as + /// if the plugin is disabled and if it was installed from a testing URL. This is designed for use with manifests on disk. /// - [JsonIgnore] - public PluginRepository SourceRepo { get; set; } = null; + internal record RemotePluginManifest : PluginManifest + { + /// + /// Gets or sets the plugin repository this manifest came from. Used in reporting which third party repo a manifest + /// may have come from in the plugins available view. This functionality should not be included in the plugin master. + /// + [JsonIgnore] + public PluginRepository SourceRepo { get; set; } = null; + } } diff --git a/Dalamud/Plugin/Ipc/Exceptions/IpcError.cs b/Dalamud/Plugin/Ipc/Exceptions/IpcError.cs index 5cc0ccae9..053331dce 100644 --- a/Dalamud/Plugin/Ipc/Exceptions/IpcError.cs +++ b/Dalamud/Plugin/Ipc/Exceptions/IpcError.cs @@ -1,35 +1,36 @@ using System; -namespace Dalamud.Plugin.Ipc.Exceptions; - -/// -/// This exception is thrown when an IPC errors are encountered. -/// -public abstract class IpcError : Exception +namespace Dalamud.Plugin.Ipc.Exceptions { /// - /// Initializes a new instance of the class. + /// This exception is thrown when an IPC errors are encountered. /// - public IpcError() + public abstract class IpcError : Exception { - } + /// + /// Initializes a new instance of the class. + /// + public IpcError() + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - public IpcError(string message) - : base(message) - { - } + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public IpcError(string message) + : base(message) + { + } - /// - /// Initializes a new instance of the class. - /// - /// The message that describes the error. - /// The exception that is the cause of the current exception. - public IpcError(string message, Exception ex) - : base(message, ex) - { + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + public IpcError(string message, Exception ex) + : base(message, ex) + { + } } } diff --git a/Dalamud/Plugin/Ipc/Exceptions/IpcLengthMismatchError.cs b/Dalamud/Plugin/Ipc/Exceptions/IpcLengthMismatchError.cs index 47b204975..bb5f64070 100644 --- a/Dalamud/Plugin/Ipc/Exceptions/IpcLengthMismatchError.cs +++ b/Dalamud/Plugin/Ipc/Exceptions/IpcLengthMismatchError.cs @@ -1,18 +1,19 @@ -namespace Dalamud.Plugin.Ipc.Exceptions; - -/// -/// This exception is thrown when an IPC method is invoked and the number of types does not match what was previously registered. -/// -public class IpcLengthMismatchError : IpcError +namespace Dalamud.Plugin.Ipc.Exceptions { /// - /// Initializes a new instance of the class. + /// This exception is thrown when an IPC method is invoked and the number of types does not match what was previously registered. /// - /// Name of the IPC method. - /// The amount of types requested when checking out the IPC. - /// The amount of types registered by the IPC. - public IpcLengthMismatchError(string name, int requestedLength, int actualLength) - : base($"IPC method {name} has a different number of types than was requested. {requestedLength} != {actualLength}") + public class IpcLengthMismatchError : IpcError { + /// + /// Initializes a new instance of the class. + /// + /// Name of the IPC method. + /// The amount of types requested when checking out the IPC. + /// The amount of types registered by the IPC. + public IpcLengthMismatchError(string name, int requestedLength, int actualLength) + : base($"IPC method {name} has a different number of types than was requested. {requestedLength} != {actualLength}") + { + } } } diff --git a/Dalamud/Plugin/Ipc/Exceptions/IpcNotReadyError.cs b/Dalamud/Plugin/Ipc/Exceptions/IpcNotReadyError.cs index 1d7803369..6bfd87ba8 100644 --- a/Dalamud/Plugin/Ipc/Exceptions/IpcNotReadyError.cs +++ b/Dalamud/Plugin/Ipc/Exceptions/IpcNotReadyError.cs @@ -1,16 +1,17 @@ -namespace Dalamud.Plugin.Ipc.Exceptions; - -/// -/// This exception is thrown when an IPC method is invoked, but no actions or funcs have been registered yet. -/// -public class IpcNotReadyError : IpcError +namespace Dalamud.Plugin.Ipc.Exceptions { /// - /// Initializes a new instance of the class. + /// This exception is thrown when an IPC method is invoked, but no actions or funcs have been registered yet. /// - /// Name of the IPC method. - public IpcNotReadyError(string name) - : base($"IPC method {name} was not registered yet") + public class IpcNotReadyError : IpcError { + /// + /// Initializes a new instance of the class. + /// + /// Name of the IPC method. + public IpcNotReadyError(string name) + : base($"IPC method {name} was not registered yet") + { + } } } diff --git a/Dalamud/Plugin/Ipc/Exceptions/IpcTypeMismatchError.cs b/Dalamud/Plugin/Ipc/Exceptions/IpcTypeMismatchError.cs index 1aa191b78..2de5adce8 100644 --- a/Dalamud/Plugin/Ipc/Exceptions/IpcTypeMismatchError.cs +++ b/Dalamud/Plugin/Ipc/Exceptions/IpcTypeMismatchError.cs @@ -1,21 +1,22 @@ using System; -namespace Dalamud.Plugin.Ipc.Exceptions; - -/// -/// This exception is thrown when an IPC method is checked out, but the type does not match what was previously registered. -/// -public class IpcTypeMismatchError : IpcError +namespace Dalamud.Plugin.Ipc.Exceptions { /// - /// Initializes a new instance of the class. + /// This exception is thrown when an IPC method is checked out, but the type does not match what was previously registered. /// - /// Name of the IPC method. - /// The before type. - /// The after type. - /// The exception that is the cause of the current exception. - public IpcTypeMismatchError(string name, Type requestedType, Type actualType, Exception ex) - : base($"IPC method {name} blew up when converting from {requestedType.Name} to {actualType}", ex) + public class IpcTypeMismatchError : IpcError { + /// + /// Initializes a new instance of the class. + /// + /// Name of the IPC method. + /// The before type. + /// The after type. + /// The exception that is the cause of the current exception. + public IpcTypeMismatchError(string name, Type requestedType, Type actualType, Exception ex) + : base($"IPC method {name} blew up when converting from {requestedType.Name} to {actualType}", ex) + { + } } } diff --git a/Dalamud/Plugin/Ipc/ICallGateProvider.cs b/Dalamud/Plugin/Ipc/ICallGateProvider.cs index 333878d07..62b95c809 100644 --- a/Dalamud/Plugin/Ipc/ICallGateProvider.cs +++ b/Dalamud/Plugin/Ipc/ICallGateProvider.cs @@ -4,177 +4,178 @@ using Dalamud.Plugin.Ipc.Internal; #pragma warning disable SA1402 // File may only contain a single type -namespace Dalamud.Plugin.Ipc; - -/// -public interface ICallGateProvider +namespace Dalamud.Plugin.Ipc { - /// - public void RegisterAction(Action action); + /// + public interface ICallGateProvider + { + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(); -} + /// + public void SendMessage(); + } -/// -public interface ICallGateProvider -{ - /// - public void RegisterAction(Action action); + /// + public interface ICallGateProvider + { + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1); -} + /// + public void SendMessage(T1 arg1); + } -/// -public interface ICallGateProvider -{ - /// - public void RegisterAction(Action action); + /// + public interface ICallGateProvider + { + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2); -} + /// + public void SendMessage(T1 arg1, T2 arg2); + } -/// -public interface ICallGateProvider -{ - /// - public void RegisterAction(Action action); + /// + public interface ICallGateProvider + { + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3); -} + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3); + } -/// -public interface ICallGateProvider -{ - /// - public void RegisterAction(Action action); + /// + public interface ICallGateProvider + { + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4); -} + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4); + } -/// -public interface ICallGateProvider -{ - /// - public void RegisterAction(Action action); + /// + public interface ICallGateProvider + { + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); -} + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + } -/// -public interface ICallGateProvider -{ - /// - public void RegisterAction(Action action); + /// + public interface ICallGateProvider + { + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); -} + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); + } -/// -public interface ICallGateProvider -{ - /// - public void RegisterAction(Action action); + /// + public interface ICallGateProvider + { + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); -} + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); + } -/// -public interface ICallGateProvider -{ - /// - public void RegisterAction(Action action); + /// + public interface ICallGateProvider + { + /// + public void RegisterAction(Action action); - /// - public void RegisterFunc(Func func); + /// + public void RegisterFunc(Func func); - /// - public void UnregisterAction(); + /// + public void UnregisterAction(); - /// - public void UnregisterFunc(); + /// + public void UnregisterFunc(); - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); + } } #pragma warning restore SA1402 // File may only contain a single type diff --git a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs index 94afa200a..d5ddd1329 100644 --- a/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs +++ b/Dalamud/Plugin/Ipc/ICallGateSubscriber.cs @@ -4,150 +4,151 @@ using Dalamud.Plugin.Ipc.Internal; #pragma warning disable SA1402 // File may only contain a single type -namespace Dalamud.Plugin.Ipc; - -/// -public interface ICallGateSubscriber +namespace Dalamud.Plugin.Ipc { - /// - public void Subscribe(Action action); + /// + public interface ICallGateSubscriber + { + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(); + /// + public void InvokeAction(); - /// - public TRet InvokeFunc(); -} + /// + public TRet InvokeFunc(); + } -/// -public interface ICallGateSubscriber -{ - /// - public void Subscribe(Action action); + /// + public interface ICallGateSubscriber + { + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1); + /// + public void InvokeAction(T1 arg1); - /// - public TRet InvokeFunc(T1 arg1); -} + /// + public TRet InvokeFunc(T1 arg1); + } -/// -public interface ICallGateSubscriber -{ - /// - public void Subscribe(Action action); + /// + public interface ICallGateSubscriber + { + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2); + /// + public void InvokeAction(T1 arg1, T2 arg2); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2); -} + /// + public TRet InvokeFunc(T1 arg1, T2 arg2); + } -/// -public interface ICallGateSubscriber -{ - /// - public void Subscribe(Action action); + /// + public interface ICallGateSubscriber + { + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3); -} + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3); + } -/// -public interface ICallGateSubscriber -{ - /// - public void Subscribe(Action action); + /// + public interface ICallGateSubscriber + { + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4); -} + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4); + } -/// -public interface ICallGateSubscriber -{ - /// - public void Subscribe(Action action); + /// + public interface ICallGateSubscriber + { + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); -} + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + } -/// -public interface ICallGateSubscriber -{ - /// - public void Subscribe(Action action); + /// + public interface ICallGateSubscriber + { + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); -} + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); + } -/// -public interface ICallGateSubscriber -{ - /// - public void Subscribe(Action action); + /// + public interface ICallGateSubscriber + { + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); -} + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); + } -/// -public interface ICallGateSubscriber -{ - /// - public void Subscribe(Action action); + /// + public interface ICallGateSubscriber + { + /// + public void Subscribe(Action action); - /// - public void Unsubscribe(Action action); + /// + public void Unsubscribe(Action action); - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8); + } } #pragma warning restore SA1402 // File may only contain a single type diff --git a/Dalamud/Plugin/Ipc/Internal/CallGate.cs b/Dalamud/Plugin/Ipc/Internal/CallGate.cs index a9a422c05..dd89b02bc 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGate.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGate.cs @@ -1,30 +1,31 @@ using System.Collections.Generic; -namespace Dalamud.Plugin.Ipc.Internal; - -/// -/// This class facilitates inter-plugin communication. -/// -internal class CallGate +namespace Dalamud.Plugin.Ipc.Internal { - private readonly Dictionary gates = new(); - /// - /// Initializes a new instance of the class. + /// This class facilitates inter-plugin communication. /// - internal CallGate() + internal class CallGate { - } + private readonly Dictionary gates = new(); - /// - /// Gets the provider associated with the specified name. - /// - /// Name of the IPC registration. - /// A CallGate registered under the given name. - public CallGateChannel GetOrCreateChannel(string name) - { - if (!this.gates.TryGetValue(name, out var gate)) - gate = this.gates[name] = new CallGateChannel(name); - return gate; + /// + /// Initializes a new instance of the class. + /// + internal CallGate() + { + } + + /// + /// Gets the provider associated with the specified name. + /// + /// Name of the IPC registration. + /// A CallGate registered under the given name. + public CallGateChannel GetOrCreateChannel(string name) + { + if (!this.gates.TryGetValue(name, out var gate)) + gate = this.gates[name] = new CallGateChannel(name); + return gate; + } } } diff --git a/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs b/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs index c4ea95cd5..7dbda203e 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGateChannel.cs @@ -7,179 +7,180 @@ using Dalamud.Plugin.Ipc.Exceptions; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Plugin.Ipc.Internal; - -/// -/// This class implements the channels and serialization needed for the typed gates to interact with each other. -/// -internal class CallGateChannel +namespace Dalamud.Plugin.Ipc.Internal { /// - /// Initializes a new instance of the class. + /// This class implements the channels and serialization needed for the typed gates to interact with each other. /// - /// The name of this IPC registration. - public CallGateChannel(string name) + internal class CallGateChannel { - this.Name = name; - } - - /// - /// Gets the name of the IPC registration. - /// - public string Name { get; init; } - - /// - /// Gets a list of delegate subscriptions for when SendMessage is called. - /// - public List Subscriptions { get; } = new(); - - /// - /// Gets or sets an action for when InvokeAction is called. - /// - public Delegate Action { get; set; } - - /// - /// Gets or sets a func for when InvokeFunc is called. - /// - public Delegate Func { get; set; } - - /// - /// Invoke all actions that have subscribed to this IPC. - /// - /// Message arguments. - internal void SendMessage(object?[]? args) - { - if (this.Subscriptions.Count == 0) - return; - - foreach (var subscription in this.Subscriptions) + /// + /// Initializes a new instance of the class. + /// + /// The name of this IPC registration. + public CallGateChannel(string name) { - var methodInfo = subscription.GetMethodInfo(); + this.Name = name; + } + + /// + /// Gets the name of the IPC registration. + /// + public string Name { get; init; } + + /// + /// Gets a list of delegate subscriptions for when SendMessage is called. + /// + public List Subscriptions { get; } = new(); + + /// + /// Gets or sets an action for when InvokeAction is called. + /// + public Delegate Action { get; set; } + + /// + /// Gets or sets a func for when InvokeFunc is called. + /// + public Delegate Func { get; set; } + + /// + /// Invoke all actions that have subscribed to this IPC. + /// + /// Message arguments. + internal void SendMessage(object?[]? args) + { + if (this.Subscriptions.Count == 0) + return; + + foreach (var subscription in this.Subscriptions) + { + var methodInfo = subscription.GetMethodInfo(); + this.CheckAndConvertArgs(args, methodInfo); + + subscription.DynamicInvoke(args); + } + } + + /// + /// Invoke an action registered for inter-plugin communication. + /// + /// Action arguments. + /// This is thrown when the IPC publisher has not registered a func for calling yet. + internal void InvokeAction(object?[]? args) + { + if (this.Action == null) + throw new IpcNotReadyError(this.Name); + + var methodInfo = this.Action.GetMethodInfo(); this.CheckAndConvertArgs(args, methodInfo); - subscription.DynamicInvoke(args); + this.Action.DynamicInvoke(args); } - } - /// - /// Invoke an action registered for inter-plugin communication. - /// - /// Action arguments. - /// This is thrown when the IPC publisher has not registered a func for calling yet. - internal void InvokeAction(object?[]? args) - { - if (this.Action == null) - throw new IpcNotReadyError(this.Name); - - var methodInfo = this.Action.GetMethodInfo(); - this.CheckAndConvertArgs(args, methodInfo); - - this.Action.DynamicInvoke(args); - } - - /// - /// Invoke a function registered for inter-plugin communication. - /// - /// Func arguments. - /// The return value. - /// The return type. - /// This is thrown when the IPC publisher has not registered a func for calling yet. - internal TRet InvokeFunc(object?[]? args) - { - if (this.Func == null) - throw new IpcNotReadyError(this.Name); - - var methodInfo = this.Func.GetMethodInfo(); - this.CheckAndConvertArgs(args, methodInfo); - - var result = this.Func.DynamicInvoke(args); - - if (typeof(TRet) != methodInfo.ReturnType) - result = this.ConvertObject(result, typeof(TRet)); - - return (TRet)result; - } - - private void CheckAndConvertArgs(object?[]? args, MethodInfo methodInfo) - { - var paramTypes = methodInfo.GetParameters() - .Select(pi => pi.ParameterType).ToArray(); - - if (args.Length != paramTypes.Length) - throw new IpcLengthMismatchError(this.Name, args.Length, paramTypes.Length); - - for (var i = 0; i < args.Length; i++) + /// + /// Invoke a function registered for inter-plugin communication. + /// + /// Func arguments. + /// The return value. + /// The return type. + /// This is thrown when the IPC publisher has not registered a func for calling yet. + internal TRet InvokeFunc(object?[]? args) { - var arg = args[i]; - var paramType = paramTypes[i]; + if (this.Func == null) + throw new IpcNotReadyError(this.Name); - var argType = arg.GetType(); - if (argType != paramType) + var methodInfo = this.Func.GetMethodInfo(); + this.CheckAndConvertArgs(args, methodInfo); + + var result = this.Func.DynamicInvoke(args); + + if (typeof(TRet) != methodInfo.ReturnType) + result = this.ConvertObject(result, typeof(TRet)); + + return (TRet)result; + } + + private void CheckAndConvertArgs(object?[]? args, MethodInfo methodInfo) + { + var paramTypes = methodInfo.GetParameters() + .Select(pi => pi.ParameterType).ToArray(); + + if (args.Length != paramTypes.Length) + throw new IpcLengthMismatchError(this.Name, args.Length, paramTypes.Length); + + for (var i = 0; i < args.Length; i++) { - // check the inheritance tree - var baseTypes = this.GenerateTypes(argType.BaseType); - if (baseTypes.Any(t => t == paramType)) + var arg = args[i]; + var paramType = paramTypes[i]; + + var argType = arg.GetType(); + if (argType != paramType) { - // The source type inherits from the destination type - continue; + // check the inheritance tree + var baseTypes = this.GenerateTypes(argType.BaseType); + if (baseTypes.Any(t => t == paramType)) + { + // The source type inherits from the destination type + continue; + } + + args[i] = this.ConvertObject(arg, paramType); } - - args[i] = this.ConvertObject(arg, paramType); } } - } - private IEnumerable GenerateTypes(Type type) - { - while (type != null && type != typeof(object)) + private IEnumerable GenerateTypes(Type type) { - yield return type; - type = type.BaseType; - } - } - - private object? ConvertObject(object? obj, Type type) - { - var json = JsonConvert.SerializeObject(obj); - - try - { - return JsonConvert.DeserializeObject(json, type); - } - catch (Exception) - { - Log.Verbose($"Could not convert {obj.GetType().Name} to {type.Name}, will look for compatible type instead"); - } - - // If type -> type fails, try to find an object that matches. - var sourceType = obj.GetType(); - var fieldNames = sourceType.GetFields(BindingFlags.Public | BindingFlags.Instance) - .Select(f => f.Name); - var propNames = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(p => p.Name); - - var assignableTypes = type.Assembly.GetTypes() - .Where(t => type.IsAssignableFrom(t) && type != t) - .ToArray(); - - foreach (var assignableType in assignableTypes) - { - var matchesFields = assignableType.GetFields().All(f => fieldNames.Contains(f.Name)); - var matchesProps = assignableType.GetProperties().All(p => propNames.Contains(p.Name)); - if (matchesFields && matchesProps) + while (type != null && type != typeof(object)) { - type = assignableType; - break; + yield return type; + type = type.BaseType; } } - try + private object? ConvertObject(object? obj, Type type) { - return JsonConvert.DeserializeObject(json, type); - } - catch (Exception ex) - { - throw new IpcTypeMismatchError(this.Name, obj.GetType(), type, ex); + var json = JsonConvert.SerializeObject(obj); + + try + { + return JsonConvert.DeserializeObject(json, type); + } + catch (Exception) + { + Log.Verbose($"Could not convert {obj.GetType().Name} to {type.Name}, will look for compatible type instead"); + } + + // If type -> type fails, try to find an object that matches. + var sourceType = obj.GetType(); + var fieldNames = sourceType.GetFields(BindingFlags.Public | BindingFlags.Instance) + .Select(f => f.Name); + var propNames = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(p => p.Name); + + var assignableTypes = type.Assembly.GetTypes() + .Where(t => type.IsAssignableFrom(t) && type != t) + .ToArray(); + + foreach (var assignableType in assignableTypes) + { + var matchesFields = assignableType.GetFields().All(f => fieldNames.Contains(f.Name)); + var matchesProps = assignableType.GetProperties().All(p => propNames.Contains(p.Name)); + if (matchesFields && matchesProps) + { + type = assignableType; + break; + } + } + + try + { + return JsonConvert.DeserializeObject(json, type); + } + catch (Exception ex) + { + throw new IpcTypeMismatchError(this.Name, obj.GetType(), type, ex); + } } } } diff --git a/Dalamud/Plugin/Ipc/Internal/CallGatePubSub.cs b/Dalamud/Plugin/Ipc/Internal/CallGatePubSub.cs index 39d5b9f4d..da1bcda49 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGatePubSub.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGatePubSub.cs @@ -2,348 +2,349 @@ using System; #pragma warning disable SA1402 // File may only contain a single type -namespace Dalamud.Plugin.Ipc.Internal; - -/// -internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber +namespace Dalamud.Plugin.Ipc.Internal { - /// - public CallGatePubSub(string name) - : base(name) + /// + internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { + /// + public CallGatePubSub(string name) + : base(name) + { + } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage() + => base.SendMessage(); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction() + => base.InvokeAction(); + + /// + public TRet InvokeFunc() + => this.InvokeFunc(); } - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage() - => base.SendMessage(); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction() - => base.InvokeAction(); - - /// - public TRet InvokeFunc() - => this.InvokeFunc(); -} - -/// -internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber -{ - /// - public CallGatePubSub(string name) - : base(name) + /// + internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { + /// + public CallGatePubSub(string name) + : base(name) + { + } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1) + => base.SendMessage(arg1); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1) + => base.InvokeAction(arg1); + + /// + public TRet InvokeFunc(T1 arg1) + => this.InvokeFunc(arg1); } - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1) - => base.SendMessage(arg1); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1) - => base.InvokeAction(arg1); - - /// - public TRet InvokeFunc(T1 arg1) - => this.InvokeFunc(arg1); -} - -/// -internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber -{ - /// - public CallGatePubSub(string name) - : base(name) + /// + internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { + /// + public CallGatePubSub(string name) + : base(name) + { + } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2) + => base.SendMessage(arg1, arg2); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2) + => base.InvokeAction(arg1, arg2); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2) + => this.InvokeFunc(arg1, arg2); } - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2) - => base.SendMessage(arg1, arg2); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2) - => base.InvokeAction(arg1, arg2); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2) - => this.InvokeFunc(arg1, arg2); -} - -/// -internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber -{ - /// - public CallGatePubSub(string name) - : base(name) + /// + internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { + /// + public CallGatePubSub(string name) + : base(name) + { + } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3) + => base.SendMessage(arg1, arg2, arg3); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3) + => base.InvokeAction(arg1, arg2, arg3); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3) + => this.InvokeFunc(arg1, arg2, arg3); } - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3) - => base.SendMessage(arg1, arg2, arg3); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3) - => base.InvokeAction(arg1, arg2, arg3); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3) - => this.InvokeFunc(arg1, arg2, arg3); -} - -/// -internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber -{ - /// - public CallGatePubSub(string name) - : base(name) + /// + internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { + /// + public CallGatePubSub(string name) + : base(name) + { + } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + => base.SendMessage(arg1, arg2, arg3, arg4); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + => base.InvokeAction(arg1, arg2, arg3, arg4); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4) + => this.InvokeFunc(arg1, arg2, arg3, arg4); } - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4) - => base.SendMessage(arg1, arg2, arg3, arg4); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4) - => base.InvokeAction(arg1, arg2, arg3, arg4); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4) - => this.InvokeFunc(arg1, arg2, arg3, arg4); -} - -/// -internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber -{ - /// - public CallGatePubSub(string name) - : base(name) + /// + internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { + /// + public CallGatePubSub(string name) + : base(name) + { + } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + => base.SendMessage(arg1, arg2, arg3, arg4, arg5); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + => base.InvokeAction(arg1, arg2, arg3, arg4, arg5); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5); } - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - => base.SendMessage(arg1, arg2, arg3, arg4, arg5); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - => base.InvokeAction(arg1, arg2, arg3, arg4, arg5); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) - => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5); -} - -/// -internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber -{ - /// - public CallGatePubSub(string name) - : base(name) + /// + internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { + /// + public CallGatePubSub(string name) + : base(name) + { + } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6); } - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) - => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6); -} - -/// -internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber -{ - /// - public CallGatePubSub(string name) - : base(name) + /// + internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { + /// + public CallGatePubSub(string name) + : base(name) + { + } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6, arg7); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6, arg7); } - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6, arg7); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6, arg7); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) - => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6, arg7); -} - -/// -internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber -{ - /// - public CallGatePubSub(string name) - : base(name) + /// + internal class CallGatePubSub : CallGatePubSubBase, ICallGateProvider, ICallGateSubscriber { + /// + public CallGatePubSub(string name) + : base(name) + { + } + + /// + public void RegisterAction(Action action) + => base.RegisterAction(action); + + /// + public void RegisterFunc(Func func) + => base.RegisterFunc(func); + + /// + public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + /// + public void Subscribe(Action action) + => base.Subscribe(action); + + /// + public void Unsubscribe(Action action) + => base.Unsubscribe(action); + + /// + public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + + /// + public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } - - /// - public void RegisterAction(Action action) - => base.RegisterAction(action); - - /// - public void RegisterFunc(Func func) - => base.RegisterFunc(func); - - /// - public void SendMessage(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - => base.SendMessage(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - - /// - public void Subscribe(Action action) - => base.Subscribe(action); - - /// - public void Unsubscribe(Action action) - => base.Unsubscribe(action); - - /// - public void InvokeAction(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - => base.InvokeAction(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); - - /// - public TRet InvokeFunc(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) - => this.InvokeFunc(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } #pragma warning restore SA1402 // File may only contain a single type diff --git a/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs b/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs index 40c0c4a59..19b99de05 100644 --- a/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs +++ b/Dalamud/Plugin/Ipc/Internal/CallGatePubSubBase.cs @@ -2,89 +2,90 @@ using System; using Dalamud.Plugin.Ipc.Exceptions; -namespace Dalamud.Plugin.Ipc.Internal; - -/// -/// This class facilitates inter-plugin communication. -/// -internal abstract class CallGatePubSubBase +namespace Dalamud.Plugin.Ipc.Internal { /// - /// Initializes a new instance of the class. + /// This class facilitates inter-plugin communication. /// - /// The name of the IPC registration. - public CallGatePubSubBase(string name) + internal abstract class CallGatePubSubBase { - this.Channel = Service.Get().GetOrCreateChannel(name); + /// + /// Initializes a new instance of the class. + /// + /// The name of the IPC registration. + public CallGatePubSubBase(string name) + { + this.Channel = Service.Get().GetOrCreateChannel(name); + } + + /// + /// Gets the underlying channel implementation. + /// + protected CallGateChannel Channel { get; init; } + + /// + /// Removes a registered Action from inter-plugin communication. + /// + public void UnregisterAction() + => this.Channel.Action = null; + + /// + /// Removes a registered Func from inter-plugin communication. + /// + public void UnregisterFunc() + => this.Channel.Func = null; + + /// + /// Registers an Action for inter-plugin communication. + /// + /// Action to register. + private protected void RegisterAction(Delegate action) + => this.Channel.Action = action; + + /// + /// Registers a Func for inter-plugin communication. + /// + /// Func to register. + private protected void RegisterFunc(Delegate func) + => this.Channel.Func = func; + + /// + /// Subscribe an expression to this registration. + /// + /// Action to subscribe. + private protected void Subscribe(Delegate action) + => this.Channel.Subscriptions.Add(action); + + /// + /// Unsubscribe an expression from this registration. + /// + /// Action to unsubscribe. + private protected void Unsubscribe(Delegate action) + => this.Channel.Subscriptions.Remove(action); + + /// + /// Invoke an action registered for inter-plugin communication. + /// + /// Action arguments. + /// This is thrown when the IPC publisher has not registered an action for calling yet. + private protected void InvokeAction(params object?[]? args) + => this.Channel.InvokeAction(args); + + /// + /// Invoke a function registered for inter-plugin communication. + /// + /// Parameter args. + /// The return value. + /// The return type. + /// This is thrown when the IPC publisher has not registered a func for calling yet. + private protected TRet InvokeFunc(params object?[]? args) + => this.Channel.InvokeFunc(args); + + /// + /// Invoke all actions that have subscribed to this IPC. + /// + /// Delegate arguments. + private protected void SendMessage(params object?[]? args) + => this.Channel.SendMessage(args); } - - /// - /// Gets the underlying channel implementation. - /// - protected CallGateChannel Channel { get; init; } - - /// - /// Removes a registered Action from inter-plugin communication. - /// - public void UnregisterAction() - => this.Channel.Action = null; - - /// - /// Removes a registered Func from inter-plugin communication. - /// - public void UnregisterFunc() - => this.Channel.Func = null; - - /// - /// Registers an Action for inter-plugin communication. - /// - /// Action to register. - private protected void RegisterAction(Delegate action) - => this.Channel.Action = action; - - /// - /// Registers a Func for inter-plugin communication. - /// - /// Func to register. - private protected void RegisterFunc(Delegate func) - => this.Channel.Func = func; - - /// - /// Subscribe an expression to this registration. - /// - /// Action to subscribe. - private protected void Subscribe(Delegate action) - => this.Channel.Subscriptions.Add(action); - - /// - /// Unsubscribe an expression from this registration. - /// - /// Action to unsubscribe. - private protected void Unsubscribe(Delegate action) - => this.Channel.Subscriptions.Remove(action); - - /// - /// Invoke an action registered for inter-plugin communication. - /// - /// Action arguments. - /// This is thrown when the IPC publisher has not registered an action for calling yet. - private protected void InvokeAction(params object?[]? args) - => this.Channel.InvokeAction(args); - - /// - /// Invoke a function registered for inter-plugin communication. - /// - /// Parameter args. - /// The return value. - /// The return type. - /// This is thrown when the IPC publisher has not registered a func for calling yet. - private protected TRet InvokeFunc(params object?[]? args) - => this.Channel.InvokeFunc(args); - - /// - /// Invoke all actions that have subscribed to this IPC. - /// - /// Delegate arguments. - private protected void SendMessage(params object?[]? args) - => this.Channel.SendMessage(args); } diff --git a/Dalamud/Plugin/PluginLoadReason.cs b/Dalamud/Plugin/PluginLoadReason.cs index ade95ae67..846525b0f 100644 --- a/Dalamud/Plugin/PluginLoadReason.cs +++ b/Dalamud/Plugin/PluginLoadReason.cs @@ -1,32 +1,33 @@ -namespace Dalamud.Plugin; - -/// -/// This enum reflects reasons for loading a plugin. -/// -public enum PluginLoadReason +namespace Dalamud.Plugin { /// - /// We don't know why this plugin was loaded. + /// This enum reflects reasons for loading a plugin. /// - Unknown, + public enum PluginLoadReason + { + /// + /// We don't know why this plugin was loaded. + /// + Unknown, - /// - /// This plugin was loaded because it was installed with the plugin installer. - /// - Installer, + /// + /// This plugin was loaded because it was installed with the plugin installer. + /// + Installer, - /// - /// This plugin was loaded because it was just updated. - /// - Update, + /// + /// This plugin was loaded because it was just updated. + /// + Update, - /// - /// This plugin was loaded because it was told to reload. - /// - Reload, + /// + /// This plugin was loaded because it was told to reload. + /// + Reload, - /// - /// This plugin was loaded because the game was started or Dalamud was reinjected. - /// - Boot, + /// + /// This plugin was loaded because the game was started or Dalamud was reinjected. + /// + Boot, + } } diff --git a/Dalamud/SafeMemory.cs b/Dalamud/SafeMemory.cs index 3a93ad652..32972ec61 100644 --- a/Dalamud/SafeMemory.cs +++ b/Dalamud/SafeMemory.cs @@ -2,262 +2,263 @@ using System; using System.Runtime.InteropServices; using System.Text; -namespace Dalamud; - -/// -/// Class facilitating safe memory access. -/// -/// -/// Attention! The performance of these methods is severely worse than regular calls. -/// Please consider using those instead in performance-critical code. -/// -public static class SafeMemory +namespace Dalamud { - private static readonly IntPtr Handle; - - static SafeMemory() - { - Handle = Imports.GetCurrentProcess(); - } - /// - /// Read a byte array from the current process. + /// Class facilitating safe memory access. /// - /// The address to read from. - /// The amount of bytes to read. - /// The result buffer. - /// Whether or not the read succeeded. - public static bool ReadBytes(IntPtr address, int count, out byte[] buffer) + /// + /// Attention! The performance of these methods is severely worse than regular calls. + /// Please consider using those instead in performance-critical code. + /// + public static class SafeMemory { - buffer = new byte[count <= 0 ? 0 : count]; - return Imports.ReadProcessMemory(Handle, address, buffer, buffer.Length, out _); - } + private static readonly IntPtr Handle; - /// - /// Write a byte array to the current process. - /// - /// The address to write to. - /// The buffer to write. - /// Whether or not the write succeeded. - public static bool WriteBytes(IntPtr address, byte[] buffer) - { - return Imports.WriteProcessMemory(Handle, address, buffer, buffer.Length, out _); - } - - /// - /// Read an object of the specified struct from the current process. - /// - /// The type of the struct. - /// The address to read from. - /// The resulting object. - /// Whether or not the read succeeded. - public static bool Read(IntPtr address, out T result) where T : struct - { - if (!ReadBytes(address, SizeCache.Size, out var buffer)) + static SafeMemory() { - result = default; - return false; + Handle = Imports.GetCurrentProcess(); } - using var mem = new LocalMemory(buffer.Length); - mem.Write(buffer); + /// + /// Read a byte array from the current process. + /// + /// The address to read from. + /// The amount of bytes to read. + /// The result buffer. + /// Whether or not the read succeeded. + public static bool ReadBytes(IntPtr address, int count, out byte[] buffer) + { + buffer = new byte[count <= 0 ? 0 : count]; + return Imports.ReadProcessMemory(Handle, address, buffer, buffer.Length, out _); + } - result = mem.Read(); - return true; - } + /// + /// Write a byte array to the current process. + /// + /// The address to write to. + /// The buffer to write. + /// Whether or not the write succeeded. + public static bool WriteBytes(IntPtr address, byte[] buffer) + { + return Imports.WriteProcessMemory(Handle, address, buffer, buffer.Length, out _); + } - /// - /// Read an array of objects of the specified struct from the current process. - /// - /// The type of the struct. - /// The address to read from. - /// The length of the array. - /// An array of the read objects, or null if any entry of the array failed to read. - public static T[]? Read(IntPtr address, int count) where T : struct - { - var size = SizeOf(); - if (!ReadBytes(address, count * size, out var buffer)) - return null; - var result = new T[count]; - using var mem = new LocalMemory(buffer.Length); - mem.Write(buffer); - for (var i = 0; i < result.Length; i++) - result[i] = mem.Read(i * size); - return result; - } + /// + /// Read an object of the specified struct from the current process. + /// + /// The type of the struct. + /// The address to read from. + /// The resulting object. + /// Whether or not the read succeeded. + public static bool Read(IntPtr address, out T result) where T : struct + { + if (!ReadBytes(address, SizeCache.Size, out var buffer)) + { + result = default; + return false; + } - /// - /// Write a struct to the current process. - /// - /// The type of the struct. - /// The address to write to. - /// The object to write. - /// Whether or not the write succeeded. - public static bool Write(IntPtr address, T obj) where T : struct - { - using var mem = new LocalMemory(SizeCache.Size); - mem.Write(obj); - return WriteBytes(address, mem.Read()); - } + using var mem = new LocalMemory(buffer.Length); + mem.Write(buffer); - /// - /// Write an array of structs to the current process. - /// - /// The type of the structs. - /// The address to write to. - /// The array to write. - /// Whether or not the write succeeded. - public static bool Write(IntPtr address, T[] objArray) where T : struct - { - if (objArray == null || objArray.Length == 0) + result = mem.Read(); return true; - var size = SizeCache.Size; - using var mem = new LocalMemory(objArray.Length * size); - for (var i = 0; i < objArray.Length; i++) - mem.Write(objArray[i], i * size); - return WriteBytes(address, mem.Read()); - } - - /// - /// Read a string from the current process(UTF-8). - /// - /// - /// Attention! This will use the .NET Encoding.UTF8 class to decode the text. - /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, - /// since Encoding.UTF8 destroys the FFXIV payload structure. - /// - /// The address to read from. - /// The maximum length of the string. - /// The read string, or null in case the read was not successful. - public static string? ReadString(IntPtr address, int maxLength = 256) - { - return ReadString(address, Encoding.UTF8, maxLength); - } - - /// - /// Read a string from the current process(UTF-8). - /// - /// - /// Attention! This will use the .NET Encoding class to decode the text. - /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, - /// since Encoding may destroy the FFXIV payload structure. - /// - /// The address to read from. - /// The encoding to use to decode the string. - /// The maximum length of the string. - /// The read string, or null in case the read was not successful. - public static string? ReadString(IntPtr address, Encoding encoding, int maxLength = 256) - { - if (!ReadBytes(address, maxLength, out var buffer)) - return null; - var data = encoding.GetString(buffer); - var eosPos = data.IndexOf('\0'); - return eosPos == -1 ? data : data.Substring(0, eosPos); - } - - /// - /// Write a string to the current process. - /// - /// - /// Attention! This will use the .NET Encoding class to encode the text. - /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, - /// since Encoding may destroy the FFXIV payload structure. - /// - /// The address to write to. - /// The string to write. - /// Whether or not the write succeeded. - public static bool WriteString(IntPtr address, string str) - { - return WriteString(address, str, Encoding.UTF8); - } - - /// - /// Write a string to the current process. - /// - /// - /// Attention! This will use the .NET Encoding class to encode the text. - /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, - /// since Encoding may destroy the FFXIV payload structure. - /// - /// The address to write to. - /// The string to write. - /// The encoding to use. - /// Whether or not the write succeeded. - public static bool WriteString(IntPtr address, string str, Encoding encoding) - { - if (string.IsNullOrEmpty(str)) - return true; - return WriteBytes(address, encoding.GetBytes(str + "\0")); - } - - /// - /// Get the size of the passed type. - /// - /// The type to inspect. - /// The size of the passed type. - public static int SizeOf() where T : struct - { - return SizeCache.Size; - } - - private static class SizeCache - { - // ReSharper disable once StaticMemberInGenericType - public static readonly int Size; - - static SizeCache() - { - var type = typeof(T); - if (type.IsEnum) - type = type.GetEnumUnderlyingType(); - Size = Type.GetTypeCode(type) == TypeCode.Boolean ? 1 : Marshal.SizeOf(type); - } - } - - private static class Imports - { - [DllImport("kernel32", SetLastError = true)] - public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead); - - [DllImport("kernel32", SetLastError = true)] - public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten); - - [DllImport("kernel32", SetLastError = false)] - public static extern IntPtr GetCurrentProcess(); - } - - private sealed class LocalMemory : IDisposable - { - private readonly int size; - - private IntPtr hGlobal; - - public LocalMemory(int size) - { - this.size = size; - this.hGlobal = Marshal.AllocHGlobal(this.size); } - ~LocalMemory() => this.Dispose(); - - public byte[] Read() + /// + /// Read an array of objects of the specified struct from the current process. + /// + /// The type of the struct. + /// The address to read from. + /// The length of the array. + /// An array of the read objects, or null if any entry of the array failed to read. + public static T[]? Read(IntPtr address, int count) where T : struct { - var bytes = new byte[this.size]; - Marshal.Copy(this.hGlobal, bytes, 0, this.size); - return bytes; + var size = SizeOf(); + if (!ReadBytes(address, count * size, out var buffer)) + return null; + var result = new T[count]; + using var mem = new LocalMemory(buffer.Length); + mem.Write(buffer); + for (var i = 0; i < result.Length; i++) + result[i] = mem.Read(i * size); + return result; } - public T Read(int offset = 0) => (T)Marshal.PtrToStructure(this.hGlobal + offset, typeof(T)); - - public void Write(byte[] data, int index = 0) => Marshal.Copy(data, index, this.hGlobal, this.size); - - public void Write(T data, int offset = 0) => Marshal.StructureToPtr(data, this.hGlobal + offset, false); - - public void Dispose() + /// + /// Write a struct to the current process. + /// + /// The type of the struct. + /// The address to write to. + /// The object to write. + /// Whether or not the write succeeded. + public static bool Write(IntPtr address, T obj) where T : struct { - Marshal.FreeHGlobal(this.hGlobal); - this.hGlobal = IntPtr.Zero; - GC.SuppressFinalize(this); + using var mem = new LocalMemory(SizeCache.Size); + mem.Write(obj); + return WriteBytes(address, mem.Read()); + } + + /// + /// Write an array of structs to the current process. + /// + /// The type of the structs. + /// The address to write to. + /// The array to write. + /// Whether or not the write succeeded. + public static bool Write(IntPtr address, T[] objArray) where T : struct + { + if (objArray == null || objArray.Length == 0) + return true; + var size = SizeCache.Size; + using var mem = new LocalMemory(objArray.Length * size); + for (var i = 0; i < objArray.Length; i++) + mem.Write(objArray[i], i * size); + return WriteBytes(address, mem.Read()); + } + + /// + /// Read a string from the current process(UTF-8). + /// + /// + /// Attention! This will use the .NET Encoding.UTF8 class to decode the text. + /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, + /// since Encoding.UTF8 destroys the FFXIV payload structure. + /// + /// The address to read from. + /// The maximum length of the string. + /// The read string, or null in case the read was not successful. + public static string? ReadString(IntPtr address, int maxLength = 256) + { + return ReadString(address, Encoding.UTF8, maxLength); + } + + /// + /// Read a string from the current process(UTF-8). + /// + /// + /// Attention! This will use the .NET Encoding class to decode the text. + /// If you read a FFXIV string, please use ReadBytes and parse the string with the applicable class, + /// since Encoding may destroy the FFXIV payload structure. + /// + /// The address to read from. + /// The encoding to use to decode the string. + /// The maximum length of the string. + /// The read string, or null in case the read was not successful. + public static string? ReadString(IntPtr address, Encoding encoding, int maxLength = 256) + { + if (!ReadBytes(address, maxLength, out var buffer)) + return null; + var data = encoding.GetString(buffer); + var eosPos = data.IndexOf('\0'); + return eosPos == -1 ? data : data.Substring(0, eosPos); + } + + /// + /// Write a string to the current process. + /// + /// + /// Attention! This will use the .NET Encoding class to encode the text. + /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, + /// since Encoding may destroy the FFXIV payload structure. + /// + /// The address to write to. + /// The string to write. + /// Whether or not the write succeeded. + public static bool WriteString(IntPtr address, string str) + { + return WriteString(address, str, Encoding.UTF8); + } + + /// + /// Write a string to the current process. + /// + /// + /// Attention! This will use the .NET Encoding class to encode the text. + /// If you read a FFXIV string, please use WriteBytes with the applicable encoded SeString, + /// since Encoding may destroy the FFXIV payload structure. + /// + /// The address to write to. + /// The string to write. + /// The encoding to use. + /// Whether or not the write succeeded. + public static bool WriteString(IntPtr address, string str, Encoding encoding) + { + if (string.IsNullOrEmpty(str)) + return true; + return WriteBytes(address, encoding.GetBytes(str + "\0")); + } + + /// + /// Get the size of the passed type. + /// + /// The type to inspect. + /// The size of the passed type. + public static int SizeOf() where T : struct + { + return SizeCache.Size; + } + + private static class SizeCache + { + // ReSharper disable once StaticMemberInGenericType + public static readonly int Size; + + static SizeCache() + { + var type = typeof(T); + if (type.IsEnum) + type = type.GetEnumUnderlyingType(); + Size = Type.GetTypeCode(type) == TypeCode.Boolean ? 1 : Marshal.SizeOf(type); + } + } + + private static class Imports + { + [DllImport("kernel32", SetLastError = true)] + public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int nSize, out int lpNumberOfBytesRead); + + [DllImport("kernel32", SetLastError = true)] + public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten); + + [DllImport("kernel32", SetLastError = false)] + public static extern IntPtr GetCurrentProcess(); + } + + private sealed class LocalMemory : IDisposable + { + private readonly int size; + + private IntPtr hGlobal; + + public LocalMemory(int size) + { + this.size = size; + this.hGlobal = Marshal.AllocHGlobal(this.size); + } + + ~LocalMemory() => this.Dispose(); + + public byte[] Read() + { + var bytes = new byte[this.size]; + Marshal.Copy(this.hGlobal, bytes, 0, this.size); + return bytes; + } + + public T Read(int offset = 0) => (T)Marshal.PtrToStructure(this.hGlobal + offset, typeof(T)); + + public void Write(byte[] data, int index = 0) => Marshal.Copy(data, index, this.hGlobal, this.size); + + public void Write(T data, int offset = 0) => Marshal.StructureToPtr(data, this.hGlobal + offset, false); + + public void Dispose() + { + Marshal.FreeHGlobal(this.hGlobal); + this.hGlobal = IntPtr.Zero; + GC.SuppressFinalize(this); + } } } } diff --git a/Dalamud/Service{T}.cs b/Dalamud/Service{T}.cs index dc9e78aa9..203b9f286 100644 --- a/Dalamud/Service{T}.cs +++ b/Dalamud/Service{T}.cs @@ -5,120 +5,121 @@ using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; -namespace Dalamud; - -/// -/// Basic service locator. -/// -/// -/// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI. -/// -/// The class you want to store in the service locator. -internal static class Service where T : class +namespace Dalamud { - private static readonly ModuleLog Log = new("SVC"); - - private static T? instance; - - static Service() - { - } - /// - /// Sets the type in the service locator to the given object. + /// Basic service locator. /// - /// Object to set. - /// The set object. - public static T Set(T obj) + /// + /// Only used internally within Dalamud, if plugins need access to things it should be _only_ via DI. + /// + /// The class you want to store in the service locator. + internal static class Service where T : class { - SetInstanceObject(obj); + private static readonly ModuleLog Log = new("SVC"); - return instance!; - } + private static T? instance; - /// - /// Sets the type in the service locator via the default parameterless constructor. - /// - /// The set object. - public static T Set() - { - if (instance != null) - throw new Exception($"Service {typeof(T).FullName} was set twice"); - - var obj = (T?)Activator.CreateInstance(typeof(T), true); - - SetInstanceObject(obj); - - return instance!; - } - - /// - /// Sets a type in the service locator via a constructor with the given parameter types. - /// - /// Constructor arguments. - /// The set object. - public static T Set(params object[] args) - { - if (args == null) + static Service() { - throw new ArgumentNullException(nameof(args), $"Service locator was passed a null for type {typeof(T).FullName} parameterized constructor "); } - var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding; - var obj = (T?)Activator.CreateInstance(typeof(T), flags, null, args, null, null); - - SetInstanceObject(obj); - - return obj; - } - - /// - /// Attempt to pull the instance out of the service locator. - /// - /// The object if registered. - /// Thrown when the object instance is not present in the service locator. - public static T Get() - { - return instance ?? throw new InvalidOperationException($"{typeof(T).FullName} has not been registered in the service locator!"); - } - - /// - /// Attempt to pull the instance out of the service locator. - /// - /// The object if registered, null otherwise. - public static T? GetNullable() - { - return instance; - } - - private static void SetInstanceObject(T instance) - { - Service.instance = instance ?? throw new ArgumentNullException(nameof(instance), $"Service locator received a null for type {typeof(T).FullName}"); - - var availableToPlugins = RegisterInIoCContainer(instance); - - if (availableToPlugins) - Log.Information($"Registered {typeof(T).FullName} into service locator and exposed to plugins"); - else - Log.Information($"Registered {typeof(T).FullName} into service locator privately"); - } - - private static bool RegisterInIoCContainer(T instance) - { - var attr = typeof(T).GetCustomAttribute(); - if (attr == null) + /// + /// Sets the type in the service locator to the given object. + /// + /// Object to set. + /// The set object. + public static T Set(T obj) { - return false; + SetInstanceObject(obj); + + return instance!; } - var ioc = Service.GetNullable(); - if (ioc == null) + /// + /// Sets the type in the service locator via the default parameterless constructor. + /// + /// The set object. + public static T Set() { - return false; + if (instance != null) + throw new Exception($"Service {typeof(T).FullName} was set twice"); + + var obj = (T?)Activator.CreateInstance(typeof(T), true); + + SetInstanceObject(obj); + + return instance!; } - ioc.RegisterSingleton(instance); + /// + /// Sets a type in the service locator via a constructor with the given parameter types. + /// + /// Constructor arguments. + /// The set object. + public static T Set(params object[] args) + { + if (args == null) + { + throw new ArgumentNullException(nameof(args), $"Service locator was passed a null for type {typeof(T).FullName} parameterized constructor "); + } - return true; + var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding; + var obj = (T?)Activator.CreateInstance(typeof(T), flags, null, args, null, null); + + SetInstanceObject(obj); + + return obj; + } + + /// + /// Attempt to pull the instance out of the service locator. + /// + /// The object if registered. + /// Thrown when the object instance is not present in the service locator. + public static T Get() + { + return instance ?? throw new InvalidOperationException($"{typeof(T).FullName} has not been registered in the service locator!"); + } + + /// + /// Attempt to pull the instance out of the service locator. + /// + /// The object if registered, null otherwise. + public static T? GetNullable() + { + return instance; + } + + private static void SetInstanceObject(T instance) + { + Service.instance = instance ?? throw new ArgumentNullException(nameof(instance), $"Service locator received a null for type {typeof(T).FullName}"); + + var availableToPlugins = RegisterInIoCContainer(instance); + + if (availableToPlugins) + Log.Information($"Registered {typeof(T).FullName} into service locator and exposed to plugins"); + else + Log.Information($"Registered {typeof(T).FullName} into service locator privately"); + } + + private static bool RegisterInIoCContainer(T instance) + { + var attr = typeof(T).GetCustomAttribute(); + if (attr == null) + { + return false; + } + + var ioc = Service.GetNullable(); + if (ioc == null) + { + return false; + } + + ioc.RegisterSingleton(instance); + + return true; + } } } diff --git a/Dalamud/Support/BugBait.cs b/Dalamud/Support/BugBait.cs index 8bdb345dd..9c5db6514 100644 --- a/Dalamud/Support/BugBait.cs +++ b/Dalamud/Support/BugBait.cs @@ -6,66 +6,67 @@ using Dalamud.Plugin.Internal.Types; using Dalamud.Utility; using Newtonsoft.Json; -namespace Dalamud.Support; - -/// -/// Class responsible for sending feedback. -/// -internal static class BugBait +namespace Dalamud.Support { - private const string BugBaitUrl = "https://kiko.goats.dev/feedback"; - /// - /// Send feedback to Discord. + /// Class responsible for sending feedback. /// - /// The plugin to send feedback about. - /// The content of the feedback. - /// The reporter name. - /// Whether or not the most recent exception to occur should be included in the report. - /// A representing the asynchronous operation. - public static async Task SendFeedback(PluginManifest plugin, string content, string reporter, bool includeException) + internal static class BugBait { - if (content.IsNullOrWhitespace()) - return; + private const string BugBaitUrl = "https://kiko.goats.dev/feedback"; - var model = new FeedbackModel + /// + /// Send feedback to Discord. + /// + /// The plugin to send feedback about. + /// The content of the feedback. + /// The reporter name. + /// Whether or not the most recent exception to occur should be included in the report. + /// A representing the asynchronous operation. + public static async Task SendFeedback(PluginManifest plugin, string content, string reporter, bool includeException) { - Content = content, - Reporter = reporter, - Name = plugin.InternalName, - Version = plugin.AssemblyVersion.ToString(), - DalamudHash = Util.GetGitHash(), - }; + if (content.IsNullOrWhitespace()) + return; - if (includeException) - { - model.Exception = Troubleshooting.LastException == null ? "Was included, but none happened" : Troubleshooting.LastException?.ToString(); + var model = new FeedbackModel + { + Content = content, + Reporter = reporter, + Name = plugin.InternalName, + Version = plugin.AssemblyVersion.ToString(), + DalamudHash = Util.GetGitHash(), + }; + + if (includeException) + { + model.Exception = Troubleshooting.LastException == null ? "Was included, but none happened" : Troubleshooting.LastException?.ToString(); + } + + var postContent = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); + var response = await Util.HttpClient.PostAsync(BugBaitUrl, postContent); + + response.EnsureSuccessStatusCode(); } - var postContent = new StringContent(JsonConvert.SerializeObject(model), Encoding.UTF8, "application/json"); - var response = await Util.HttpClient.PostAsync(BugBaitUrl, postContent); + private class FeedbackModel + { + [JsonProperty("content")] + public string? Content { get; set; } - response.EnsureSuccessStatusCode(); - } + [JsonProperty("name")] + public string? Name { get; set; } - private class FeedbackModel - { - [JsonProperty("content")] - public string? Content { get; set; } + [JsonProperty("dhash")] + public string? DalamudHash { get; set; } - [JsonProperty("name")] - public string? Name { get; set; } + [JsonProperty("version")] + public string? Version { get; set; } - [JsonProperty("dhash")] - public string? DalamudHash { get; set; } + [JsonProperty("reporter")] + public string? Reporter { get; set; } - [JsonProperty("version")] - public string? Version { get; set; } - - [JsonProperty("reporter")] - public string? Reporter { get; set; } - - [JsonProperty("exception")] - public string? Exception { get; set; } + [JsonProperty("exception")] + public string? Exception { get; set; } + } } } diff --git a/Dalamud/Support/Troubleshooting.cs b/Dalamud/Support/Troubleshooting.cs index faddbc923..8034bb979 100644 --- a/Dalamud/Support/Troubleshooting.cs +++ b/Dalamud/Support/Troubleshooting.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Dalamud.Configuration; using Dalamud.Configuration.Internal; using Dalamud.Interface.Internal; using Dalamud.Plugin.Internal; @@ -11,109 +12,110 @@ using Dalamud.Utility; using Newtonsoft.Json; using Serilog; -namespace Dalamud.Support; - -/// -/// Class responsible for printing troubleshooting information to the log. -/// -public static class Troubleshooting +namespace Dalamud.Support { /// - /// Gets the most recent exception to occur. + /// Class responsible for printing troubleshooting information to the log. /// - public static Exception? LastException { get; private set; } - - /// - /// Log the last exception in a parseable format to serilog. - /// - /// The exception to log. - /// Additional context. - public static void LogException(Exception exception, string context) + public static class Troubleshooting { - LastException = exception; + /// + /// Gets the most recent exception to occur. + /// + public static Exception? LastException { get; private set; } - try + /// + /// Log the last exception in a parseable format to serilog. + /// + /// The exception to log. + /// Additional context. + public static void LogException(Exception exception, string context) { - var payload = new ExceptionPayload + LastException = exception; + + try { - Context = context, - When = DateTime.Now, - Info = exception.ToString(), - }; + var payload = new ExceptionPayload + { + Context = context, + When = DateTime.Now, + Info = exception.ToString(), + }; - var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); - Log.Information($"LASTEXCEPTION:{encodedPayload}"); - } - catch (Exception ex) - { - Log.Error(ex, "Could not print exception."); - } - } - - /// - /// Log troubleshooting information in a parseable format to Serilog. - /// - internal static void LogTroubleshooting() - { - var startInfo = Service.Get(); - var configuration = Service.Get(); - var interfaceManager = Service.GetNullable(); - var pluginManager = Service.GetNullable(); - - try - { - var payload = new TroubleshootingPayload + var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); + Log.Information($"LASTEXCEPTION:{encodedPayload}"); + } + catch (Exception ex) { - LoadedPlugins = pluginManager?.InstalledPlugins?.Select(x => x.Manifest)?.ToArray(), - DalamudVersion = Util.AssemblyVersion, - DalamudGitHash = Util.GetGitHash(), - GameVersion = startInfo.GameVersion.ToString(), - Language = startInfo.Language.ToString(), - DoDalamudTest = configuration.DoDalamudTest, - DoPluginTest = configuration.DoPluginTest, - InterfaceLoaded = interfaceManager?.IsReady ?? false, - ThirdRepo = configuration.ThirdRepoList, - ForcedMinHook = EnvironmentConfiguration.DalamudForceMinHook, - }; - - var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); - Log.Information($"TROUBLESHOOTING:{encodedPayload}"); + Log.Error(ex, "Could not print exception."); + } } - catch (Exception ex) + + /// + /// Log troubleshooting information in a parseable format to Serilog. + /// + internal static void LogTroubleshooting() { - Log.Error(ex, "Could not print troubleshooting."); + var startInfo = Service.Get(); + var configuration = Service.Get(); + var interfaceManager = Service.GetNullable(); + var pluginManager = Service.GetNullable(); + + try + { + var payload = new TroubleshootingPayload + { + LoadedPlugins = pluginManager?.InstalledPlugins?.Select(x => x.Manifest)?.ToArray(), + DalamudVersion = Util.AssemblyVersion, + DalamudGitHash = Util.GetGitHash(), + GameVersion = startInfo.GameVersion.ToString(), + Language = startInfo.Language.ToString(), + DoDalamudTest = configuration.DoDalamudTest, + DoPluginTest = configuration.DoPluginTest, + InterfaceLoaded = interfaceManager?.IsReady ?? false, + ThirdRepo = configuration.ThirdRepoList, + ForcedMinHook = EnvironmentConfiguration.DalamudForceMinHook, + }; + + var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); + Log.Information($"TROUBLESHOOTING:{encodedPayload}"); + } + catch (Exception ex) + { + Log.Error(ex, "Could not print troubleshooting."); + } } - } - private class ExceptionPayload - { - public DateTime When { get; set; } + private class ExceptionPayload + { + public DateTime When { get; set; } - public string Info { get; set; } + public string Info { get; set; } - public string Context { get; set; } - } + public string Context { get; set; } + } - private class TroubleshootingPayload - { - public PluginManifest[] LoadedPlugins { get; set; } + private class TroubleshootingPayload + { + public PluginManifest[] LoadedPlugins { get; set; } - public string DalamudVersion { get; set; } + public string DalamudVersion { get; set; } - public string DalamudGitHash { get; set; } + public string DalamudGitHash { get; set; } - public string GameVersion { get; set; } + public string GameVersion { get; set; } - public string Language { get; set; } + public string Language { get; set; } - public bool DoDalamudTest { get; set; } + public bool DoDalamudTest { get; set; } - public bool DoPluginTest { get; set; } + public bool DoPluginTest { get; set; } - public bool InterfaceLoaded { get; set; } + public bool InterfaceLoaded { get; set; } - public bool ForcedMinHook { get; set; } + public bool ForcedMinHook { get; set; } - public List ThirdRepo { get; set; } + public List ThirdRepo { get; set; } + } } } diff --git a/Dalamud/Utility/EnumExtensions.cs b/Dalamud/Utility/EnumExtensions.cs index 8868e4c1f..5a2d9172b 100644 --- a/Dalamud/Utility/EnumExtensions.cs +++ b/Dalamud/Utility/EnumExtensions.cs @@ -1,27 +1,28 @@ using System; using System.Linq; -namespace Dalamud.Utility; - -/// -/// Extension methods for enums. -/// -public static class EnumExtensions +namespace Dalamud.Utility { /// - /// Gets an attribute on an enum. + /// Extension methods for enums. /// - /// The type of attribute to get. - /// The enum value that has an attached attribute. - /// The attached attribute, if any. - public static TAttribute GetAttribute(this Enum value) - where TAttribute : Attribute + public static class EnumExtensions { - var type = value.GetType(); - var name = Enum.GetName(type, value); - return type.GetField(name) // I prefer to get attributes this way - .GetCustomAttributes(false) - .OfType() - .SingleOrDefault(); + /// + /// Gets an attribute on an enum. + /// + /// The type of attribute to get. + /// The enum value that has an attached attribute. + /// The attached attribute, if any. + public static TAttribute GetAttribute(this Enum value) + where TAttribute : Attribute + { + var type = value.GetType(); + var name = Enum.GetName(type, value); + return type.GetField(name) // I prefer to get attributes this way + .GetCustomAttributes(false) + .OfType() + .SingleOrDefault(); + } } } diff --git a/Dalamud/Utility/SeStringExtensions.cs b/Dalamud/Utility/SeStringExtensions.cs index 94391f767..dca423eab 100644 --- a/Dalamud/Utility/SeStringExtensions.cs +++ b/Dalamud/Utility/SeStringExtensions.cs @@ -1,17 +1,18 @@ -using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling; -namespace Dalamud.Utility; - -/// -/// Extension methods for SeStrings. -/// -public static class SeStringExtensions +namespace Dalamud.Utility { /// - /// Convert a Lumina SeString into a Dalamud SeString. - /// This conversion re-parses the string. + /// Extension methods for SeStrings. /// - /// The original Lumina SeString. - /// The re-parsed Dalamud SeString. - public static SeString ToDalamudString(this Lumina.Text.SeString originalString) => SeString.Parse(originalString.RawData); + public static class SeStringExtensions + { + /// + /// Convert a Lumina SeString into a Dalamud SeString. + /// This conversion re-parses the string. + /// + /// The original Lumina SeString. + /// The re-parsed Dalamud SeString. + public static SeString ToDalamudString(this Lumina.Text.SeString originalString) => SeString.Parse(originalString.RawData); + } } diff --git a/Dalamud/Utility/StringExtensions.cs b/Dalamud/Utility/StringExtensions.cs index fafb5a7f7..f452a2a0f 100644 --- a/Dalamud/Utility/StringExtensions.cs +++ b/Dalamud/Utility/StringExtensions.cs @@ -1,29 +1,30 @@ -namespace Dalamud.Utility; - -/// -/// Extension methods for strings. -/// -public static class StringExtensions +namespace Dalamud.Utility { /// - /// An extension method to chain usage of string.Format. + /// Extension methods for strings. /// - /// Format string. - /// Format arguments. - /// Formatted string. - public static string Format(this string format, params object[] args) => string.Format(format, args); + public static class StringExtensions + { + /// + /// An extension method to chain usage of string.Format. + /// + /// Format string. + /// Format arguments. + /// Formatted string. + public static string Format(this string format, params object[] args) => string.Format(format, args); - /// - /// Indicates whether the specified string is null or an empty string (""). - /// - /// The string to test. - /// true if the value parameter is null or an empty string (""); otherwise, false. - public static bool IsNullOrEmpty(this string value) => string.IsNullOrEmpty(value); + /// + /// Indicates whether the specified string is null or an empty string (""). + /// + /// The string to test. + /// true if the value parameter is null or an empty string (""); otherwise, false. + public static bool IsNullOrEmpty(this string value) => string.IsNullOrEmpty(value); - /// - /// Indicates whether a specified string is null, empty, or consists only of white-space characters. - /// - /// The string to test. - /// true if the value parameter is null or an empty string (""), or if value consists exclusively of white-space characters. - public static bool IsNullOrWhitespace(this string value) => string.IsNullOrWhiteSpace(value); + /// + /// Indicates whether a specified string is null, empty, or consists only of white-space characters. + /// + /// The string to test. + /// true if the value parameter is null or an empty string (""), or if value consists exclusively of white-space characters. + public static bool IsNullOrWhitespace(this string value) => string.IsNullOrWhiteSpace(value); + } } diff --git a/Dalamud/Utility/TexFileExtensions.cs b/Dalamud/Utility/TexFileExtensions.cs index 5abea692a..ddfccba9c 100644 --- a/Dalamud/Utility/TexFileExtensions.cs +++ b/Dalamud/Utility/TexFileExtensions.cs @@ -1,31 +1,32 @@ using ImGuiScene; using Lumina.Data.Files; -namespace Dalamud.Utility; - -/// -/// Extensions to . -/// -public static class TexFileExtensions +namespace Dalamud.Utility { /// - /// Returns the image data formatted for . + /// Extensions to . /// - /// The TexFile to format. - /// The formatted image data. - public static byte[] GetRgbaImageData(this TexFile texFile) + public static class TexFileExtensions { - var imageData = texFile.ImageData; - var dst = new byte[imageData.Length]; - - for (var i = 0; i < dst.Length; i += 4) + /// + /// Returns the image data formatted for . + /// + /// The TexFile to format. + /// The formatted image data. + public static byte[] GetRgbaImageData(this TexFile texFile) { - dst[i] = imageData[i + 2]; - dst[i + 1] = imageData[i + 1]; - dst[i + 2] = imageData[i]; - dst[i + 3] = imageData[i + 3]; - } + var imageData = texFile.ImageData; + var dst = new byte[imageData.Length]; - return dst; + for (var i = 0; i < dst.Length; i += 4) + { + dst[i] = imageData[i + 2]; + dst[i + 1] = imageData[i + 1]; + dst[i + 2] = imageData[i]; + dst[i + 3] = imageData[i + 3]; + } + + return dst; + } } } diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 55f6ae3d3..8a39b7aea 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -15,280 +15,281 @@ using ImGuiNET; using Microsoft.Win32; using Serilog; -namespace Dalamud.Utility; - -/// -/// Class providing various helper methods for use in Dalamud and plugins. -/// -public static class Util +namespace Dalamud.Utility { - private static string gitHashInternal; - /// - /// Gets an httpclient for usage. - /// Do NOT await this. + /// Class providing various helper methods for use in Dalamud and plugins. /// - public static HttpClient HttpClient { get; } = new(); - - /// - /// Gets the assembly version of Dalamud. - /// - public static string AssemblyVersion { get; } = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); - - /// - /// Gets the git hash value from the assembly - /// or null if it cannot be found. - /// - /// The git hash of the assembly. - public static string GetGitHash() + public static class Util { - if (gitHashInternal != null) + private static string gitHashInternal; + + /// + /// Gets an httpclient for usage. + /// Do NOT await this. + /// + public static HttpClient HttpClient { get; } = new(); + + /// + /// Gets the assembly version of Dalamud. + /// + public static string AssemblyVersion { get; } = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); + + /// + /// Gets the git hash value from the assembly + /// or null if it cannot be found. + /// + /// The git hash of the assembly. + public static string GetGitHash() + { + if (gitHashInternal != null) + return gitHashInternal; + + var asm = typeof(Util).Assembly; + var attrs = asm.GetCustomAttributes(); + + gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value; + return gitHashInternal; - - var asm = typeof(Util).Assembly; - var attrs = asm.GetCustomAttributes(); - - gitHashInternal = attrs.FirstOrDefault(a => a.Key == "GitHash")?.Value; - - return gitHashInternal; - } - - /// - /// Read memory from an offset and hexdump them via Serilog. - /// - /// The offset to read from. - /// The length to read. - public static void DumpMemory(IntPtr offset, int len = 512) - { - try - { - SafeMemory.ReadBytes(offset, len, out var data); - Log.Information(ByteArrayToHex(data)); } - catch (Exception ex) + + /// + /// Read memory from an offset and hexdump them via Serilog. + /// + /// The offset to read from. + /// The length to read. + public static void DumpMemory(IntPtr offset, int len = 512) { - Log.Error(ex, "Read failed"); - } - } - - /// - /// Create a hexdump of the provided bytes. - /// - /// The bytes to hexdump. - /// The offset in the byte array to start at. - /// The amount of bytes to display per line. - /// The generated hexdump in string form. - public static string ByteArrayToHex(byte[] bytes, int offset = 0, int bytesPerLine = 16) - { - if (bytes == null) return string.Empty; - - var hexChars = "0123456789ABCDEF".ToCharArray(); - - var offsetBlock = 8 + 3; - var byteBlock = offsetBlock + (bytesPerLine * 3) + ((bytesPerLine - 1) / 8) + 2; - var lineLength = byteBlock + bytesPerLine + Environment.NewLine.Length; - - var line = (new string(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); - var numLines = (bytes.Length + bytesPerLine - 1) / bytesPerLine; - - var sb = new StringBuilder(numLines * lineLength); - - for (var i = 0; i < bytes.Length; i += bytesPerLine) - { - var h = i + offset; - - line[0] = hexChars[(h >> 28) & 0xF]; - line[1] = hexChars[(h >> 24) & 0xF]; - line[2] = hexChars[(h >> 20) & 0xF]; - line[3] = hexChars[(h >> 16) & 0xF]; - line[4] = hexChars[(h >> 12) & 0xF]; - line[5] = hexChars[(h >> 8) & 0xF]; - line[6] = hexChars[(h >> 4) & 0xF]; - line[7] = hexChars[(h >> 0) & 0xF]; - - var hexColumn = offsetBlock; - var charColumn = byteBlock; - - for (var j = 0; j < bytesPerLine; j++) + try { - if (j > 0 && (j & 7) == 0) hexColumn++; + SafeMemory.ReadBytes(offset, len, out var data); + Log.Information(ByteArrayToHex(data)); + } + catch (Exception ex) + { + Log.Error(ex, "Read failed"); + } + } - if (i + j >= bytes.Length) + /// + /// Create a hexdump of the provided bytes. + /// + /// The bytes to hexdump. + /// The offset in the byte array to start at. + /// The amount of bytes to display per line. + /// The generated hexdump in string form. + public static string ByteArrayToHex(byte[] bytes, int offset = 0, int bytesPerLine = 16) + { + if (bytes == null) return string.Empty; + + var hexChars = "0123456789ABCDEF".ToCharArray(); + + var offsetBlock = 8 + 3; + var byteBlock = offsetBlock + (bytesPerLine * 3) + ((bytesPerLine - 1) / 8) + 2; + var lineLength = byteBlock + bytesPerLine + Environment.NewLine.Length; + + var line = (new string(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); + var numLines = (bytes.Length + bytesPerLine - 1) / bytesPerLine; + + var sb = new StringBuilder(numLines * lineLength); + + for (var i = 0; i < bytes.Length; i += bytesPerLine) + { + var h = i + offset; + + line[0] = hexChars[(h >> 28) & 0xF]; + line[1] = hexChars[(h >> 24) & 0xF]; + line[2] = hexChars[(h >> 20) & 0xF]; + line[3] = hexChars[(h >> 16) & 0xF]; + line[4] = hexChars[(h >> 12) & 0xF]; + line[5] = hexChars[(h >> 8) & 0xF]; + line[6] = hexChars[(h >> 4) & 0xF]; + line[7] = hexChars[(h >> 0) & 0xF]; + + var hexColumn = offsetBlock; + var charColumn = byteBlock; + + for (var j = 0; j < bytesPerLine; j++) { - line[hexColumn] = ' '; - line[hexColumn + 1] = ' '; - line[charColumn] = ' '; - } - else - { - var by = bytes[i + j]; - line[hexColumn] = hexChars[(by >> 4) & 0xF]; - line[hexColumn + 1] = hexChars[by & 0xF]; - line[charColumn] = by < 32 ? '.' : (char)by; + if (j > 0 && (j & 7) == 0) hexColumn++; + + if (i + j >= bytes.Length) + { + line[hexColumn] = ' '; + line[hexColumn + 1] = ' '; + line[charColumn] = ' '; + } + else + { + var by = bytes[i + j]; + line[hexColumn] = hexChars[(by >> 4) & 0xF]; + line[hexColumn + 1] = hexChars[by & 0xF]; + line[charColumn] = by < 32 ? '.' : (char)by; + } + + hexColumn += 3; + charColumn++; } - hexColumn += 3; - charColumn++; + sb.Append(line); } - sb.Append(line); + return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); } - return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray()); - } - - /// - /// Show all properties and fields of the provided object via ImGui. - /// - /// The object to show. - public static void ShowObject(object obj) - { - var type = obj.GetType(); - - ImGui.Text($"Object Dump({type.Name}) for {obj}({obj.GetHashCode()})"); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.TextColored(ImGuiColors.DalamudOrange, "-> Properties:"); - - ImGui.Indent(); - - foreach (var propertyInfo in type.GetProperties()) + /// + /// Show all properties and fields of the provided object via ImGui. + /// + /// The object to show. + public static void ShowObject(object obj) { - ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: {propertyInfo.GetValue(obj)}"); + var type = obj.GetType(); + + ImGui.Text($"Object Dump({type.Name}) for {obj}({obj.GetHashCode()})"); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.TextColored(ImGuiColors.DalamudOrange, "-> Properties:"); + + ImGui.Indent(); + + foreach (var propertyInfo in type.GetProperties()) + { + ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: {propertyInfo.GetValue(obj)}"); + } + + ImGui.Unindent(); + + ImGuiHelpers.ScaledDummy(5); + + ImGui.TextColored(ImGuiColors.HealerGreen, "-> Fields:"); + + ImGui.Indent(); + + foreach (var fieldInfo in type.GetFields()) + { + ImGui.TextColored(ImGuiColors.HealerGreen, $" {fieldInfo.Name}: {fieldInfo.GetValue(obj)}"); + } + + ImGui.Unindent(); } - ImGui.Unindent(); - - ImGuiHelpers.ScaledDummy(5); - - ImGui.TextColored(ImGuiColors.HealerGreen, "-> Fields:"); - - ImGui.Indent(); - - foreach (var fieldInfo in type.GetFields()) + /// + /// Display an error MessageBox and exit the current process. + /// + /// MessageBox body. + /// MessageBox caption (title). + public static void Fatal(string message, string caption) { - ImGui.TextColored(ImGuiColors.HealerGreen, $" {fieldInfo.Name}: {fieldInfo.GetValue(obj)}"); + var flags = NativeFunctions.MessageBoxType.Ok | NativeFunctions.MessageBoxType.IconError; + _ = NativeFunctions.MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); + + Environment.Exit(-1); } - ImGui.Unindent(); - } - - /// - /// Display an error MessageBox and exit the current process. - /// - /// MessageBox body. - /// MessageBox caption (title). - public static void Fatal(string message, string caption) - { - var flags = NativeFunctions.MessageBoxType.Ok | NativeFunctions.MessageBoxType.IconError; - _ = NativeFunctions.MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); - - Environment.Exit(-1); - } - - /// - /// Retrieve a UTF8 string from a null terminated byte array. - /// - /// A null terminated UTF8 byte array. - /// A UTF8 encoded string. - public static string GetUTF8String(byte[] array) - { - var count = 0; - for (; count < array.Length; count++) + /// + /// Retrieve a UTF8 string from a null terminated byte array. + /// + /// A null terminated UTF8 byte array. + /// A UTF8 encoded string. + public static string GetUTF8String(byte[] array) { - if (array[count] == 0) - break; + var count = 0; + for (; count < array.Length; count++) + { + if (array[count] == 0) + break; + } + + string text; + if (count == array.Length) + { + text = Encoding.UTF8.GetString(array); + Log.Warning($"Warning: text exceeds underlying array length ({text})"); + } + else + { + text = Encoding.UTF8.GetString(array, 0, count); + } + + return text; } - string text; - if (count == array.Length) + /// + /// Compress a string using GZip. + /// + /// The input string. + /// The compressed output bytes. + public static byte[] CompressString(string str) { - text = Encoding.UTF8.GetString(array); - Log.Warning($"Warning: text exceeds underlying array length ({text})"); + var bytes = Encoding.UTF8.GetBytes(str); + + using var msi = new MemoryStream(bytes); + using var mso = new MemoryStream(); + using var gs = new GZipStream(mso, CompressionMode.Compress); + + CopyTo(msi, gs); + + return mso.ToArray(); } - else + + /// + /// Decompress a string using GZip. + /// + /// The input bytes. + /// The compressed output string. + public static string DecompressString(byte[] bytes) { - text = Encoding.UTF8.GetString(array, 0, count); + using var msi = new MemoryStream(bytes); + using var mso = new MemoryStream(); + using var gs = new GZipStream(msi, CompressionMode.Decompress); + + CopyTo(gs, mso); + + return Encoding.UTF8.GetString(mso.ToArray()); } - return text; - } - - /// - /// Compress a string using GZip. - /// - /// The input string. - /// The compressed output bytes. - public static byte[] CompressString(string str) - { - var bytes = Encoding.UTF8.GetBytes(str); - - using var msi = new MemoryStream(bytes); - using var mso = new MemoryStream(); - using var gs = new GZipStream(mso, CompressionMode.Compress); - - CopyTo(msi, gs); - - return mso.ToArray(); - } - - /// - /// Decompress a string using GZip. - /// - /// The input bytes. - /// The compressed output string. - public static string DecompressString(byte[] bytes) - { - using var msi = new MemoryStream(bytes); - using var mso = new MemoryStream(); - using var gs = new GZipStream(msi, CompressionMode.Decompress); - - CopyTo(gs, mso); - - return Encoding.UTF8.GetString(mso.ToArray()); - } - - /// - /// Copy one stream to another. - /// - /// The source stream. - /// The destination stream. - /// The maximum length to copy. - public static void CopyTo(Stream src, Stream dest, int len = 4069) - { - var bytes = new byte[len]; - int cnt; - - while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0) dest.Write(bytes, 0, cnt); - } - - /// - /// Heuristically determine if Dalamud is running on Linux/WINE. - /// - /// Whether or not Dalamud is running on Linux/WINE. - public static bool IsLinux() - { - bool Check1() + /// + /// Copy one stream to another. + /// + /// The source stream. + /// The destination stream. + /// The maximum length to copy. + public static void CopyTo(Stream src, Stream dest, int len = 4069) { - return EnvironmentConfiguration.XlWineOnLinux; + var bytes = new byte[len]; + int cnt; + + while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0) dest.Write(bytes, 0, cnt); } - bool Check2() + /// + /// Heuristically determine if Dalamud is running on Linux/WINE. + /// + /// Whether or not Dalamud is running on Linux/WINE. + public static bool IsLinux() { - var hModule = NativeFunctions.GetModuleHandleW("ntdll.dll"); - var proc1 = NativeFunctions.GetProcAddress(hModule, "wine_get_version"); - var proc2 = NativeFunctions.GetProcAddress(hModule, "wine_get_build_id"); + bool Check1() + { + return EnvironmentConfiguration.XlWineOnLinux; + } - return proc1 != IntPtr.Zero || proc2 != IntPtr.Zero; + bool Check2() + { + var hModule = NativeFunctions.GetModuleHandleW("ntdll.dll"); + var proc1 = NativeFunctions.GetProcAddress(hModule, "wine_get_version"); + var proc2 = NativeFunctions.GetProcAddress(hModule, "wine_get_build_id"); + + return proc1 != IntPtr.Zero || proc2 != IntPtr.Zero; + } + + bool Check3() + { + return Registry.CurrentUser.OpenSubKey(@"Software\Wine") != null || + Registry.LocalMachine.OpenSubKey(@"Software\Wine") != null; + } + + return Check1() || Check2() || Check3(); } - - bool Check3() - { - return Registry.CurrentUser.OpenSubKey(@"Software\Wine") != null || - Registry.LocalMachine.OpenSubKey(@"Software\Wine") != null; - } - - return Check1() || Check2() || Check3(); } } diff --git a/Dalamud/Utility/VectorExtensions.cs b/Dalamud/Utility/VectorExtensions.cs index f617c8420..0a14299c2 100644 --- a/Dalamud/Utility/VectorExtensions.cs +++ b/Dalamud/Utility/VectorExtensions.cs @@ -1,51 +1,52 @@ using System.Numerics; -namespace Dalamud.Utility; - -/// -/// Extension methods for System.Numerics.VectorN and SharpDX.VectorN. -/// -public static class VectorExtensions +namespace Dalamud.Utility { /// - /// Converts a SharpDX vector to System.Numerics. + /// Extension methods for System.Numerics.VectorN and SharpDX.VectorN. /// - /// Vector to convert. - /// A converted vector. - public static Vector2 ToSystem(this SharpDX.Vector2 vec) => new(x: vec.X, y: vec.Y); + public static class VectorExtensions + { + /// + /// Converts a SharpDX vector to System.Numerics. + /// + /// Vector to convert. + /// A converted vector. + public static Vector2 ToSystem(this SharpDX.Vector2 vec) => new(x: vec.X, y: vec.Y); - /// - /// Converts a SharpDX vector to System.Numerics. - /// - /// Vector to convert. - /// A converted vector. - public static Vector3 ToSystem(this SharpDX.Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); + /// + /// Converts a SharpDX vector to System.Numerics. + /// + /// Vector to convert. + /// A converted vector. + public static Vector3 ToSystem(this SharpDX.Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); - /// - /// Converts a SharpDX vector to System.Numerics. - /// - /// Vector to convert. - /// A converted vector. - public static Vector4 ToSystem(this SharpDX.Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); + /// + /// Converts a SharpDX vector to System.Numerics. + /// + /// Vector to convert. + /// A converted vector. + public static Vector4 ToSystem(this SharpDX.Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); - /// - /// Converts a System.Numerics vector to SharpDX. - /// - /// Vector to convert. - /// A converted vector. - public static SharpDX.Vector2 ToSharpDX(this Vector2 vec) => new(x: vec.X, y: vec.Y); + /// + /// Converts a System.Numerics vector to SharpDX. + /// + /// Vector to convert. + /// A converted vector. + public static SharpDX.Vector2 ToSharpDX(this Vector2 vec) => new(x: vec.X, y: vec.Y); - /// - /// Converts a System.Numerics vector to SharpDX. - /// - /// Vector to convert. - /// A converted vector. - public static SharpDX.Vector3 ToSharpDX(this Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); + /// + /// Converts a System.Numerics vector to SharpDX. + /// + /// Vector to convert. + /// A converted vector. + public static SharpDX.Vector3 ToSharpDX(this Vector3 vec) => new(x: vec.X, y: vec.Y, z: vec.Z); - /// - /// Converts a System.Numerics vector to SharpDX. - /// - /// Vector to convert. - /// A converted vector. - public static SharpDX.Vector4 ToSharpDX(this Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); + /// + /// Converts a System.Numerics vector to SharpDX. + /// + /// Vector to convert. + /// A converted vector. + public static SharpDX.Vector4 ToSharpDX(this Vector4 vec) => new(x: vec.X, y: vec.Y, z: vec.Z, w: vec.W); + } }