Revert "refactor(Dalamud): switch to file-scoped namespaces"

This reverts commit b5f34c3199.
This commit is contained in:
goat 2021-11-18 15:23:40 +01:00
parent d473826247
commit 1561fbac00
No known key found for this signature in database
GPG key ID: 7773BB5B43BA52E5
325 changed files with 45549 additions and 45209 deletions

View file

@ -7,178 +7,179 @@ using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Serilog;
namespace Dalamud.Game.Network;
/// <summary>
/// This class handles interacting with game network events.
/// </summary>
[PluginInterface]
[InterfaceVersion("1.0")]
public sealed class GameNetwork : IDisposable
namespace Dalamud.Game.Network
{
private readonly GameNetworkAddressResolver address;
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook;
private readonly Hook<ProcessZonePacketUpDelegate> processZonePacketUpHook;
private readonly Queue<byte[]> zoneInjectQueue = new();
private IntPtr baseAddress;
/// <summary>
/// Initializes a new instance of the <see cref="GameNetwork"/> class.
/// This class handles interacting with game network events.
/// </summary>
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<ProcessZonePacketDownDelegate> processZonePacketDownHook;
private readonly Hook<ProcessZonePacketUpDelegate> processZonePacketUpHook;
private readonly Queue<byte[]> 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<ProcessZonePacketDownDelegate>(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour);
this.processZonePacketUpHook = new Hook<ProcessZonePacketUpDelegate>(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour);
}
/// <summary>
/// The delegate type of a network message event.
/// </summary>
/// <param name="dataPtr">The pointer to the raw data.</param>
/// <param name="opCode">The operation ID code.</param>
/// <param name="sourceActorId">The source actor ID.</param>
/// <param name="targetActorId">The taret actor ID.</param>
/// <param name="direction">The direction of the packed.</param>
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);
/// <summary>
/// Event that is called when a network message is sent/received.
/// </summary>
public event OnNetworkMessageDelegate NetworkMessage;
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
this.processZonePacketDownHook.Enable();
this.processZonePacketUpHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.processZonePacketDownHook.Dispose();
this.processZonePacketUpHook.Dispose();
}
/// <summary>
/// Process a chat queue.
/// </summary>
internal void UpdateQueue()
{
while (this.zoneInjectQueue.Count > 0)
/// <summary>
/// Initializes a new instance of the <see cref="GameNetwork"/> class.
/// </summary>
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<ProcessZonePacketDownDelegate>(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour);
this.processZonePacketUpHook = new Hook<ProcessZonePacketUpDelegate>(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour);
}
/// <summary>
/// The delegate type of a network message event.
/// </summary>
/// <param name="dataPtr">The pointer to the raw data.</param>
/// <param name="opCode">The operation ID code.</param>
/// <param name="sourceActorId">The source actor ID.</param>
/// <param name="targetActorId">The taret actor ID.</param>
/// <param name="direction">The direction of the packed.</param>
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);
/// <summary>
/// Event that is called when a network message is sent/received.
/// </summary>
public event OnNetworkMessageDelegate NetworkMessage;
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
this.processZonePacketDownHook.Enable();
this.processZonePacketUpHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.processZonePacketDownHook.Dispose();
this.processZonePacketUpHook.Dispose();
}
/// <summary>
/// Process a chat queue.
/// </summary>
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);
// }
}

View file

@ -1,28 +1,31 @@
using System;
namespace Dalamud.Game.Network;
using Dalamud.Game.Internal;
/// <summary>
/// The address resolver for the <see cref="GameNetwork"/> class.
/// </summary>
public sealed class GameNetworkAddressResolver : BaseAddressResolver
namespace Dalamud.Game.Network
{
/// <summary>
/// Gets the address of the ProcessZonePacketDown method.
/// The address resolver for the <see cref="GameNetwork"/> class.
/// </summary>
public IntPtr ProcessZonePacketDown { get; private set; }
/// <summary>
/// Gets the address of the ProcessZonePacketUp method.
/// </summary>
public IntPtr ProcessZonePacketUp { get; private set; }
/// <inheritdoc/>
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 ?? ?? ?? ??");
/// <summary>
/// Gets the address of the ProcessZonePacketDown method.
/// </summary>
public IntPtr ProcessZonePacketDown { get; private set; }
/// <summary>
/// Gets the address of the ProcessZonePacketUp method.
/// </summary>
public IntPtr ProcessZonePacketUp { get; private set; }
/// <inheritdoc/>
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 ?? ?? ?? ??");
}
}
}

View file

@ -2,31 +2,32 @@ using System.Threading.Tasks;
using Dalamud.Game.Network.Structures;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders;
/// <summary>
/// An interface binding for the Universalis uploader.
/// </summary>
internal interface IMarketBoardUploader
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders
{
/// <summary>
/// Upload data about an item.
/// An interface binding for the Universalis uploader.
/// </summary>
/// <param name="item">The item request data being uploaded.</param>
/// <returns>An async task.</returns>
Task Upload(MarketBoardItemRequest item);
internal interface IMarketBoardUploader
{
/// <summary>
/// Upload data about an item.
/// </summary>
/// <param name="item">The item request data being uploaded.</param>
/// <returns>An async task.</returns>
Task Upload(MarketBoardItemRequest item);
/// <summary>
/// Upload tax rate data.
/// </summary>
/// <param name="taxRates">The tax rate data being uploaded.</param>
/// <returns>An async task.</returns>
Task UploadTax(MarketTaxRates taxRates);
/// <summary>
/// Upload tax rate data.
/// </summary>
/// <param name="taxRates">The tax rate data being uploaded.</param>
/// <returns>An async task.</returns>
Task UploadTax(MarketTaxRates taxRates);
/// <summary>
/// Upload information about a purchase this client has made.
/// </summary>
/// <param name="purchaseHandler">The purchase handler data associated with the sale.</param>
/// <returns>An async task.</returns>
Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler);
/// <summary>
/// Upload information about a purchase this client has made.
/// </summary>
/// <param name="purchaseHandler">The purchase handler data associated with the sale.</param>
/// <returns>An async task.</returns>
Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler);
}
}

View file

