diff --git a/RetainerTrack/Handlers/GameHooks.cs b/RetainerTrack/Handlers/GameHooks.cs index 6a93a74..6e933b3 100644 --- a/RetainerTrack/Handlers/GameHooks.cs +++ b/RetainerTrack/Handlers/GameHooks.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using Dalamud.Hooking; using Dalamud.Memory; +using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using Microsoft.Extensions.Logging; @@ -35,13 +36,13 @@ namespace RetainerTrack.Handlers #pragma warning restore CS0649 - public GameHooks(ILogger logger, PersistenceContext persistenceContext) + public GameHooks(ILogger logger, PersistenceContext persistenceContext, IGameInteropProvider gameInteropProvider) { _logger = logger; _persistenceContext = persistenceContext; _logger.LogDebug("Initializing game hooks"); - SignatureHelper.Initialise(this); + gameInteropProvider.InitializeFromAttributes(this); CharacterNameResultHook.Enable(); SocialListResultHook.Enable(); diff --git a/RetainerTrack/Handlers/MarketBoardOfferingsHandler.cs b/RetainerTrack/Handlers/MarketBoardOfferingsHandler.cs new file mode 100644 index 0000000..40bc556 --- /dev/null +++ b/RetainerTrack/Handlers/MarketBoardOfferingsHandler.cs @@ -0,0 +1,79 @@ +using System; +using System.Threading.Tasks; +using Dalamud.Game.Network.Structures; +using Dalamud.Hooking; +using Dalamud.Plugin.Services; +using FFXIVClientStructs.FFXIV.Client.UI; +using FFXIVClientStructs.FFXIV.Client.UI.Info; +using Microsoft.Extensions.Logging; + +namespace RetainerTrack.Handlers +{ + internal sealed class MarketBoardOfferingsHandler : IDisposable + { + private unsafe delegate void* MarketBoardOfferings(InfoProxyItemSearch* a1, nint packetData); + + private readonly ILogger _logger; + private readonly IClientState _clientState; + private readonly PersistenceContext _persistenceContext; + private readonly Hook _marketBoardOfferingsHook; + + public unsafe MarketBoardOfferingsHandler( + ILogger logger, + IClientState clientState, + IGameGui gameGui, + IGameInteropProvider gameInteropProvider, + PersistenceContext persistenceContext) + { + _logger = logger; + _clientState = clientState; + _persistenceContext = persistenceContext; + + _logger.LogDebug("Setting up offerings hook"); + var uiModule = (UIModule*)gameGui.GetUIModule(); + var infoModule = uiModule->GetInfoModule(); + var proxy = infoModule->GetInfoProxyById(11); + _marketBoardOfferingsHook = + gameInteropProvider.HookFromAddress((nint)proxy->vtbl[12], + MarketBoardOfferingsDetour); + _marketBoardOfferingsHook.Enable(); + _logger.LogDebug("Offerings hook enabled successfully"); + } + + public void Dispose() + { + _marketBoardOfferingsHook.Dispose(); + } + + // adapted from https://github.com/tesu/PennyPincher/commit/0f9b3963fd4a6e9b87f585ee491d4de59a93f7a3 + private unsafe void* MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetData) + { + try + { + if (packetData != nint.Zero) + { + ParseOfferings(packetData); + } + } + catch (Exception e) + { + _logger.LogError(e, "Could not parse marketboard offerings."); + } + + return _marketBoardOfferingsHook.Original(a1, packetData); + } + + private void ParseOfferings(nint dataPtr) + { + ushort worldId = (ushort?)_clientState.LocalPlayer?.CurrentWorld.Id ?? 0; + if (worldId == 0) + { + _logger.LogInformation("Skipping market board handler, current world unknown"); + return; + } + + var listings = MarketBoardCurrentOfferings.Read(dataPtr); + Task.Run(() => _persistenceContext.HandleMarketBoardPage(listings, worldId)); + } + } +} diff --git a/RetainerTrack/Handlers/MarketBoardUIHandler.cs b/RetainerTrack/Handlers/MarketBoardUIHandler.cs index adca723..6bd957b 100644 --- a/RetainerTrack/Handlers/MarketBoardUIHandler.cs +++ b/RetainerTrack/Handlers/MarketBoardUIHandler.cs @@ -1,7 +1,8 @@ using System; -using Dalamud.Game; -using Dalamud.Game.Gui; +using Dalamud.Game.Addon.Lifecycle; +using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; using Dalamud.Hooking; +using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; using Microsoft.Extensions.Logging; @@ -10,55 +11,33 @@ namespace RetainerTrack.Handlers { internal sealed unsafe class MarketBoardUiHandler : IDisposable { + private const string AddonName = "ItemSearchResult"; + private readonly ILogger _logger; - private readonly Framework _framework; - private readonly GameGui _gameGui; private readonly PersistenceContext _persistenceContext; - - private Hook? _drawHook; - - private delegate void Draw(AtkUnitBase* addon); + private readonly IAddonLifecycle _addonLifecycle; public MarketBoardUiHandler( ILogger logger, - Framework framework, - GameGui gameGui, - PersistenceContext persistenceContext) + PersistenceContext persistenceContext, + IAddonLifecycle addonLifecycle) { _logger = logger; - _framework = framework; - _gameGui = gameGui; _persistenceContext = persistenceContext; + _addonLifecycle = addonLifecycle; - _framework.Update += FrameworkUpdate; + _addonLifecycle.RegisterListener(AddonEvent.PreDraw, AddonName, PreDraw); } - private AddonItemSearchResult* ItemSearchResult => (AddonItemSearchResult*)_gameGui.GetAddonByName("ItemSearchResult"); - - private void FrameworkUpdate(Framework framework) + private void PreDraw(AddonEvent type, AddonArgs args) { - var addon = ItemSearchResult; - if (addon == null) - return; - - _drawHook ??= Hook.FromAddress( - new nint(addon->AtkUnitBase.AtkEventListener.vfunc[42]), - AddonDraw); - _drawHook.Enable(); - _framework.Update -= FrameworkUpdate; + UpdateRetainerNames((AddonItemSearchResult*)args.Addon); } - private void AddonDraw(AtkUnitBase* addon) - { - UpdateRetainerNames(); - _drawHook!.Original(addon); - } - - private void UpdateRetainerNames() + private void UpdateRetainerNames(AddonItemSearchResult* addon) { try { - var addon = ItemSearchResult; if (addon == null || !addon->AtkUnitBase.IsVisible) return; @@ -95,8 +74,7 @@ namespace RetainerTrack.Handlers public void Dispose() { - _drawHook?.Dispose(); - _framework.Update -= FrameworkUpdate; + _addonLifecycle.UnregisterListener(AddonEvent.PreDraw, AddonName, PreDraw); } } } diff --git a/RetainerTrack/Handlers/NetworkHandler.cs b/RetainerTrack/Handlers/NetworkHandler.cs deleted file mode 100644 index 326971c..0000000 --- a/RetainerTrack/Handlers/NetworkHandler.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading.Tasks; -using Dalamud.Data; -using Dalamud.Game.ClientState; -using Dalamud.Game.Network; -using Dalamud.Game.Network.Structures; -using Microsoft.Extensions.Logging; -using Framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework; - -namespace RetainerTrack.Handlers -{ - internal sealed class NetworkHandler : IDisposable - { - private readonly ILogger _logger; - private readonly GameNetwork _gameNetwork; - private readonly DataManager _dataManager; - private readonly ClientState _clientState; - private readonly PersistenceContext _persistenceContext; - - public NetworkHandler( - ILogger logger, - GameNetwork gameNetwork, - DataManager dataManager, - ClientState clientState, - PersistenceContext persistenceContext) - { - _logger = logger; - _gameNetwork = gameNetwork; - _dataManager = dataManager; - _clientState = clientState; - _persistenceContext = persistenceContext; - - _gameNetwork.NetworkMessage += NetworkMessage; - } - - public void Dispose() - { - _gameNetwork.NetworkMessage -= NetworkMessage; - } - - private void NetworkMessage(nint dataPtr, ushort opcode, uint sourceActorId, uint targetActorId, - NetworkMessageDirection direction) - { - if (direction != NetworkMessageDirection.ZoneDown || !_dataManager.IsDataReady) - return; - - if (opcode == _dataManager.ServerOpCodes["MarketBoardOfferings"]) - { - ushort worldId = (ushort?)_clientState.LocalPlayer?.CurrentWorld.Id ?? 0; - if (worldId == 0) - { - _logger.LogInformation("Skipping market board handler, current world unknown"); - return; - } - - var listings = MarketBoardCurrentOfferings.Read(dataPtr); - Task.Run(() => _persistenceContext.HandleMarketBoardPage(listings, worldId)); - } - } - } -} diff --git a/RetainerTrack/Handlers/PartyHandler.cs b/RetainerTrack/Handlers/PartyHandler.cs index db1eaed..7ab02e7 100644 --- a/RetainerTrack/Handlers/PartyHandler.cs +++ b/RetainerTrack/Handlers/PartyHandler.cs @@ -1,22 +1,21 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Dalamud.Game; -using Dalamud.Game.ClientState; using Dalamud.Memory; +using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Group; namespace RetainerTrack.Handlers { internal sealed class PartyHandler : IDisposable { - private readonly Framework _framework; - private readonly ClientState _clientState; + private readonly IFramework _framework; + private readonly IClientState _clientState; private readonly PersistenceContext _persistenceContext; private long _lastUpdate = 0; - public PartyHandler(Framework framework, ClientState clientState, PersistenceContext persistenceContext) + public PartyHandler(IFramework framework, IClientState clientState, PersistenceContext persistenceContext) { _framework = framework; _clientState = clientState; @@ -25,7 +24,7 @@ namespace RetainerTrack.Handlers _framework.Update += FrameworkUpdate; } - private unsafe void FrameworkUpdate(Framework _) + private unsafe void FrameworkUpdate(IFramework _) { long now = Environment.TickCount64; if (!_clientState.IsLoggedIn || _clientState.IsPvPExcludingDen || now - _lastUpdate < 180_000) diff --git a/RetainerTrack/Handlers/PersistenceContext.cs b/RetainerTrack/Handlers/PersistenceContext.cs index 0b8c277..e5877b3 100644 --- a/RetainerTrack/Handlers/PersistenceContext.cs +++ b/RetainerTrack/Handlers/PersistenceContext.cs @@ -2,8 +2,8 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using Dalamud.Game.ClientState; using Dalamud.Game.Network.Structures; +using Dalamud.Plugin.Services; using LiteDB; using Microsoft.Extensions.Logging; using RetainerTrack.Database; @@ -13,12 +13,12 @@ namespace RetainerTrack.Handlers internal sealed class PersistenceContext { private readonly ILogger _logger; - private readonly ClientState _clientState; + private readonly IClientState _clientState; private readonly LiteDatabase _liteDatabase; private readonly ConcurrentDictionary> _worldRetainerCache = new(); private readonly ConcurrentDictionary _playerNameCache = new(); - public PersistenceContext(ILogger logger, ClientState clientState, + public PersistenceContext(ILogger logger, IClientState clientState, LiteDatabase liteDatabase) { _logger = logger; diff --git a/RetainerTrack/RetainerTrack.csproj b/RetainerTrack/RetainerTrack.csproj index 7eda85a..3ad06d5 100644 --- a/RetainerTrack/RetainerTrack.csproj +++ b/RetainerTrack/RetainerTrack.csproj @@ -2,7 +2,7 @@ net7.0-windows - 1.0 + 2.0 11.0 enable win-x64 @@ -25,9 +25,9 @@ - - - + + + diff --git a/RetainerTrack/RetainerTrackPlugin.cs b/RetainerTrack/RetainerTrackPlugin.cs index 2d80e0b..62fe8ee 100644 --- a/RetainerTrack/RetainerTrackPlugin.cs +++ b/RetainerTrack/RetainerTrackPlugin.cs @@ -1,11 +1,7 @@ using System.IO; -using Dalamud.Data; using Dalamud.Extensions.MicrosoftLogging; -using Dalamud.Game; -using Dalamud.Game.ClientState; -using Dalamud.Game.Gui; -using Dalamud.Game.Network; using Dalamud.Plugin; +using Dalamud.Plugin.Services; using LiteDB; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -19,27 +15,26 @@ namespace RetainerTrack { private readonly ServiceProvider? _serviceProvider; - public string Name => "RetainerTrack"; - public RetainerTrackPlugin( DalamudPluginInterface pluginInterface, - GameNetwork gameNetwork, - DataManager dataManager, - Framework framework, - ClientState clientState, - GameGui gameGui) + IFramework framework, + IClientState clientState, + IGameGui gameGui, + IGameInteropProvider gameInteropProvider, + IAddonLifecycle addonLifecycle, + IPluginLog pluginLog) { ServiceCollection serviceCollection = new(); serviceCollection.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace) .ClearProviders() - .AddDalamudLogger(this)); + .AddDalamudLogger(pluginLog)); serviceCollection.AddSingleton(this); serviceCollection.AddSingleton(pluginInterface); - serviceCollection.AddSingleton(gameNetwork); - serviceCollection.AddSingleton(dataManager); serviceCollection.AddSingleton(framework); serviceCollection.AddSingleton(clientState); serviceCollection.AddSingleton(gameGui); + serviceCollection.AddSingleton(gameInteropProvider); + serviceCollection.AddSingleton(addonLifecycle); serviceCollection.AddSingleton(_ => new LiteDatabase(new ConnectionString @@ -50,7 +45,7 @@ namespace RetainerTrack })); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); @@ -64,7 +59,7 @@ namespace RetainerTrack .EnsureIndex(x => x.Id); _serviceProvider.GetRequiredService(); - _serviceProvider.GetRequiredService(); + _serviceProvider.GetRequiredService(); _serviceProvider.GetRequiredService(); _serviceProvider.GetRequiredService(); } diff --git a/RetainerTrack/packages.lock.json b/RetainerTrack/packages.lock.json index 5668a04..9bf043f 100644 --- a/RetainerTrack/packages.lock.json +++ b/RetainerTrack/packages.lock.json @@ -4,24 +4,24 @@ "net7.0-windows7.0": { "Dalamud.Extensions.MicrosoftLogging": { "type": "Direct", - "requested": "[1.0.0, )", - "resolved": "1.0.0", - "contentHash": "nPjMrT9n9GJ+TYF1lyVhlvhmFyN4ajMX2ccclgyMc8MNpOGZwxrJ4VEtrUUk7UkuX2wAhtnNsjrcf5sER3/CbA==", + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "qp2idn5GuPouUxHHFytMrorbhlcupsgPdO87HjxlBfTY+JID+qoTfPmA5V6HBP1a4DuXGPbk4JtoO/hMmnQrtw==", "dependencies": { "Microsoft.Extensions.Logging": "7.0.0" } }, "DalamudPackager": { "type": "Direct", - "requested": "[2.1.10, )", - "resolved": "2.1.10", - "contentHash": "S6NrvvOnLgT4GDdgwuKVJjbFo+8ZEj+JsEYk9ojjOR/MMfv1dIFpT8aRJQfI24rtDcw1uF+GnSSMN4WW1yt7fw==" + "requested": "[2.1.12, )", + "resolved": "2.1.12", + "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" }, "LiteDB": { "type": "Direct", - "requested": "[5.0.15, )", - "resolved": "5.0.15", - "contentHash": "nucyfCOGSATH553BxplxExP3BOqEwmHt0B57426EIaQjD3CC1Odb52VVCGgTxyYaD2oe3B/cJk8jDo6XiBJqPg==" + "requested": "[5.0.17, )", + "resolved": "5.0.17", + "contentHash": "cKPvkdlzIts3ZKu/BzoIc/Y71e4VFKlij4LyioPFATZMot+wB7EAm1FFbZSJez6coJmQUoIg/3yHE1MMU+zOdg==" }, "Microsoft.Extensions.DependencyInjection": { "type": "Direct",