├── .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 | }
--------------------------------------------------------------------------------