@ -4,63 +4,64 @@ using System.IO;
using Dalamud.Game.Network.Structures;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders;
/// <summary>
/// This represents a submission to a marketboard aggregation website.
/// </summary>
internal class MarketBoardItemRequest
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders
{
private MarketBoardItemRequest()
/// <summary>
/// This represents a submission to a marketboard aggregation website.
/// </summary>
internal class MarketBoardItemRequest
{
}
private MarketBoardItemRequest()
{
}
/// <summary>
/// Gets the catalog ID.
/// </summary>
public uint CatalogId { get; private set; }
/// <summary>
/// Gets the catalog ID.
/// </summary>
public uint CatalogId { get; private set; }
/// <summary>
/// Gets the amount to arrive.
/// </summary>
public byte AmountToArrive { get; private set; }
/// <summary>
/// Gets the amount to arrive.
/// </summary>
public byte AmountToArrive { get; private set; }
/// <summary>
/// Gets the offered item listings.
/// </summary>
public List<MarketBoardCurrentOfferings.MarketBoardItemListing> Listings { get; } = new();
/// <summary>
/// Gets the offered item listings.
/// </summary>
public List<MarketBoardCurrentOfferings.MarketBoardItemListing> Listings { get; } = new();
/// <summary>
/// Gets the historical item listings.
/// </summary>
public List<MarketBoardHistory.MarketBoardHistoryListing> History { get; } = new();
/// <summary>
/// Gets the historical item listings.
/// </summary>
public List<MarketBoardHistory.MarketBoardHistoryListing> History { get; } = new();
/// <summary>
/// Gets or sets the listing request ID.
/// </summary>
public int ListingsRequestId { get; set; } = -1;
/// <summary>
/// Gets or sets the listing request ID.
/// </summary>
public int ListingsRequestId { get; set; } = -1;
/// <summary>
/// Gets a value indicating whether the upload is complete.
/// </summary>
public bool IsDone => this.Listings.Count == this.AmountToArrive && this.History.Count != 0;
/// <summary>
/// Gets a value indicating whether the upload is complete.
/// </summary>
public bool IsDone => this.Listings.Count == this.AmountToArrive && this.History.Count != 0;
/// <summary>
/// Read a packet off the wire.
/// </summary>
/// <param name="dataPtr">Packet data.</param>
/// <returns>An object representing the data read.</returns>
public static unsafe MarketBoardItemRequest Read(IntPtr dataPtr)
{
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
using var reader = new BinaryReader(stream);
/// <summary>
/// Read a packet off the wire.
/// </summary>
/// <param name="dataPtr">Packet data.</param>
/// <returns>An object representing the data read.</returns>
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;
}
}
}

View file

