diff --git a/EpicAccount.cs b/EpicAccount.cs index 308dda3..f5e6d2e 100644 --- a/EpicAccount.cs +++ b/EpicAccount.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http.Headers; using System.Net.Http.Json; using System.Text; @@ -12,6 +13,7 @@ class EpicAccount { private const string EXCHANGE_API_URL = "/account/api/oauth/exchange"; private const string VERIFY_API_URL = "/account/api/oauth/verify"; + private const string LOGOUT_API_URL = "/account/api/oauth/sessions/kill"; public string? AccountId; public string? DisplayName; @@ -75,6 +77,12 @@ public async Task VerifyToken() return verify_response!.expires_in > 60; } + public async Task Logout() + { + HttpResponseMessage resp = await HTTPClient.DeleteAsync($"{LOGOUT_API_URL}/{AccessToken}"); + return resp.StatusCode == HttpStatusCode.NoContent || resp.StatusCode == HttpStatusCode.OK; + } + public StoredAccountInfo MakeStoredAccountInfo() { StoredAccountInfo info = new StoredAccountInfo(); diff --git a/Program.cs b/Program.cs index b2fa2ec..cf362ad 100644 --- a/Program.cs +++ b/Program.cs @@ -14,7 +14,7 @@ internal class Program static void PrintUsage() { - Console.WriteLine("Usage: EricLauncher.exe [executable path] (options) (game arguments)"); + Console.WriteLine("Usage: EricLauncher.exe [game executable path or verb] (options) (game arguments)"); Console.WriteLine(); Console.WriteLine("Options:"); Console.WriteLine(" --accountId [id] - use a specific Epic Games account ID to sign in."); @@ -25,6 +25,9 @@ static void PrintUsage() Console.WriteLine(" --offline - skips the Epic Games login flow, to launch the game in offline mode."); Console.WriteLine(" --manifest [file] - specify a specific manifest file to use."); Console.WriteLine(); + Console.WriteLine("Verbs:"); + Console.WriteLine(" logout - Logs out of Epic Games."); + Console.WriteLine(); } static async Task Main(string[] args) @@ -86,13 +89,16 @@ static async Task Main(string[] args) // handle special exe names bool exchange_code_only = false; bool caldera_only = false; - if (exe_name.StartsWith("EricExchange")) exchange_code_only = true; - if (exe_name.EndsWith("EricCaldera")) caldera_only = true; - // both these options imply a dry run with no manifest - if (exchange_code_only || caldera_only) + bool logout = false; + if (exe_name.StartsWith("exchange")) exchange_code_only = true; + if (exe_name.EndsWith("caldera")) caldera_only = true; + if (exe_name == "logout") logout = true; + // all these options imply an online dry run with no manifest + if (exchange_code_only || caldera_only || logout) { no_manifest = true; dry_run = true; + offline = false; } // always run as a dry run if we're on Linux or FreeBSD @@ -225,6 +231,20 @@ static async Task Main(string[] args) else Console.WriteLine($"Logged in as {account.AccountId}!"); + if (logout) + { + DeleteAccountInfo(account.AccountId!); + if (GetDefaultAccount() == account.AccountId!) + DeleteDefaultAccount(); + + bool success = await account.Logout(); + if (success) + Console.WriteLine("Successfully logged out!"); + else // hdd still doesn't have session but it exists in backend... + Console.WriteLine("Logged out!"); + return; + } + // save our refresh token for later usage if (!Directory.Exists(BaseAppDataFolder)) Directory.CreateDirectory(BaseAppDataFolder); @@ -420,6 +440,17 @@ static void StoreAccountInfo(StoredAccountInfo info, bool set_default = false) File.WriteAllText($"{BaseAppDataFolder}/default.json", $"{{\"AccountId\": \"{info.AccountId!}\"}}"); } + static void DeleteAccountInfo(string account_id) + { + string path = $"{BaseAppDataFolder}/{account_id}.json"; + try + { + File.Delete(path); + } + catch { } + return; + } + static StoredAccountInfo? GetAccountInfo(string account_id) { string path = $"{BaseAppDataFolder}/{account_id}.json"; @@ -457,7 +488,7 @@ static void StoreAccountInfo(StoredAccountInfo info, bool set_default = false) static string? GetDefaultAccount() { string path = $"{BaseAppDataFolder}/default.json"; - if (!File.Exists($"{BaseAppDataFolder}/default.json")) + if (!File.Exists(path)) return null; try { @@ -469,5 +500,16 @@ static void StoreAccountInfo(StoredAccountInfo info, bool set_default = false) catch { } return null; } + + static void DeleteDefaultAccount() + { + string path = $"{BaseAppDataFolder}/default.json"; + try + { + File.Delete(path); + } + catch { } + return; + } } } diff --git a/README.md b/README.md index 5128658..db46b17 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ This is designed to be run from the command line, but you can drag and drop a ga Alternatively you could make a batch file or shortcut, or create a shortcut in a launcher such as Steam - pointing to EricLauncher, with the game you want to launch as the launch arguments ``` -Usage: EricLauncher.exe [executable path] (options) (game arguments) +Usage: EricLauncher.exe [game executable path or verb] (options) (game arguments) Options: --accountId [id] - use a specific Epic Games account ID to sign in. @@ -35,13 +35,16 @@ Options: --dryRun - goes through the Epic Games login flow, but does not launch the game. --offline - skips the Epic Games login flow, to launch the game in offline mode. --manifest [file] - specify a specific manifest file to use. + +Verbs: + logout - Logs out of Epic Games. ``` The account ID parameter is only required if you are using multiple accounts. Omitting this value will use (or save) a default account. For best results, make sure the game has been launched at least once by the official Epic Games Launcher, and the provided executable path is the same one that gets launched by the official launcher. -Epic Games session files are stored in `%localappdata%\EricLauncher`. +Epic Games session files are stored in `%localappdata%\EricLauncher`. You can log out of EricLauncher by running `EricLauncher.exe logout`. ## TODO