diff --git a/DepotDownloader/AccountSettingsStore.cs b/DepotDownloader/AccountSettingsStore.cs index 7f2bbfef..203a3290 100644 --- a/DepotDownloader/AccountSettingsStore.cs +++ b/DepotDownloader/AccountSettingsStore.cs @@ -29,11 +29,16 @@ class AccountSettingsStore string FileName; - AccountSettingsStore() + public string DefaultUsername { get; private set; } + + public bool LoadedFromEnv { get; private set; } + + AccountSettingsStore(bool loadedFromEnv = false) { ContentServerPenalty = new ConcurrentDictionary(); LoginTokens = new(StringComparer.OrdinalIgnoreCase); GuardData = new(StringComparer.OrdinalIgnoreCase); + LoadedFromEnv = loadedFromEnv; } static bool Loaded @@ -49,7 +54,28 @@ public static void LoadFromFile(string filename) if (Loaded) throw new Exception("Config already loaded"); - if (IsolatedStorage.FileExists(filename)) + var env = System.Environment.GetEnvironmentVariable("DEPOTDOWNLOADER_TOKEN"); + if (!string.IsNullOrWhiteSpace(env)) + { + Instance = new AccountSettingsStore(true); + var pieces = env.Trim().Split(":", 3); + if (pieces.Length == 2 || pieces.Length == 3) + { + var username = pieces[0]; + var token = pieces[1]; + Instance.DefaultUsername = username; + Instance.LoginTokens[username] = token; + if (pieces.Length == 3 && !string.IsNullOrWhiteSpace(pieces[2])) + { + Instance.GuardData[username] = pieces[2]; + } + } + else + { + Console.Error.WriteLine("Failed to parse DEPOTDOWNLOADER_TOKEN"); + } + } + else if (IsolatedStorage.FileExists(filename)) { try { @@ -59,7 +85,7 @@ public static void LoadFromFile(string filename) } catch (IOException ex) { - Console.WriteLine("Failed to load account settings: {0}", ex.Message); + Console.Error.WriteLine("Failed to load account settings: {0}", ex.Message); Instance = new AccountSettingsStore(); } } @@ -76,6 +102,9 @@ public static void Save() if (!Loaded) throw new Exception("Saved config before loading"); + if (Instance.LoadedFromEnv) + return; // don't save credentials loaded from env vars; the whole point is that the user is managing the storage, not the program. + try { using var fs = IsolatedStorage.OpenFile(Instance.FileName, FileMode.Create, FileAccess.Write); @@ -84,7 +113,25 @@ public static void Save() } catch (IOException ex) { - Console.WriteLine("Failed to save account settings: {0}", ex.Message); + Console.Error.WriteLine("Failed to save account settings: {0}", ex.Message); + } + } + + public static void PrintToConsole() + { + if (!Loaded) + throw new Exception("Printed config before loading"); + + foreach (var (username, token) in Instance.LoginTokens) + { + _ = Instance.GuardData.TryGetValue(username, out var guard); + if (guard == null) guard = ""; + Console.WriteLine($"{username}:{token}:{guard}"); + } + + if (Instance.LoginTokens.Count == 0) + { + Console.Error.WriteLine("warn: no accounts saved"); } } } diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index 26b7f16f..39ac1a39 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -40,6 +40,19 @@ static async Task Main(string[] args) AccountSettingsStore.LoadFromFile("account.config"); + if (HasParameter(args, "-show-tokens") || HasParameter(args, "-show-token")) + { + if (args.Length != 1) + { + Console.Error.WriteLine("Error: -show-tokens must be used by itself"); + return 1; + } + + AccountSettingsStore.PrintToConsole(); + + return 0; + } + #region Common Options // Not using HasParameter because it is case insensitive @@ -58,15 +71,15 @@ static async Task Main(string[] args) DebugLog.Enabled = true; DebugLog.AddListener((category, message) => { - Console.WriteLine("[{0}] {1}", category, message); + Console.Error.WriteLine("[{0}] {1}", category, message); }); var httpEventListener = new HttpDiagnosticEventListener(); } - var username = GetParameter(args, "-username") ?? GetParameter(args, "-user"); + var username = GetParameter(args, "-username") ?? GetParameter(args, "-user") ?? AccountSettingsStore.Instance.DefaultUsername; var password = GetParameter(args, "-password") ?? GetParameter(args, "-pass"); - ContentDownloader.Config.RememberPassword = HasParameter(args, "-remember-password"); + ContentDownloader.Config.RememberPassword = HasParameter(args, "-remember-password") || AccountSettingsStore.Instance.LoadedFromEnv; ContentDownloader.Config.UseQrCode = HasParameter(args, "-qr"); ContentDownloader.Config.SkipAppConfirmation = HasParameter(args, "-no-mobile"); @@ -74,13 +87,13 @@ static async Task Main(string[] args) { if (ContentDownloader.Config.RememberPassword && !ContentDownloader.Config.UseQrCode) { - Console.WriteLine("Error: -remember-password can not be used without -username or -qr."); + Console.Error.WriteLine("Error: -remember-password can not be used without -username or -qr."); return 1; } } else if (ContentDownloader.Config.UseQrCode) { - Console.WriteLine("Error: -qr can not be used with -username."); + Console.Error.WriteLine("Error: -qr can not be used with -username."); return 1; } @@ -490,10 +503,12 @@ static void PrintUsage() Console.WriteLine(" depotdownloader -app [-depot [-manifest ]]"); Console.WriteLine(" [-username [-password ]] [other options]"); Console.WriteLine(); - Console.WriteLine("Usage: downloading a workshop item using pubfile id"); + Console.WriteLine("Usage: downloading a workshop item using pubfile id:"); Console.WriteLine(" depotdownloader -app -pubfile [-username [-password ]]"); - Console.WriteLine("Usage: downloading a workshop item using ugc id"); + Console.WriteLine("Usage: downloading a workshop item using ugc id:"); Console.WriteLine(" depotdownloader -app -ugc [-username [-password ]]"); + Console.WriteLine("Usage: show currently stored tokens for use in DEPOTDOWNLOADER_TOKEN:"); + Console.WriteLine(" depotdownloader -show-tokens"); Console.WriteLine(); Console.WriteLine("Parameters:"); Console.WriteLine(" -app <#> - the AppID to download."); @@ -516,6 +531,7 @@ static void PrintUsage() Console.WriteLine(" -password - the password of the account to login to for restricted content."); Console.WriteLine(" -remember-password - if set, remember the password for subsequent logins of this user."); Console.WriteLine(" use -username -remember-password as login credentials."); + Console.WriteLine(" note: this actually saves a login token, same as the steam client."); Console.WriteLine(" -qr - display a login QR code to be scanned with the Steam mobile app"); Console.WriteLine(" -no-mobile - prefer entering a 2FA code instead of prompting to accept in the Steam mobile app"); Console.WriteLine(); @@ -530,8 +546,15 @@ static void PrintUsage() Console.WriteLine(" -loginid <#> - a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently."); Console.WriteLine(" -use-lancache - forces downloads over the local network via a Lancache instance."); Console.WriteLine(); + Console.WriteLine(" -show-tokens - show token data saved by a previous -remember-password, for DEPOTDOWNLOADER_TOKEN"); + Console.WriteLine(" must be used by itself"); + Console.WriteLine(); Console.WriteLine(" -debug - enable verbose debug logging."); Console.WriteLine(" -V or --version - print version and runtime."); + Console.WriteLine(); + Console.WriteLine("Env vars:"); + Console.WriteLine(" DEPOTDOWNLOADER_TOKEN - a username, token, and optional guard data separated by colons ':'."); + Console.WriteLine(" if provided, it is equivalent to setting -username -remember-password"); } static void PrintVersion(bool printExtra = false) diff --git a/README.md b/README.md index 810c9466..a06b5808 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Parameter | Description `-qr` | display a login QR code to be scanned with the Steam mobile app `-no-mobile` | prefer entering a 2FA code instead of prompting to accept in the Steam mobile app. `-loginid <#>` | a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently. +`-show-tokens` | print out tokens previously saved by a `-remember-password` call, for use in `DEPOTDOWNLOADER_TOKEN`. #### Downloading @@ -110,6 +111,43 @@ Parameter | Description `-debug` | enable verbose debug logging. `-V` or `--version` | print version and runtime. +## How to use saved token on another machine + +First, run a command to log in, making sure to use `-remember-password`: + +```powershell +./DepotDownloader -app 730 -manifest-only -username -qr -remember-password +``` + +Then use `-show-tokens`: + +```powershell +./DepotDownloader -show-tokens +``` + +This will show something starting with your username and a colon, like `GabeN:eyAid`... + +Use like: + +(windows powershell) +```powershell +$env:DEPOTDOWNLOADER_TOKEN='GabeN:eyAid...' +./DepotDownloader +./DepotDownloader +``` + +(linux shell, single command) +```bash +DEPOTDOWNLOADER_TOKEN="GabeN:eyAid..." ./DepotDownloader +``` + +(linux shell, multiple commands) +```bash +export DEPOTDOWNLOADER_TOKEN="GabeN:eyAid..." +./DepotDownloader +./DepotDownloader +``` + ## Frequently Asked Questions ### Why am I prompted to enter a 2-factor code every time I run the app?