@ -1,57 +1,58 @@
using Newtonsoft.Json;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types;
/// <summary>
/// A Universalis API structure.
/// </summary>
internal class UniversalisHistoryEntry
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types
{
/// <summary>
/// Gets or sets a value indicating whether the item is HQ or not.
/// A Universalis API structure.
/// </summary>
[JsonProperty("hq")]
public bool Hq { get; set; }
internal class UniversalisHistoryEntry
{
/// <summary>
/// Gets or sets a value indicating whether the item is HQ or not.
/// </summary>
[JsonProperty("hq")]
public bool Hq { get; set; }
/// <summary>
/// Gets or sets the item price per unit.
/// </summary>
[JsonProperty("pricePerUnit")]
public uint PricePerUnit { get; set; }
/// <summary>
/// Gets or sets the item price per unit.
/// </summary>
[JsonProperty("pricePerUnit")]
public uint PricePerUnit { get; set; }
/// <summary>
/// Gets or sets the quantity of items available.
/// </summary>
[JsonProperty("quantity")]
public uint Quantity { get; set; }
/// <summary>
/// Gets or sets the quantity of items available.
/// </summary>
[JsonProperty("quantity")]
public uint Quantity { get; set; }
/// <summary>
/// Gets or sets the name of the buyer.
/// </summary>
[JsonProperty("buyerName")]
public string BuyerName { get; set; }
/// <summary>
/// Gets or sets the name of the buyer.
/// </summary>
[JsonProperty("buyerName")]
public string BuyerName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this item was on a mannequin.
/// </summary>
[JsonProperty("onMannequin")]
public bool OnMannequin { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this item was on a mannequin.
/// </summary>
[JsonProperty("onMannequin")]
public bool OnMannequin { get; set; }
/// <summary>
/// Gets or sets the seller ID.
/// </summary>
[JsonProperty("sellerID")]
public string SellerId { get; set; }
/// <summary>
/// Gets or sets the seller ID.
/// </summary>
[JsonProperty("sellerID")]
public string SellerId { get; set; }
/// <summary>
/// Gets or sets the buyer ID.
/// </summary>
[JsonProperty("buyerID")]
public string BuyerId { get; set; }
/// <summary>
/// Gets or sets the buyer ID.
/// </summary>
[JsonProperty("buyerID")]
public string BuyerId { get; set; }
/// <summary>
/// Gets or sets the timestamp of the transaction.
/// </summary>
[JsonProperty("timestamp")]
public long Timestamp { get; set; }
/// <summary>
/// Gets or sets the timestamp of the transaction.
/// </summary>
[JsonProperty("timestamp")]
public long Timestamp { get; set; }
}
}

View file

@ -2,34 +2,35 @@ using System.Collections.Generic;
using Newtonsoft.Json;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types;
/// <summary>
/// A Universalis API structure.
/// </summary>
internal class UniversalisHistoryUploadRequest
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types
{
/// <summary>
/// Gets or sets the world ID.
/// A Universalis API structure.
/// </summary>
[JsonProperty("worldID")]
public uint WorldId { get; set; }
internal class UniversalisHistoryUploadRequest
{
/// <summary>
/// Gets or sets the world ID.
/// </summary>
[JsonProperty("worldID")]
public uint WorldId { get; set; }
/// <summary>
/// Gets or sets the item ID.
/// </summary>
[JsonProperty("itemID")]
public uint ItemId { get; set; }
/// <summary>
/// Gets or sets the item ID.
/// </summary>
[JsonProperty("itemID")]
public uint ItemId { get; set; }
/// <summary>
/// Gets or sets the list of available entries.
/// </summary>
[JsonProperty("entries")]
public List<UniversalisHistoryEntry> Entries { get; set; }
/// <summary>
/// Gets or sets the list of available entries.
/// </summary>
[JsonProperty("entries")]
public List<UniversalisHistoryEntry> Entries { get; set; }
/// <summary>
/// Gets or sets the uploader ID.
/// </summary>
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
/// <summary>
/// Gets or sets the uploader ID.
/// </summary>
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
}
}

View file

@ -1,39 +1,40 @@
using Newtonsoft.Json;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types;
/// <summary>
/// Request payload for market board purchases.
/// </summary>
internal class UniversalisItemListingDeleteRequest
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types
{
/// <summary>
/// Gets or sets the object ID of the retainer associated with the sale.
/// Request payload for market board purchases.
/// </summary>
[JsonProperty("retainerID")]
public string RetainerId { get; set; }
internal class UniversalisItemListingDeleteRequest
{
/// <summary>
/// Gets or sets the object ID of the retainer associated with the sale.
/// </summary>
[JsonProperty("retainerID")]
public string RetainerId { get; set; }
/// <summary>
/// Gets or sets the object ID of the item listing.
/// </summary>
[JsonProperty("listingID")]
public string ListingId { get; set; }
/// <summary>
/// Gets or sets the object ID of the item listing.
/// </summary>
[JsonProperty("listingID")]
public string ListingId { get; set; }
/// <summary>
/// Gets or sets the quantity of the item that was purchased.
/// </summary>
[JsonProperty("quantity")]
public uint Quantity { get; set; }
/// <summary>
/// Gets or sets the quantity of the item that was purchased.
/// </summary>
[JsonProperty("quantity")]
public uint Quantity { get; set; }
/// <summary>
/// Gets or sets the unit price of the item.
/// </summary>
[JsonProperty("pricePerUnit")]
public uint PricePerUnit { get; set; }
/// <summary>
/// Gets or sets the unit price of the item.
/// </summary>
[JsonProperty("pricePerUnit")]
public uint PricePerUnit { get; set; }
/// <summary>
/// Gets or sets the uploader ID.
/// </summary>
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
/// <summary>
/// Gets or sets the uploader ID.
/// </summary>
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
}
}

View file

@ -2,94 +2,95 @@ using System.Collections.Generic;
using Newtonsoft.Json;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types;
/// <summary>
/// A Universalis API structure.
/// </summary>
internal class UniversalisItemListingsEntry
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types
{
/// <summary>
/// Gets or sets the listing ID.
/// A Universalis API structure.
/// </summary>
[JsonProperty("listingID")]
public string ListingId { get; set; }
internal class UniversalisItemListingsEntry
{
/// <summary>
/// Gets or sets the listing ID.
/// </summary>
[JsonProperty("listingID")]
public string ListingId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the item is HQ.
/// </summary>
[JsonProperty("hq")]
public bool Hq { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the item is HQ.
/// </summary>
[JsonProperty("hq")]
public bool Hq { get; set; }
/// <summary>
/// Gets or sets the item price per unit.
/// </summary>
[JsonProperty("pricePerUnit")]
public uint PricePerUnit { get; set; }
/// <summary>
/// Gets or sets the item price per unit.
/// </summary>
[JsonProperty("pricePerUnit")]
public uint PricePerUnit { get; set; }
/// <summary>
/// Gets or sets the item quantity.
/// </summary>
[JsonProperty("quantity")]
public uint Quantity { get; set; }
/// <summary>
/// Gets or sets the item quantity.
/// </summary>
[JsonProperty("quantity")]
public uint Quantity { get; set; }
/// <summary>
/// Gets or sets the name of the retainer selling the item.
/// </summary>
[JsonProperty("retainerName")]
public string RetainerName { get; set; }
/// <summary>
/// Gets or sets the name of the retainer selling the item.
/// </summary>
[JsonProperty("retainerName")]
public string RetainerName { get; set; }
/// <summary>
/// Gets or sets the ID of the retainer selling the item.
/// </summary>
[JsonProperty("retainerID")]
public string RetainerId { get; set; }
/// <summary>
/// Gets or sets the ID of the retainer selling the item.
/// </summary>
[JsonProperty("retainerID")]
public string RetainerId { get; set; }
/// <summary>
/// Gets or sets the name of the user who created the entry.
/// </summary>
[JsonProperty("creatorName")]
public string CreatorName { get; set; }
/// <summary>
/// Gets or sets the name of the user who created the entry.
/// </summary>
[JsonProperty("creatorName")]
public string CreatorName { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the item is on a mannequin.
/// </summary>
[JsonProperty("onMannequin")]
public bool OnMannequin { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the item is on a mannequin.
/// </summary>
[JsonProperty("onMannequin")]
public bool OnMannequin { get; set; }
/// <summary>
/// Gets or sets the seller ID.
/// </summary>
[JsonProperty("sellerID")]
public string SellerId { get; set; }
/// <summary>
/// Gets or sets the seller ID.
/// </summary>
[JsonProperty("sellerID")]
public string SellerId { get; set; }
/// <summary>
/// Gets or sets the ID of the user who created the entry.
/// </summary>
[JsonProperty("creatorID")]
public string CreatorId { get; set; }
/// <summary>
/// Gets or sets the ID of the user who created the entry.
/// </summary>
[JsonProperty("creatorID")]
public string CreatorId { get; set; }
/// <summary>
/// Gets or sets the ID of the dye on the item.
/// </summary>
[JsonProperty("stainID")]
public int StainId { get; set; }
/// <summary>
/// Gets or sets the ID of the dye on the item.
/// </summary>
[JsonProperty("stainID")]
public int StainId { get; set; }
/// <summary>
/// Gets or sets the city where the selling retainer resides.
/// </summary>
[JsonProperty("retainerCity")]
public int RetainerCity { get; set; }
/// <summary>
/// Gets or sets the city where the selling retainer resides.
/// </summary>
[JsonProperty("retainerCity")]
public int RetainerCity { get; set; }
/// <summary>
/// Gets or sets the last time the entry was reviewed.
/// </summary>
[JsonProperty("lastReviewTime")]
public long LastReviewTime { get; set; }
/// <summary>
/// Gets or sets the last time the entry was reviewed.
/// </summary>
[JsonProperty("lastReviewTime")]
public long LastReviewTime { get; set; }
/// <summary>
/// Gets or sets the materia attached to the item.
/// </summary>
[JsonProperty("materia")]
public List<UniversalisItemMateria> Materia { get; set; }
/// <summary>
/// Gets or sets the materia attached to the item.
/// </summary>
[JsonProperty("materia")]
public List<UniversalisItemMateria> Materia { get; set; }
}
}

View file

@ -2,34 +2,35 @@ using System.Collections.Generic;
using Newtonsoft.Json;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types;
/// <summary>
/// A Universalis API structure.
/// </summary>
internal class UniversalisItemListingsUploadRequest
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types
{
/// <summary>
/// Gets or sets the world ID.
/// A Universalis API structure.
/// </summary>
[JsonProperty("worldID")]
public uint WorldId { get; set; }
internal class UniversalisItemListingsUploadRequest
{
/// <summary>
/// Gets or sets the world ID.
/// </summary>
[JsonProperty("worldID")]
public uint WorldId { get; set; }
/// <summary>
/// Gets or sets the item ID.
/// </summary>
[JsonProperty("itemID")]
public uint ItemId { get; set; }
/// <summary>
/// Gets or sets the item ID.
/// </summary>
[JsonProperty("itemID")]
public uint ItemId { get; set; }
/// <summary>
/// Gets or sets the list of available items.
/// </summary>
[JsonProperty("listings")]
public List<UniversalisItemListingsEntry> Listings { get; set; }
/// <summary>
/// Gets or sets the list of available items.
/// </summary>
[JsonProperty("listings")]
public List<UniversalisItemListingsEntry> Listings { get; set; }
/// <summary>
/// Gets or sets the uploader ID.
/// </summary>
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
/// <summary>
/// Gets or sets the uploader ID.
/// </summary>
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
}
}

View file

@ -1,21 +1,22 @@
using Newtonsoft.Json;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types;
/// <summary>
/// A Universalis API structure.
/// </summary>
internal class UniversalisItemMateria
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types
{
/// <summary>
/// Gets or sets the item slot ID.
/// A Universalis API structure.
/// </summary>
[JsonProperty("slotID")]
public int SlotId { get; set; }
internal class UniversalisItemMateria
{
/// <summary>
/// Gets or sets the item slot ID.
/// </summary>
[JsonProperty("slotID")]
public int SlotId { get; set; }
/// <summary>
/// Gets or sets the materia ID.
/// </summary>
[JsonProperty("materiaID")]
public int MateriaId { get; set; }
/// <summary>
/// Gets or sets the materia ID.
/// </summary>
[JsonProperty("materiaID")]
public int MateriaId { get; set; }
}
}

View file

@ -1,45 +1,46 @@
using Newtonsoft.Json;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types;
/// <summary>
/// A Universalis API structure.
/// </summary>
internal class UniversalisTaxData
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types
{
/// <summary>
/// Gets or sets Limsa Lominsa's current tax rate.
/// A Universalis API structure.
/// </summary>
[JsonProperty("limsaLominsa")]
public uint LimsaLominsa { get; set; }
internal class UniversalisTaxData
{
/// <summary>
/// Gets or sets Limsa Lominsa's current tax rate.
/// </summary>
[JsonProperty("limsaLominsa")]
public uint LimsaLominsa { get; set; }
/// <summary>
/// Gets or sets Gridania's current tax rate.
/// </summary>
[JsonProperty("gridania")]
public uint Gridania { get; set; }
/// <summary>
/// Gets or sets Gridania's current tax rate.
/// </summary>
[JsonProperty("gridania")]
public uint Gridania { get; set; }
/// <summary>
/// Gets or sets Ul'dah's current tax rate.
/// </summary>
[JsonProperty("uldah")]
public uint Uldah { get; set; }
/// <summary>
/// Gets or sets Ul'dah's current tax rate.
/// </summary>
[JsonProperty("uldah")]
public uint Uldah { get; set; }
/// <summary>
/// Gets or sets Ishgard's current tax rate.
/// </summary>
[JsonProperty("ishgard")]
public uint Ishgard { get; set; }
/// <summary>
/// Gets or sets Ishgard's current tax rate.
/// </summary>
[JsonProperty("ishgard")]
public uint Ishgard { get; set; }
/// <summary>
/// Gets or sets Kugane's current tax rate.
/// </summary>
[JsonProperty("kugane")]
public uint Kugane { get; set; }
/// <summary>
/// Gets or sets Kugane's current tax rate.
/// </summary>
[JsonProperty("kugane")]
public uint Kugane { get; set; }
/// <summary>
/// Gets or sets The Crystarium's current tax rate.
/// </summary>
[JsonProperty("crystarium")]
public uint Crystarium { get; set; }
/// <summary>
/// Gets or sets The Crystarium's current tax rate.
/// </summary>
[JsonProperty("crystarium")]
public uint Crystarium { get; set; }
}
}

View file

@ -1,27 +1,28 @@
using Newtonsoft.Json;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types;
/// <summary>
/// A Universalis API structure.
/// </summary>
internal class UniversalisTaxUploadRequest
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis.Types
{
/// <summary>
/// Gets or sets the uploader's ID.
/// A Universalis API structure.
/// </summary>
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
internal class UniversalisTaxUploadRequest
{
/// <summary>
/// Gets or sets the uploader's ID.
/// </summary>
[JsonProperty("uploaderID")]
public string UploaderId { get; set; }
/// <summary>
/// Gets or sets the world to retrieve data from.
/// </summary>
[JsonProperty("worldID")]
public uint WorldId { get; set; }
/// <summary>
/// Gets or sets the world to retrieve data from.
/// </summary>
[JsonProperty("worldID")]
public uint WorldId { get; set; }
/// <summary>
/// Gets or sets tax data for each city's market.
/// </summary>
[JsonProperty("marketTaxRates")]
public UniversalisTaxData TaxData { get; set; }
/// <summary>
/// Gets or sets tax data for each city's market.
/// </summary>
[JsonProperty("marketTaxRates")]
public UniversalisTaxData TaxData { get; set; }
}
}

View file

@ -10,181 +10,182 @@ using Dalamud.Utility;
using Newtonsoft.Json;
using Serilog;
namespace Dalamud.Game.Network.Internal.MarketBoardUploaders.Universalis;
/// <summary>
/// This class represents an uploader for contributing data to Universalis.
/// </summary>
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";
/// <summary>
/// Initializes a new instance of the <see cref="UniversalisMarketBoardUploader"/> class.
/// This class represents an uploader for contributing data to Universalis.
/// </summary>
public UniversalisMarketBoardUploader()
internal class UniversalisMarketBoardUploader : IMarketBoardUploader
{
}
private const string ApiBase = "https://universalis.app";
// private const string ApiBase = "https://127.0.0.1:443";
/// <inheritdoc/>
public async Task Upload(MarketBoardItemRequest request)
{
var clientState = Service<ClientState.ClientState>.Get();
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
Log.Verbose("Starting Universalis upload.");
var uploader = clientState.LocalContentId;
// ====================================================================================
var listingsUploadObject = new UniversalisItemListingsUploadRequest
/// <summary>
/// Initializes a new instance of the <see cref="UniversalisMarketBoardUploader"/> class.
/// </summary>
public UniversalisMarketBoardUploader()
{
WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0,
UploaderId = uploader.ToString(),
ItemId = request.CatalogId,
Listings = new List<UniversalisItemListingsEntry>(),
};
}
foreach (var marketBoardItemListing in request.Listings)
/// <inheritdoc/>
public async Task Upload(MarketBoardItemRequest request)
{
var universalisListing = new UniversalisItemListingsEntry
var clientState = Service<ClientState.ClientState>.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<UniversalisItemMateria>(),
WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0,
UploaderId = uploader.ToString(),
ItemId = request.CatalogId,
Listings = new List<UniversalisItemListingsEntry>(),
};
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<UniversalisItemMateria>(),
};
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<UniversalisHistoryEntry>(),
};
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
/// <inheritdoc/>
public async Task UploadTax(MarketTaxRates taxRates)
{
WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0,
UploaderId = uploader.ToString(),
ItemId = request.CatalogId,
Entries = new List<UniversalisHistoryEntry>(),
};
var clientState = Service<ClientState.ClientState>.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);
}
/// <inheritdoc/>
public async Task UploadTax(MarketTaxRates taxRates)
{
var clientState = Service<ClientState.ClientState>.Get();
// ====================================================================================
var taxUploadObject = new UniversalisTaxUploadRequest
/// <inheritdoc/>
/// <remarks>
/// 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.
/// </remarks>
public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler)
{
WorldId = clientState.LocalPlayer?.CurrentWorld.Id ?? 0,
UploaderId = clientState.LocalContentId.ToString(),
TaxData = new UniversalisTaxData
var clientState = Service<ClientState.ClientState>.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.");
}
// ====================================================================================
/// <inheritdoc/>
/// <remarks>
/// 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.
/// </remarks>
public async Task UploadPurchase(MarketBoardPurchaseHandler purchaseHandler)
{
var clientState = Service<ClientState.ClientState>.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.");
}
}
}

