Skip to content

Commit

Permalink
🔌 Fix several issues and add debug logging (#15)
Browse files Browse the repository at this point in the history
* Added some clear error messages, made sure application runs only once.

* Minor style changes

* Added support for Garena client, and renamed Utils to LeagueMonitor for slight clarity

* Fix error where League client has to be running, plus make the popups less obnoxious

* Added option to start on boot.

- Startup message is surpressed when the setting is on.
- Checks whether the app is moved if the setting is on, and sets the new app location as the startup application

* Most minor nitpick ever

* 🎉 Version bump to 1.1.0

* Fix issue with people unable to connect to League of Legends because of an SSL issue

* Prevent undefined behaviour when application isn't running as an administrator

* Catch issues connecting to the League Client

* Add all debug logging present

* Remove unused file reference

* Remove another reference to app manifest

* Remove ancient files

* Request admin when command line is null

* Improve SSL checking
  • Loading branch information
Querijn Heijmans authored and molenzwiebel committed Jun 29, 2019
1 parent 692fdce commit a1c5dd2
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 48 deletions.
33 changes: 33 additions & 0 deletions conduit/Administrator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Diagnostics;
using System.Reflection;
using System.Security.Principal;

namespace Conduit
{
public static class Administrator
{
public static bool IsAdmin()
{
using (WindowsIdentity identity = WindowsIdentity.GetCurrent())
{
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
}

public static void Elevate()
{
var currentProcessInfo = new ProcessStartInfo
{
UseShellExecute = true,
WorkingDirectory = Environment.CurrentDirectory,
FileName = Assembly.GetEntryAssembly().Location,
Verb = "runas"
};

Process.Start(currentProcessInfo);
Environment.Exit(0);
}
}
}
2 changes: 2 additions & 0 deletions conduit/Conduit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Administrator.cs" />
<Compile Include="ConnectionManager.cs" />
<Compile Include="CryptoHelpers.cs" />
<Compile Include="DebugLogger.cs" />
<Compile Include="DeviceConnectionPrompt.xaml.cs">
<DependentUpon>DeviceConnectionPrompt.xaml</DependentUpon>
</Compile>
Expand Down
31 changes: 23 additions & 8 deletions conduit/ConnectionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,15 @@ public ConnectionManager(App app)
// Hook up league events.
league.OnConnected += () =>
{
DebugLogger.Global.WriteMessage($"ConnectionManager is connected to League of Legends.");
isNewLaunch = true;
Connect();
};
league.OnDisconnected += Close;
league.OnDisconnected += () =>
{
DebugLogger.Global.WriteMessage($"ConnectionManager is disconnected from League of Legends.");
Close();
};
}

/**
Expand All @@ -45,16 +50,17 @@ public ConnectionManager(App app)
*/
public async void Connect()
{
Console.WriteLine("[+] Connecting to rift...");

if (hubConnectionHandler != null) throw new Exception("Already connected.");
if (!league.IsConnected) return;

try
{
DebugLogger.Global.WriteMessage("Connecting to Rift...");

// Cancel pending reconnect if there is one.
if (reconnectCancellationTokenSource != null)
{
DebugLogger.Global.WriteMessage($"Canceling older reconnect to Rift.");
reconnectCancellationTokenSource.Cancel();
reconnectCancellationTokenSource = null;
}
Expand All @@ -63,19 +69,23 @@ public async void Connect()
bool valid = false; // in case first startup and hub token is empty
if (!Persistence.GetHubToken().IsNullOrEmpty())
{
DebugLogger.Global.WriteMessage("Requesting hub token..");
var response = await httpClient.GetStringAsync(Program.HUB + "/check?token=" + Persistence.GetHubToken());
valid = response == "true";
DebugLogger.Global.WriteMessage($"Hub token validity: {(valid ? "valid" : "invalid")}.");
}

// ... and request a new one if it isn't.
if (!valid)
{
DebugLogger.Global.WriteMessage($"Requesting hub token..");
var payload = "{\"pubkey\":\"" + CryptoHelpers.ExportPublicKey() + "\"}";
var responseBlob = await httpClient.PostAsync(Program.HUB + "/register", new StringContent(payload, Encoding.UTF8, "application/json"));
var response = SimpleJson.DeserializeObject<dynamic>(await responseBlob.Content.ReadAsStringAsync());
if (!response["ok"]) throw new Exception("Could not receive JWT from Rift");

Persistence.SetHubToken(response["token"]);
DebugLogger.Global.WriteMessage($"Hub token: {response["token"]}.");
}

// Connect to hub. Will error if token is invalid or server is down, which will prompt a reconnection.
Expand All @@ -85,13 +95,16 @@ public async void Connect()
// We assume to be connected.
if (isNewLaunch)
{
DebugLogger.Global.WriteMessage($"Creating New Launch popup.");
app.ShowNotification("Connected to League. Click here for instructions on how to control your League client from your phone.");
isNewLaunch = false;
}

hasTriedImmediateReconnect = false;
} catch
}
catch (Exception e)
{
DebugLogger.Global.WriteError($"Connection to Rift failed, an exception occurred: {e.ToString()}");
// Something happened that we didn't anticipate for.
// Just try again in a bit.
CloseAndReconnect();
Expand All @@ -103,7 +116,7 @@ public async void Connect()
*/
public void Close()
{
Console.WriteLine("[+] Disconnecting from rift.");
DebugLogger.Global.WriteMessage("Disconnecting from rift.");

// Already closed, don't error but just ignore.
if (hubConnectionHandler == null) return;
Expand All @@ -130,17 +143,19 @@ public async void CloseAndReconnect()
// If this is an immediate reconnect,
if (hasTriedImmediateReconnect)
{
Console.WriteLine("[+] Reconnecting to rift in 5s...");
DebugLogger.Global.WriteWarning("Could not connect to Rift, retrying to connect to Rift in 5s...");
await Task.Delay(5000, reconnectCancellationTokenSource.Token);
} else
{
Console.WriteLine("[+] Reconnecting to rift immediately...");
DebugLogger.Global.WriteWarning("Could not connect to Rift, retrying to connect to Rift immediately...");
hasTriedImmediateReconnect = true;
}

Connect();
} catch (TaskCanceledException)
}
catch (TaskCanceledException e)
{
DebugLogger.Global.WriteError($"Our reconnect to Rift got canceled, reason: {e.ToString()}");
// Delay got cancelled. Ignore error.
}
}
Expand Down
35 changes: 35 additions & 0 deletions conduit/DebugLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.IO;

