From 581b469d791c371b3f8a4b1a78203d2ce19c1795 Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Tue, 24 Aug 2021 01:43:09 -0400 Subject: [PATCH] refactor: move to net5 --- ExpandedSearchInfo/ExpandedSearchInfo.csproj | 16 +++- ExpandedSearchInfo/ExpandedSearchInfo.yaml | 3 + ExpandedSearchInfo/FodyWeavers.xml | 3 - ExpandedSearchInfo/GameFunctions.cs | 15 ++-- ExpandedSearchInfo/Plugin.cs | 40 ++++++--- ExpandedSearchInfo/PluginUi.cs | 42 +++++----- .../Providers/BaseHtmlProvider.cs | 2 +- ExpandedSearchInfo/Providers/CarrdProvider.cs | 4 +- ExpandedSearchInfo/Providers/FListProvider.cs | 12 +-- ExpandedSearchInfo/Providers/IProvider.cs | 4 +- .../Providers/PastebinProvider.cs | 6 +- .../Providers/PlainTextProvider.cs | 8 +- .../Providers/RefsheetProvider.cs | 10 ++- ExpandedSearchInfo/SearchInfoRepository.cs | 39 +++++---- icon.png | Bin 0 -> 17234 bytes icon.svg | 76 ++++++++++++++++++ 16 files changed, 194 insertions(+), 86 deletions(-) delete mode 100755 ExpandedSearchInfo/FodyWeavers.xml create mode 100644 icon.png create mode 100755 icon.svg diff --git a/ExpandedSearchInfo/ExpandedSearchInfo.csproj b/ExpandedSearchInfo/ExpandedSearchInfo.csproj index 701406c..708a27c 100755 --- a/ExpandedSearchInfo/ExpandedSearchInfo.csproj +++ b/ExpandedSearchInfo/ExpandedSearchInfo.csproj @@ -1,11 +1,13 @@ - net48 + net5-windows 1.3.4 latest enable true + false + true @@ -13,6 +15,10 @@ $(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll False + + $(AppData)\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll + False + $(AppData)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll False @@ -29,10 +35,12 @@ - - - + + + + + diff --git a/ExpandedSearchInfo/ExpandedSearchInfo.yaml b/ExpandedSearchInfo/ExpandedSearchInfo.yaml index dc7ff6c..26f3985 100644 --- a/ExpandedSearchInfo/ExpandedSearchInfo.yaml +++ b/ExpandedSearchInfo/ExpandedSearchInfo.yaml @@ -1,5 +1,6 @@ author: ascclemens name: Expanded Search Info +punchline: Displays extra information pulled from search info when examining. description: |- Displays extra information pulled from search info when examining. @@ -12,4 +13,6 @@ description: |- Simply examine someone with a search info containing pointers to one of these locations and the plugin will display information automatically. + + Icon: expand by Gregor Cresnar from the Noun Project repo_url: https://git.sr.ht/~jkcclemens/ExpandedSearchInfo diff --git a/ExpandedSearchInfo/FodyWeavers.xml b/ExpandedSearchInfo/FodyWeavers.xml deleted file mode 100755 index 2dfb1f4..0000000 --- a/ExpandedSearchInfo/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/ExpandedSearchInfo/GameFunctions.cs b/ExpandedSearchInfo/GameFunctions.cs index ef9d9fe..d1c8cc7 100644 --- a/ExpandedSearchInfo/GameFunctions.cs +++ b/ExpandedSearchInfo/GameFunctions.cs @@ -1,8 +1,7 @@ using System; -using System.Runtime.InteropServices; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Hooking; -using Dalamud.Plugin; +using Dalamud.Logging; namespace ExpandedSearchInfo { public class GameFunctions : IDisposable { @@ -12,15 +11,15 @@ namespace ExpandedSearchInfo { private readonly Hook? _searchInfoDownloadedHook; - internal delegate void ReceiveSearchInfoEventDelegate(int actorId, SeString info); + internal delegate void ReceiveSearchInfoEventDelegate(uint objectId, SeString info); internal event ReceiveSearchInfoEventDelegate? ReceiveSearchInfo; internal GameFunctions(Plugin plugin) { this.Plugin = plugin; - var sidPtr = this.Plugin.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 56 48 83 EC 20 49 8B E8 8B DA"); - this._searchInfoDownloadedHook = new Hook(sidPtr, new SearchInfoDownloadedDelegate(this.SearchInfoDownloaded)); + var sidPtr = this.Plugin.SigScanner.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 56 48 83 EC 20 49 8B E8 8B DA"); + this._searchInfoDownloadedHook = new Hook(sidPtr, this.SearchInfoDownloaded); this._searchInfoDownloadedHook.Enable(); } @@ -28,14 +27,14 @@ namespace ExpandedSearchInfo { this._searchInfoDownloadedHook?.Dispose(); } - private byte SearchInfoDownloaded(IntPtr data, IntPtr a2, IntPtr searchInfoPtr, IntPtr a4) { + private unsafe byte SearchInfoDownloaded(IntPtr data, IntPtr a2, IntPtr searchInfoPtr, IntPtr a4) { var result = this._searchInfoDownloadedHook!.Original(data, a2, searchInfoPtr, a4); try { // Updated: 4.5 - var actorId = Marshal.ReadInt32(data + 48); + var actorId = *(uint*) (data + 48); - var searchInfo = this.Plugin.Interface.SeStringManager.ReadRawSeString(searchInfoPtr); + var searchInfo = this.Plugin.SeStringManager.ReadRawSeString(searchInfoPtr); this.ReceiveSearchInfo?.Invoke(actorId, searchInfo); } catch (Exception ex) { diff --git a/ExpandedSearchInfo/Plugin.cs b/ExpandedSearchInfo/Plugin.cs index c03910a..df9234a 100644 --- a/ExpandedSearchInfo/Plugin.cs +++ b/ExpandedSearchInfo/Plugin.cs @@ -1,4 +1,9 @@ -using Dalamud.Game.Command; +using Dalamud.Game; +using Dalamud.Game.ClientState.Objects; +using Dalamud.Game.Command; +using Dalamud.Game.Gui; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.IoC; using Dalamud.Plugin; namespace ExpandedSearchInfo { @@ -6,15 +11,30 @@ namespace ExpandedSearchInfo { public class Plugin : IDalamudPlugin { public string Name => "Expanded Search Info"; - internal PluginConfiguration Config { get; private set; } = null!; - internal DalamudPluginInterface Interface { get; private set; } = null!; - internal GameFunctions Functions { get; private set; } = null!; - internal SearchInfoRepository Repository { get; private set; } = null!; - private PluginUi Ui { get; set; } = null!; + [PluginService] + internal DalamudPluginInterface Interface { get; init; } = null!; - public void Initialize(DalamudPluginInterface pluginInterface) { - this.Interface = pluginInterface; + [PluginService] + internal CommandManager CommandManager { get; init; } = null!; + [PluginService] + internal GameGui GameGui { get; init; } = null!; + + [PluginService] + internal ObjectTable ObjectTable { get; init; } = null!; + + [PluginService] + internal SeStringManager SeStringManager { get; init; } = null!; + + [PluginService] + internal SigScanner SigScanner { get; init; } = null!; + + internal PluginConfiguration Config { get; } + internal GameFunctions Functions { get; } + internal SearchInfoRepository Repository { get; } + private PluginUi Ui { get; } + + public Plugin() { this.Config = (PluginConfiguration?) this.Interface.GetPluginConfig() ?? new PluginConfiguration(); this.Config.Initialise(this); @@ -22,13 +42,13 @@ namespace ExpandedSearchInfo { this.Repository = new SearchInfoRepository(this); this.Ui = new PluginUi(this); - this.Interface.CommandManager.AddHandler("/esi", new CommandInfo(this.OnCommand) { + this.CommandManager.AddHandler("/esi", new CommandInfo(this.OnCommand) { HelpMessage = "Toggles Expanded Search Info's configuration window", }); } public void Dispose() { - this.Interface.CommandManager.RemoveHandler("/esi"); + this.CommandManager.RemoveHandler("/esi"); this.Ui.Dispose(); this.Repository.Dispose(); this.Functions.Dispose(); diff --git a/ExpandedSearchInfo/PluginUi.cs b/ExpandedSearchInfo/PluginUi.cs index e9c10c6..2f200fe 100644 --- a/ExpandedSearchInfo/PluginUi.cs +++ b/ExpandedSearchInfo/PluginUi.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Numerics; using Dalamud.Interface; +using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; namespace ExpandedSearchInfo { @@ -18,17 +19,17 @@ namespace ExpandedSearchInfo { internal PluginUi(Plugin plugin) { this.Plugin = plugin; - this.Plugin.Interface.UiBuilder.OnBuildUi += this.Draw; - this.Plugin.Interface.UiBuilder.OnOpenConfigUi += this.OnOpenConfigUi; + this.Plugin.Interface.UiBuilder.Draw += this.Draw; + this.Plugin.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi; } - private void OnOpenConfigUi(object sender, EventArgs e) { + private void OnOpenConfigUi() { this.ConfigVisible = true; } public void Dispose() { - this.Plugin.Interface.UiBuilder.OnOpenConfigUi -= this.OnOpenConfigUi; - this.Plugin.Interface.UiBuilder.OnBuildUi -= this.Draw; + this.Plugin.Interface.UiBuilder.OpenConfigUi -= this.OnOpenConfigUi; + this.Plugin.Interface.UiBuilder.Draw -= this.Draw; } private static bool IconButton(FontAwesomeIcon icon, string? id = null) { @@ -128,30 +129,31 @@ namespace ExpandedSearchInfo { ImGui.End(); } - private void DrawExpandedSearchInfo() { + private unsafe void DrawExpandedSearchInfo() { // check if the examine window is open - var addon = this.Plugin.Interface.Framework.Gui.GetAddonByName("CharacterInspect", 1); - if (addon is not {Visible: true}) { + var addonPtr = this.Plugin.GameGui.GetAddonByName("CharacterInspect", 1); + if (addonPtr == IntPtr.Zero) { + return; + } + + var addon = (AtkUnitBase*) addonPtr; + if (addon->IsVisible) { return; } // get examine window info - float width; - float height; - short x; - short y; - - try { - width = addon.Width; - height = addon.Height; - x = addon.X; - y = addon.Y; - } catch (Exception) { + var rootNode = addon->RootNode; + if (rootNode == null) { return; } + var width = rootNode->Width * addon->Scale; + var height = rootNode->Height * addon->Scale; + var x = addon->X; + var y = addon->Y; + // check the last actor id recorded (should be who the examine window is showing) - var actorId = this.Plugin.Repository.LastActorId; + var actorId = this.Plugin.Repository.LastObjectId; if (actorId == 0 || !this.Plugin.Repository.SearchInfos.TryGetValue(actorId, out var expanded)) { return; } diff --git a/ExpandedSearchInfo/Providers/BaseHtmlProvider.cs b/ExpandedSearchInfo/Providers/BaseHtmlProvider.cs index f57e979..e1e2f0f 100644 --- a/ExpandedSearchInfo/Providers/BaseHtmlProvider.cs +++ b/ExpandedSearchInfo/Providers/BaseHtmlProvider.cs @@ -24,7 +24,7 @@ namespace ExpandedSearchInfo.Providers { public abstract bool Matches(Uri uri); - public abstract IEnumerable? ExtractUris(int actorId, string info); + public abstract IEnumerable? ExtractUris(uint objectId, string info); public abstract Task ExtractInfo(HttpResponseMessage response); diff --git a/ExpandedSearchInfo/Providers/CarrdProvider.cs b/ExpandedSearchInfo/Providers/CarrdProvider.cs index bc28ebe..49f0f4d 100644 --- a/ExpandedSearchInfo/Providers/CarrdProvider.cs +++ b/ExpandedSearchInfo/Providers/CarrdProvider.cs @@ -34,7 +34,7 @@ namespace ExpandedSearchInfo.Providers { public override bool Matches(Uri uri) => Domains.Any(domain => uri.Host.EndsWith(domain)); - public override IEnumerable? ExtractUris(int actorId, string info) => null; + public override IEnumerable? ExtractUris(uint objectId, string info) => null; public override async Task ExtractInfo(HttpResponseMessage response) { var document = await this.DownloadDocument(response); @@ -83,7 +83,7 @@ namespace ExpandedSearchInfo.Providers { return new TextSection( this, $"{document.Title} (Carrd)", - response.RequestMessage.RequestUri, + response.RequestMessage!.RequestUri!, text ); } diff --git a/ExpandedSearchInfo/Providers/FListProvider.cs b/ExpandedSearchInfo/Providers/FListProvider.cs index 59ab496..1733df0 100644 --- a/ExpandedSearchInfo/Providers/FListProvider.cs +++ b/ExpandedSearchInfo/Providers/FListProvider.cs @@ -27,19 +27,19 @@ namespace ExpandedSearchInfo.Providers { public override void DrawConfig() { } - public override bool Matches(Uri uri) => (uri.Host == "www.f-list.net" || uri.Host == "f-list.net") && uri.AbsolutePath.StartsWith("/c/"); + public override bool Matches(Uri uri) => uri.Host is "www.f-list.net" or "f-list.net" && uri.AbsolutePath.StartsWith("/c/"); - public override IEnumerable? ExtractUris(int actorId, string info) { + public override IEnumerable? ExtractUris(uint objectId, string info) { if (!info.ToLowerInvariant().Contains("c/")) { return null; } - var actor = this.Plugin.Interface.ClientState.Actors.FirstOrDefault(actor => actor.ActorId == actorId); - if (actor == null) { + var obj = this.Plugin.ObjectTable.FirstOrDefault(obj => obj.ObjectId == objectId); + if (obj == null) { return null; } - var safeName = actor.Name.Replace("'", ""); + var safeName = obj.Name.ToString().Replace("'", ""); return new[] { new Uri($"https://www.f-list.net/c/{Uri.EscapeUriString(safeName)}"), @@ -99,7 +99,7 @@ namespace ExpandedSearchInfo.Providers { return new FListSection( this, $"{charName} (F-List)", - response.RequestMessage.RequestUri, + response.RequestMessage!.RequestUri!, info, stats, fave, diff --git a/ExpandedSearchInfo/Providers/IProvider.cs b/ExpandedSearchInfo/Providers/IProvider.cs index b0cd31c..dfa2dd1 100644 --- a/ExpandedSearchInfo/Providers/IProvider.cs +++ b/ExpandedSearchInfo/Providers/IProvider.cs @@ -34,10 +34,10 @@ namespace ExpandedSearchInfo.Providers { /// For providers that require Uris, this can return null. /// For providers that don't require Uris, this must return a Uri extracted from the given search info. /// - /// The actor ID associated with the search info + /// The actor ID associated with the search info /// A character's full search info /// null for providers that require Uris, a Uri for providers that don't - IEnumerable? ExtractUris(int actorId, string info); + IEnumerable? ExtractUris(uint objectId, string info); /// /// Extract the search info to be displayed given the HTTP response from a Uri. diff --git a/ExpandedSearchInfo/Providers/PastebinProvider.cs b/ExpandedSearchInfo/Providers/PastebinProvider.cs index 1a5fea9..ccb1a58 100644 --- a/ExpandedSearchInfo/Providers/PastebinProvider.cs +++ b/ExpandedSearchInfo/Providers/PastebinProvider.cs @@ -30,7 +30,7 @@ namespace ExpandedSearchInfo.Providers { public bool Matches(Uri uri) => uri.Host == "pastebin.com" && uri.AbsolutePath.Length > 1; - public IEnumerable? ExtractUris(int actorId, string info) { + public IEnumerable? ExtractUris(uint objectId, string info) { var matches = Matcher.Matches(info); return matches.Count == 0 ? null @@ -38,11 +38,11 @@ namespace ExpandedSearchInfo.Providers { } public async Task ExtractInfo(HttpResponseMessage response) { - if (response.Content.Headers.ContentType.MediaType != "text/plain") { + if (response.Content.Headers.ContentType?.MediaType != "text/plain") { return null; } - var id = response.RequestMessage.RequestUri.AbsolutePath.Split('/').LastOrDefault(); + var id = response.RequestMessage!.RequestUri!.AbsolutePath.Split('/').LastOrDefault(); var info = await response.Content.ReadAsStringAsync(); diff --git a/ExpandedSearchInfo/Providers/PlainTextProvider.cs b/ExpandedSearchInfo/Providers/PlainTextProvider.cs index 28258fc..c4d9b28 100644 --- a/ExpandedSearchInfo/Providers/PlainTextProvider.cs +++ b/ExpandedSearchInfo/Providers/PlainTextProvider.cs @@ -26,17 +26,17 @@ namespace ExpandedSearchInfo.Providers { public bool Matches(Uri uri) => true; - public IEnumerable? ExtractUris(int actorId, string info) => null; + public IEnumerable? ExtractUris(uint objectId, string info) => null; public async Task ExtractInfo(HttpResponseMessage response) { - if (response.Content.Headers.ContentType.MediaType != "text/plain") { + if (response.Content.Headers.ContentType?.MediaType != "text/plain") { return null; } var info = await response.Content.ReadAsStringAsync(); - var uri = response.RequestMessage.RequestUri; - return new TextSection(this, $"Text##{uri}", response.RequestMessage.RequestUri, info); + var uri = response.RequestMessage!.RequestUri!; + return new TextSection(this, $"Text##{uri}", uri, info); } } } diff --git a/ExpandedSearchInfo/Providers/RefsheetProvider.cs b/ExpandedSearchInfo/Providers/RefsheetProvider.cs index a989862..d0616c9 100644 --- a/ExpandedSearchInfo/Providers/RefsheetProvider.cs +++ b/ExpandedSearchInfo/Providers/RefsheetProvider.cs @@ -29,9 +29,9 @@ namespace ExpandedSearchInfo.Providers { public override void DrawConfig() { } - public override bool Matches(Uri uri) => uri.Host == "refsheet.net" || uri.Host == "ref.st"; + public override bool Matches(Uri uri) => uri.Host is "refsheet.net" or "ref.st"; - public override IEnumerable? ExtractUris(int actorId, string info) => null; + public override IEnumerable? ExtractUris(uint objectId, string info) => null; public override async Task ExtractInfo(HttpResponseMessage response) { var document = await this.DownloadDocument(response); @@ -52,6 +52,10 @@ namespace ExpandedSearchInfo.Providers { var json = jsonLine.Substring(JsonLineStart.Length, jsonLine.Length - JsonLineStart.Length - 1); var parsed = JsonConvert.DeserializeObject(json); + if (parsed == null) { + return null; + } + var character = parsed.EagerLoad.Character; // get character name @@ -113,7 +117,7 @@ namespace ExpandedSearchInfo.Providers { return new RefsheetSection( this, $"{name} (Refsheet)", - response.RequestMessage.RequestUri, + response.RequestMessage!.RequestUri!, attributes, notes, cards diff --git a/ExpandedSearchInfo/SearchInfoRepository.cs b/ExpandedSearchInfo/SearchInfoRepository.cs index 7940148..ca2b1af 100644 --- a/ExpandedSearchInfo/SearchInfoRepository.cs +++ b/ExpandedSearchInfo/SearchInfoRepository.cs @@ -4,10 +4,9 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; -using System.Threading; using System.Threading.Tasks; using Dalamud.Game.Text.SeStringHandling; -using Dalamud.Plugin; +using Dalamud.Logging; using ExpandedSearchInfo.Providers; using ExpandedSearchInfo.Sections; using Nager.PublicSuffix; @@ -26,8 +25,8 @@ namespace ExpandedSearchInfo { public class SearchInfoRepository : IDisposable { private Plugin Plugin { get; } private DomainParser Parser { get; } - internal ConcurrentDictionary SearchInfos { get; } = new(); - internal int LastActorId { get; private set; } + internal ConcurrentDictionary SearchInfos { get; } = new(); + internal uint LastObjectId { get; private set; } private List Providers { get; } = new(); internal IEnumerable AllProviders => this.Providers; @@ -62,48 +61,48 @@ namespace ExpandedSearchInfo { this.Providers.Add(new PlainTextProvider(this.Plugin)); } - private void ProcessSearchInfo(int actorId, SeString raw) { - this.LastActorId = actorId; + private void ProcessSearchInfo(uint objectId, SeString raw) { + this.LastObjectId = objectId; var info = raw.TextValue; // if empty search info, short circuit if (string.IsNullOrWhiteSpace(info)) { // remove any existing search info - this.SearchInfos.TryRemove(actorId, out _); + this.SearchInfos.TryRemove(objectId, out _); return; } // check to see if info has changed #if RELEASE - if (this.SearchInfos.TryGetValue(actorId, out var existing)) { + if (this.SearchInfos.TryGetValue(objectId, out var existing)) { if (existing.Info == info) { return; } } #endif - new Thread(async () => { + Task.Run(async () => { try { - await this.DoExtraction(actorId, info); + await this.DoExtraction(objectId, info); } catch (Exception ex) { PluginLog.LogError($"Error in extraction thread:\n{ex}"); } - }).Start(); + }); } - private async Task DoExtraction(int actorId, string info) { + private async Task DoExtraction(uint objectId, string info) { var downloadUris = new List(); // extract uris from the search info with providers var extractedUris = this.Providers .Where(provider => provider.Config.Enabled && provider.ExtractsUris) - .Select(provider => provider.ExtractUris(actorId, info)) + .Select(provider => provider.ExtractUris(objectId, info)) .Where(uris => uris != null) - .SelectMany(uris => uris); + .SelectMany(uris => uris!); // add the extracted uris to the list - downloadUris.AddRange(extractedUris!); + downloadUris.AddRange(extractedUris); // go word-by-word and try to parse a uri foreach (var word in info.Split(' ', '\n', '\r')) { @@ -128,15 +127,15 @@ namespace ExpandedSearchInfo { // if there were no uris found or extracted, remove existing search info and stop if (downloadUris.Count == 0) { - this.SearchInfos.TryRemove(actorId, out _); + this.SearchInfos.TryRemove(objectId, out _); return; } // do the downloads - await this.DownloadAndExtract(actorId, info, downloadUris); + await this.DownloadAndExtract(objectId, info, downloadUris); } - private async Task DownloadAndExtract(int actorId, string info, IEnumerable uris) { + private async Task DownloadAndExtract(uint objectId, string info, IEnumerable uris) { var handler = new HttpClientHandler { UseCookies = true, AllowAutoRedirect = true, @@ -183,12 +182,12 @@ namespace ExpandedSearchInfo { // remove expanded search info if no sections resulted if (sections.Count == 0) { - this.SearchInfos.TryRemove(actorId, out _); + this.SearchInfos.TryRemove(objectId, out _); return; } // otherwise set the expanded search info for this actor id - this.SearchInfos[actorId] = new ExpandedSearchInfo(info, sections); + this.SearchInfos[objectId] = new ExpandedSearchInfo(info, sections); } } } diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0a8e7c2acfe5fdd08dc2d8b6d7a3fb94189ada4c GIT binary patch literal 17234 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 +