View file

@ -16,277 +16,278 @@ using Dalamud.Utility;
using Lumina.Excel.GeneratedSheets;
using Serilog;
namespace Dalamud.Game.Network.Internal;
/// <summary>
/// This class handles network notifications and uploading market board data.
/// </summary>
internal class NetworkHandlers
namespace Dalamud.Game.Network.Internal
{
private readonly List<MarketBoardItemRequest> marketBoardRequests = new();
private readonly bool optOutMbUploads;
private readonly IMarketBoardUploader uploader;
private MarketBoardPurchaseHandler marketBoardPurchaseHandler;
/// <summary>
/// Initializes a new instance of the <see cref="NetworkHandlers"/> class.
/// This class handles network notifications and uploading market board data.
/// </summary>
public NetworkHandlers()
internal class NetworkHandlers
{
this.optOutMbUploads = Service<DalamudStartInfo>.Get().OptOutMbCollection;
private readonly List<MarketBoardItemRequest> marketBoardRequests = new();
this.uploader = new UniversalisMarketBoardUploader();
private readonly bool optOutMbUploads;
private readonly IMarketBoardUploader uploader;
Service<GameNetwork>.Get().NetworkMessage += this.OnNetworkMessage;
}
private MarketBoardPurchaseHandler marketBoardPurchaseHandler;
/// <summary>
/// Event which gets fired when a duty is ready.
/// </summary>
public event EventHandler<ContentFinderCondition> CfPop;
private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction)
{
var dataManager = Service<DataManager>.GetNullable();
if (dataManager?.IsDataReady == false)
return;
var configuration = Service<DalamudConfiguration>.Get();
if (direction == NetworkMessageDirection.ZoneUp)
/// <summary>
/// Initializes a new instance of the <see cref="NetworkHandlers"/> class.
/// </summary>
public NetworkHandlers()
{
this.optOutMbUploads = Service<DalamudStartInfo>.Get().OptOutMbCollection;
this.uploader = new UniversalisMarketBoardUploader();
Service<GameNetwork>.Get().NetworkMessage += this.OnNetworkMessage;
}
/// <summary>
/// Event which gets fired when a duty is ready.
/// </summary>
public event EventHandler<ContentFinderCondition> CfPop;
private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction)
{
var dataManager = Service<DataManager>.GetNullable();
if (dataManager?.IsDataReady == false)
return;
var configuration = Service<DalamudConfiguration>.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<DataManager>.Get();
var configuration = Service<DalamudConfiguration>.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<ContentFinderCondition>()!;
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<NativeFunctions.FlashWindowInfo>(),
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<ChatGui>.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<DataManager>.Get();
var configuration = Service<DalamudConfiguration>.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<ContentFinderCondition>()!;
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<NativeFunctions.FlashWindowInfo>(),
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<ChatGui>.Get().Print($"Duty pop: {cfcName}");
}
this.CfPop?.Invoke(this, cfCondition);
}).ContinueWith((task) => Log.Error(task.Exception, "CfPop.Invoke failed."), TaskContinuationOptions.OnlyOnFaulted);
}
}