namespace Conduit
{
public class DebugLogger
{
public static DebugLogger Global = new DebugLogger("global.txt");

private StreamWriter writer;

public DebugLogger(string fileName)
{
writer = new StreamWriter(Path.Combine(Persistence.DATA_DIRECTORY, fileName), true);
writer.AutoFlush = true;
writer.WriteLine($"\n\n\n --- {DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss")} --- ");
writer.WriteLine($"Started logging to {fileName}...");
}

public void WriteError(string error)
{
writer.WriteLine($"[ERROR {DateTime.Now.ToString("HH:mm:ss")}] {error}");
}

public void WriteMessage(string message)
{
writer.WriteLine($"[MSG {DateTime.Now.ToString("HH:mm:ss")}] {message}");
}

public void WriteWarning(string warning)
{
writer.WriteLine($"[WRN {DateTime.Now.ToString("HH:mm:ss")}] {warning}");
}
}
}
92 changes: 58 additions & 34 deletions conduit/LeagueConnection.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Authentication;
Expand All @@ -16,11 +17,7 @@ namespace Conduit
*/
class LeagueConnection
{
private static readonly HttpClient HTTP_CLIENT = new HttpClient(new HttpClientHandler()
{
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
ServerCertificateCustomValidationCallback = (a, b, c, d) => true
});
private static HttpClient HTTP_CLIENT;

private WebSocket socketConnection;
private Tuple<Process, string, string> processInfo;
Expand All @@ -41,6 +38,24 @@ class LeagueConnection
*/
public LeagueConnection()
{
try
{
HTTP_CLIENT = new HttpClient(new HttpClientHandler()
{
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls,
ServerCertificateCustomValidationCallback = (a, b, c, d) => true
});
}
catch
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;

HTTP_CLIENT = new HttpClient(new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = (a, b, c, d) => true
});
}

