Add project files.
This commit is contained in:
24
LeagueAPI/Utils/IPortTokenBehavior.cs
Normal file
24
LeagueAPI/Utils/IPortTokenBehavior.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace LeagueAPI.Utils;
|
||||
|
||||
public interface IPortTokenBehavior
|
||||
{
|
||||
bool TryGet(Process process, out string remotingAuthToken, out int appPort, [NotNullWhen(false)] out Exception? exception);
|
||||
|
||||
void MapArguments(IReadOnlyList<string> args, Dictionary<string, string> _args)
|
||||
{
|
||||
foreach (string arg in args)
|
||||
{
|
||||
if (arg.Length > 0 && arg.Contains('='))
|
||||
{
|
||||
string text = arg;
|
||||
string[] array = text[2..].Split("=");
|
||||
string key = array[0];
|
||||
string value = array[1];
|
||||
_args[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
LeagueAPI/Utils/LcuHttpClient.cs
Normal file
42
LeagueAPI/Utils/LcuHttpClient.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace LeagueAPI.Utils;
|
||||
|
||||
public class LcuHttpClient : HttpClient
|
||||
{
|
||||
private static readonly Lazy<LcuHttpClient> LazyInstance = new(() => new LcuHttpClient(new LcuHttpClientHandler()));
|
||||
internal static LcuHttpClient Instance => LazyInstance.Value;
|
||||
|
||||
private LcuHttpClientHandler Handler { get; }
|
||||
public ProcessInfo? ProcessInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return Handler.ProcessInfo;
|
||||
}
|
||||
internal set
|
||||
{
|
||||
Handler.ProcessInfo = value;
|
||||
}
|
||||
}
|
||||
|
||||
public RiotAuthentication? RiotAuthentication => Handler.RiotAuthentication;
|
||||
|
||||
internal LcuHttpClient(LcuHttpClientHandler lcuHttpClientHandler)
|
||||
: base(lcuHttpClientHandler)
|
||||
{
|
||||
Handler = lcuHttpClientHandler;
|
||||
base.BaseAddress = new Uri("https://127.0.0.1");
|
||||
}
|
||||
|
||||
public async Task<T?> GetContentAsync<T>(string requestUri) where T : class
|
||||
{
|
||||
HttpResponseMessage response = await GetAsync(requestUri);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
return JsonSerializer.Deserialize<T>(content);
|
||||
}
|
||||
}
|
||||
151
LeagueAPI/Utils/LcuHttpClientHandler.cs
Normal file
151
LeagueAPI/Utils/LcuHttpClientHandler.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
namespace LeagueAPI.Utils;
|
||||
|
||||
internal class LcuHttpClientHandler : HttpClientHandler
|
||||
{
|
||||
private Lazy<bool> _isFirstRequest = new(() => true);
|
||||
|
||||
private Lazy<bool> _isFailing = new(() => false);
|
||||
|
||||
public ProcessInfo? ProcessInfo { get; internal set; }
|
||||
|
||||
public RiotAuthentication? RiotAuthentication
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ProcessInfo is not null)
|
||||
{
|
||||
return new RiotAuthentication(ProcessInfo.RemotingAuthToken);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string? BaseAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ProcessInfo != null)
|
||||
{
|
||||
return $"https://127.0.0.1:{ProcessInfo.AppPort}";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal LcuHttpClientHandler()
|
||||
{
|
||||
base.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
|
||||
}
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
HttpResponseMessage result;
|
||||
int num;
|
||||
try
|
||||
{
|
||||
if (_isFirstRequest.Value)
|
||||
{
|
||||
_isFirstRequest = new Lazy<bool>(() => false);
|
||||
SetProcessInfo();
|
||||
}
|
||||
|
||||
if (_isFailing.Value)
|
||||
{
|
||||
_isFailing = new Lazy<bool>(() => false);
|
||||
SetProcessInfo();
|
||||
}
|
||||
|
||||
PrepareRequestMessage(request);
|
||||
result = await base.SendAsync(request, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
|
||||
return result;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
_isFailing = new Lazy<bool>(() => true);
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
num = 1;
|
||||
}
|
||||
|
||||
if (num == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetProcessInfo();
|
||||
PrepareRequestMessage(request);
|
||||
return await base.SendAsync(request, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
_isFailing = new Lazy<bool>(() => true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Failed to send request");
|
||||
}
|
||||
|
||||
protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isFirstRequest.Value)
|
||||
{
|
||||
_isFirstRequest = new Lazy<bool>(() => false);
|
||||
SetProcessInfo();
|
||||
}
|
||||
|
||||
if (_isFailing.Value)
|
||||
{
|
||||
_isFailing = new Lazy<bool>(() => false);
|
||||
SetProcessInfo();
|
||||
}
|
||||
|
||||
PrepareRequestMessage(request);
|
||||
return base.Send(request, cancellationToken);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
_isFailing = new Lazy<bool>(() => true);
|
||||
throw;
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetProcessInfo();
|
||||
PrepareRequestMessage(request);
|
||||
return base.Send(request, cancellationToken);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
_isFailing = new Lazy<bool>(() => true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetProcessInfo()
|
||||
{
|
||||
ProcessInfo = ProcessFinder.GetProcessInfo();
|
||||
if (!ProcessFinder.IsPortOpen(ProcessInfo))
|
||||
{
|
||||
throw new InvalidOperationException("Failed to connect to LCUx process port.");
|
||||
}
|
||||
}
|
||||
|
||||
private void PrepareRequestMessage(HttpRequestMessage request)
|
||||
{
|
||||
if (BaseAddress != null)
|
||||
{
|
||||
Uri requestUri = new(BaseAddress + (request.RequestUri?.PathAndQuery ?? "/"));
|
||||
request.RequestUri = requestUri;
|
||||
}
|
||||
|
||||
request.Headers.Authorization = RiotAuthentication?.ToAuthenticationHeaderValue();
|
||||
}
|
||||
}
|
||||
43
LeagueAPI/Utils/PortTokenWithLockfile.cs
Normal file
43
LeagueAPI/Utils/PortTokenWithLockfile.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace LeagueAPI.Utils;
|
||||
|
||||
internal class PortTokenWithLockfile : IPortTokenBehavior
|
||||
{
|
||||
public bool TryGet(Process process, out string remotingAuthToken, out int appPort, [NotNullWhen(false)] out Exception? exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
DirectoryInfo directory = new(Path.GetDirectoryName(process.MainModule?.FileName) ?? throw new FileNotFoundException("Lockfile not found.", "lockfile"));
|
||||
FileInfo lockfile = new(Path.Join(directory.FullName, "lockfile"));
|
||||
while (!lockfile.Exists)
|
||||
{
|
||||
if (directory.Root == directory)
|
||||
{
|
||||
break;
|
||||
}
|
||||
directory = directory.Parent ?? directory.Root;
|
||||
lockfile = new(Path.Join(directory.FullName, "lockfile"));
|
||||
}
|
||||
|
||||
using FileStream stream = lockfile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using StreamReader streamReader = new(stream);
|
||||
string[] array = streamReader.ReadToEnd().Split(":");
|
||||
if (array is not [string clientName, string pid, string port, string secret, string protocol] || !int.TryParse(port, out appPort))
|
||||
{
|
||||
throw new Exception("Failed to parse lockfile.");
|
||||
}
|
||||
remotingAuthToken = secret;
|
||||
exception = null;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
remotingAuthToken = string.Empty;
|
||||
appPort = 0;
|
||||
exception = ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
LeagueAPI/Utils/PortTokenWithProcessList.cs
Normal file
80
LeagueAPI/Utils/PortTokenWithProcessList.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace LeagueAPI.Utils;
|
||||
|
||||
internal partial class PortTokenWithProcessList : IPortTokenBehavior
|
||||
{
|
||||
[GeneratedRegex("--remoting-auth-token=([\\w-]*)")]
|
||||
private static partial Regex AuthTokenRegex();
|
||||
[GeneratedRegex("--app-port=([0-9]*)")]
|
||||
private static partial Regex AppPortRegex();
|
||||
|
||||
public bool TryGet(Process process, out string remotingAuthToken, out int appPort, [NotNullWhen(false)] out Exception? exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessStartInfo startInfo;
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
Arguments = "Get-CimInstance Win32_Process -Filter \"\"\"name = 'LeagueClientUx.exe'\"\"\" | Select-Object -ExpandProperty CommandLine",
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!OperatingSystem.IsMacOS())
|
||||
{
|
||||
throw new PlatformNotSupportedException("This behavior is only supported on Windows or MacOS.");
|
||||
}
|
||||
|
||||
startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "/bin/bash",
|
||||
Arguments = "-c \"ps x -o args | grep 'LeagueClientUx'\"",
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
}
|
||||
|
||||
using Process process2 = new()
|
||||
{
|
||||
StartInfo = startInfo
|
||||
};
|
||||
process2.Start();
|
||||
process2.WaitForExit();
|
||||
string text = process2.StandardOutput.ReadToEnd();
|
||||
string text2 = process2.StandardError.ReadToEnd();
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text2))
|
||||
{
|
||||
throw new Exception(text2);
|
||||
}
|
||||
|
||||
throw new FormatException("The output string is empty.");
|
||||
}
|
||||
|
||||
remotingAuthToken = AuthTokenRegex().Match(text).Groups[1].Value;
|
||||
appPort = int.Parse(AppPortRegex().Match(text).Groups[1].Value);
|
||||
exception = null;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
remotingAuthToken = string.Empty;
|
||||
appPort = 0;
|
||||
exception = ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
LeagueAPI/Utils/ProcessFinder.cs
Normal file
59
LeagueAPI/Utils/ProcessFinder.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace LeagueAPI.Utils;
|
||||
|
||||
public static class ProcessFinder
|
||||
{
|
||||
public static Process GetProcess()
|
||||
{
|
||||
Process[] processesByName = Process.GetProcessesByName("LeagueClientUx");
|
||||
return processesByName.FirstOrDefault(p => p.ProcessName == "LeagueClientUx")
|
||||
?? throw new InvalidOperationException("Failed to find LCUx process.");
|
||||
}
|
||||
|
||||
public static ProcessInfo GetProcessInfo()
|
||||
{
|
||||
return new ProcessInfo(GetProcess());
|
||||
}
|
||||
|
||||
public static bool IsActive()
|
||||
{
|
||||
try
|
||||
{
|
||||
GetProcessInfo();
|
||||
return true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPortOpen()
|
||||
{
|
||||
try
|
||||
{
|
||||
return IsPortOpen(GetProcessInfo());
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPortOpen(ProcessInfo processInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
using TcpClient tcpClient = new();
|
||||
tcpClient.Connect("127.0.0.1", processInfo.AppPort);
|
||||
tcpClient.Close();
|
||||
return true;
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
LeagueAPI/Utils/ProcessInfo.cs
Normal file
33
LeagueAPI/Utils/ProcessInfo.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace LeagueAPI.Utils;
|
||||
|
||||
public class ProcessInfo
|
||||
{
|
||||
private static readonly List<IPortTokenBehavior> Behaviors =
|
||||
[
|
||||
new PortTokenWithLockfile(),
|
||||
new PortTokenWithProcessList(),
|
||||
];
|
||||
|
||||
public int AppPort { get; }
|
||||
public string RemotingAuthToken { get; }
|
||||
|
||||
internal ProcessInfo(Process process)
|
||||
{
|
||||
List<Exception> exceptions = [];
|
||||
foreach (IPortTokenBehavior behavior in Behaviors)
|
||||
{
|
||||
if (behavior.TryGet(process, out string remotingAuthToken, out int appPort, out Exception? exception))
|
||||
{
|
||||
RemotingAuthToken = remotingAuthToken;
|
||||
AppPort = appPort;
|
||||
return;
|
||||
}
|
||||
|
||||
exceptions.Add(exception);
|
||||
}
|
||||
|
||||
throw new AggregateException("Unable to obtain process information.", exceptions);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user