View file

@ -5,57 +5,58 @@ using System.Runtime.InteropServices;
using Dalamud.Hooking;
using Dalamud.Hooking.Internal;
namespace Dalamud.Game.Network.Internal;
/// <summary>
/// This class enables TCP optimizations in the game socket for better performance.
/// </summary>
internal sealed class WinSockHandlers : IDisposable
namespace Dalamud.Game.Network.Internal
{
private Hook<SocketDelegate> ws2SocketHook;
/// <summary>
/// Initializes a new instance of the <see cref="WinSockHandlers"/> class.
/// This class enables TCP optimizations in the game socket for better performance.
/// </summary>
public WinSockHandlers()
internal sealed class WinSockHandlers : IDisposable
{
this.ws2SocketHook = Hook<SocketDelegate>.FromSymbol("ws2_32.dll", "socket", this.OnSocket, true);
this.ws2SocketHook?.Enable();
}
private Hook<SocketDelegate> ws2SocketHook;
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate IntPtr SocketDelegate(int af, int type, int protocol);
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
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)
/// <summary>
/// Initializes a new instance of the <see cref="WinSockHandlers"/> class.
/// </summary>
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<SocketDelegate>.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);
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
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;
}
}
}

View file

@ -1,17 +1,18 @@
namespace Dalamud.Game.Network;
/// <summary>
/// This represents the direction of a network message.
/// </summary>
public enum NetworkMessageDirection
namespace Dalamud.Game.Network
{
/// <summary>
/// A zone down message.
/// This represents the direction of a network message.
/// </summary>
ZoneDown,
public enum NetworkMessageDirection
{
/// <summary>
/// A zone down message.
/// </summary>
ZoneDown,
/// <summary>
/// A zone up message.
/// </summary>
ZoneUp,
/// <summary>
/// A zone up message.
/// </summary>
ZoneUp,
}
}

