├── .gitignore ├── Ekko.sln ├── LICENSE ├── README.md └── src └── Ekko ├── Ekko.csproj ├── LeagueApi.cs ├── LeagueClient.cs ├── LeagueClientWatcher.cs └── Platform ├── LinuxPlatform.cs ├── OSXPlatform.cs ├── PlatformBase.cs └── WindowsPlatform.cs /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | vcs.xml 7 | .idea 8 | -------------------------------------------------------------------------------- /Ekko.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C250E45B-AAC4-4319-BFE2-EA7B292936E4}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ekko", "src\Ekko\Ekko.csproj", "{78A83431-CB97-44FD-A880-D4BF25A53CE5}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{0CD6D1AA-313B-4774-A342-FCD1CD6ADD63}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(NestedProjects) = preSolution 15 | {78A83431-CB97-44FD-A880-D4BF25A53CE5} = {C250E45B-AAC4-4319-BFE2-EA7B292936E4} 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {78A83431-CB97-44FD-A880-D4BF25A53CE5}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {78A83431-CB97-44FD-A880-D4BF25A53CE5}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {78A83431-CB97-44FD-A880-D4BF25A53CE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {78A83431-CB97-44FD-A880-D4BF25A53CE5}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | EndGlobalSection 23 | EndGlobal 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Inception 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ekko 2 | Ekko is a small library that allows you to interact with the LCU API (The API that the League Client exposes). It is compatible with Windows, Linux, and MacOS and it targets .NET 7 and .NET Standard 2.0. 3 | 4 | ## Example 5 | ### Here is a simple icon changer. 6 | ```csharp 7 | using System.Text; 8 | using Ekko; 9 | 10 | Console.Title = "SimpleIconChanger"; 11 | Console.ForegroundColor = ConsoleColor.White; 12 | var token = new CancellationTokenSource(); 13 | var watcher = new LeagueClientWatcher(); 14 | watcher.OnLeagueClient += async (clientWatcher, client) => 15 | { 16 | Console.Clear(); 17 | token.Cancel(); 18 | var api = new LeagueApi(client); 19 | Console.WriteLine("Making sure you are logged in..."); 20 | await api.EnsureLoggedIn(); 21 | Console.Clear(); 22 | Console.WriteLine("Input the console icon you want (default 29):"); 23 | var input = Console.ReadLine(); 24 | int icon = 29; 25 | if (Int32.TryParse(input, out var output)) 26 | { 27 | icon = output; 28 | } 29 | 30 | var response = await api.SendAsync(HttpMethod.Put, "/lol-summoner/v1/current-summoner/icon", new StringContent($"{{\"profileIconId\":{icon}}}",Encoding.UTF8,"application/json")); 31 | if (string.IsNullOrEmpty(response) || !response.Contains($"profileIconId\":{icon}")) 32 | Console.WriteLine("Fail!"); 33 | else 34 | Console.WriteLine("Success!"); 35 | Console.ReadLine(); 36 | Environment.Exit(0); 37 | }; 38 | Console.WriteLine("Waiting for league client!"); 39 | await watcher.Observe(token.Token); 40 | await Task.Delay(-1); 41 | ``` 42 | -------------------------------------------------------------------------------- /src/Ekko/Ekko.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | enable 5 | enable 6 | 11 7 | net7.0;netstandard2.0 8 | true 9 | 1.0.3 10 | 0xInception 11 | https://github.com/0xInception/Ekko 12 | https://github.com/0xInception/Ekko/blob/master/LICENSE 13 | LCU toolkit 14 | Cross platform toolkit for league of legend's LCU with multiple client support. 15 | https://github.com/0xInception/Ekko 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Ekko/LeagueApi.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Headers; 2 | using System.Text; 3 | 4 | namespace Ekko; 5 | 6 | public class LeagueApi 7 | { 8 | private readonly string _authToken; 9 | private readonly int _authPort; 10 | private HttpClient _client; 11 | private readonly string _baseUrl; 12 | 13 | public LeagueApi(string authToken,int authPort) 14 | { 15 | _authToken = authToken; 16 | _authPort = authPort; 17 | _client = new HttpClient(new HttpClientHandler() 18 | { 19 | ServerCertificateCustomValidationCallback = (_, _, _, _) => true 20 | }); 21 | _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", 22 | Convert.ToBase64String(Encoding.ASCII.GetBytes($"riot:{authToken}"))); 23 | _client.DefaultRequestHeaders.Add("User-Agent", "LeagueOfLegendsClient"); 24 | _client.DefaultRequestHeaders.Add("Accept","application/json"); 25 | _baseUrl = $"https://127.0.0.1:{_authPort}"; 26 | } 27 | public async Task SendAsync(HttpMethod method,string endpoint,HttpContent? body = null) 28 | { 29 | var req = new HttpRequestMessage(method, $"{_baseUrl}{endpoint}"); 30 | if (body is not null) 31 | req.Content = body; 32 | var result = await _client.SendAsync(req); 33 | return await result.Content.ReadAsStringAsync(); 34 | } 35 | } -------------------------------------------------------------------------------- /src/Ekko/LeagueClient.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace Ekko; 4 | 5 | public class LeagueClient 6 | { 7 | internal LeagueClient(int pid,ClientAuthInfo info,Process process) 8 | { 9 | Pid = pid; 10 | ClientAuthInfo = info; 11 | Process = process; 12 | } 13 | public int Pid { get; set; } 14 | public ClientAuthInfo ClientAuthInfo { get; set; } 15 | public Process Process { get; set; } 16 | } 17 | 18 | public class ClientAuthInfo 19 | { 20 | public string RiotClientAuthToken { get; set; } 21 | public int RiotClientPort { get; set; } 22 | public string RemotingAuthToken { get; set; } 23 | public int RemotingPort { get; set; } 24 | } -------------------------------------------------------------------------------- /src/Ekko/LeagueClientWatcher.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using System.Text.RegularExpressions; 4 | using Ekko.Platform; 5 | 6 | namespace Ekko; 7 | 8 | public delegate void OnLeagueClient(LeagueClientWatcher watcher, LeagueClient client); 9 | public class LeagueClientWatcher 10 | { 11 | private readonly PlatformBase _platform; 12 | 13 | public LeagueClientWatcher() 14 | { 15 | _platform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? new WindowsPlatform() : 16 | RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? new OsxPlatform() : new LinuxPlatform(); 17 | Clients = new List(); 18 | } 19 | 20 | public List Clients { get; } 21 | public event OnLeagueClient? OnLeagueClient; 22 | public event OnLeagueClient? OnLeagueClientExit; 23 | 24 | public async Task Observe(CancellationToken token = new()) 25 | { 26 | while (true) 27 | { 28 | if (token.IsCancellationRequested) 29 | return; 30 | 31 | var processes = Process.GetProcesses(); 32 | foreach (var process in processes.Where(p => p.ProcessName is "LeagueClientUx" or "LeagueClientUx.exe")) // Wine turns it into an .exe process. 33 | { 34 | if (token.IsCancellationRequested) 35 | return; 36 | if (Clients.Any(d => d.Pid == process.Id)) 37 | continue; 38 | var info = _platform.ExtractArguments(process.Id); 39 | if (info is null) 40 | continue; 41 | var client = new LeagueClient(process.Id,info, process); 42 | process.EnableRaisingEvents = true; 43 | process.Exited += (_, _) => 44 | { 45 | Clients.Remove(client); 46 | OnLeagueClientExitInvoke(client); 47 | }; 48 | Clients.Add(client); 49 | OnLeagueClientInvoke(client); 50 | 51 | } 52 | 53 | Thread.Sleep(1000); 54 | } 55 | } 56 | 57 | 58 | private void OnLeagueClientExitInvoke(LeagueClient client) 59 | { 60 | OnLeagueClientExit?.Invoke(this, client); 61 | } 62 | 63 | private void OnLeagueClientInvoke(LeagueClient client) 64 | { 65 | OnLeagueClient?.Invoke(this, client); 66 | } 67 | } -------------------------------------------------------------------------------- /src/Ekko/Platform/LinuxPlatform.cs: -------------------------------------------------------------------------------- 1 | namespace Ekko.Platform; 2 | 3 | public class LinuxPlatform : PlatformBase 4 | { 5 | protected override string FileName => "/bin/bash"; 6 | protected override string AuthTokenRegex => "--(riotclient|remoting)-auth-token=(.*?)( --|\n|$|\")"; 7 | protected override string PortRegex => "--(riotclient-|)app-port=(.*?)( --|\n|$|\")"; 8 | protected override string GetCommand(int pid) =>$"-c \"ps -ww -fp {pid}\""; 9 | 10 | } -------------------------------------------------------------------------------- /src/Ekko/Platform/OSXPlatform.cs: -------------------------------------------------------------------------------- 1 | namespace Ekko.Platform; 2 | 3 | public class OsxPlatform : PlatformBase 4 | { 5 | protected override string FileName => "/bin/bash"; 6 | protected override string AuthTokenRegex => "--(riotclient|remoting)-auth-token=(.*?)( --|\n|$|\")"; 7 | protected override string PortRegex => "--(riotclient-|)app-port=(.*?)( --|\n|$|\")"; 8 | protected override string GetCommand(int pid) => $"/C \"ps -ww -fp {pid}\""; 9 | 10 | } -------------------------------------------------------------------------------- /src/Ekko/Platform/PlatformBase.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace Ekko.Platform; 5 | 6 | public abstract class PlatformBase 7 | { 8 | protected abstract string FileName { get; } 9 | protected abstract string AuthTokenRegex { get; } 10 | protected abstract string PortRegex { get; } 11 | protected abstract string GetCommand(int pid); 12 | 13 | public ClientAuthInfo? ExtractArguments(int pid) 14 | { 15 | var command = GetCommand(pid); 16 | var commandLine = GetCommandLine(FileName, command); 17 | return string.IsNullOrEmpty(commandLine) ? null : ExtractArguments(commandLine); 18 | } 19 | public ClientAuthInfo? ExtractArguments(string commandLine) 20 | { 21 | var authToken = Regex.Matches(commandLine, AuthTokenRegex); 22 | var port = Regex.Matches(commandLine, PortRegex); 23 | 24 | var authInfo = new ClientAuthInfo(); 25 | foreach (Match match in authToken) 26 | { 27 | if (match.Groups.Count != 4) 28 | continue; 29 | 30 | switch (match.Groups[1].Value) 31 | { 32 | case "riotclient": 33 | authInfo.RiotClientAuthToken = match.Groups[2].Value; 34 | break; 35 | case "remoting": 36 | authInfo.RemotingAuthToken = match.Groups[2].Value; 37 | break; 38 | } 39 | } 40 | 41 | foreach (Match match in port) 42 | { 43 | if (match.Groups.Count != 4) 44 | continue; 45 | 46 | switch (match.Groups[1].Value) 47 | { 48 | case "riotclient-": 49 | authInfo.RiotClientPort = Convert.ToInt32(match.Groups[2].Value); 50 | break; 51 | case "": 52 | authInfo.RemotingPort = Convert.ToInt32(match.Groups[2].Value); 53 | break; 54 | } 55 | } 56 | 57 | return authInfo; 58 | } 59 | private string? GetCommandLine(string fileName,string command) 60 | { 61 | var output = string.Empty; 62 | try 63 | { 64 | var startInfo = new ProcessStartInfo 65 | { 66 | Verb = "runas", 67 | FileName = fileName, 68 | Arguments = command, 69 | WindowStyle = ProcessWindowStyle.Hidden, 70 | UseShellExecute = false, 71 | CreateNoWindow = true, 72 | RedirectStandardOutput = true, 73 | RedirectStandardError = false 74 | }; 75 | 76 | var proc = Process.Start(startInfo); 77 | output = proc?.StandardOutput.ReadToEnd(); 78 | proc?.WaitForExit(5000); 79 | 80 | return output; 81 | } 82 | catch (Exception) 83 | { 84 | return output; 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/Ekko/Platform/WindowsPlatform.cs: -------------------------------------------------------------------------------- 1 | namespace Ekko.Platform; 2 | 3 | public class WindowsPlatform : PlatformBase 4 | { 5 | protected override string FileName => "cmd.exe"; 6 | protected override string AuthTokenRegex => "--(riotclient|remoting)-auth-token=(.*?)( --|\n|$|\")"; 7 | protected override string PortRegex => "--(riotclient-|)app-port=(.*?)( --|\n|$|\")"; 8 | protected override string GetCommand(int pid) => $"/C \"WMIC PROCESS WHERE ProcessId={pid} GET commandline\""; 9 | 10 | } --------------------------------------------------------------------------------