157 lines
4.9 KiB
C#
157 lines
4.9 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using System.Net.WebSockets;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using LeagueAPI.Utils;
|
|
|
|
namespace LeagueAPI;
|
|
|
|
public class LcuWebsocket : IDisposable
|
|
{
|
|
private const int OPCODE_SUBSCRIBE = 5;
|
|
private const int OPCODE_UNSUBSCRIBE = 6;
|
|
private const int OPCODE_SEND = 8;
|
|
private static readonly IReadOnlyList<string> SUBSCRIBE_EVENTS =
|
|
[
|
|
"OnJsonApiEvent_lol-champ-select_v1_session",
|
|
"OnJsonApiEvent_lol-matchmaking_v1_ready-check",
|
|
];
|
|
|
|
private bool _isDisposed;
|
|
|
|
private readonly ClientWebSocket _socket = new();
|
|
|
|
public event EventHandler? Connecting;
|
|
public event EventHandler? Connected;
|
|
public event EventHandler? Disconnected;
|
|
public event EventHandler<LcuApiEvent>? LcuApiEvent;
|
|
public event EventHandler<Exception>? LcuApiException;
|
|
|
|
[MemberNotNull(nameof(RiotAuthentication))]
|
|
public ProcessInfo? ProcessInfo { get; internal set; }
|
|
public RiotAuthentication? RiotAuthentication
|
|
{
|
|
get
|
|
{
|
|
if (ProcessInfo is not null)
|
|
{
|
|
return new RiotAuthentication(ProcessInfo.RemotingAuthToken);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task Connect()
|
|
{
|
|
Connecting?.Invoke(this, EventArgs.Empty);
|
|
try
|
|
{
|
|
ProcessInfo = ProcessFinder.GetProcessInfo();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return;
|
|
}
|
|
if (!ProcessFinder.IsPortOpen(ProcessInfo))
|
|
{
|
|
throw new InvalidOperationException("Failed to connect to LCUx process port.");
|
|
}
|
|
|
|
// ignore SSL certificate errors
|
|
_socket.Options.RemoteCertificateValidationCallback = (_, _, _, _) => true;
|
|
_socket.Options.Credentials = RiotAuthentication.ToNetworkCredential();
|
|
|
|
Uri uri = new($"wss://127.0.0.1:{ProcessInfo.AppPort}/");
|
|
try
|
|
{
|
|
await _socket.ConnectAsync(uri, CancellationToken.None);
|
|
|
|
Connected?.Invoke(this, EventArgs.Empty);
|
|
|
|
foreach (string eventName in SUBSCRIBE_EVENTS)
|
|
{
|
|
string message = $"[{OPCODE_SUBSCRIBE}, \"{eventName}\"]";
|
|
byte[] buffer = Encoding.UTF8.GetBytes(message);
|
|
Memory<byte> memory = new(buffer);
|
|
await _socket.SendAsync(memory, WebSocketMessageType.Text, true, CancellationToken.None);
|
|
}
|
|
|
|
while (_socket.State is WebSocketState.Open)
|
|
{
|
|
try
|
|
{
|
|
WebsocketMessageResult result = await _socket.ReadFullMessage(CancellationToken.None);
|
|
if (result.MessageType is WebSocketMessageType.Close)
|
|
{
|
|
await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
|
|
}
|
|
else
|
|
{
|
|
if (result.Message.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
JsonElement response = JsonElement.Parse(result.Message);
|
|
if (response.ValueKind != JsonValueKind.Array
|
|
|| response.GetArrayLength() != 3
|
|
|| !response[0].TryGetInt32(out int value)
|
|
|| value != OPCODE_SEND)
|
|
{
|
|
continue;
|
|
}
|
|
LcuApiEvent? apiEvent = response[2].Deserialize<LcuApiEvent>();
|
|
if (apiEvent is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LcuApiEvent?.Invoke(this, apiEvent);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LcuApiException?.Invoke(this, ex);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex) when (ex is not WebSocketException)
|
|
{
|
|
LcuApiException?.Invoke(this, ex);
|
|
}
|
|
finally
|
|
{
|
|
Disconnected?.Invoke(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
#region IDisposable
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!_isDisposed)
|
|
{
|
|
if (disposing)
|
|
{
|
|
if (_socket.State == WebSocketState.Open)
|
|
{
|
|
_socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None)
|
|
.Wait();
|
|
}
|
|
_socket?.Dispose();
|
|
ProcessInfo = null;
|
|
}
|
|
|
|
_isDisposed = true;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
|
Dispose(disposing: true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
#endregion
|
|
}
|