View file

@ -3,224 +3,225 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Dalamud.Game.Network.Structures;
/// <summary>
/// This class represents the current market board offerings from a game network packet.
/// </summary>
public class MarketBoardCurrentOfferings
namespace Dalamud.Game.Network.Structures
{
private MarketBoardCurrentOfferings()
/// <summary>
/// This class represents the current market board offerings from a game network packet.
/// </summary>
public class MarketBoardCurrentOfferings
{
}
/// <summary>
/// Gets the list of individual item listings.
/// </summary>
public List<MarketBoardItemListing> ItemListings { get; } = new();
/// <summary>
/// Gets the listing end index.
/// </summary>
public int ListingIndexEnd { get; internal set; }
/// <summary>
/// Gets the listing start index.
/// </summary>
public int ListingIndexStart { get; internal set; }
/// <summary>
/// Gets the request ID.
/// </summary>
public int RequestId { get; internal set; }
/// <summary>
/// Read a <see cref="MarketBoardCurrentOfferings"/> object from memory.
/// </summary>
/// <param name="dataPtr">Address to read.</param>
/// <returns>A new <see cref="MarketBoardCurrentOfferings"/> object.</returns>
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;
/// <summary>
/// Gets the list of individual item listings.
/// </summary>
public List<MarketBoardItemListing> ItemListings { get; } = new();
reader.ReadUInt16(); // container
reader.ReadUInt32(); // slot
reader.ReadUInt16(); // durability
reader.ReadUInt16(); // spiritbond
/// <summary>
/// Gets the listing end index.
/// </summary>
public int ListingIndexEnd { get; internal set; }
for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++)
/// <summary>
/// Gets the listing start index.
/// </summary>
public int ListingIndexStart { get; internal set; }
/// <summary>
/// Gets the request ID.
/// </summary>
public int RequestId { get; internal set; }
/// <summary>
/// Read a <see cref="MarketBoardCurrentOfferings"/> object from memory.
/// </summary>
/// <param name="dataPtr">Address to read.</param>
/// <returns>A new <see cref="MarketBoardCurrentOfferings"/> object.</returns>
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;
}
/// <summary>
/// This class represents the current market board offering of a single item from the <see cref="MarketBoardCurrentOfferings"/> network packet.
/// </summary>
public class MarketBoardItemListing
{
/// <summary>
/// Initializes a new instance of the <see cref="MarketBoardItemListing"/> class.
/// </summary>
internal MarketBoardItemListing()
{
return output;
}
/// <summary>
/// Gets the artisan ID.
/// This class represents the current market board offering of a single item from the <see cref="MarketBoardCurrentOfferings"/> network packet.
/// </summary>
public ulong ArtisanId { get; internal set; }
/// <summary>
/// Gets the catalog ID.
/// </summary>
public uint CatalogId { get; internal set; }
/// <summary>
/// Gets a value indicating whether the item is HQ.
/// </summary>
public bool IsHq { get; internal set; }
/// <summary>
/// Gets the item quantity.
/// </summary>
public uint ItemQuantity { get; internal set; }
/// <summary>
/// Gets the time this offering was last reviewed.
/// </summary>
public DateTime LastReviewTime { get; internal set; }
/// <summary>
/// Gets the listing ID.
/// </summary>
public ulong ListingId { get; internal set; }
/// <summary>
/// Gets the list of materia attached to this item.
/// </summary>
public List<ItemMateria> Materia { get; } = new();
/// <summary>
/// Gets the amount of attached materia.
/// </summary>
public int MateriaCount { get; internal set; }
/// <summary>
/// Gets a value indicating whether this item is on a mannequin.
/// </summary>
public bool OnMannequin { get; internal set; }
/// <summary>
/// Gets the player name.
/// </summary>
public string PlayerName { get; internal set; }
/// <summary>
/// Gets the price per unit.
/// </summary>
public uint PricePerUnit { get; internal set; }
/// <summary>
/// Gets the city ID of the retainer selling the item.
/// </summary>
public int RetainerCityId { get; internal set; }
/// <summary>
/// Gets the ID of the retainer selling the item.
/// </summary>
public ulong RetainerId { get; internal set; }
/// <summary>
/// Gets the name of the retainer.
/// </summary>
public string RetainerName { get; internal set; }
/// <summary>
/// Gets the ID of the retainer's owner.
/// </summary>
public ulong RetainerOwnerId { get; internal set; }
/// <summary>
/// Gets the stain or applied dye of the item.
/// </summary>
public int StainId { get; internal set; }
/// <summary>
/// Gets the total tax.
/// </summary>
public uint TotalTax { get; internal set; }
/// <summary>
/// This represents the materia slotted to an <see cref="MarketBoardItemListing"/>.
/// </summary>
public class ItemMateria
public class MarketBoardItemListing
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemMateria"/> class.
/// Initializes a new instance of the <see cref="MarketBoardItemListing"/> class.
/// </summary>
internal ItemMateria()
internal MarketBoardItemListing()
{
}
/// <summary>
/// Gets the materia index.
/// Gets the artisan ID.
/// </summary>
public int Index { get; internal set; }
public ulong ArtisanId { get; internal set; }
/// <summary>
/// Gets the materia ID.
/// Gets the catalog ID.
/// </summary>
public int MateriaId { get; internal set; }
public uint CatalogId { get; internal set; }
/// <summary>
/// Gets a value indicating whether the item is HQ.
/// </summary>
public bool IsHq { get; internal set; }
/// <summary>
/// Gets the item quantity.
/// </summary>
public uint ItemQuantity { get; internal set; }
/// <summary>
/// Gets the time this offering was last reviewed.
/// </summary>
public DateTime LastReviewTime { get; internal set; }
/// <summary>
/// Gets the listing ID.
/// </summary>
public ulong ListingId { get; internal set; }
/// <summary>
/// Gets the list of materia attached to this item.
/// </summary>
public List<ItemMateria> Materia { get; } = new();
/// <summary>
/// Gets the amount of attached materia.
/// </summary>
public int MateriaCount { get; internal set; }
/// <summary>
/// Gets a value indicating whether this item is on a mannequin.
/// </summary>
public bool OnMannequin { get; internal set; }
/// <summary>
/// Gets the player name.
/// </summary>
public string PlayerName { get; internal set; }
/// <summary>
/// Gets the price per unit.
/// </summary>
public uint PricePerUnit { get; internal set; }
/// <summary>
/// Gets the city ID of the retainer selling the item.
/// </summary>
public int RetainerCityId { get; internal set; }
/// <summary>
/// Gets the ID of the retainer selling the item.
/// </summary>
public ulong RetainerId { get; internal set; }
/// <summary>
/// Gets the name of the retainer.
/// </summary>
public string RetainerName { get; internal set; }
/// <summary>
/// Gets the ID of the retainer's owner.
/// </summary>
public ulong RetainerOwnerId { get; internal set; }
/// <summary>
/// Gets the stain or applied dye of the item.
/// </summary>
public int StainId { get; internal set; }
/// <summary>
/// Gets the total tax.
/// </summary>
public uint TotalTax { get; internal set; }
/// <summary>
/// This represents the materia slotted to an <see cref="MarketBoardItemListing"/>.
/// </summary>
public class ItemMateria
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemMateria"/> class.
/// </summary>
internal ItemMateria()
{
}
/// <summary>
/// Gets the materia index.
/// </summary>
public int Index { get; internal set; }
/// <summary>
/// Gets the materia ID.
/// </summary>
public int MateriaId { get; internal set; }
}
}
}
}