// Run after a slight delay.
Task.Delay(2000).ContinueWith(e => TryConnectOrRetry());
}
Expand All @@ -59,35 +74,44 @@ public void ClearAllListeners()
*/
private void TryConnect()
{
// We're already connected.
if (connected) return;

// Check league status, abort if league is not running.
var status = LeagueUtils.GetLeagueStatus();
if (status == null) return;

// Update local state.
processInfo = status;
connected = true;

// Set the password and base address for our httpclient so we don't have to specify it every time.
var byteArray = Encoding.ASCII.GetBytes("riot:" + status.Item2);
HTTP_CLIENT.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

// Connect to our websocket.
socketConnection = new WebSocket("wss://127.0.0.1:" + status.Item3 + "/", "wamp");
socketConnection.SetCredentials("riot", status.Item2, true);
socketConnection.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
socketConnection.SslConfiguration.ServerCertificateValidationCallback = (a, b, c, d) => true;
socketConnection.OnMessage += HandleMessage;
socketConnection.OnClose += HandleDisconnect;
socketConnection.Connect();

// Subscribe to Json API events from the LCU.
socketConnection.Send("[5,\"OnJsonApiEvent\"]");

// Emit our events.
OnConnected?.Invoke();
try
{
// We're already connected.
if (connected) return;

// Check league status, abort if league is not running.
var status = LeagueUtils.GetLeagueStatus();
if (status == null) return;

// Set the password and base address for our httpclient so we don't have to specify it every time.
var byteArray = Encoding.ASCII.GetBytes("riot:" + status.Item2);
HTTP_CLIENT.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

// Connect to our websocket.
socketConnection = new WebSocket("wss://127.0.0.1:" + status.Item3 + "/", "wamp");
socketConnection.SetCredentials("riot", status.Item2, true);

socketConnection.SslConfiguration.EnabledSslProtocols = SslProtocols.Tls12;
socketConnection.SslConfiguration.ServerCertificateValidationCallback = (a, b, c, d) => true;
socketConnection.OnMessage += HandleMessage;
socketConnection.OnClose += HandleDisconnect;
socketConnection.Connect();
// Subscribe to Json API events from the LCU.
socketConnection.Send("[5,\"OnJsonApiEvent\"]");

// Update local state.
processInfo = status;
connected = true;

// Emit our events.
OnConnected?.Invoke();
}
catch (Exception e)
{
processInfo = null;
connected = false;
DebugLogger.Global.WriteError($"Exception occurred trying to connect to League of Legends: {e.ToString()}");
}
}

/**
Expand Down
34 changes: 28 additions & 6 deletions conduit/LeagueUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,35 @@ public static Tuple<Process, string, string> GetLeagueStatus()
using (var moc = mos.Get())
{
var commandLine = (string)moc.OfType<ManagementObject>().First()["CommandLine"];
if (commandLine == null) // We can't get the commandline sometimes without admin access, so let's elevate
{
if (Administrator.IsAdmin())
{
DebugLogger.Global.WriteError("We cannot determine why the commandline is null! Cannot get the League of Legends information from this process!");
continue;
}
else
{
Administrator.Elevate();
}
}

// Use regex to extract data, return it.
return new Tuple<Process, string, string>(
p,
AUTH_TOKEN_REGEX.Match(commandLine).Groups[1].Value,
PORT_REGEX.Match(commandLine).Groups[1].Value
);
try
{
var authToken = AUTH_TOKEN_REGEX.Match(commandLine).Groups[1].Value;
var port = PORT_REGEX.Match(commandLine).Groups[1].Value;
// Use regex to extract data, return it.
return new Tuple<Process, string, string>
(
p,
authToken,
port
);
}
catch (Exception e)
{
DebugLogger.Global.WriteError($"Error while trying to get the status for LeagueClientUx: {e.ToString()}\n\n(CommandLine = {commandLine})");
}
}
}

Expand Down

0 comments on commit a1c5dd2

Please sign in to comment.