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 0000000..0a8e7c2 Binary files /dev/null and b/icon.png differ diff --git a/icon.svg b/icon.svg new file mode 100755 index 0000000..95182db --- /dev/null +++ b/icon.svg @@ -0,0 +1,76 @@ + +