From 86382d3ebe723b40ac420a6b791afc5b430f29db Mon Sep 17 00:00:00 2001 From: Denrage Date: Wed, 12 Jun 2024 11:37:05 +0200 Subject: [PATCH 1/2] Updated arc service to work with the new bridge --- .../GameServices/ArcDps/V2/ArcDpsClient.cs | 82 +++++++++++-------- .../GameServices/ArcDps/V2/CommonFields.cs | 2 +- .../BincodeBinaryReaderExtensions.cs | 21 +++-- .../GameServices/ArcDps/V2/MessageType.cs | 13 +++ .../ArcDps/V2/Processors/BincodeSerializer.cs | 2 +- Blish HUD/GameServices/ArcDpsService.cs | 8 +- Blish HUD/GameServices/ArcDpsServiceV2.cs | 11 ++- 7 files changed, 91 insertions(+), 48 deletions(-) create mode 100644 Blish HUD/GameServices/ArcDps/V2/MessageType.cs diff --git a/Blish HUD/GameServices/ArcDps/V2/ArcDpsClient.cs b/Blish HUD/GameServices/ArcDps/V2/ArcDpsClient.cs index 782854f8..e61da74b 100644 --- a/Blish HUD/GameServices/ArcDps/V2/ArcDpsClient.cs +++ b/Blish HUD/GameServices/ArcDps/V2/ArcDpsClient.cs @@ -3,6 +3,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; @@ -29,7 +30,7 @@ internal class ArcDpsClient : IArcDpsClient { public event EventHandler Error; - public bool IsConnected => this.isConnected && this.Client.Connected; + public bool IsConnected => isConnected && Client.Connected; public TcpClient Client { get; } @@ -40,7 +41,7 @@ public ArcDpsClient(ArcDpsBridgeVersion arcDpsBridgeVersion) { processors.Add(1, new ImGuiProcessor()); - if (this.arcDpsBridgeVersion == ArcDpsBridgeVersion.V1) { + if (arcDpsBridgeVersion == ArcDpsBridgeVersion.V1) { processors.Add(2, new LegacyCombatProcessor()); processors.Add(3, new LegacyCombatProcessor()); } else { @@ -49,19 +50,19 @@ public ArcDpsClient(ArcDpsBridgeVersion arcDpsBridgeVersion) { } // hardcoded message queue size. One Collection per message type. This is done just for optimizations - this.messageQueues = new BlockingCollection[4]; + messageQueues = new BlockingCollection[byte.MaxValue]; - this.Client = new TcpClient(); + Client = new TcpClient(); } public void RegisterMessageTypeListener(int type, Func listener) where T : struct { - var processor = (MessageProcessor)this.processors[type]; + var processor = (MessageProcessor)processors[type]; if (messageQueues[type] == null) { messageQueues[type] = new BlockingCollection(); try { - Task.Run(() => this.ProcessMessage(processor, messageQueues[type])); + Task.Run(() => ProcessMessage(processor, messageQueues[type])); } catch (OperationCanceledException) { // NOP } @@ -91,17 +92,17 @@ private void ProcessMessage(MessageProcessor processor, BlockingCollectionCancellationToken to cancel the whole client public void Initialize(IPEndPoint endpoint, CancellationToken ct) { this.ct = ct; - this.Client.Connect(endpoint); + Client.Connect(endpoint); _logger.Info("Connected to arcdps endpoint on: " + endpoint.ToString()); - this.networkStream = this.Client.GetStream(); - this.isConnected = true; + networkStream = Client.GetStream(); + isConnected = true; try { - if (this.arcDpsBridgeVersion == ArcDpsBridgeVersion.V1) { - Task.Run(async () => await this.LegacyReceive(ct), ct); + if (arcDpsBridgeVersion == ArcDpsBridgeVersion.V1) { + Task.Run(async () => await LegacyReceive(ct), ct); } else { - Task.Run(async () => await this.Receive(ct), ct); + Task.Run(async () => await Receive(ct), ct); } } catch (OperationCanceledException) { // NOP @@ -110,39 +111,43 @@ public void Initialize(IPEndPoint endpoint, CancellationToken ct) { public void Disconnect() { if (isConnected) { - if (this.Client.Connected) { - this.Client.Close(); - this.Client.Dispose(); + if (Client.Connected) { + Client.Close(); + Client.Dispose(); _logger.Info("Disconnected from arcdps endpoint"); } - this.isConnected = false; - this.Disconnected?.Invoke(); + isConnected = false; + Disconnected?.Invoke(); } } private async Task LegacyReceive(CancellationToken ct) { - _logger.Info($"Start Legacy Receive Task for {this.Client.Client.RemoteEndPoint?.ToString()}"); + _logger.Info($"Start Legacy Receive Task for {Client.Client.RemoteEndPoint?.ToString()}"); try { var messageHeaderBuffer = new byte[9]; ArrayPool pool = ArrayPool.Shared; - while (this.Client.Connected) { + while (Client.Connected) { ct.ThrowIfCancellationRequested(); - if (this.Client.Available == 0) { + if (Client.Available == 0) { await Task.Delay(1, ct); } - ReadFromStream(this.networkStream, messageHeaderBuffer, 9); + ReadFromStream(networkStream, messageHeaderBuffer, 9); // In V1 the message type is part of the message and therefor included in message length, so we subtract it here var messageLength = Unsafe.ReadUnaligned(ref messageHeaderBuffer[0]) - 1; var messageType = messageHeaderBuffer[8]; var messageBuffer = pool.Rent(messageLength); - ReadFromStream(this.networkStream, messageBuffer, messageLength); + ReadFromStream(networkStream, messageBuffer, messageLength); - this.messageQueues[messageType]?.Add(messageBuffer); + if (messageQueues[messageType] != null) { + messageQueues[messageType]?.Add(messageBuffer); + } else { + pool.Return(messageBuffer); + } #if DEBUG Interlocked.Increment(ref Counter); #endif @@ -150,44 +155,49 @@ private async Task LegacyReceive(CancellationToken ct) { } } catch (Exception ex) { _logger.Error(ex.ToString()); - this.Error?.Invoke(this, SocketError.SocketError); - this.Disconnect(); + Error?.Invoke(this, SocketError.SocketError); + Disconnect(); } - _logger.Info($"Legacy Receive Task for {this.Client.Client?.RemoteEndPoint?.ToString()} stopped"); + _logger.Info($"Legacy Receive Task for {Client.Client?.RemoteEndPoint?.ToString()} stopped"); } private async Task Receive(CancellationToken ct) { - _logger.Info($"Start Receive Task for {this.Client.Client.RemoteEndPoint?.ToString()}"); + _logger.Info($"Start Receive Task for {Client.Client.RemoteEndPoint?.ToString()}"); try { var messageHeaderBuffer = new byte[5]; ArrayPool pool = ArrayPool.Shared; - while (this.Client.Connected) { + while (Client.Connected) { ct.ThrowIfCancellationRequested(); - if (this.Client.Available == 0) { + if (Client.Available == 0) { await Task.Delay(1, ct); } - ReadFromStream(this.networkStream, messageHeaderBuffer, 5); + ReadFromStream(networkStream, messageHeaderBuffer, 5); - var messageLength = Unsafe.ReadUnaligned(ref messageHeaderBuffer[0]); + var messageLength = Unsafe.ReadUnaligned(ref messageHeaderBuffer[0]) - 1; var messageType = messageHeaderBuffer[4]; var messageBuffer = pool.Rent(messageLength); - ReadFromStream(this.networkStream, messageBuffer, messageLength); - this.messageQueues[messageType]?.Add(messageBuffer); + ReadFromStream(networkStream, messageBuffer, messageLength); + + if (messageQueues[messageType] != null) { + messageQueues[messageType]?.Add(messageBuffer); + } else { + pool.Return(messageBuffer); + } #if DEBUG Interlocked.Increment(ref Counter); #endif } } catch (Exception ex) { _logger.Error(ex.ToString()); - this.Error?.Invoke(this, SocketError.SocketError); - this.Disconnect(); + Error?.Invoke(this, SocketError.SocketError); + Disconnect(); } - _logger.Info($"Receive Task for {this.Client.Client?.RemoteEndPoint?.ToString()} stopped"); + _logger.Info($"Receive Task for {Client.Client?.RemoteEndPoint?.ToString()} stopped"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Blish HUD/GameServices/ArcDps/V2/CommonFields.cs b/Blish HUD/GameServices/ArcDps/V2/CommonFields.cs index d34eae32..623ce0a1 100644 --- a/Blish HUD/GameServices/ArcDps/V2/CommonFields.cs +++ b/Blish HUD/GameServices/ArcDps/V2/CommonFields.cs @@ -41,7 +41,7 @@ public void Activate() { if (_enabled) return; _enabled = true; - GameService.ArcDpsV2.RegisterMessageType(2, CombatHandler); + GameService.ArcDpsV2.RegisterMessageType(MessageType.CombatEventArea, CombatHandler); } private Task CombatHandler(CombatCallback combatEvent, CancellationToken ct) { diff --git a/Blish HUD/GameServices/ArcDps/V2/Extensions/BincodeBinaryReaderExtensions.cs b/Blish HUD/GameServices/ArcDps/V2/Extensions/BincodeBinaryReaderExtensions.cs index 1ee91fd4..966ba598 100644 --- a/Blish HUD/GameServices/ArcDps/V2/Extensions/BincodeBinaryReaderExtensions.cs +++ b/Blish HUD/GameServices/ArcDps/V2/Extensions/BincodeBinaryReaderExtensions.cs @@ -6,10 +6,19 @@ namespace Blish_HUD.GameServices.ArcDps.V2.Extensions { public static class BincodeBinaryReaderExtensions { public static CombatCallback ParseCombatCallback(this BincodeBinaryReader reader) { var result = default(CombatCallback); - result.Event = reader.ParseCombatEvent(); - result.Source = reader.ParseAgent(); - result.Destination = reader.ParseAgent(); - result.SkillName = reader.Convert.ParseString(); + if (reader.Convert.ParseByte() == 1) { + result.Event = reader.ParseCombatEvent(); + } + if (reader.Convert.ParseByte() == 1) { + result.Source = reader.ParseAgent(); + } + if (reader.Convert.ParseByte() == 1) { + result.Destination = reader.ParseAgent(); + } + if (reader.Convert.ParseByte() == 1) { + result.SkillName = reader.Convert.ParseString(); + } + result.Id = reader.Convert.ParseULong(); result.Revision = reader.Convert.ParseULong(); @@ -50,7 +59,9 @@ public static CombatEvent ParseCombatEvent(this BincodeBinaryReader reader) { public static Agent ParseAgent(this BincodeBinaryReader reader) { var result = default(Agent); - result.Name = reader.Convert.ParseString(); + if (reader.Convert.ParseByte() == 1) { + result.Name = reader.Convert.ParseString(); + } result.Id = reader.Convert.ParseUSize(); result.Profession = reader.Convert.ParseUInt(); result.Elite = reader.Convert.ParseUInt(); diff --git a/Blish HUD/GameServices/ArcDps/V2/MessageType.cs b/Blish HUD/GameServices/ArcDps/V2/MessageType.cs new file mode 100644 index 00000000..5decd01d --- /dev/null +++ b/Blish HUD/GameServices/ArcDps/V2/MessageType.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Blish_HUD.GameServices.ArcDps.V2 { + public enum MessageType { + ImGui = 1, + CombatEventArea = 2, + CombatEventLocal = 3, + } +} diff --git a/Blish HUD/GameServices/ArcDps/V2/Processors/BincodeSerializer.cs b/Blish HUD/GameServices/ArcDps/V2/Processors/BincodeSerializer.cs index 5e1e9af9..f92d8b9e 100644 --- a/Blish HUD/GameServices/ArcDps/V2/Processors/BincodeSerializer.cs +++ b/Blish HUD/GameServices/ArcDps/V2/Processors/BincodeSerializer.cs @@ -23,7 +23,7 @@ public static double Convert(BinaryReader reader) { } public static class IntConverter { - public static bool UseVarint { get; set; } = true; + public static bool UseVarint { get; set; } = false; public class VarintEncoding { public static readonly VarintEncoding Instance = new VarintEncoding(); diff --git a/Blish HUD/GameServices/ArcDpsService.cs b/Blish HUD/GameServices/ArcDpsService.cs index 64546765..a97b0b3b 100644 --- a/Blish HUD/GameServices/ArcDpsService.cs +++ b/Blish HUD/GameServices/ArcDpsService.cs @@ -67,11 +67,11 @@ public class ArcDpsService : GameService { public void SubscribeToCombatEventId(Action func, params uint[] skillIds) { if (!_subscribed) { - GameService.ArcDpsV2.RegisterMessageType(2, async (combatEvent, ct) => { + GameService.ArcDpsV2.RegisterMessageType(GameServices.ArcDps.V2.MessageType.CombatEventArea, async (combatEvent, ct) => { DispatchSkillSubscriptions(combatEvent, RawCombatEventArgs.CombatEventType.Area); await System.Threading.Tasks.Task.CompletedTask; }); - GameService.ArcDpsV2.RegisterMessageType(3, async (combatEvent, ct) => { + GameService.ArcDpsV2.RegisterMessageType(GameServices.ArcDps.V2.MessageType.CombatEventLocal, async (combatEvent, ct) => { DispatchSkillSubscriptions(combatEvent, RawCombatEventArgs.CombatEventType.Local); await System.Threading.Tasks.Task.CompletedTask; }); @@ -117,13 +117,13 @@ protected override void Initialize() { this.RawCombatEvent += (a, b) => { Interlocked.Increment(ref Counter); }; #endif - GameService.ArcDpsV2.RegisterMessageType(2, async (combatEvent, ct) => { + GameService.ArcDpsV2.RegisterMessageType(GameServices.ArcDps.V2.MessageType.CombatEventArea, async (combatEvent, ct) => { var rawCombat = ConvertFrom(combatEvent, RawCombatEventArgs.CombatEventType.Area); this.RawCombatEvent?.Invoke(this, rawCombat); await System.Threading.Tasks.Task.CompletedTask; }); - GameService.ArcDpsV2.RegisterMessageType(3, async (combatEvent, ct) => { + GameService.ArcDpsV2.RegisterMessageType(GameServices.ArcDps.V2.MessageType.CombatEventLocal, async (combatEvent, ct) => { var rawCombat = ConvertFrom(combatEvent, RawCombatEventArgs.CombatEventType.Local); this.RawCombatEvent?.Invoke(this, rawCombat); await System.Threading.Tasks.Task.CompletedTask; diff --git a/Blish HUD/GameServices/ArcDpsServiceV2.cs b/Blish HUD/GameServices/ArcDpsServiceV2.cs index 43d96291..c794aa7d 100644 --- a/Blish HUD/GameServices/ArcDpsServiceV2.cs +++ b/Blish HUD/GameServices/ArcDpsServiceV2.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Net; using System.Net.Sockets; using System.Threading; @@ -9,8 +10,11 @@ using Blish_HUD.ArcDps; using Blish_HUD.GameServices.ArcDps; using Blish_HUD.GameServices.ArcDps.V2; +using Blish_HUD.GameServices.ArcDps.V2.Extensions; using Blish_HUD.GameServices.ArcDps.V2.Models; +using Blish_HUD.GameServices.ArcDps.V2.Processors; using Microsoft.Xna.Framework; +using SharpDX; namespace Blish_HUD { @@ -69,6 +73,11 @@ private set { } } + public void RegisterMessageType(MessageType type, Func listener) + where T : struct { + RegisterMessageType((int)type, listener); + } + public void RegisterMessageType(int type, Func listener) where T : struct { Action action = () => _arcDpsClient.RegisterMessageTypeListener(type, listener); @@ -120,7 +129,7 @@ private void Start(uint processId) { _arcDpsClient.Error += SocketErrorHandler; _arcDpsClient.Initialize(new IPEndPoint(IPAddress.Loopback, GetPort(processId, version)), _arcDpsClientCancellationTokenSource.Token); - RegisterMessageType(1, async (imGuiCallback, ct) => { + RegisterMessageType(MessageType.ImGui, async (imGuiCallback, ct) => { this.HudIsActive = imGuiCallback.NotCharacterSelectOrLoading != 0; }); } From 8dea94a5ecd367caf79693f6bebf4fc89e018bd1 Mon Sep 17 00:00:00 2001 From: Denrage Date: Fri, 14 Jun 2024 02:03:13 +0200 Subject: [PATCH 2/2] Added unofficial extras support --- .../GameServices/ArcDps/V2/ArcDpsClient.cs | 147 ++++++++++-------- .../BincodeBinaryReaderExtensions.cs | 36 ++--- .../GameServices/ArcDps/V2/IArcDpsClient.cs | 6 +- .../GameServices/ArcDps/V2/MessageType.cs | 4 + .../Models/Unofficial Extras/ChannelType.cs | 2 +- .../Unofficial Extras/ChatMessageInfo.cs | 8 +- .../V2/Models/Unofficial Extras/UserRole.cs | 2 +- .../ArcDps/V2/Processors/BincodeSerializer.cs | 5 +- .../{ => Processors}/CombatEventProcessor.cs | 3 +- .../UnofficialExtrasMessageInfoProcessor.cs | 25 +++ .../UnofficialExtrasUserInfoProcessor.cs | 25 +++ Blish HUD/GameServices/ArcDpsServiceV2.cs | 8 +- 12 files changed, 170 insertions(+), 101 deletions(-) rename Blish HUD/GameServices/ArcDps/V2/{ => Processors}/CombatEventProcessor.cs (88%) create mode 100644 Blish HUD/GameServices/ArcDps/V2/Processors/UnofficialExtrasMessageInfoProcessor.cs create mode 100644 Blish HUD/GameServices/ArcDps/V2/Processors/UnofficialExtrasUserInfoProcessor.cs diff --git a/Blish HUD/GameServices/ArcDps/V2/ArcDpsClient.cs b/Blish HUD/GameServices/ArcDps/V2/ArcDpsClient.cs index e61da74b..14ff6628 100644 --- a/Blish HUD/GameServices/ArcDps/V2/ArcDpsClient.cs +++ b/Blish HUD/GameServices/ArcDps/V2/ArcDpsClient.cs @@ -9,6 +9,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Blish_HUD.GameServices.ArcDps.Models.UnofficialExtras; using Blish_HUD.GameServices.ArcDps.V2; using Blish_HUD.GameServices.ArcDps.V2.Processors; @@ -20,49 +21,56 @@ internal class ArcDpsClient : IArcDpsClient { #endif private static readonly Logger _logger = Logger.GetLogger(); - private readonly BlockingCollection[] messageQueues; - private readonly Dictionary processors = new Dictionary(); - private readonly ArcDpsBridgeVersion arcDpsBridgeVersion; - private bool isConnected = false; - private NetworkStream networkStream; - private CancellationToken ct; - private bool disposedValue; + private readonly BlockingCollection[] _messageQueues; + private readonly Dictionary _processors = new Dictionary(); + private readonly ArcDpsBridgeVersion _arcDpsBridgeVersion; + private bool _isConnected = false; + private NetworkStream _networkStream; + private CancellationTokenSource _cancellationTokenSource; + private CancellationTokenSource _linkedTokenSource; + private CancellationToken _linkedToken; + private bool _disposedValue; + private CancellationToken _ct; public event EventHandler Error; - public bool IsConnected => isConnected && Client.Connected; + public bool IsConnected => _isConnected && (Client?.Connected ?? false); - public TcpClient Client { get; } + public TcpClient Client { get; private set; } public event Action Disconnected; public ArcDpsClient(ArcDpsBridgeVersion arcDpsBridgeVersion) { - this.arcDpsBridgeVersion = arcDpsBridgeVersion; + this._arcDpsBridgeVersion = arcDpsBridgeVersion; - processors.Add(1, new ImGuiProcessor()); + _processors.Add(1, new ImGuiProcessor()); if (arcDpsBridgeVersion == ArcDpsBridgeVersion.V1) { - processors.Add(2, new LegacyCombatProcessor()); - processors.Add(3, new LegacyCombatProcessor()); + _processors.Add((int)MessageType.CombatEventArea, new LegacyCombatProcessor()); + _processors.Add((int)MessageType.CombatEventLocal, new LegacyCombatProcessor()); } else { - processors.Add(2, new CombatEventProcessor()); - processors.Add(3, new CombatEventProcessor()); + _processors.Add((int)MessageType.CombatEventArea, new CombatEventProcessor()); + _processors.Add((int)MessageType.CombatEventLocal, new CombatEventProcessor()); + _processors.Add((int)MessageType.UserInfo, new UnofficialExtrasUserInfoProcessor()); + _processors.Add((int)MessageType.ChatMessage, new UnofficialExtrasMessageInfoProcessor()); } // hardcoded message queue size. One Collection per message type. This is done just for optimizations - messageQueues = new BlockingCollection[byte.MaxValue]; + _messageQueues = new BlockingCollection[byte.MaxValue]; - Client = new TcpClient(); } + public bool IsMessageTypeAvailable(MessageType type) + => this._processors.ContainsKey((int)type); + public void RegisterMessageTypeListener(int type, Func listener) where T : struct { - var processor = (MessageProcessor)processors[type]; - if (messageQueues[type] == null) { - messageQueues[type] = new BlockingCollection(); + var processor = (MessageProcessor)_processors[type]; + if (_messageQueues[type] == null) { + _messageQueues[type] = new BlockingCollection(); try { - Task.Run(() => ProcessMessage(processor, messageQueues[type])); + Task.Run(() => ProcessMessage(processor, _messageQueues[type])); } catch (OperationCanceledException) { // NOP } @@ -72,17 +80,17 @@ public void RegisterMessageTypeListener(int type, Func messageQueue) { - while (!ct.IsCancellationRequested) { - ct.ThrowIfCancellationRequested(); + while (!_linkedToken.IsCancellationRequested) { + _linkedToken.ThrowIfCancellationRequested(); Task.Delay(1).Wait(); foreach (var item in messageQueue.GetConsumingEnumerable()) { - ct.ThrowIfCancellationRequested(); - processor.Process(item, ct); + _linkedToken.ThrowIfCancellationRequested(); + processor.Process(item, _linkedToken); ArrayPool.Shared.Return(item); } } - ct.ThrowIfCancellationRequested(); + _linkedToken.ThrowIfCancellationRequested(); } /// @@ -91,18 +99,25 @@ private void ProcessMessage(MessageProcessor processor, BlockingCollection /// CancellationToken to cancel the whole client public void Initialize(IPEndPoint endpoint, CancellationToken ct) { - this.ct = ct; + this._ct = ct; + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource = new CancellationTokenSource(); + _linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct, this._cancellationTokenSource.Token); + _linkedToken = _linkedTokenSource.Token; + Client?.Dispose(); + Client = new TcpClient(); + Client.ReceiveBufferSize = 4096; Client.Connect(endpoint); _logger.Info("Connected to arcdps endpoint on: " + endpoint.ToString()); - networkStream = Client.GetStream(); - isConnected = true; + _networkStream = Client.GetStream(); + _isConnected = true; try { - if (arcDpsBridgeVersion == ArcDpsBridgeVersion.V1) { - Task.Run(async () => await LegacyReceive(ct), ct); + if (_arcDpsBridgeVersion == ArcDpsBridgeVersion.V1) { + Task.Run(async () => await LegacyReceive(_linkedToken), _linkedToken); } else { - Task.Run(async () => await Receive(ct), ct); + Task.Run(async () => await Receive(_linkedToken), _linkedToken); } } catch (OperationCanceledException) { // NOP @@ -110,44 +125,36 @@ public void Initialize(IPEndPoint endpoint, CancellationToken ct) { } public void Disconnect() { - if (isConnected) { - if (Client.Connected) { + if (_isConnected) { + if (Client?.Connected ?? false) { Client.Close(); Client.Dispose(); _logger.Info("Disconnected from arcdps endpoint"); } - isConnected = false; + _isConnected = false; Disconnected?.Invoke(); } } private async Task LegacyReceive(CancellationToken ct) { - _logger.Info($"Start Legacy Receive Task for {Client.Client.RemoteEndPoint?.ToString()}"); + _logger.Info($"Start Legacy Receive Task for {Client?.Client.RemoteEndPoint?.ToString()}"); try { var messageHeaderBuffer = new byte[9]; ArrayPool pool = ArrayPool.Shared; - while (Client.Connected) { + while (Client?.Connected ?? false) { ct.ThrowIfCancellationRequested(); if (Client.Available == 0) { await Task.Delay(1, ct); } - ReadFromStream(networkStream, messageHeaderBuffer, 9); + ReadFromStream(_networkStream, messageHeaderBuffer, 9); - // In V1 the message type is part of the message and therefor included in message length, so we subtract it here var messageLength = Unsafe.ReadUnaligned(ref messageHeaderBuffer[0]) - 1; var messageType = messageHeaderBuffer[8]; - var messageBuffer = pool.Rent(messageLength); - ReadFromStream(networkStream, messageBuffer, messageLength); - - if (messageQueues[messageType] != null) { - messageQueues[messageType]?.Add(messageBuffer); - } else { - pool.Return(messageBuffer); - } + ReadMessage(pool, messageLength, _networkStream, _messageQueues, messageType); #if DEBUG Interlocked.Increment(ref Counter); #endif @@ -159,45 +166,54 @@ private async Task LegacyReceive(CancellationToken ct) { Disconnect(); } - _logger.Info($"Legacy Receive Task for {Client.Client?.RemoteEndPoint?.ToString()} stopped"); + _logger.Info($"Legacy Receive Task for {Client?.Client.RemoteEndPoint?.ToString()} stopped"); } private async Task Receive(CancellationToken ct) { - _logger.Info($"Start Receive Task for {Client.Client.RemoteEndPoint?.ToString()}"); + _logger.Info($"Start Receive Task for {Client?.Client.RemoteEndPoint?.ToString()}"); try { var messageHeaderBuffer = new byte[5]; ArrayPool pool = ArrayPool.Shared; - while (Client.Connected) { + while (Client?.Connected ?? false) { ct.ThrowIfCancellationRequested(); if (Client.Available == 0) { await Task.Delay(1, ct); } - ReadFromStream(networkStream, messageHeaderBuffer, 5); + ReadFromStream(_networkStream, messageHeaderBuffer, 5); var messageLength = Unsafe.ReadUnaligned(ref messageHeaderBuffer[0]) - 1; var messageType = messageHeaderBuffer[4]; - var messageBuffer = pool.Rent(messageLength); - ReadFromStream(networkStream, messageBuffer, messageLength); - - if (messageQueues[messageType] != null) { - messageQueues[messageType]?.Add(messageBuffer); - } else { - pool.Return(messageBuffer); - } + ReadMessage(pool, messageLength, _networkStream, _messageQueues, messageType); #if DEBUG Interlocked.Increment(ref Counter); #endif } + + // Reconnect if the bridge closes the connection. + // Pass on the cancellationToken from the creator of this class + this.Initialize((IPEndPoint)this.Client.Client.RemoteEndPoint, this._ct); } catch (Exception ex) { _logger.Error(ex.ToString()); Error?.Invoke(this, SocketError.SocketError); Disconnect(); } - _logger.Info($"Receive Task for {Client.Client?.RemoteEndPoint?.ToString()} stopped"); + _logger.Info($"Receive Task for {Client?.Client.RemoteEndPoint?.ToString()} stopped"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ReadMessage(ArrayPool pool, int messageLength, Stream networkStream, BlockingCollection[] messageQueues, byte messageType) { + var messageBuffer = pool.Rent(messageLength); + ReadFromStream(networkStream, messageBuffer, messageLength); + + if (messageQueues[messageType] != null) { + messageQueues[messageType]?.Add(messageBuffer); + } else { + pool.Return(messageBuffer); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -209,20 +225,21 @@ private static void ReadFromStream(Stream stream, byte[] buffer, int length) { } protected virtual void Dispose(bool disposing) { - if (!disposedValue) { + if (!_disposedValue) { if (disposing) { - Client.Dispose(); - foreach (var item in messageQueues) { + _cancellationTokenSource.Cancel(); + Client?.Dispose(); + foreach (var item in _messageQueues) { if (item.Count != 0) { foreach (var message in item) { ArrayPool.Shared.Return(message); } } } - networkStream.Dispose(); + _networkStream?.Dispose(); } - disposedValue = true; + _disposedValue = true; } } diff --git a/Blish HUD/GameServices/ArcDps/V2/Extensions/BincodeBinaryReaderExtensions.cs b/Blish HUD/GameServices/ArcDps/V2/Extensions/BincodeBinaryReaderExtensions.cs index 966ba598..cbe3c2dc 100644 --- a/Blish HUD/GameServices/ArcDps/V2/Extensions/BincodeBinaryReaderExtensions.cs +++ b/Blish HUD/GameServices/ArcDps/V2/Extensions/BincodeBinaryReaderExtensions.cs @@ -1,24 +1,16 @@ using Blish_HUD.GameServices.ArcDps.Models.UnofficialExtras; using Blish_HUD.GameServices.ArcDps.V2.Models; using Blish_HUD.GameServices.ArcDps.V2.Processors; +using System; namespace Blish_HUD.GameServices.ArcDps.V2.Extensions { public static class BincodeBinaryReaderExtensions { public static CombatCallback ParseCombatCallback(this BincodeBinaryReader reader) { var result = default(CombatCallback); - if (reader.Convert.ParseByte() == 1) { - result.Event = reader.ParseCombatEvent(); - } - if (reader.Convert.ParseByte() == 1) { - result.Source = reader.ParseAgent(); - } - if (reader.Convert.ParseByte() == 1) { - result.Destination = reader.ParseAgent(); - } - if (reader.Convert.ParseByte() == 1) { - result.SkillName = reader.Convert.ParseString(); - } - + result.Event = ParseOptional(reader, reader.ParseCombatEvent); + result.Source = ParseOptional(reader, reader.ParseAgent); + result.Destination = ParseOptional(reader, reader.ParseAgent); + result.SkillName = ParseOptional(reader, reader.Convert.ParseString); result.Id = reader.Convert.ParseULong(); result.Revision = reader.Convert.ParseULong(); @@ -59,9 +51,7 @@ public static CombatEvent ParseCombatEvent(this BincodeBinaryReader reader) { public static Agent ParseAgent(this BincodeBinaryReader reader) { var result = default(Agent); - if (reader.Convert.ParseByte() == 1) { - result.Name = reader.Convert.ParseString(); - } + result.Name = ParseOptional(reader, reader.Convert.ParseString); result.Id = reader.Convert.ParseUSize(); result.Profession = reader.Convert.ParseUInt(); result.Elite = reader.Convert.ParseUInt(); @@ -72,7 +62,7 @@ public static Agent ParseAgent(this BincodeBinaryReader reader) { public static UserInfo ParseUserInfo(this BincodeBinaryReader reader) { var result = default(UserInfo); - result.AccountName = reader.Convert.ParseString(); + result.AccountName = ParseOptional(reader, reader.Convert.ParseString); result.JoinTime = reader.Convert.ParseULong(); result.Role = ParseEnum((byte)reader.Convert.ParseUInt(), (int)UserRole.None, UserRole.None); result.Subgroup = reader.Convert.ParseByte(); @@ -82,14 +72,13 @@ public static UserInfo ParseUserInfo(this BincodeBinaryReader reader) { return result; } - public static ChatMessageInfo ParseChatMessageInfo(BincodeBinaryReader reader) { + public static ChatMessageInfo ParseChatMessageInfo(this BincodeBinaryReader reader) { var result = default(ChatMessageInfo); result.ChannelId = reader.Convert.ParseUInt(); result.ChannelType = ParseEnum((byte)reader.Convert.ParseUInt(), (int)ChannelType.Invalid, ChannelType.Invalid); result.Subgroup = reader.Convert.ParseByte(); result.IsBroadcast = reader.Convert.ParseBool(); - result._unused1 = reader.Convert.ParseByte(); - result.TimeStamp = reader.Convert.ParseString(); + result.TimeStamp = DateTime.Parse(reader.Convert.ParseString()); result.AccountName = reader.Convert.ParseString(); result.CharacterName = reader.Convert.ParseString(); result.Text = reader.Convert.ParseString(); @@ -105,6 +94,13 @@ private static T ParseEnum(byte enumByteValue, int maxValue, T unknown) return (T)(object)enumByteValue; } + + private static T ParseOptional(BincodeBinaryReader reader, Func parse) { + if (reader.ReadByte() == 1) { + return parse(); + } + return default; + } } } diff --git a/Blish HUD/GameServices/ArcDps/V2/IArcDpsClient.cs b/Blish HUD/GameServices/ArcDps/V2/IArcDpsClient.cs index 0198504c..463922ea 100644 --- a/Blish HUD/GameServices/ArcDps/V2/IArcDpsClient.cs +++ b/Blish HUD/GameServices/ArcDps/V2/IArcDpsClient.cs @@ -14,9 +14,11 @@ internal interface IArcDpsClient : IDisposable { event EventHandler Error; void Disconnect(); - - void Initialize(IPEndPoint endpoint, CancellationToken ct); + void Initialize(IPEndPoint endpoint, CancellationToken ct); + + bool IsMessageTypeAvailable(MessageType type); + void RegisterMessageTypeListener(int type, Func listener) where T : struct; } } diff --git a/Blish HUD/GameServices/ArcDps/V2/MessageType.cs b/Blish HUD/GameServices/ArcDps/V2/MessageType.cs index 5decd01d..986648fe 100644 --- a/Blish HUD/GameServices/ArcDps/V2/MessageType.cs +++ b/Blish HUD/GameServices/ArcDps/V2/MessageType.cs @@ -6,8 +6,12 @@ namespace Blish_HUD.GameServices.ArcDps.V2 { public enum MessageType { + // ArcDPS ImGui = 1, CombatEventArea = 2, CombatEventLocal = 3, + // Unofficial Extras + UserInfo = 4, + ChatMessage = 5, } } diff --git a/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/ChannelType.cs b/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/ChannelType.cs index 068b84c1..70b232cb 100644 --- a/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/ChannelType.cs +++ b/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/ChannelType.cs @@ -1,5 +1,5 @@ namespace Blish_HUD.GameServices.ArcDps.Models.UnofficialExtras { - public enum ChannelType { + public enum ChannelType : byte { Party = 0, Squad = 1, _Reserved = 2, diff --git a/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/ChatMessageInfo.cs b/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/ChatMessageInfo.cs index fb8afc7e..ed0b45e3 100644 --- a/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/ChatMessageInfo.cs +++ b/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/ChatMessageInfo.cs @@ -1,4 +1,6 @@ -namespace Blish_HUD.GameServices.ArcDps.Models.UnofficialExtras { +using System; + +namespace Blish_HUD.GameServices.ArcDps.Models.UnofficialExtras { public struct ChatMessageInfo { public uint ChannelId { get; set; } @@ -8,9 +10,7 @@ public struct ChatMessageInfo { public bool IsBroadcast { get; set; } - public byte _unused1 { get; set; } - - public string TimeStamp { get; set; } + public DateTime TimeStamp { get; set; } public string AccountName { get; set; } diff --git a/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/UserRole.cs b/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/UserRole.cs index 5eec2238..25e4fdfa 100644 --- a/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/UserRole.cs +++ b/Blish HUD/GameServices/ArcDps/V2/Models/Unofficial Extras/UserRole.cs @@ -1,5 +1,5 @@ namespace Blish_HUD.GameServices.ArcDps.Models.UnofficialExtras { - public enum UserRole { + public enum UserRole : byte { SquadLeader = 0, Lieutenant = 1, Member = 2, diff --git a/Blish HUD/GameServices/ArcDps/V2/Processors/BincodeSerializer.cs b/Blish HUD/GameServices/ArcDps/V2/Processors/BincodeSerializer.cs index f92d8b9e..4e28b216 100644 --- a/Blish HUD/GameServices/ArcDps/V2/Processors/BincodeSerializer.cs +++ b/Blish HUD/GameServices/ArcDps/V2/Processors/BincodeSerializer.cs @@ -23,7 +23,7 @@ public static double Convert(BinaryReader reader) { } public static class IntConverter { - public static bool UseVarint { get; set; } = false; + public static bool UseVarint { get; set; } = true; public class VarintEncoding { public static readonly VarintEncoding Instance = new VarintEncoding(); @@ -69,9 +69,6 @@ public static sbyte Convert(BinaryReader reader) { } public static byte ConvertUnsigned(BinaryReader reader) { - if (UseVarint) { - return (byte)VarintEncoding.Instance.ConvertUnsigned(reader); - } return reader.ReadByte(); } } diff --git a/Blish HUD/GameServices/ArcDps/V2/CombatEventProcessor.cs b/Blish HUD/GameServices/ArcDps/V2/Processors/CombatEventProcessor.cs similarity index 88% rename from Blish HUD/GameServices/ArcDps/V2/CombatEventProcessor.cs rename to Blish HUD/GameServices/ArcDps/V2/Processors/CombatEventProcessor.cs index 47a6e4b0..cf13cc65 100644 --- a/Blish HUD/GameServices/ArcDps/V2/CombatEventProcessor.cs +++ b/Blish HUD/GameServices/ArcDps/V2/Processors/CombatEventProcessor.cs @@ -1,11 +1,10 @@ using Blish_HUD.GameServices.ArcDps.V2.Extensions; using Blish_HUD.GameServices.ArcDps.V2.Models; -using Blish_HUD.GameServices.ArcDps.V2.Processors; using SharpDX; using System; using System.IO; -namespace Blish_HUD.GameServices.ArcDps.V2 { +namespace Blish_HUD.GameServices.ArcDps.V2.Processors { internal class CombatEventProcessor : MessageProcessor { internal override bool TryInternalProcess(byte[] message, out CombatCallback result) { try { diff --git a/Blish HUD/GameServices/ArcDps/V2/Processors/UnofficialExtrasMessageInfoProcessor.cs b/Blish HUD/GameServices/ArcDps/V2/Processors/UnofficialExtrasMessageInfoProcessor.cs new file mode 100644 index 00000000..6e3bf239 --- /dev/null +++ b/Blish HUD/GameServices/ArcDps/V2/Processors/UnofficialExtrasMessageInfoProcessor.cs @@ -0,0 +1,25 @@ +using Blish_HUD.GameServices.ArcDps.Models.UnofficialExtras; +using Blish_HUD.GameServices.ArcDps.V2.Extensions; +using Blish_HUD.GameServices.ArcDps.V2.Models; +using Blish_HUD.GameServices.ArcDps.V2.Processors; +using SharpDX; +using System; +using System.IO; + +namespace Blish_HUD.GameServices.ArcDps.V2 { + internal class UnofficialExtrasMessageInfoProcessor : MessageProcessor { + internal override bool TryInternalProcess(byte[] message, out ChatMessageInfo result) { + try { + using var memoryStream = new MemoryStream(message); + using var binaryReader = new BincodeBinaryReader(memoryStream); + result = binaryReader.ParseChatMessageInfo(); + return true; + + } catch (Exception) { + result = default; + return false; + } + + } + } +} diff --git a/Blish HUD/GameServices/ArcDps/V2/Processors/UnofficialExtrasUserInfoProcessor.cs b/Blish HUD/GameServices/ArcDps/V2/Processors/UnofficialExtrasUserInfoProcessor.cs new file mode 100644 index 00000000..c79f5660 --- /dev/null +++ b/Blish HUD/GameServices/ArcDps/V2/Processors/UnofficialExtrasUserInfoProcessor.cs @@ -0,0 +1,25 @@ +using Blish_HUD.GameServices.ArcDps.Models.UnofficialExtras; +using Blish_HUD.GameServices.ArcDps.V2.Extensions; +using Blish_HUD.GameServices.ArcDps.V2.Models; +using Blish_HUD.GameServices.ArcDps.V2.Processors; +using SharpDX; +using System; +using System.IO; + +namespace Blish_HUD.GameServices.ArcDps.V2 { + internal class UnofficialExtrasUserInfoProcessor : MessageProcessor { + internal override bool TryInternalProcess(byte[] message, out UserInfo result) { + try { + using var memoryStream = new MemoryStream(message); + using var binaryReader = new BincodeBinaryReader(memoryStream); + result = binaryReader.ParseUserInfo(); + return true; + + } catch (Exception) { + result = default; + return false; + } + + } + } +} diff --git a/Blish HUD/GameServices/ArcDpsServiceV2.cs b/Blish HUD/GameServices/ArcDpsServiceV2.cs index c794aa7d..67a5aaac 100644 --- a/Blish HUD/GameServices/ArcDpsServiceV2.cs +++ b/Blish HUD/GameServices/ArcDpsServiceV2.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Blish_HUD.ArcDps; using Blish_HUD.GameServices.ArcDps; +using Blish_HUD.GameServices.ArcDps.Models.UnofficialExtras; using Blish_HUD.GameServices.ArcDps.V2; using Blish_HUD.GameServices.ArcDps.V2.Extensions; using Blish_HUD.GameServices.ArcDps.V2.Models; @@ -54,7 +55,7 @@ public class ArcDpsServiceV2 : GameService { /// /// Indicates if the socket listener for the arcdps service is running and arcdps sent an update in the last second. /// - public bool Running => (this._arcDpsClient?.Client.Connected ?? false) && this.RenderPresent; + public bool Running => (this._arcDpsClient?.Client?.Connected ?? false) && this.RenderPresent; /// /// Indicates if arcdps currently draws its HUD (not in character select, cut scenes or loading screens) @@ -73,6 +74,9 @@ private set { } } + public bool IsMessageTypeAvailable(MessageType type) + => _arcDpsClient.IsMessageTypeAvailable(type); + public void RegisterMessageType(MessageType type, Func listener) where T : struct { RegisterMessageType((int)type, listener); @@ -157,7 +161,7 @@ protected override void Unload() { _arcDpsClientCancellationTokenSource?.Dispose(); _stopwatch?.Stop(); _arcDpsClient?.Disconnect(); - + if (_arcDpsClient != null) { _arcDpsClient.Error -= SocketErrorHandler; }