diff --git a/Dalamud/Data/DataManager.cs b/Dalamud/Data/DataManager.cs index 6195532ab..b08c6ffe7 100644 --- a/Dalamud/Data/DataManager.cs +++ b/Dalamud/Data/DataManager.cs @@ -37,26 +37,10 @@ internal sealed class DataManager : IDisposable, IServiceType, IDataManager { this.Language = (ClientLanguage)dalamud.StartInfo.Language; - // Set up default values so plugins do not null-reference when data is being loaded. - this.ClientOpCodes = this.ServerOpCodes = new ReadOnlyDictionary(new Dictionary()); - - var baseDir = dalamud.AssetDirectory.FullName; 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); - + using (Timings.Start("Lumina Init")) { var luminaOptions = new LuminaOptions @@ -130,17 +114,6 @@ internal sealed class DataManager : IDisposable, IServiceType, IDataManager /// public ClientLanguage Language { get; private set; } - /// - /// Gets a list of server opcodes. - /// - public ReadOnlyDictionary ServerOpCodes { get; private set; } - - /// - /// Gets a list of client opcodes. - /// - [UsedImplicitly] - public ReadOnlyDictionary ClientOpCodes { get; private set; } - /// public GameData GameData { get; private set; } diff --git a/Dalamud/Game/Network/Internal/NetworkHandlers.cs b/Dalamud/Game/Network/Internal/NetworkHandlers.cs index 77bf99c1b..01e92a373 100644 --- a/Dalamud/Game/Network/Internal/NetworkHandlers.cs +++ b/Dalamud/Game/Network/Internal/NetworkHandlers.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Dalamud.Configuration.Internal; @@ -14,7 +12,9 @@ using Dalamud.Game.Gui; using Dalamud.Game.Network.Internal.MarketBoardUploaders; using Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis; using Dalamud.Game.Network.Structures; +using Dalamud.Hooking; using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.UI.Info; using Lumina.Excel.GeneratedSheets; using Serilog; @@ -24,16 +24,30 @@ namespace Dalamud.Game.Network.Internal; /// This class handles network notifications and uploading market board data. /// [ServiceManager.EarlyLoadedService] -internal class NetworkHandlers : IDisposable, IServiceType +internal unsafe class NetworkHandlers : IDisposable, IServiceType { private readonly IMarketBoardUploader uploader; - private readonly IObservable messages; + private readonly IObservable mbPurchaseObservable; + private readonly IObservable mbHistoryObservable; + private readonly IObservable mbTaxesObservable; + private readonly IObservable mbItemRequestObservable; + private readonly IObservable mbOfferingsObservable; + private readonly IObservable mbPurchaseSentObservable; private readonly IDisposable handleMarketBoardItemRequest; private readonly IDisposable handleMarketTaxRates; private readonly IDisposable handleMarketBoardPurchaseHandler; - private readonly IDisposable handleCfPop; + + private readonly NetworkHandlersAddressResolver addressResolver; + + private readonly Hook cfPopHook; + private readonly Hook mbPurchaseHook; + private readonly Hook mbHistoryHook; + private readonly Hook customTalkHook; // used for marketboard taxes + private readonly Hook mbItemRequestStartHook; + private readonly Hook mbOfferingsHook; + private readonly Hook mbSendPurchaseRequestHook; [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -41,42 +55,156 @@ internal class NetworkHandlers : IDisposable, IServiceType private bool disposing; [ServiceManager.ServiceConstructor] - private NetworkHandlers(GameNetwork gameNetwork) + private NetworkHandlers(GameNetwork gameNetwork, TargetSigScanner sigScanner) { this.uploader = new UniversalisMarketBoardUploader(); + + this.addressResolver = new NetworkHandlersAddressResolver(); + this.addressResolver.Setup(sigScanner); + this.CfPop = _ => { }; - this.messages = Observable.Create(observer => + this.mbPurchaseObservable = Observable.Create(observer => { - void Observe(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) - { - var dataManager = Service.GetNullable(); - observer.OnNext(new NetworkMessage - { - DataManager = dataManager, - Data = dataPtr, - Opcode = opCode, - SourceActorId = sourceActorId, - TargetActorId = targetActorId, - Direction = direction, - }); - } + this.MarketBoardPurchaseReceived += Observe; + return () => { this.MarketBoardPurchaseReceived -= Observe; }; - gameNetwork.NetworkMessage += Observe; - return () => { gameNetwork.NetworkMessage -= Observe; }; + void Observe(nint packetPtr) + { + observer.OnNext(MarketBoardPurchase.Read(packetPtr)); + } + }); + + this.mbHistoryObservable = Observable.Create(observer => + { + this.MarketBoardHistoryReceived += Observe; + return () => { this.MarketBoardHistoryReceived -= Observe; }; + + void Observe(nint packetPtr) + { + observer.OnNext(MarketBoardHistory.Read(packetPtr)); + } + }); + + this.mbTaxesObservable = Observable.Create(observer => + { + this.MarketBoardTaxesReceived += Observe; + return () => { this.MarketBoardTaxesReceived -= Observe; }; + + void Observe(nint dataPtr) + { + // n.b. we precleared the packet information so we're sure that this is *just* tax rate info. + observer.OnNext(MarketTaxRates.ReadFromCustomTalk(dataPtr)); + } + }); + + this.mbItemRequestObservable = Observable.Create(observer => + { + this.MarketBoardItemRequestStartReceived += Observe; + return () => this.MarketBoardItemRequestStartReceived -= Observe; + + void Observe(nint dataPtr) + { + observer.OnNext(MarketBoardItemRequest.Read(dataPtr)); + } + }); + + this.mbOfferingsObservable = Observable.Create(observer => + { + this.MarketBoardOfferingsReceived += Observe; + return () => { this.MarketBoardOfferingsReceived -= Observe; }; + + void Observe(nint packetPtr) + { + observer.OnNext(MarketBoardCurrentOfferings.Read(packetPtr)); + } + }); + + this.mbPurchaseSentObservable = Observable.Create(observer => + { + this.MarketBoardPurchaseRequestSent += Observe; + return () => { this.MarketBoardPurchaseRequestSent -= Observe; }; + + void Observe(nint dataPtr) + { + // fortunately, this dataptr has the same structure as the sent packet. + observer.OnNext(MarketBoardPurchaseHandler.Read(dataPtr)); + } }); this.handleMarketBoardItemRequest = this.HandleMarketBoardItemRequest(); this.handleMarketTaxRates = this.HandleMarketTaxRates(); this.handleMarketBoardPurchaseHandler = this.HandleMarketBoardPurchaseHandler(); - this.handleCfPop = this.HandleCfPop(); + + this.mbPurchaseHook = + Hook.FromAddress( + this.addressResolver.MarketBoardPurchasePacketHandler, + this.MarketPurchasePacketDetour); + this.mbPurchaseHook.Enable(); + + this.mbHistoryHook = + Hook.FromAddress( + this.addressResolver.MarketBoardHistoryPacketHandler, + this.MarketHistoryPacketDetour); + this.mbHistoryHook.Enable(); + + this.customTalkHook = + Hook.FromAddress( + this.addressResolver.CustomTalkEventResponsePacketHandler, + this.CustomTalkReceiveResponseDetour); + this.customTalkHook.Enable(); + + this.mbItemRequestStartHook = Hook.FromAddress( + this.addressResolver.MarketBoardItemRequestStartPacketHandler, + this.MarketItemRequestStartDetour); + this.mbItemRequestStartHook.Enable(); + + this.mbOfferingsHook = Hook.FromAddress( + this.addressResolver.InfoProxyItemSearchAddPage, + this.MarketBoardOfferingsDetour); + this.mbOfferingsHook.Enable(); + + this.mbSendPurchaseRequestHook = Hook.FromAddress( + this.addressResolver.BuildMarketBoardPurchaseHandlerPacket, + this.MarketBoardSendPurchaseRequestDetour); + this.mbSendPurchaseRequestHook.Enable(); + + this.cfPopHook = Hook.FromAddress(this.addressResolver.CfPopPacketHandler, this.CfPopDetour); + this.cfPopHook.Enable(); } + private delegate nint MarketBoardPurchasePacketHandler(nint a1, nint packetRef); + + private delegate nint MarketBoardHistoryPacketHandler(nint self, nint packetData, uint a3, char a4); + + private delegate void CustomTalkReceiveResponse( + nuint a1, ushort eventId, byte responseId, uint* args, byte argCount); + + private delegate nint MarketBoardItemRequestStartPacketHandler(nint a1, nint packetRef); + + private delegate byte InfoProxyItemSearchAddPage(nint self, nint packetRef); + + private delegate byte MarketBoardSendPurchaseRequestPacket(InfoProxyItemSearch* infoProxy); + + private delegate nint CfPopDelegate(nint packetData); + /// /// Event which gets fired when a duty is ready. /// public event Action CfPop; + private event Action? MarketBoardPurchaseReceived; + + private event Action? MarketBoardHistoryReceived; + + private event Action? MarketBoardTaxesReceived; + + private event Action? MarketBoardItemRequestStartReceived; + + private event Action? MarketBoardOfferingsReceived; + + private event Action? MarketBoardPurchaseRequestSent; + /// /// Disposes of managed and unmanaged resources. /// @@ -98,81 +226,75 @@ internal class NetworkHandlers : IDisposable, IServiceType this.handleMarketBoardItemRequest.Dispose(); this.handleMarketTaxRates.Dispose(); this.handleMarketBoardPurchaseHandler.Dispose(); - this.handleCfPop.Dispose(); + + this.mbPurchaseHook.Dispose(); + this.mbHistoryHook.Dispose(); + this.customTalkHook.Dispose(); + this.mbItemRequestStartHook.Dispose(); + this.mbOfferingsHook.Dispose(); + this.mbSendPurchaseRequestHook.Dispose(); + this.cfPopHook.Dispose(); } - private IObservable OnNetworkMessage() + private unsafe nint CfPopDetour(nint packetData) { - return this.messages.Where(message => message.DataManager?.IsDataReady == true); - } + var result = this.cfPopHook.OriginalDisposeSafe(packetData); - private IObservable OnMarketBoardItemRequestStart() - { - return this.OnNetworkMessage() - .Where(message => message.Direction == NetworkMessageDirection.ZoneDown) - .Where(message => message.Opcode == - message.DataManager?.ServerOpCodes["MarketBoardItemRequestStart"]) - .Select(message => MarketBoardItemRequest.Read(message.Data)); - } + try + { + using var stream = new UnmanagedMemoryStream((byte*)packetData, 64); + using var reader = new BinaryReader(stream); - private IObservable OnMarketBoardOfferings() - { - return this.OnNetworkMessage() - .Where(message => message.Direction == NetworkMessageDirection.ZoneDown) - .Where(message => message.Opcode == message.DataManager?.ServerOpCodes["MarketBoardOfferings"]) - .Select(message => MarketBoardCurrentOfferings.Read(message.Data)); - } + var notifyType = reader.ReadByte(); + stream.Position += 0x1B; + var conditionId = reader.ReadUInt16(); - private IObservable OnMarketBoardHistory() - { - return this.OnNetworkMessage() - .Where(message => message.Direction == NetworkMessageDirection.ZoneDown) - .Where(message => message.Opcode == message.DataManager?.ServerOpCodes["MarketBoardHistory"]) - .Select(message => MarketBoardHistory.Read(message.Data)); - } + if (notifyType != 3) + return result; - private IObservable OnMarketTaxRates() - { - return this.OnNetworkMessage() - .Where(message => message.Direction == NetworkMessageDirection.ZoneDown) - .Where(message => message.Opcode == message.DataManager?.ServerOpCodes["MarketTaxRates"]) - .Where(message => - { - // Only some categories of the result dialog packet contain market tax rates - var category = (uint)Marshal.ReadInt32(message.Data); - return category == 720905; - }) - .Select(message => MarketTaxRates.Read(message.Data)) - .Where(taxes => taxes.Category == 0xb0009); - } + if (this.configuration.DutyFinderTaskbarFlash) + Util.FlashWindow(); - private IObservable OnMarketBoardPurchaseHandler() - { - return this.OnNetworkMessage() - .Where(message => message.Direction == NetworkMessageDirection.ZoneUp) - .Where(message => message.Opcode == message.DataManager?.ClientOpCodes["MarketBoardPurchaseHandler"]) - .Select(message => MarketBoardPurchaseHandler.Read(message.Data)); - } + var cfConditionSheet = Service.Get().GetExcelSheet()!; + var cfCondition = cfConditionSheet.GetRow(conditionId); - private IObservable OnMarketBoardPurchase() - { - return this.OnNetworkMessage() - .Where(message => message.Direction == NetworkMessageDirection.ZoneDown) - .Where(message => message.Opcode == message.DataManager?.ServerOpCodes["MarketBoardPurchase"]) - .Select(message => MarketBoardPurchase.Read(message.Data)); - } + if (cfCondition == null) + { + Log.Error("CFC key {ConditionId} not in Lumina data", conditionId); + return result; + } - private IObservable OnCfNotifyPop() - { - return this.OnNetworkMessage() - .Where(message => message.Direction == NetworkMessageDirection.ZoneDown) - .Where(message => message.Opcode == message.DataManager?.ServerOpCodes["CfNotifyPop"]); + var cfcName = cfCondition.Name.ToString(); + if (cfcName.IsNullOrEmpty()) + { + cfcName = "Duty Roulette"; + cfCondition.Image = 112324; + } + + Task.Run(() => + { + if (this.configuration.DutyFinderChatMessage) + { + Service.GetNullable()?.Print($"Duty pop: {cfcName}"); + } + + this.CfPop.InvokeSafely(cfCondition); + }).ContinueWith( + task => Log.Error(task.Exception, "CfPop.Invoke failed"), + TaskContinuationOptions.OnlyOnFaulted); + } + catch (Exception ex) + { + Log.Error(ex, "CfPopDetour threw an exception"); + } + + return result; } private IObservable> OnMarketBoardListingsBatch( IObservable start) { - var offeringsObservable = this.OnMarketBoardOfferings().Publish().RefCount(); + var offeringsObservable = this.mbOfferingsObservable.Publish().RefCount(); void LogEndObserved(MarketBoardCurrentOfferings offerings) { @@ -222,7 +344,7 @@ internal class NetworkHandlers : IDisposable, IServiceType private IObservable> OnMarketBoardSalesBatch( IObservable start) { - var historyObservable = this.OnMarketBoardHistory().Publish().RefCount(); + var historyObservable = this.mbHistoryObservable.Publish().RefCount(); void LogHistoryObserved(MarketBoardHistory history) { @@ -265,7 +387,7 @@ internal class NetworkHandlers : IDisposable, IServiceType request.AmountToArrive); } - var startObservable = this.OnMarketBoardItemRequestStart() + var startObservable = this.mbItemRequestObservable .Where(request => request.Ok).Do(LogStartObserved) .Publish() .RefCount(); @@ -292,7 +414,9 @@ internal class NetworkHandlers : IDisposable, IServiceType { if (listings.Count != request.AmountToArrive) { - Log.Error("Wrong number of Market Board listings received for request: {ListingsCount} != {RequestAmountToArrive} item#{RequestCatalogId}", listings.Count, request.AmountToArrive, request.CatalogId); + Log.Error( + "Wrong number of Market Board listings received for request: {ListingsCount} != {RequestAmountToArrive} item#{RequestCatalogId}", + listings.Count, request.AmountToArrive, request.CatalogId); return; } @@ -319,7 +443,7 @@ internal class NetworkHandlers : IDisposable, IServiceType private IDisposable HandleMarketTaxRates() { - return this.OnMarketTaxRates() + return this.mbTaxesObservable .Where(this.ShouldUpload) .SubscribeOn(ThreadPoolScheduler.Instance) .Subscribe( @@ -345,8 +469,8 @@ internal class NetworkHandlers : IDisposable, IServiceType private IDisposable HandleMarketBoardPurchaseHandler() { - return this.OnMarketBoardPurchaseHandler() - .Zip(this.OnMarketBoardPurchase()) + return this.mbPurchaseSentObservable + .Zip(this.mbPurchaseObservable) .Where(this.ShouldUpload) .SubscribeOn(ThreadPoolScheduler.Instance) .Subscribe( @@ -376,85 +500,93 @@ internal class NetworkHandlers : IDisposable, IServiceType ex => Log.Error(ex, "Failed to handle Market Board purchase event")); } - private unsafe IDisposable HandleCfPop() - { - return this.OnCfNotifyPop() - .SubscribeOn(ThreadPoolScheduler.Instance) - .Subscribe( - message => - { - using var stream = new UnmanagedMemoryStream((byte*)message.Data.ToPointer(), 64); - using var reader = new BinaryReader(stream); - - var notifyType = reader.ReadByte(); - stream.Position += 0x1B; - var conditionId = reader.ReadUInt16(); - - if (notifyType != 3) - return; - - var cfConditionSheet = message.DataManager!.GetExcelSheet()!; - var cfCondition = cfConditionSheet.GetRow(conditionId); - - if (cfCondition == null) - { - Log.Error("CFC key {ConditionId} not in Lumina data", conditionId); - return; - } - - var cfcName = cfCondition.Name.ToString(); - if (cfcName.IsNullOrEmpty()) - { - cfcName = "Duty Roulette"; - cfCondition.Image = 112324; - } - - // Flash window - if (this.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 (this.configuration.DutyFinderChatMessage) - { - Service.GetNullable()?.Print($"Duty pop: {cfcName}"); - } - - this.CfPop.InvokeSafely(cfCondition); - }).ContinueWith( - task => Log.Error(task.Exception, "CfPop.Invoke failed"), - TaskContinuationOptions.OnlyOnFaulted); - }, - ex => Log.Error(ex, "Failed to handle Market Board purchase event")); - } - private bool ShouldUpload(T any) { return this.configuration.IsMbCollect; } - private class NetworkMessage + private nint MarketPurchasePacketDetour(nint a1, nint packetData) { - public DataManager? DataManager { get; init; } + try + { + this.MarketBoardPurchaseReceived?.InvokeSafely(packetData); + } + catch (Exception ex) + { + Log.Error(ex, "MarketPurchasePacketHandler threw an exception"); + } + + return this.mbPurchaseHook.OriginalDisposeSafe(a1, packetData); + } - public IntPtr Data { get; init; } + private nint MarketHistoryPacketDetour(nint a1, nint packetData, uint a3, char a4) + { + try + { + this.MarketBoardHistoryReceived?.InvokeSafely(packetData); + } + catch (Exception ex) + { + Log.Error(ex, "MarketHistoryPacketDetour threw an exception"); + } + + return this.mbHistoryHook.OriginalDisposeSafe(a1, packetData, a3, a4); + } - public ushort Opcode { get; init; } + private void CustomTalkReceiveResponseDetour(nuint a1, ushort eventId, byte responseId, uint* args, byte argCount) + { + try + { + if (eventId == 7 && responseId == 8) + this.MarketBoardTaxesReceived?.InvokeSafely((nint)args); + } + catch (Exception ex) + { + Log.Error(ex, "CustomTalkReceiveResponseDetour threw an exception"); + } - public uint SourceActorId { get; init; } + this.customTalkHook.OriginalDisposeSafe(a1, eventId, responseId, args, argCount); + } - public uint TargetActorId { get; init; } + private nint MarketItemRequestStartDetour(nint a1, nint packetRef) + { + try + { + this.MarketBoardItemRequestStartReceived?.InvokeSafely(packetRef); + } + catch (Exception ex) + { + Log.Error(ex, "MarketItemRequestStartDetour threw an exception"); + } + + return this.mbItemRequestStartHook.OriginalDisposeSafe(a1, packetRef); + } - public NetworkMessageDirection Direction { get; init; } + private byte MarketBoardOfferingsDetour(nint a1, nint packetRef) + { + try + { + this.MarketBoardOfferingsReceived?.InvokeSafely(packetRef); + } + catch (Exception ex) + { + Log.Error(ex, "MarketBoardOfferingsDetour threw an exception"); + } + + return this.mbOfferingsHook.OriginalDisposeSafe(a1, packetRef); + } + + private byte MarketBoardSendPurchaseRequestDetour(InfoProxyItemSearch* infoProxyItemSearch) + { + try + { + this.MarketBoardPurchaseRequestSent?.InvokeSafely((nint)infoProxyItemSearch + 0x5680); + } + catch (Exception ex) + { + Log.Error(ex, "MarketBoardSendPurchaseRequestDetour threw an exception"); + } + + return this.mbSendPurchaseRequestHook.OriginalDisposeSafe(infoProxyItemSearch); } } diff --git a/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs new file mode 100644 index 000000000..8b4788c74 --- /dev/null +++ b/Dalamud/Game/Network/Internal/NetworkHandlersAddressResolver.cs @@ -0,0 +1,64 @@ +namespace Dalamud.Game.Network.Internal; + +/// +/// Internal address resolver for the network handlers. +/// +internal class NetworkHandlersAddressResolver : BaseAddressResolver +{ + /// + /// Gets or sets the pointer to the method responsible for handling CfPop packets. + /// + public nint CfPopPacketHandler { get; set; } + + /// + /// Gets or sets the pointer to the method responsible for handling market board history. In this case, we are + /// sigging the packet handler method directly. + /// + public nint MarketBoardHistoryPacketHandler { get; set; } + + /// + /// Gets or sets the pointer to the method responsible for processing the market board purchase packet. In this + /// case, we are sigging the packet handler method directly. + /// + public nint MarketBoardPurchasePacketHandler { get; set; } + + /// + /// Gets or sets the pointer to the method responsible for custom talk events. Necessary for marketboard tax data, + /// as this isn't really exposed anywhere else. + /// + public nint CustomTalkEventResponsePacketHandler { get; set; } + + /// + /// Gets or sets the pointer to the method responsible for the marketboard ItemRequestStart packet. + /// + public nint MarketBoardItemRequestStartPacketHandler { get; set; } + + /// + /// Gets or sets the pointer to the InfoProxyItemSearch.AddPage method, used to load market data. + /// + public nint InfoProxyItemSearchAddPage { get; set; } + + /// + /// Gets or sets the pointer to the method inside InfoProxyItemSearch that is responsible for building and sending + /// a purchase request packet. + /// + public nint BuildMarketBoardPurchaseHandlerPacket { get; set; } + + /// + protected override void Setup64Bit(SigScanner scanner) + { + this.CfPopPacketHandler = scanner.ScanText("40 53 57 48 83 EC 78 48 8B D9 48 8D 0D"); + this.MarketBoardHistoryPacketHandler = scanner.ScanText( + "40 53 48 83 EC 20 48 8B 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 85 C0 74 36 4C 8B 00 48 8B C8 41 FF 90 ?? ?? ?? ?? 48 8B C8 BA ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 85 C0 74 17 48 8D 53 04"); + this.MarketBoardPurchasePacketHandler = + scanner.ScanText("40 55 53 57 48 8B EC 48 83 EC 70 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 45 F0 48 8B 0D"); + this.CustomTalkEventResponsePacketHandler = + scanner.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 49 8B D9 41 0F B6 F8 0F B7 F2 8B E9 E8 ?? ?? ?? ?? 48 8B C8 44 0F B6 CF 0F B6 44 24 ?? 44 0F B7 C6 88 44 24 ?? 8B D5 48 89 5C 24"); + this.MarketBoardItemRequestStartPacketHandler = + scanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 40 48 8B 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B F8"); + this.InfoProxyItemSearchAddPage = + scanner.ScanText("48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 0F B6 82 ?? ?? ?? ?? 48 8B FA 48 8B D9 38 41 19 74 54"); + this.BuildMarketBoardPurchaseHandlerPacket = + scanner.ScanText("40 53 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B D9 48 8B 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 4C 8B D0 48 85 C0 0F 84 ?? ?? ?? ?? 8B 8B"); + } +} diff --git a/Dalamud/Game/Network/Structures/MarketTaxRates.cs b/Dalamud/Game/Network/Structures/MarketTaxRates.cs index 53ce41d44..42e1d8cce 100644 --- a/Dalamud/Game/Network/Structures/MarketTaxRates.cs +++ b/Dalamud/Game/Network/Structures/MarketTaxRates.cs @@ -1,4 +1,3 @@ -using System; using System.IO; namespace Dalamud.Game.Network.Structures; @@ -77,4 +76,27 @@ public class MarketTaxRates return output; } + + /// + /// Generate a MarketTaxRates wrapper class from information located in a CustomTalk packet. + /// + /// The pointer to the relevant CustomTalk data. + /// Returns a wrapped and ready-to-go MarketTaxRates record. + public static unsafe MarketTaxRates ReadFromCustomTalk(IntPtr dataPtr) + { + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); + + return new MarketTaxRates + { + Category = 0xb0009, // shim + LimsaLominsaTax = reader.ReadUInt32(), + GridaniaTax = reader.ReadUInt32(), + UldahTax = reader.ReadUInt32(), + IshgardTax = reader.ReadUInt32(), + KuganeTax = reader.ReadUInt32(), + CrystariumTax = reader.ReadUInt32(), + SharlayanTax = reader.ReadUInt32(), + }; + } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs index e7bce0b84..eab1ab781 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs @@ -30,7 +30,6 @@ internal class NetworkMonitorWidget : IDataWindowWidget } private readonly ConcurrentQueue packets = new(); - private readonly Dictionary opCodeDict = new(); private bool trackNetwork; private int trackedPackets; @@ -71,9 +70,6 @@ internal class NetworkMonitorWidget : IDataWindowWidget this.filterString = string.Empty; this.packets.Clear(); this.Ready = true; - var dataManager = Service.Get(); - foreach (var (name, code) in dataManager.ClientOpCodes.Concat(dataManager.ServerOpCodes)) - this.opCodeDict.TryAdd(code, (name, this.GetSizeFromName(name))); } /// @@ -106,7 +102,7 @@ internal class NetworkMonitorWidget : IDataWindowWidget this.DrawFilterInput(); this.DrawNegativeFilterInput(); - ImGuiTable.DrawTable(string.Empty, this.packets, this.DrawNetworkPacket, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Direction", "Known Name", "OpCode", "Hex", "Target", "Source", "Data"); + ImGuiTable.DrawTable(string.Empty, this.packets, this.DrawNetworkPacket, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Direction", "OpCode", "Hex", "Target", "Source", "Data"); } private void DrawNetworkPacket(NetworkPacketData data) @@ -114,16 +110,6 @@ internal class NetworkMonitorWidget : IDataWindowWidget ImGui.TableNextColumn(); ImGui.TextUnformatted(data.Direction.ToString()); - ImGui.TableNextColumn(); - if (this.opCodeDict.TryGetValue(data.OpCode, out var pair)) - { - ImGui.TextUnformatted(pair.Name); - } - else - { - ImGui.Dummy(new Vector2(150 * ImGuiHelpers.GlobalScale, 0)); - } - ImGui.TableNextColumn(); ImGui.TextUnformatted(data.OpCode.ToString()); @@ -217,7 +203,7 @@ internal class NetworkMonitorWidget : IDataWindowWidget } private int GetSizeFromOpCode(ushort opCode) - => this.opCodeDict.TryGetValue(opCode, out var pair) ? pair.Size : 0; + => 0; /// Add known packet-name -> packet struct size associations here to copy the byte data for such packets. > private int GetSizeFromName(string name) @@ -228,5 +214,5 @@ internal class NetworkMonitorWidget : IDataWindowWidget /// The filter should find opCodes by number (decimal and hex) and name, if existing. private string OpCodeToString(ushort opCode) - => this.opCodeDict.TryGetValue(opCode, out var pair) ? $"{opCode}\0{opCode:X}\0{pair.Name}" : $"{opCode}\0{opCode:X}"; + => $"{opCode}\0{opCode:X}"; } diff --git a/Dalamud/Plugin/Services/IDataManager.cs b/Dalamud/Plugin/Services/IDataManager.cs index 4977b65b3..e4b249319 100644 --- a/Dalamud/Plugin/Services/IDataManager.cs +++ b/Dalamud/Plugin/Services/IDataManager.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; - -using ImGuiScene; using Lumina; using Lumina.Data; -using Lumina.Data.Files; using Lumina.Excel; namespace Dalamud.Plugin.Services; @@ -19,7 +13,7 @@ public interface IDataManager /// Gets the current game client language. /// public ClientLanguage Language { get; } - + /// /// Gets a object which gives access to any excel/game data. /// diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index 3916a5789..782d2ab37 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -9,7 +9,6 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; - using Dalamud.Configuration.Internal; using Dalamud.Data; using Dalamud.Game; @@ -40,7 +39,8 @@ public static class Util /// /// Gets the assembly version of Dalamud. /// - public static string AssemblyVersion { get; } = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); + public static string AssemblyVersion { get; } = + Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString(); /// /// Check two byte arrays for equality. @@ -276,14 +276,16 @@ public static class Util 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)) + foreach (var f in obj.GetType() + .GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) { 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}]"); + ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), + $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]"); } else { @@ -294,7 +296,7 @@ public static class Util ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); ImGui.SameLine(); - ShowValue(addr, new List(path) { f.Name }, f.FieldType, f.GetValue(obj)); + ShowValue(addr, new List(path) {f.Name}, f.FieldType, f.GetValue(obj)); } foreach (var p in obj.GetType().GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) @@ -304,7 +306,7 @@ public static class Util ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); ImGui.SameLine(); - ShowValue(addr, new List(path) { p.Name }, p.PropertyType, p.GetValue(obj)); + ShowValue(addr, new List(path) {p.Name}, p.PropertyType, p.GetValue(obj)); } ImGui.TreePop(); @@ -399,7 +401,8 @@ public static class Util /// Specify whether to exit immediately. public static void Fatal(string message, string caption, bool exit = true) { - var flags = NativeFunctions.MessageBoxType.Ok | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.Topmost; + var flags = NativeFunctions.MessageBoxType.Ok | NativeFunctions.MessageBoxType.IconError | + NativeFunctions.MessageBoxType.Topmost; _ = NativeFunctions.MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags); if (exit) @@ -413,7 +416,7 @@ public static class Util /// Human readable version. public static string FormatBytes(long bytes) { - string[] suffix = { "B", "KB", "MB", "GB", "TB" }; + string[] suffix = {"B", "KB", "MB", "GB", "TB"}; int i; double dblSByte = bytes; for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024) @@ -601,7 +604,7 @@ public static class Util } } } - } + } finally { foreach (var enumerator in enumerators) @@ -611,6 +614,27 @@ public static class Util } } + /// + /// Request that Windows flash the game window to grab the user's attention. + /// + /// Attempt to flash even if the game is currently focused. + public static void FlashWindow(bool flashIfOpen = false) + { + if (NativeFunctions.ApplicationIsActivated() && flashIfOpen) + return; + + 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); + } + /// /// Overwrite text in a file by first writing it to a temporary file, and then /// moving that file to the path specified. @@ -693,7 +717,8 @@ public static class Util /// Log message to print, if specified and an error occurs. /// Module logger, if any. /// The type of object to dispose. - internal static void ExplicitDisposeIgnoreExceptions(this T obj, string? logMessage = null, ModuleLog? moduleLog = null) where T : IDisposable + internal static void ExplicitDisposeIgnoreExceptions( + this T obj, string? logMessage = null, ModuleLog? moduleLog = null) where T : IDisposable { try {