View file

@ -3,120 +3,121 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Dalamud.Game.Network.Structures;
/// <summary>
/// This class represents the market board history from a game network packet.
/// </summary>
public class MarketBoardHistory
namespace Dalamud.Game.Network.Structures
{
/// <summary>
/// Initializes a new instance of the <see cref="MarketBoardHistory"/> class.
/// This class represents the market board history from a game network packet.
/// </summary>
internal MarketBoardHistory()
public class MarketBoardHistory
{
}
/// <summary>
/// Gets the catalog ID.
/// </summary>
public uint CatalogId { get; private set; }
/// <summary>
/// Gets the second catalog ID.
/// </summary>
public uint CatalogId2 { get; private set; }
/// <summary>
/// Gets the list of individual item history listings.
/// </summary>
public List<MarketBoardHistoryListing> HistoryListings { get; } = new();
/// <summary>
/// Read a <see cref="MarketBoardHistory"/> object from memory.
/// </summary>
/// <param name="dataPtr">Address to read.</param>
/// <returns>A new <see cref="MarketBoardHistory"/> object.</returns>
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++)
/// <summary>
/// Initializes a new instance of the <see cref="MarketBoardHistory"/> class.
/// </summary>
internal MarketBoardHistory()
{
var listingEntry = new MarketBoardHistoryListing
}
/// <summary>
/// Gets the catalog ID.
/// </summary>
public uint CatalogId { get; private set; }
/// <summary>
/// Gets the second catalog ID.
/// </summary>
public uint CatalogId2 { get; private set; }
/// <summary>
/// Gets the list of individual item history listings.
/// </summary>
public List<MarketBoardHistoryListing> HistoryListings { get; } = new();
/// <summary>
/// Read a <see cref="MarketBoardHistory"/> object from memory.
/// </summary>
/// <param name="dataPtr">Address to read.</param>
/// <returns>A new <see cref="MarketBoardHistory"/> object.</returns>
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;
}
/// <summary>
/// This class represents the market board history of a single item from the <see cref="MarketBoardHistory"/> network packet.
/// </summary>
public class MarketBoardHistoryListing
{
/// <summary>
/// Initializes a new instance of the <see cref="MarketBoardHistoryListing"/> class.
/// This class represents the market board history of a single item from the <see cref="MarketBoardHistory"/> network packet.
/// </summary>
internal MarketBoardHistoryListing()
public class MarketBoardHistoryListing
{
/// <summary>
/// Initializes a new instance of the <see cref="MarketBoardHistoryListing"/> class.
/// </summary>
internal MarketBoardHistoryListing()
{
}
/// <summary>
/// Gets the buyer's name.
/// </summary>
public string BuyerName { get; internal set; }
/// <summary>
/// Gets the next entry's catalog ID.
/// </summary>
public uint NextCatalogId { get; internal set; }
/// <summary>
/// Gets a value indicating whether the item is HQ.
/// </summary>
public bool IsHq { get; internal set; }
/// <summary>
/// Gets a value indicating whether the item is on a mannequin.
/// </summary>
public bool OnMannequin { get; internal set; }
/// <summary>
/// Gets the time of purchase.
/// </summary>
public DateTime PurchaseTime { get; internal set; }
/// <summary>
/// Gets the quantity.
/// </summary>
public uint Quantity { get; internal set; }
/// <summary>
/// Gets the sale price.
/// </summary>
public uint SalePrice { get; internal set; }
}
/// <summary>
/// Gets the buyer's name.
/// </summary>
public string BuyerName { get; internal set; }
/// <summary>
/// Gets the next entry's catalog ID.
/// </summary>
public uint NextCatalogId { get; internal set; }
/// <summary>
/// Gets a value indicating whether the item is HQ.
/// </summary>
public bool IsHq { get; internal set; }
/// <summary>
/// Gets a value indicating whether the item is on a mannequin.
/// </summary>
public bool OnMannequin { get; internal set; }
/// <summary>
/// Gets the time of purchase.
/// </summary>
public DateTime PurchaseTime { get; internal set; }
/// <summary>
/// Gets the quantity.
/// </summary>
public uint Quantity { get; internal set; }
/// <summary>
/// Gets the sale price.
/// </summary>
public uint SalePrice { get; internal set; }
}
}

View file

@ -1,44 +1,45 @@
using System;
using System.IO;
namespace Dalamud.Game.Network.Structures;
/// <summary>
/// Represents market board purchase information. This message is received from the
/// server when a purchase is made at a market board.
/// </summary>
internal class MarketBoardPurchase
namespace Dalamud.Game.Network.Structures
{
private MarketBoardPurchase()
/// <summary>
/// Represents market board purchase information. This message is received from the
/// server when a purchase is made at a market board.
/// </summary>
internal class MarketBoardPurchase
{
}
private MarketBoardPurchase()
{
}
/// <summary>
/// Gets the item ID of the item that was purchased.
/// </summary>
public uint CatalogId { get; private set; }
/// <summary>
/// Gets the item ID of the item that was purchased.
/// </summary>
public uint CatalogId { get; private set; }
/// <summary>
/// Gets the quantity of the item that was purchased.
/// </summary>
public uint ItemQuantity { get; private set; }
/// <summary>
/// Gets the quantity of the item that was purchased.
/// </summary>
public uint ItemQuantity { get; private set; }
/// <summary>
/// Reads market board purchase information from the struct at the provided pointer.
/// </summary>
/// <param name="dataPtr">A pointer to a struct containing market board purchase information from the server.</param>
/// <returns>An object representing the data read.</returns>
public static unsafe MarketBoardPurchase Read(IntPtr dataPtr)
{
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
using var reader = new BinaryReader(stream);
/// <summary>
/// Reads market board purchase information from the struct at the provided pointer.
/// </summary>
/// <param name="dataPtr">A pointer to a struct containing market board purchase information from the server.</param>
/// <returns>An object representing the data read.</returns>
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;
}
}
}

View file

@ -1,61 +1,62 @@
using System;
using System.IO;
namespace Dalamud.Game.Network.Structures;
/// <summary>
/// Represents market board purchase information. This message is sent from the
/// client when a purchase is made at a market board.
/// </summary>
internal class MarketBoardPurchaseHandler
namespace Dalamud.Game.Network.Structures
{
private MarketBoardPurchaseHandler()
/// <summary>
/// Represents market board purchase information. This message is sent from the
/// client when a purchase is made at a market board.
/// </summary>
internal class MarketBoardPurchaseHandler
{
}
private MarketBoardPurchaseHandler()
{
}
/// <summary>
/// Gets the object ID of the retainer associated with the sale.
/// </summary>
public ulong RetainerId { get; private set; }
/// <summary>
/// Gets the object ID of the retainer associated with the sale.
/// </summary>
public ulong RetainerId { get; private set; }
/// <summary>
/// Gets the object ID of the item listing.
/// </summary>
public ulong ListingId { get; private set; }
/// <summary>
/// Gets the object ID of the item listing.
/// </summary>
public ulong ListingId { get; private set; }
/// <summary>
/// Gets the item ID of the item that was purchased.
/// </summary>
public uint CatalogId { get; private set; }
/// <summary>
/// Gets the item ID of the item that was purchased.
/// </summary>
public uint CatalogId { get; private set; }
/// <summary>
/// Gets the quantity of the item that was purchased.
/// </summary>
public uint ItemQuantity { get; private set; }
/// <summary>
/// Gets the quantity of the item that was purchased.
/// </summary>
public uint ItemQuantity { get; private set; }
/// <summary>
/// Gets the unit price of the item.
/// </summary>
public uint PricePerUnit { get; private set; }
/// <summary>
/// Gets the unit price of the item.
/// </summary>
public uint PricePerUnit { get; private set; }
/// <summary>
/// Reads market board purchase information from the struct at the provided pointer.
/// </summary>
/// <param name="dataPtr">A pointer to a struct containing market board purchase information from the client.</param>
/// <returns>An object representing the data read.</returns>
public static unsafe MarketBoardPurchaseHandler Read(IntPtr dataPtr)
{
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
using var reader = new BinaryReader(stream);
/// <summary>
/// Reads market board purchase information from the struct at the provided pointer.
/// </summary>
/// <param name="dataPtr">A pointer to a struct containing market board purchase information from the client.</param>
/// <returns>An object representing the data read.</returns>
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;
}
}
}

View file

@ -1,67 +1,68 @@
using System;
using System.IO;
namespace Dalamud.Game.Network.Structures;
/// <summary>
/// This class represents the market tax rates from a game network packet.
/// </summary>
public class MarketTaxRates
namespace Dalamud.Game.Network.Structures
{
private MarketTaxRates()
/// <summary>
/// This class represents the market tax rates from a game network packet.
/// </summary>
public class MarketTaxRates
{
}
private MarketTaxRates()
{
}
/// <summary>
/// Gets the tax rate in Limsa Lominsa.
/// </summary>
public uint LimsaLominsaTax { get; private set; }
/// <summary>
/// Gets the tax rate in Limsa Lominsa.
/// </summary>
public uint LimsaLominsaTax { get; private set; }
/// <summary>
/// Gets the tax rate in Gridania.
/// </summary>
public uint GridaniaTax { get; private set; }
/// <summary>
/// Gets the tax rate in Gridania.
/// </summary>
public uint GridaniaTax { get; private set; }
/// <summary>
/// Gets the tax rate in Ul'dah.
/// </summary>
public uint UldahTax { get; private set; }
/// <summary>
/// Gets the tax rate in Ul'dah.
/// </summary>
public uint UldahTax { get; private set; }
/// <summary>
/// Gets the tax rate in Ishgard.
/// </summary>
public uint IshgardTax { get; private set; }
/// <summary>
/// Gets the tax rate in Ishgard.
/// </summary>
public uint IshgardTax { get; private set; }
/// <summary>
/// Gets the tax rate in Kugane.
/// </summary>
public uint KuganeTax { get; private set; }
/// <summary>
/// Gets the tax rate in Kugane.
/// </summary>
public uint KuganeTax { get; private set; }
/// <summary>
/// Gets the tax rate in the Crystarium.
/// </summary>
public uint CrystariumTax { get; private set; }
/// <summary>
/// Gets the tax rate in the Crystarium.
/// </summary>
public uint CrystariumTax { get; private set; }
/// <summary>
/// Read a <see cref="MarketTaxRates"/> object from memory.
/// </summary>
/// <param name="dataPtr">Address to read.</param>
/// <returns>A new <see cref="MarketTaxRates"/> object.</returns>
public static unsafe MarketTaxRates Read(IntPtr dataPtr)
{
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
using var reader = new BinaryReader(stream);
/// <summary>
/// Read a <see cref="MarketTaxRates"/> object from memory.
/// </summary>
/// <param name="dataPtr">Address to read.</param>
/// <returns>A new <see cref="MarketTaxRates"/> object.</returns>
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;
}
}
}