From d8ccf800ca1869637ee1ba59ee1d1cce6d1d8651 Mon Sep 17 00:00:00 2001 From: Er2 Date: Tue, 12 Mar 2024 15:37:08 +0300 Subject: [PATCH 01/38] Add FreeBSD support --- DepotDownloader/PlatformUtilities.cs | 20 ++++++++++++++++++++ DepotDownloader/Util.cs | 6 ++++++ 2 files changed, 26 insertions(+) diff --git a/DepotDownloader/PlatformUtilities.cs b/DepotDownloader/PlatformUtilities.cs index 9ffb70f..a4f0b80 100644 --- a/DepotDownloader/PlatformUtilities.cs +++ b/DepotDownloader/PlatformUtilities.cs @@ -34,6 +34,12 @@ namespace DepotDownloader [FieldOffset(4)] public readonly ushort st_mode; } + [StructLayout(LayoutKind.Explicit, Size = 224)] + private readonly struct StatFBSDX64 + { + [FieldOffset(24)] public readonly ushort st_mode; + } + [DllImport("libc", EntryPoint = "__xstat", SetLastError = true)] private static extern int statLinuxX64(int version, string path, out StatLinuxX64 statLinux); @@ -49,6 +55,9 @@ namespace DepotDownloader [DllImport("libc", EntryPoint = "stat$INODE64", SetLastError = true)] private static extern int statOSXCompat(string path, out StatOSX stat); + [DllImport("libc", EntryPoint = "stat", SetLastError = true)] + private static extern int statFBSDX64(string path, out StatFBSDX64 stat); + [DllImport("libc", SetLastError = true)] private static extern int chmod(string path, uint mode); @@ -87,6 +96,17 @@ namespace DepotDownloader } } } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) + { + switch (RuntimeInformation.ProcessArchitecture) + { + case Architecture.X64: + { + ThrowIf(statFBSDX64(path, out var stat)); + return stat.st_mode; + } + } + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { switch (RuntimeInformation.ProcessArchitecture) diff --git a/DepotDownloader/Util.cs b/DepotDownloader/Util.cs index d249c1f..e8c20bc 100644 --- a/DepotDownloader/Util.cs +++ b/DepotDownloader/Util.cs @@ -27,6 +27,12 @@ namespace DepotDownloader return "linux"; } + if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) + { + // Return linux as freebsd steam client doesn't exist yet + return "linux"; + } + return "unknown"; } From beb8ef85076bfc8e983e9e122cc0d15996f556ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 19:13:00 +0000 Subject: [PATCH 02/38] Bump QRCoder from 1.4.3 to 1.6.0 Bumps [QRCoder](https://github.com/codebude/QRCoder) from 1.4.3 to 1.6.0. - [Release notes](https://github.com/codebude/QRCoder/releases) - [Commits](https://github.com/codebude/QRCoder/compare/v1.4.3...v1.6.0) --- updated-dependencies: - dependency-name: QRCoder dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- DepotDownloader/DepotDownloader.csproj | 40 +++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/DepotDownloader/DepotDownloader.csproj b/DepotDownloader/DepotDownloader.csproj index 47cfd28..0d5153d 100644 --- a/DepotDownloader/DepotDownloader.csproj +++ b/DepotDownloader/DepotDownloader.csproj @@ -1,20 +1,20 @@ - - - Exe - net8.0 - true - LatestMajor - 2.6.0 - Steam Downloading Utility - SteamRE Team - Copyright © SteamRE Team 2024 - ..\Icon\DepotDownloader.ico - true - - - - - - - - + + + Exe + net8.0 + true + LatestMajor + 2.6.0 + Steam Downloading Utility + SteamRE Team + Copyright © SteamRE Team 2024 + ..\Icon\DepotDownloader.ico + true + + + + + + + + From 9d04652f60f8d129bdaccd1baa78a1b02addd061 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sun, 21 Jul 2024 11:59:01 +0300 Subject: [PATCH 03/38] Add SymmetricDecryptECB to util --- DepotDownloader/ContentDownloader.cs | 2 +- DepotDownloader/Util.cs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index d919161..e9fe25a 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -244,7 +244,7 @@ namespace DepotDownloader byte[] manifest_bytes; try { - manifest_bytes = CryptoHelper.SymmetricDecryptECB(input, appBetaPassword); + manifest_bytes = Util.SymmetricDecryptECB(input, appBetaPassword); } catch (Exception e) { diff --git a/DepotDownloader/Util.cs b/DepotDownloader/Util.cs index d249c1f..39af1ae 100644 --- a/DepotDownloader/Util.cs +++ b/DepotDownloader/Util.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -133,6 +134,23 @@ namespace DepotDownloader ).ToString(); } + /// + /// Decrypts using AES/ECB/PKCS7 + /// + public static byte[] SymmetricDecryptECB(byte[] input, byte[] key) + { + using var aes = Aes.Create(); + aes.BlockSize = 128; + aes.KeySize = 256; + aes.Mode = CipherMode.ECB; + aes.Padding = PaddingMode.PKCS7; + + using var aesTransform = aes.CreateDecryptor(key, null); + var output = aesTransform.TransformFinalBlock(input, 0, input.Length); + + return output; + } + public static async Task InvokeAsync(IEnumerable> taskFactories, int maxDegreeOfParallelism) { ArgumentNullException.ThrowIfNull(taskFactories); From 65c07130a1f3beac9b489f5a52ee4a58ff1f3c40 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sun, 21 Jul 2024 12:03:42 +0300 Subject: [PATCH 04/38] Fix style violations --- .editorconfig | 15 ----------- DepotDownloader/ContentDownloader.cs | 40 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/.editorconfig b/.editorconfig index d05495c..8b93976 100644 --- a/.editorconfig +++ b/.editorconfig @@ -95,7 +95,6 @@ dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_s dotnet_naming_symbols.instance_fields.applicable_kinds = field dotnet_naming_style.instance_field_style.capitalization = camel_case -dotnet_naming_style.instance_field_style.required_prefix = _ # Locals and parameters are camelCase dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion @@ -124,20 +123,6 @@ dotnet_naming_symbols.all_members.applicable_kinds = * dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# Async methods should have "Async" suffix -dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods -dotnet_naming_rule.async_methods_end_in_async.style = end_in_async -dotnet_naming_rule.async_methods_end_in_async.severity = warning - -dotnet_naming_symbols.any_async_methods.applicable_kinds = method -dotnet_naming_symbols.any_async_methods.applicable_accessibilities = * -dotnet_naming_symbols.any_async_methods.required_modifiers = async - -dotnet_naming_style.end_in_async.required_prefix = -dotnet_naming_style.end_in_async.required_suffix = Async -dotnet_naming_style.end_in_async.capitalization = pascal_case -dotnet_naming_style.end_in_async.word_separator = - # error RS2008: Enable analyzer release tracking for the analyzer project containing rule '{0}' dotnet_diagnostic.RS2008.severity = none diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index e9fe25a..e68a3a0 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -110,7 +110,7 @@ namespace DepotDownloader IEnumerable licenseQuery; if (steam3.steamUser.SteamID.AccountType == EAccountType.AnonUser) { - licenseQuery = new List { 17906 }; + licenseQuery = [17906]; } else { @@ -610,16 +610,16 @@ namespace DepotDownloader private class GlobalDownloadCounter { - public ulong TotalBytesCompressed; - public ulong TotalBytesUncompressed; + public ulong totalBytesCompressed; + public ulong totalBytesUncompressed; } private class DepotDownloadCounter { - public ulong CompleteDownloadSize; - public ulong SizeDownloaded; - public ulong DepotBytesCompressed; - public ulong DepotBytesUncompressed; + public ulong completeDownloadSize; + public ulong sizeDownloaded; + public ulong depotBytesCompressed; + public ulong depotBytesUncompressed; } private static async Task DownloadSteam3Async(List depots) @@ -666,7 +666,7 @@ namespace DepotDownloader } Console.WriteLine("Total downloaded: {0} bytes ({1} bytes uncompressed) from {2} depots", - downloadCounter.TotalBytesCompressed, downloadCounter.TotalBytesUncompressed, depots.Count); + downloadCounter.totalBytesCompressed, downloadCounter.totalBytesUncompressed, depots.Count); } private static async Task ProcessDepotManifestAndFiles(CancellationTokenSource cts, DepotDownloadInfo depot) @@ -889,7 +889,7 @@ namespace DepotDownloader Directory.CreateDirectory(Path.GetDirectoryName(fileFinalPath)); Directory.CreateDirectory(Path.GetDirectoryName(fileStagingPath)); - depotCounter.CompleteDownloadSize += file.TotalSize; + depotCounter.completeDownloadSize += file.TotalSize; } }); @@ -961,7 +961,7 @@ namespace DepotDownloader DepotConfigStore.Instance.InstalledManifestIDs[depot.DepotId] = depot.ManifestId; DepotConfigStore.Save(); - Console.WriteLine("Depot {0} - Downloaded {1} bytes ({2} bytes uncompressed)", depot.DepotId, depotCounter.DepotBytesCompressed, depotCounter.DepotBytesUncompressed); + Console.WriteLine("Depot {0} - Downloaded {1} bytes ({2} bytes uncompressed)", depot.DepotId, depotCounter.depotBytesCompressed, depotCounter.depotBytesUncompressed); } private static void DownloadSteam3AsyncDepotFile( @@ -1124,8 +1124,8 @@ namespace DepotDownloader { lock (depotDownloadCounter) { - depotDownloadCounter.SizeDownloaded += file.TotalSize; - Console.WriteLine("{0,6:#00.00}% {1}", (depotDownloadCounter.SizeDownloaded / (float)depotDownloadCounter.CompleteDownloadSize) * 100.0f, fileFinalPath); + depotDownloadCounter.sizeDownloaded += file.TotalSize; + Console.WriteLine("{0,6:#00.00}% {1}", (depotDownloadCounter.sizeDownloaded / (float)depotDownloadCounter.completeDownloadSize) * 100.0f, fileFinalPath); } return; @@ -1134,7 +1134,7 @@ namespace DepotDownloader var sizeOnDisk = (file.TotalSize - (ulong)neededChunks.Select(x => (long)x.UncompressedLength).Sum()); lock (depotDownloadCounter) { - depotDownloadCounter.SizeDownloaded += sizeOnDisk; + depotDownloadCounter.sizeDownloaded += sizeOnDisk; } } @@ -1271,22 +1271,22 @@ namespace DepotDownloader ulong sizeDownloaded = 0; lock (depotDownloadCounter) { - sizeDownloaded = depotDownloadCounter.SizeDownloaded + (ulong)chunkData.Data.Length; - depotDownloadCounter.SizeDownloaded = sizeDownloaded; - depotDownloadCounter.DepotBytesCompressed += chunk.CompressedLength; - depotDownloadCounter.DepotBytesUncompressed += chunk.UncompressedLength; + sizeDownloaded = depotDownloadCounter.sizeDownloaded + (ulong)chunkData.Data.Length; + depotDownloadCounter.sizeDownloaded = sizeDownloaded; + depotDownloadCounter.depotBytesCompressed += chunk.CompressedLength; + depotDownloadCounter.depotBytesUncompressed += chunk.UncompressedLength; } lock (downloadCounter) { - downloadCounter.TotalBytesCompressed += chunk.CompressedLength; - downloadCounter.TotalBytesUncompressed += chunk.UncompressedLength; + downloadCounter.totalBytesCompressed += chunk.CompressedLength; + downloadCounter.totalBytesUncompressed += chunk.UncompressedLength; } if (remainingChunks == 0) { var fileFinalPath = Path.Combine(depot.InstallDir, file.FileName); - Console.WriteLine("{0,6:#00.00}% {1}", (sizeDownloaded / (float)depotDownloadCounter.CompleteDownloadSize) * 100.0f, fileFinalPath); + Console.WriteLine("{0,6:#00.00}% {1}", (sizeDownloaded / (float)depotDownloadCounter.completeDownloadSize) * 100.0f, fileFinalPath); } } From ae72ac03c9ccc059f8068c4653f5432651ffbffe Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sun, 28 Jul 2024 11:17:32 +0300 Subject: [PATCH 05/38] Update steamkit, fix line endings --- .gitattributes | 2 +- DepotDownloader/DepotDownloader.csproj | 40 +++++++++++++------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.gitattributes b/.gitattributes index a6e03fc..ff73992 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ *.cs text eol=lf -*.csproj text eol=crlf +*.csproj text eol=lf *.config eol=lf *.json eol=lf diff --git a/DepotDownloader/DepotDownloader.csproj b/DepotDownloader/DepotDownloader.csproj index 0d5153d..e7f775e 100644 --- a/DepotDownloader/DepotDownloader.csproj +++ b/DepotDownloader/DepotDownloader.csproj @@ -1,20 +1,20 @@ - - - Exe - net8.0 - true - LatestMajor - 2.6.0 - Steam Downloading Utility - SteamRE Team - Copyright © SteamRE Team 2024 - ..\Icon\DepotDownloader.ico - true - - - - - - - - + + + Exe + net8.0 + true + LatestMajor + 2.6.0 + Steam Downloading Utility + SteamRE Team + Copyright © SteamRE Team 2024 + ..\Icon\DepotDownloader.ico + true + + + + + + + + From ccbffd1c080c4cee351389f27d3da7a14b601bbb Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sun, 28 Jul 2024 12:38:34 +0300 Subject: [PATCH 06/38] Use ansi escape code to indicate download progress in taskbar --- DepotDownloader/Ansi.cs | 51 ++++++++++ DepotDownloader/AnsiDetector.cs | 134 +++++++++++++++++++++++++++ DepotDownloader/ContentDownloader.cs | 31 +++++-- DepotDownloader/Program.cs | 2 + 4 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 DepotDownloader/Ansi.cs create mode 100644 DepotDownloader/AnsiDetector.cs diff --git a/DepotDownloader/Ansi.cs b/DepotDownloader/Ansi.cs new file mode 100644 index 0000000..7041deb --- /dev/null +++ b/DepotDownloader/Ansi.cs @@ -0,0 +1,51 @@ +using System; +using Spectre.Console; + +namespace DepotDownloader; + +static class Ansi +{ + // https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC + // https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences + public enum ProgressState + { + Hidden = 0, + Default = 1, + Error = 2, + Indeterminate = 3, + Warning = 4, + } + + const char ESC = (char)0x1B; + const char BEL = (char)0x07; + + private static bool useProgress; + + public static void Init() + { + if (Console.IsInputRedirected || Console.IsOutputRedirected) + { + return; + } + + var (supportsAnsi, legacyConsole) = AnsiDetector.Detect(stdError: false, upgrade: true); + + useProgress = supportsAnsi && !legacyConsole; + } + + public static void Progress(ulong downloaded, ulong total) + { + var progress = (byte)MathF.Round(downloaded / (float)total * 100.0f); + Progress(ProgressState.Default, progress); + } + + public static void Progress(ProgressState state, byte progress = 0) + { + if (!useProgress) + { + return; + } + + Console.Write($"{ESC}]9;4;{(byte)state};{progress}{BEL}"); + } +} diff --git a/DepotDownloader/AnsiDetector.cs b/DepotDownloader/AnsiDetector.cs new file mode 100644 index 0000000..2110d9c --- /dev/null +++ b/DepotDownloader/AnsiDetector.cs @@ -0,0 +1,134 @@ +// Copied from https://github.com/spectreconsole/spectre.console/blob/d79e6adc5f8e637fb35c88f987023ffda6707243/src/Spectre.Console/Internal/Backends/Ansi/AnsiDetector.cs +// MIT License - Copyright(c) 2020 Patrik Svensson, Phil Scott, Nils Andresen +// which is partially based on https://github.com/keqingrong/supports-ansi/blob/master/index.js +// + +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace Spectre.Console; + +internal static class AnsiDetector +{ + private static readonly Regex[] _regexes = + [ + new("^xterm"), // xterm, PuTTY, Mintty + new("^rxvt"), // RXVT + new("^eterm"), // Eterm + new("^screen"), // GNU screen, tmux + new("tmux"), // tmux + new("^vt100"), // DEC VT series + new("^vt102"), // DEC VT series + new("^vt220"), // DEC VT series + new("^vt320"), // DEC VT series + new("ansi"), // ANSI + new("scoansi"), // SCO ANSI + new("cygwin"), // Cygwin, MinGW + new("linux"), // Linux console + new("konsole"), // Konsole + new("bvterm"), // Bitvise SSH Client + new("^st-256color"), // Suckless Simple Terminal, st + new("alacritty"), // Alacritty + ]; + + public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool stdError, bool upgrade) + { + // Running on Windows? + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Running under ConEmu? + var conEmu = Environment.GetEnvironmentVariable("ConEmuANSI"); + if (!string.IsNullOrEmpty(conEmu) && conEmu.Equals("On", StringComparison.OrdinalIgnoreCase)) + { + return (true, false); + } + + var supportsAnsi = Windows.SupportsAnsi(upgrade, stdError, out var legacyConsole); + return (supportsAnsi, legacyConsole); + } + + return DetectFromTerm(); + } + + private static (bool SupportsAnsi, bool LegacyConsole) DetectFromTerm() + { + // Check if the terminal is of type ANSI/VT100/xterm compatible. + var term = Environment.GetEnvironmentVariable("TERM"); + if (!string.IsNullOrWhiteSpace(term)) + { + if (_regexes.Any(regex => regex.IsMatch(term))) + { + return (true, false); + } + } + + return (false, true); + } + + private static class Windows + { + private const int STD_OUTPUT_HANDLE = -11; + private const int STD_ERROR_HANDLE = -12; + private const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004; + private const uint DISABLE_NEWLINE_AUTO_RETURN = 0x0008; + + [DllImport("kernel32.dll")] + private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); + + [DllImport("kernel32.dll")] + private static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr GetStdHandle(int nStdHandle); + + [DllImport("kernel32.dll")] + public static extern uint GetLastError(); + + public static bool SupportsAnsi(bool upgrade, bool stdError, out bool isLegacy) + { + isLegacy = false; + + try + { + var @out = GetStdHandle(stdError ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE); + if (!GetConsoleMode(@out, out var mode)) + { + // Could not get console mode, try TERM (set in cygwin, WSL-Shell). + var (ansiFromTerm, legacyFromTerm) = DetectFromTerm(); + + isLegacy = ansiFromTerm ? legacyFromTerm : isLegacy; + return ansiFromTerm; + } + + if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0) + { + isLegacy = true; + + if (!upgrade) + { + return false; + } + + // Try enable ANSI support. + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + if (!SetConsoleMode(@out, mode)) + { + // Enabling failed. + return false; + } + + isLegacy = false; + } + + return true; + } + catch + { + // All we know here is that we don't support ANSI. + return false; + } + } + } +} diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index e68a3a0..6e528b9 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -610,6 +610,7 @@ namespace DepotDownloader private class GlobalDownloadCounter { + public ulong completeDownloadSize; public ulong totalBytesCompressed; public ulong totalBytesUncompressed; } @@ -624,6 +625,8 @@ namespace DepotDownloader private static async Task DownloadSteam3Async(List depots) { + Ansi.Progress(Ansi.ProgressState.Indeterminate); + var cts = new CancellationTokenSource(); cdnPool.ExhaustedToken = cts; @@ -634,7 +637,7 @@ namespace DepotDownloader // First, fetch all the manifests for each depot (including previous manifests) and perform the initial setup foreach (var depot in depots) { - var depotFileData = await ProcessDepotManifestAndFiles(cts, depot); + var depotFileData = await ProcessDepotManifestAndFiles(cts, depot, downloadCounter); if (depotFileData != null) { @@ -665,11 +668,13 @@ namespace DepotDownloader await DownloadSteam3AsyncDepotFiles(cts, downloadCounter, depotFileData, allFileNamesAllDepots); } + Ansi.Progress(Ansi.ProgressState.Hidden); + Console.WriteLine("Total downloaded: {0} bytes ({1} bytes uncompressed) from {2} depots", downloadCounter.totalBytesCompressed, downloadCounter.totalBytesUncompressed, depots.Count); } - private static async Task ProcessDepotManifestAndFiles(CancellationTokenSource cts, DepotDownloadInfo depot) + private static async Task ProcessDepotManifestAndFiles(CancellationTokenSource cts, DepotDownloadInfo depot, GlobalDownloadCounter downloadCounter) { var depotCounter = new DepotDownloadCounter(); @@ -751,7 +756,7 @@ namespace DepotDownloader } else { - Console.Write("Downloading depot manifest..."); + Console.Write("Downloading depot manifest... "); DepotManifest depotManifest = null; ulong manifestRequestCode = 0; @@ -814,7 +819,7 @@ namespace DepotDownloader if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) { - Console.WriteLine("Encountered 401 for depot manifest {0} {1}. Aborting.", depot.DepotId, depot.ManifestId); + Console.WriteLine("Encountered {2} for depot manifest {0} {1}. Aborting.", depot.DepotId, depot.ManifestId, (int)e.StatusCode); break; } @@ -889,6 +894,7 @@ namespace DepotDownloader Directory.CreateDirectory(Path.GetDirectoryName(fileFinalPath)); Directory.CreateDirectory(Path.GetDirectoryName(fileStagingPath)); + downloadCounter.completeDownloadSize += file.TotalSize; depotCounter.completeDownloadSize += file.TotalSize; } }); @@ -918,7 +924,7 @@ namespace DepotDownloader await Util.InvokeAsync( files.Select(file => new Func(async () => - await Task.Run(() => DownloadSteam3AsyncDepotFile(cts, depotFilesData, file, networkChunkQueue)))), + await Task.Run(() => DownloadSteam3AsyncDepotFile(cts, downloadCounter, depotFilesData, file, networkChunkQueue)))), maxDegreeOfParallelism: Config.MaxDownloads ); @@ -966,6 +972,7 @@ namespace DepotDownloader private static void DownloadSteam3AsyncDepotFile( CancellationTokenSource cts, + GlobalDownloadCounter downloadCounter, DepotFilesData depotFilesData, ProtoManifest.FileData file, ConcurrentQueue<(FileStreamData, ProtoManifest.FileData, ProtoManifest.ChunkData)> networkChunkQueue) @@ -1128,6 +1135,11 @@ namespace DepotDownloader Console.WriteLine("{0,6:#00.00}% {1}", (depotDownloadCounter.sizeDownloaded / (float)depotDownloadCounter.completeDownloadSize) * 100.0f, fileFinalPath); } + lock (downloadCounter) + { + downloadCounter.completeDownloadSize -= file.TotalSize; + } + return; } @@ -1136,6 +1148,11 @@ namespace DepotDownloader { depotDownloadCounter.sizeDownloaded += sizeOnDisk; } + + lock (downloadCounter) + { + downloadCounter.completeDownloadSize -= sizeOnDisk; + } } var fileIsExecutable = file.Flags.HasFlag(EDepotFileFlag.Executable); @@ -1217,7 +1234,7 @@ namespace DepotDownloader if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) { - Console.WriteLine("Encountered 401 for chunk {0}. Aborting.", chunkID); + Console.WriteLine("Encountered {1} for chunk {0}. Aborting.", chunkID, (int)e.StatusCode); break; } @@ -1281,6 +1298,8 @@ namespace DepotDownloader { downloadCounter.totalBytesCompressed += chunk.CompressedLength; downloadCounter.totalBytesUncompressed += chunk.UncompressedLength; + + Ansi.Progress(downloadCounter.totalBytesUncompressed, downloadCounter.completeDownloadSize); } if (remainingChunks == 0) diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index 14f7f88..ae44b76 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -26,6 +26,8 @@ namespace DepotDownloader return 1; } + Ansi.Init(); + DebugLog.Enabled = false; AccountSettingsStore.LoadFromFile("account.config"); From e7e2299b707919edfb5b8536e2dcbbacf5b25e61 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 13 Aug 2024 17:38:21 +0300 Subject: [PATCH 07/38] Update SteamKit --- DepotDownloader/ContentDownloader.cs | 2 +- DepotDownloader/DepotDownloader.csproj | 2 +- DepotDownloader/ProtoManifest.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index 6e528b9..6524850 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -1196,7 +1196,7 @@ namespace DepotDownloader var data = new DepotManifest.ChunkData { ChunkID = chunk.ChunkID, - Checksum = chunk.Checksum, + Checksum = BitConverter.ToUInt32(chunk.Checksum), Offset = chunk.Offset, CompressedLength = chunk.CompressedLength, UncompressedLength = chunk.UncompressedLength diff --git a/DepotDownloader/DepotDownloader.csproj b/DepotDownloader/DepotDownloader.csproj index e7f775e..2a0e67f 100644 --- a/DepotDownloader/DepotDownloader.csproj +++ b/DepotDownloader/DepotDownloader.csproj @@ -15,6 +15,6 @@ - + diff --git a/DepotDownloader/ProtoManifest.cs b/DepotDownloader/ProtoManifest.cs index e4a9c85..ccc6165 100644 --- a/DepotDownloader/ProtoManifest.cs +++ b/DepotDownloader/ProtoManifest.cs @@ -76,7 +76,7 @@ namespace DepotDownloader public ChunkData(DepotManifest.ChunkData sourceChunk) { ChunkID = sourceChunk.ChunkID; - Checksum = sourceChunk.Checksum; + Checksum = BitConverter.GetBytes(sourceChunk.Checksum); Offset = sourceChunk.Offset; CompressedLength = sourceChunk.CompressedLength; UncompressedLength = sourceChunk.UncompressedLength; From 294dbff40a068fed56b6470c77df4aaeaf5ff32c Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 13 Aug 2024 17:48:06 +0300 Subject: [PATCH 08/38] Optimize calculating adler hashes to not allocate entire chunk in memory --- DepotDownloader/ContentDownloader.cs | 16 ++++++++--- DepotDownloader/Util.cs | 40 +++++++--------------------- 2 files changed, 21 insertions(+), 35 deletions(-) diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index 6524850..726df92 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -1059,10 +1059,18 @@ namespace DepotDownloader { fsOld.Seek((long)match.OldChunk.Offset, SeekOrigin.Begin); - var tmp = new byte[match.OldChunk.UncompressedLength]; - fsOld.Read(tmp, 0, tmp.Length); + uint a = 0, b = 0; - var adler = Util.AdlerHash(tmp); + for (var i = 0; i < match.OldChunk.UncompressedLength; i++) + { + var c = (uint)fsOld.ReadByte(); + + // adler hash + a = (a + c) % 65521; + b = (b + a) % 65521; + } + + var adler = BitConverter.GetBytes(a | (b << 16)); if (!adler.SequenceEqual(match.OldChunk.Checksum)) { neededChunks.Add(match.NewChunk); @@ -1191,7 +1199,7 @@ namespace DepotDownloader var depot = depotFilesData.depotDownloadInfo; var depotDownloadCounter = depotFilesData.depotCounter; - var chunkID = Util.EncodeHexString(chunk.ChunkID); + var chunkID = Convert.ToHexString(chunk.ChunkID).ToLowerInvariant(); var data = new DepotManifest.ChunkData { diff --git a/DepotDownloader/Util.cs b/DepotDownloader/Util.cs index 3cda8da..5157d92 100644 --- a/DepotDownloader/Util.cs +++ b/DepotDownloader/Util.cs @@ -78,26 +78,23 @@ namespace DepotDownloader public static List ValidateSteam3FileChecksums(FileStream fs, ProtoManifest.ChunkData[] chunkdata) { var neededChunks = new List(); - int read; foreach (var data in chunkdata) { - var chunk = new byte[data.UncompressedLength]; fs.Seek((long)data.Offset, SeekOrigin.Begin); - read = fs.Read(chunk, 0, (int)data.UncompressedLength); - byte[] tempchunk; - if (read < data.UncompressedLength) + uint a = 0, b = 0; + + for (var i = 0; i < data.UncompressedLength; i++) { - tempchunk = new byte[read]; - Array.Copy(chunk, 0, tempchunk, 0, read); - } - else - { - tempchunk = chunk; + var c = (uint)fs.ReadByte(); + + // adler hash + a = (a + c) % 65521; + b = (b + a) % 65521; } - var adler = AdlerHash(tempchunk); + var adler = BitConverter.GetBytes(a | (b << 16)); if (!adler.SequenceEqual(data.Checksum)) { neededChunks.Add(data); @@ -107,18 +104,6 @@ namespace DepotDownloader return neededChunks; } - public static byte[] AdlerHash(byte[] input) - { - uint a = 0, b = 0; - for (var i = 0; i < input.Length; i++) - { - a = (a + input[i]) % 65521; - b = (b + a) % 65521; - } - - return BitConverter.GetBytes(a | (b << 16)); - } - public static byte[] DecodeHexString(string hex) { if (hex == null) @@ -133,13 +118,6 @@ namespace DepotDownloader return bytes; } - public static string EncodeHexString(byte[] input) - { - return input.Aggregate(new StringBuilder(), - (sb, v) => sb.Append(v.ToString("x2")) - ).ToString(); - } - /// /// Decrypts using AES/ECB/PKCS7 /// From a3fc9c4c4549b649ffbfc13f5eccf36e474da1cc Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Wed, 14 Aug 2024 10:18:24 +0300 Subject: [PATCH 09/38] Remove inlined adler hash --- DepotDownloader/ContentDownloader.cs | 13 +------------ DepotDownloader/Util.cs | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index 726df92..cc41cf9 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -1059,18 +1059,7 @@ namespace DepotDownloader { fsOld.Seek((long)match.OldChunk.Offset, SeekOrigin.Begin); - uint a = 0, b = 0; - - for (var i = 0; i < match.OldChunk.UncompressedLength; i++) - { - var c = (uint)fsOld.ReadByte(); - - // adler hash - a = (a + c) % 65521; - b = (b + a) % 65521; - } - - var adler = BitConverter.GetBytes(a | (b << 16)); + var adler = Util.AdlerHash(fsOld, (int)match.OldChunk.UncompressedLength); if (!adler.SequenceEqual(match.OldChunk.Checksum)) { neededChunks.Add(match.NewChunk); diff --git a/DepotDownloader/Util.cs b/DepotDownloader/Util.cs index 5157d92..e53c272 100644 --- a/DepotDownloader/Util.cs +++ b/DepotDownloader/Util.cs @@ -83,18 +83,7 @@ namespace DepotDownloader { fs.Seek((long)data.Offset, SeekOrigin.Begin); - uint a = 0, b = 0; - - for (var i = 0; i < data.UncompressedLength; i++) - { - var c = (uint)fs.ReadByte(); - - // adler hash - a = (a + c) % 65521; - b = (b + a) % 65521; - } - - var adler = BitConverter.GetBytes(a | (b << 16)); + var adler = AdlerHash(fs, (int)data.UncompressedLength); if (!adler.SequenceEqual(data.Checksum)) { neededChunks.Add(data); @@ -104,6 +93,20 @@ namespace DepotDownloader return neededChunks; } + public static byte[] AdlerHash(Stream stream, int length) + { + uint a = 0, b = 0; + for (var i = 0; i < length; i++) + { + var c = (uint)stream.ReadByte(); + + a = (a + c) % 65521; + b = (b + a) % 65521; + } + + return BitConverter.GetBytes(a | (b << 16)); + } + public static byte[] DecodeHexString(string hex) { if (hex == null) From baa64e9e513147fb144e45cfb1dc89fbd028ec13 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 13 Aug 2024 17:37:23 +0300 Subject: [PATCH 10/38] Update DownloadDepotChunkAsync to use array pool --- DepotDownloader/ContentDownloader.cs | 144 +++++++++++++------------ DepotDownloader/DepotDownloader.csproj | 2 +- 2 files changed, 79 insertions(+), 67 deletions(-) diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index cc41cf9..6d49bd2 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -1199,80 +1200,91 @@ namespace DepotDownloader UncompressedLength = chunk.UncompressedLength }; - DepotChunk chunkData = null; - - do - { - cts.Token.ThrowIfCancellationRequested(); - - Server connection = null; - - try - { - connection = cdnPool.GetConnection(cts.Token); - - DebugLog.WriteLine("ContentDownloader", "Downloading chunk {0} from {1} with {2}", chunkID, connection, cdnPool.ProxyServer != null ? cdnPool.ProxyServer : "no proxy"); - chunkData = await cdnPool.CDNClient.DownloadDepotChunkAsync( - depot.DepotId, - data, - connection, - depot.DepotKey, - cdnPool.ProxyServer).ConfigureAwait(false); - - cdnPool.ReturnConnection(connection); - } - catch (TaskCanceledException) - { - Console.WriteLine("Connection timeout downloading chunk {0}", chunkID); - } - catch (SteamKitWebRequestException e) - { - cdnPool.ReturnBrokenConnection(connection); - - if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) - { - Console.WriteLine("Encountered {1} for chunk {0}. Aborting.", chunkID, (int)e.StatusCode); - break; - } - - Console.WriteLine("Encountered error downloading chunk {0}: {1}", chunkID, e.StatusCode); - } - catch (OperationCanceledException) - { - break; - } - catch (Exception e) - { - cdnPool.ReturnBrokenConnection(connection); - Console.WriteLine("Encountered unexpected error downloading chunk {0}: {1}", chunkID, e.Message); - } - } while (chunkData == null); - - if (chunkData == null) - { - Console.WriteLine("Failed to find any server with chunk {0} for depot {1}. Aborting.", chunkID, depot.DepotId); - cts.Cancel(); - } - - // Throw the cancellation exception if requested so that this task is marked failed - cts.Token.ThrowIfCancellationRequested(); + var written = 0; + var chunkBuffer = ArrayPool.Shared.Rent((int)data.UncompressedLength); try { - await fileStreamData.fileLock.WaitAsync().ConfigureAwait(false); - - if (fileStreamData.fileStream == null) + do { - var fileFinalPath = Path.Combine(depot.InstallDir, file.FileName); - fileStreamData.fileStream = File.Open(fileFinalPath, FileMode.Open); + cts.Token.ThrowIfCancellationRequested(); + + Server connection = null; + + try + { + connection = cdnPool.GetConnection(cts.Token); + + DebugLog.WriteLine("ContentDownloader", "Downloading chunk {0} from {1} with {2}", chunkID, connection, cdnPool.ProxyServer != null ? cdnPool.ProxyServer : "no proxy"); + written = await cdnPool.CDNClient.DownloadDepotChunkAsync( + depot.DepotId, + data, + connection, + chunkBuffer, + depot.DepotKey, + cdnPool.ProxyServer).ConfigureAwait(false); + + cdnPool.ReturnConnection(connection); + + break; + } + catch (TaskCanceledException) + { + Console.WriteLine("Connection timeout downloading chunk {0}", chunkID); + } + catch (SteamKitWebRequestException e) + { + cdnPool.ReturnBrokenConnection(connection); + + if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) + { + Console.WriteLine("Encountered {1} for chunk {0}. Aborting.", chunkID, (int)e.StatusCode); + break; + } + + Console.WriteLine("Encountered error downloading chunk {0}: {1}", chunkID, e.StatusCode); + } + catch (OperationCanceledException) + { + break; + } + catch (Exception e) + { + cdnPool.ReturnBrokenConnection(connection); + Console.WriteLine("Encountered unexpected error downloading chunk {0}: {1}", chunkID, e.Message); + } + } while (written == 0); + + if (written == 0) + { + Console.WriteLine("Failed to find any server with chunk {0} for depot {1}. Aborting.", chunkID, depot.DepotId); + cts.Cancel(); } - fileStreamData.fileStream.Seek((long)chunkData.ChunkInfo.Offset, SeekOrigin.Begin); - await fileStreamData.fileStream.WriteAsync(chunkData.Data.AsMemory(0, chunkData.Data.Length), cts.Token); + // Throw the cancellation exception if requested so that this task is marked failed + cts.Token.ThrowIfCancellationRequested(); + + try + { + await fileStreamData.fileLock.WaitAsync().ConfigureAwait(false); + + if (fileStreamData.fileStream == null) + { + var fileFinalPath = Path.Combine(depot.InstallDir, file.FileName); + fileStreamData.fileStream = File.Open(fileFinalPath, FileMode.Open); + } + + fileStreamData.fileStream.Seek((long)data.Offset, SeekOrigin.Begin); + await fileStreamData.fileStream.WriteAsync(chunkBuffer.AsMemory(0, written), cts.Token); + } + finally + { + fileStreamData.fileLock.Release(); + } } finally { - fileStreamData.fileLock.Release(); + ArrayPool.Shared.Return(chunkBuffer); } var remainingChunks = Interlocked.Decrement(ref fileStreamData.chunksToDownload); @@ -1285,7 +1297,7 @@ namespace DepotDownloader ulong sizeDownloaded = 0; lock (depotDownloadCounter) { - sizeDownloaded = depotDownloadCounter.sizeDownloaded + (ulong)chunkData.Data.Length; + sizeDownloaded = depotDownloadCounter.sizeDownloaded + (ulong)written; depotDownloadCounter.sizeDownloaded = sizeDownloaded; depotDownloadCounter.depotBytesCompressed += chunk.CompressedLength; depotDownloadCounter.depotBytesUncompressed += chunk.UncompressedLength; diff --git a/DepotDownloader/DepotDownloader.csproj b/DepotDownloader/DepotDownloader.csproj index 2a0e67f..d069853 100644 --- a/DepotDownloader/DepotDownloader.csproj +++ b/DepotDownloader/DepotDownloader.csproj @@ -15,6 +15,6 @@ - + From 6edab0c55e111f7a535cf85697b55fa88ea08b60 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sun, 25 Aug 2024 23:54:12 +0300 Subject: [PATCH 11/38] Use ReadAllLinesAsync and async main --- DepotDownloader/Program.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index ae44b76..5221326 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -13,12 +13,7 @@ namespace DepotDownloader { class Program { - static int Main(string[] args) - => MainAsync(args).GetAwaiter().GetResult(); - - internal static readonly char[] newLineCharacters = ['\n', '\r']; - - static async Task MainAsync(string[] args) + static async Task Main(string[] args) { if (args.Length == 0) { @@ -71,15 +66,19 @@ namespace DepotDownloader try { - var fileListData = await File.ReadAllTextAsync(fileList); - var files = fileListData.Split(newLineCharacters, StringSplitOptions.RemoveEmptyEntries); - ContentDownloader.Config.UsingFileList = true; ContentDownloader.Config.FilesToDownload = new HashSet(StringComparer.OrdinalIgnoreCase); ContentDownloader.Config.FilesToDownloadRegex = []; + var files = await File.ReadAllLinesAsync(fileList); + foreach (var fileEntry in files) { + if (string.IsNullOrWhiteSpace(fileEntry)) + { + continue; + } + if (fileEntry.StartsWith(RegexPrefix)) { var rgx = new Regex(fileEntry[RegexPrefix.Length..], RegexOptions.Compiled | RegexOptions.IgnoreCase); From 33f0a7fcba6986250002731e9b75f82decd253b0 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 17:08:07 +0300 Subject: [PATCH 12/38] Reset ansi progress on disconnect --- DepotDownloader/Steam3Session.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DepotDownloader/Steam3Session.cs b/DepotDownloader/Steam3Session.cs index d2ee88a..0246272 100644 --- a/DepotDownloader/Steam3Session.cs +++ b/DepotDownloader/Steam3Session.cs @@ -403,6 +403,8 @@ namespace DepotDownloader bIsConnectionRecovery = false; steamClient.Disconnect(); + Ansi.Progress(Ansi.ProgressState.Hidden); + // flush callbacks until our disconnected event while (!bDidDisconnect) { From e2194fc8733d74444f3f807901e3127770d8d180 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 17:24:31 +0300 Subject: [PATCH 13/38] Attempt to get cdn auth when getting 403 --- DepotDownloader/ContentDownloader.cs | 42 ++++++++++++++++++++++++++-- DepotDownloader/Steam3Session.cs | 27 ++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index 6d49bd2..fa97b98 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -773,6 +773,13 @@ namespace DepotDownloader { connection = cdnPool.GetConnection(cts.Token); + string cdnToken = null; + if (steam3.CDNAuthTokens.TryGetValue((depot.DepotId, connection.Host), out var authTokenCallbackPromise)) + { + var result = await authTokenCallbackPromise.Task; + cdnToken = result.Token; + } + var now = DateTime.Now; // In order to download this manifest, we need the current manifest request code @@ -806,7 +813,8 @@ namespace DepotDownloader manifestRequestCode, connection, depot.DepotKey, - cdnPool.ProxyServer).ConfigureAwait(false); + cdnPool.ProxyServer, + cdnToken).ConfigureAwait(false); cdnPool.ReturnConnection(connection); } @@ -816,6 +824,16 @@ namespace DepotDownloader } catch (SteamKitWebRequestException e) { + // If the CDN returned 403, attempt to get a cdn auth if we didn't yet + if (e.StatusCode == HttpStatusCode.Forbidden && !steam3.CDNAuthTokens.ContainsKey((depot.DepotId, connection.Host))) + { + await steam3.RequestCDNAuthToken(depot.AppId, depot.DepotId, connection); + + cdnPool.ReturnConnection(connection); + + continue; + } + cdnPool.ReturnBrokenConnection(connection); if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) @@ -1215,6 +1233,13 @@ namespace DepotDownloader { connection = cdnPool.GetConnection(cts.Token); + string cdnToken = null; + if (steam3.CDNAuthTokens.TryGetValue((depot.DepotId, connection.Host), out var authTokenCallbackPromise)) + { + var result = await authTokenCallbackPromise.Task; + cdnToken = result.Token; + } + DebugLog.WriteLine("ContentDownloader", "Downloading chunk {0} from {1} with {2}", chunkID, connection, cdnPool.ProxyServer != null ? cdnPool.ProxyServer : "no proxy"); written = await cdnPool.CDNClient.DownloadDepotChunkAsync( depot.DepotId, @@ -1222,7 +1247,8 @@ namespace DepotDownloader connection, chunkBuffer, depot.DepotKey, - cdnPool.ProxyServer).ConfigureAwait(false); + cdnPool.ProxyServer, + cdnToken).ConfigureAwait(false); cdnPool.ReturnConnection(connection); @@ -1234,6 +1260,18 @@ namespace DepotDownloader } catch (SteamKitWebRequestException e) { + // If the CDN returned 403, attempt to get a cdn auth if we didn't yet, + // if auth task already exists, make sure it didn't complete yet, so that it gets awaited above + if (e.StatusCode == HttpStatusCode.Forbidden && + (!steam3.CDNAuthTokens.TryGetValue((depot.DepotId, connection.Host), out var authTokenCallbackPromise) || !authTokenCallbackPromise.Task.IsCompleted)) + { + await steam3.RequestCDNAuthToken(depot.AppId, depot.DepotId, connection); + + cdnPool.ReturnConnection(connection); + + continue; + } + cdnPool.ReturnBrokenConnection(connection); if (e.StatusCode == HttpStatusCode.Unauthorized || e.StatusCode == HttpStatusCode.Forbidden) diff --git a/DepotDownloader/Steam3Session.cs b/DepotDownloader/Steam3Session.cs index 0246272..9262063 100644 --- a/DepotDownloader/Steam3Session.cs +++ b/DepotDownloader/Steam3Session.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -7,6 +8,7 @@ using System.Threading.Tasks; using QRCoder; using SteamKit2; using SteamKit2.Authentication; +using SteamKit2.CDN; using SteamKit2.Internal; namespace DepotDownloader @@ -24,6 +26,7 @@ namespace DepotDownloader public Dictionary AppTokens { get; } = []; public Dictionary PackageTokens { get; } = []; public Dictionary DepotKeys { get; } = []; + public ConcurrentDictionary<(uint, string), TaskCompletionSource> CDNAuthTokens { get; } = []; public Dictionary AppInfo { get; } = []; public Dictionary PackageInfo { get; } = []; public Dictionary AppBetaPasswords { get; } = []; @@ -282,6 +285,30 @@ namespace DepotDownloader return requestCode; } + public async Task RequestCDNAuthToken(uint appid, uint depotid, Server server) + { + var cdnKey = (depotid, server.Host); + var completion = new TaskCompletionSource(); + + if (bAborted || !CDNAuthTokens.TryAdd(cdnKey, completion)) + { + return; + } + + DebugLog.WriteLine(nameof(Steam3Session), $"Requesting CDN auth token for {server.Host}"); + + var cdnAuth = await steamApps.GetCDNAuthToken(appid, depotid, server.Host); + + Console.WriteLine($"Got CDN auth token for {server.Host} result: {cdnAuth.Result} (expires {cdnAuth.Expiration})"); + + if (cdnAuth.Result != EResult.OK) + { + return; + } + + completion.TrySetResult(cdnAuth); + } + public void CheckAppBetaPassword(uint appid, string password) { var completed = false; From 1b95a6ba47922e6a1c8567163e8608db63225ef1 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 17:41:51 +0300 Subject: [PATCH 14/38] Copy license file to build output --- DepotDownloader/DepotDownloader.csproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DepotDownloader/DepotDownloader.csproj b/DepotDownloader/DepotDownloader.csproj index d069853..c4fc2ec 100644 --- a/DepotDownloader/DepotDownloader.csproj +++ b/DepotDownloader/DepotDownloader.csproj @@ -12,6 +12,12 @@ true + + + Always + + + From bae332b97a0e09cc9a98e706a312ef9fc9c25332 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 17:49:02 +0300 Subject: [PATCH 15/38] Add license header to all source files Fixes #55 --- .editorconfig | 5 +++++ DepotDownloader/AccountSettingsStore.cs | 3 +++ DepotDownloader/Ansi.cs | 3 +++ DepotDownloader/CDNClientPool.cs | 3 +++ DepotDownloader/ContentDownloader.cs | 3 +++ DepotDownloader/DepotConfigStore.cs | 3 +++ DepotDownloader/DownloadConfig.cs | 5 ++++- DepotDownloader/HttpClientFactory.cs | 3 +++ DepotDownloader/HttpDiagnosticEventListener.cs | 3 +++ DepotDownloader/PlatformUtilities.cs | 3 +++ DepotDownloader/Program.cs | 3 +++ DepotDownloader/ProtoManifest.cs | 3 +++ DepotDownloader/Steam3Session.cs | 3 +++ DepotDownloader/Util.cs | 3 +++ 14 files changed, 45 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 8b93976..3c1a03b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -146,6 +146,11 @@ dotnet_diagnostic.IDE0044.severity = warning # CSharp code style settings: [*.cs] + +# Require file header OR A source file contains a header that does not match the required text +file_header_template = This file is subject to the terms and conditions defined\nin file 'LICENSE', which is part of this source code package. +dotnet_diagnostic.IDE0073.severity = error + # Newline settings csharp_new_line_before_open_brace = all csharp_new_line_before_else = true diff --git a/DepotDownloader/AccountSettingsStore.cs b/DepotDownloader/AccountSettingsStore.cs index 2374a92..7e1594b 100644 --- a/DepotDownloader/AccountSettingsStore.cs +++ b/DepotDownloader/AccountSettingsStore.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/DepotDownloader/Ansi.cs b/DepotDownloader/Ansi.cs index 7041deb..b17b597 100644 --- a/DepotDownloader/Ansi.cs +++ b/DepotDownloader/Ansi.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using Spectre.Console; diff --git a/DepotDownloader/CDNClientPool.cs b/DepotDownloader/CDNClientPool.cs index 2ed0a08..596beea 100644 --- a/DepotDownloader/CDNClientPool.cs +++ b/DepotDownloader/CDNClientPool.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index fa97b98..9042c39 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Buffers; using System.Collections.Concurrent; diff --git a/DepotDownloader/DepotConfigStore.cs b/DepotDownloader/DepotConfigStore.cs index 99f052c..15e1539 100644 --- a/DepotDownloader/DepotConfigStore.cs +++ b/DepotDownloader/DepotConfigStore.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Collections.Generic; using System.IO; diff --git a/DepotDownloader/DownloadConfig.cs b/DepotDownloader/DownloadConfig.cs index 1022aac..97f2bad 100644 --- a/DepotDownloader/DownloadConfig.cs +++ b/DepotDownloader/DownloadConfig.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + +using System.Collections.Generic; using System.Text.RegularExpressions; namespace DepotDownloader diff --git a/DepotDownloader/HttpClientFactory.cs b/DepotDownloader/HttpClientFactory.cs index aa642bc..7a62211 100644 --- a/DepotDownloader/HttpClientFactory.cs +++ b/DepotDownloader/HttpClientFactory.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System.IO; using System.Net.Http; using System.Net.Http.Headers; diff --git a/DepotDownloader/HttpDiagnosticEventListener.cs b/DepotDownloader/HttpDiagnosticEventListener.cs index f3b1142..2602656 100644 --- a/DepotDownloader/HttpDiagnosticEventListener.cs +++ b/DepotDownloader/HttpDiagnosticEventListener.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Diagnostics.Tracing; using System.Text; diff --git a/DepotDownloader/PlatformUtilities.cs b/DepotDownloader/PlatformUtilities.cs index a4f0b80..2386eae 100644 --- a/DepotDownloader/PlatformUtilities.cs +++ b/DepotDownloader/PlatformUtilities.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Runtime.InteropServices; diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index 5221326..058654c 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Collections.Generic; using System.ComponentModel; diff --git a/DepotDownloader/ProtoManifest.cs b/DepotDownloader/ProtoManifest.cs index ccc6165..b6eb010 100644 --- a/DepotDownloader/ProtoManifest.cs +++ b/DepotDownloader/ProtoManifest.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Collections.Generic; using System.IO; diff --git a/DepotDownloader/Steam3Session.cs b/DepotDownloader/Steam3Session.cs index 9262063..906b309 100644 --- a/DepotDownloader/Steam3Session.cs +++ b/DepotDownloader/Steam3Session.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/DepotDownloader/Util.cs b/DepotDownloader/Util.cs index e53c272..46045ae 100644 --- a/DepotDownloader/Util.cs +++ b/DepotDownloader/Util.cs @@ -1,3 +1,6 @@ +// This file is subject to the terms and conditions defined +// in file 'LICENSE', which is part of this source code package. + using System; using System.Collections.Generic; using System.IO; From 75cbd8b12a08dc0bbf86503d55c95c15772d59e4 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 17:52:28 +0300 Subject: [PATCH 16/38] Enable Deterministic --- .editorconfig | 3 --- DepotDownloader/DepotDownloader.csproj | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index 3c1a03b..c982ab2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -126,9 +126,6 @@ dotnet_naming_style.pascal_case_style.capitalization = pascal_case # error RS2008: Enable analyzer release tracking for the analyzer project containing rule '{0}' dotnet_diagnostic.RS2008.severity = none -# IDE0005: Remove unnecessary import -dotnet_diagnostic.IDE0005.severity = warning - # IDE0007: Use `var` instead of explicit type dotnet_diagnostic.IDE0007.severity = warning diff --git a/DepotDownloader/DepotDownloader.csproj b/DepotDownloader/DepotDownloader.csproj index c4fc2ec..0b060b1 100644 --- a/DepotDownloader/DepotDownloader.csproj +++ b/DepotDownloader/DepotDownloader.csproj @@ -9,7 +9,7 @@ SteamRE Team Copyright © SteamRE Team 2024 ..\Icon\DepotDownloader.ico - true + true From c62d2d733fa24de164b302922535e4e389a75ef4 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 17:58:40 +0300 Subject: [PATCH 17/38] Use full matrix name in sk2-ci --- .github/workflows/sk2-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sk2-ci.yml b/.github/workflows/sk2-ci.yml index 8827a46..405b31d 100644 --- a/.github/workflows/sk2-ci.yml +++ b/.github/workflows/sk2-ci.yml @@ -36,6 +36,6 @@ jobs: uses: actions/upload-artifact@v4 if: matrix.configuration == 'Release' with: - name: DepotDownloader-${{ runner.os }} + name: DepotDownloader-${{ matrix.runs-on }} path: artifacts if-no-files-found: error From 4c88a59f17e907492db18f3c304b67ae20c2d16d Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 18:26:52 +0300 Subject: [PATCH 18/38] Display a message box when exe is not lauched from console --- DepotDownloader/PlatformUtilities.cs | 30 ++++++++++++++++++++++++++++ DepotDownloader/Program.cs | 6 ++++++ 2 files changed, 36 insertions(+) diff --git a/DepotDownloader/PlatformUtilities.cs b/DepotDownloader/PlatformUtilities.cs index 2386eae..31d26e9 100644 --- a/DepotDownloader/PlatformUtilities.cs +++ b/DepotDownloader/PlatformUtilities.cs @@ -145,5 +145,35 @@ namespace DepotDownloader : mode & ~ModeExecute))); } } + + #region WIN32_CONSOLE_STUFF + [DllImport("kernel32.dll", SetLastError = true)] + static extern uint GetConsoleProcessList(uint[] processList, uint processCount); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); + + const uint MB_OK = 0x0; + const uint MB_ICONWARNING = 0x30; + + public static void VerifyConsoleLaunch() + { + // Reference: https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922 + var processList = new uint[2]; + var processCount = GetConsoleProcessList(processList, (uint)processList.Length); + + if (processCount != 1) + { + return; + } + + _ = MessageBox( + IntPtr.Zero, + "Depot Downloader is a console application; there is no GUI.\n\nIf you do not pass any command line parameters, it prints usage info and exits.\n\nYou must use this from a terminal/console.", + "DepotDownloader", + MB_OK | MB_ICONWARNING + ); + } + #endregion } } diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index 058654c..50db98b 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -21,6 +21,12 @@ namespace DepotDownloader if (args.Length == 0) { PrintUsage(); + + if (OperatingSystem.IsWindows()) + { + PlatformUtilities.VerifyConsoleLaunch(); + } + return 1; } From 13b396b0d27fca44b5b02960adc0f990b6113101 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 18:31:27 +0300 Subject: [PATCH 19/38] Align help output with spaces --- DepotDownloader/Program.cs | 63 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index 058654c..5d62b00 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -370,45 +370,46 @@ namespace DepotDownloader static void PrintUsage() { + // Do not use tabs to align parameters here because tab size may differ Console.WriteLine(); - Console.WriteLine("Usage - downloading one or all depots for an app:"); - Console.WriteLine("\tdepotdownloader -app [-depot [-manifest ]]"); - Console.WriteLine("\t\t[-username [-password ]] [other options]"); + Console.WriteLine("Usage: downloading one or all depots for an app:"); + 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("\tdepotdownloader -app -pubfile [-username [-password ]]"); - Console.WriteLine("Usage - downloading a workshop item using ugc id"); - Console.WriteLine("\tdepotdownloader -app -ugc [-username [-password ]]"); + 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(" depotdownloader -app -ugc [-username [-password ]]"); Console.WriteLine(); Console.WriteLine("Parameters:"); - Console.WriteLine("\t-app <#>\t\t\t\t- the AppID to download."); - Console.WriteLine("\t-depot <#>\t\t\t\t- the DepotID to download."); - Console.WriteLine("\t-manifest \t\t\t- manifest id of content to download (requires -depot, default: current for branch)."); - Console.WriteLine($"\t-beta \t\t\t- download from specified branch if available (default: {ContentDownloader.DEFAULT_BRANCH})."); - Console.WriteLine("\t-betapassword \t\t- branch password if applicable."); - Console.WriteLine("\t-all-platforms\t\t\t- downloads all platform-specific depots when -app is used."); - Console.WriteLine("\t-os \t\t\t\t- the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on)"); - Console.WriteLine("\t-osarch \t\t\t\t- the architecture for which to download the game (32 or 64, default: the host's architecture)"); - Console.WriteLine("\t-all-languages\t\t\t\t- download all language-specific depots when -app is used."); - Console.WriteLine("\t-language \t\t\t\t- the language for which to download the game (default: english)"); - Console.WriteLine("\t-lowviolence\t\t\t\t- download low violence depots when -app is used."); + Console.WriteLine(" -app <#> - the AppID to download."); + Console.WriteLine(" -depot <#> - the DepotID to download."); + Console.WriteLine(" -manifest - manifest id of content to download (requires -depot, default: current for branch)."); + Console.WriteLine($" -beta - download from specified branch if available (default: {ContentDownloader.DEFAULT_BRANCH})."); + Console.WriteLine(" -betapassword - branch password if applicable."); + Console.WriteLine(" -all-platforms - downloads all platform-specific depots when -app is used."); + Console.WriteLine(" -os - the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on)"); + Console.WriteLine(" -osarch - the architecture for which to download the game (32 or 64, default: the host's architecture)"); + Console.WriteLine(" -all-languages - download all language-specific depots when -app is used."); + Console.WriteLine(" -language - the language for which to download the game (default: english)"); + Console.WriteLine(" -lowviolence - download low violence depots when -app is used."); Console.WriteLine(); - Console.WriteLine("\t-ugc <#>\t\t\t\t- the UGC ID to download."); - Console.WriteLine("\t-pubfile <#>\t\t\t- the PublishedFileId to download. (Will automatically resolve to UGC id)"); + Console.WriteLine(" -ugc <#> - the UGC ID to download."); + Console.WriteLine(" -pubfile <#> - the PublishedFileId to download. (Will automatically resolve to UGC id)"); Console.WriteLine(); - Console.WriteLine("\t-username \t\t- the username of the account to login to for restricted content."); - Console.WriteLine("\t-password \t\t- the password of the account to login to for restricted content."); - Console.WriteLine("\t-remember-password\t\t- if set, remember the password for subsequent logins of this user. (Use -username -remember-password as login credentials)"); + Console.WriteLine(" -username - the username of the account to login to for restricted content."); + 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. (Use -username -remember-password as login credentials)"); Console.WriteLine(); - Console.WriteLine("\t-dir \t\t- the directory in which to place downloaded files."); - Console.WriteLine("\t-filelist \t- a list of files to download (from the manifest). Prefix file path with 'regex:' if you want to match with regex."); - Console.WriteLine("\t-validate\t\t\t\t- Include checksum verification of files already downloaded"); + Console.WriteLine(" -dir - the directory in which to place downloaded files."); + Console.WriteLine(" -filelist - a list of files to download (from the manifest). Prefix file path with 'regex:' if you want to match with regex."); + Console.WriteLine(" -validate - Include checksum verification of files already downloaded"); Console.WriteLine(); - Console.WriteLine("\t-manifest-only\t\t\t- downloads a human readable manifest for any depots that would be downloaded."); - Console.WriteLine("\t-cellid <#>\t\t\t\t- the overridden CellID of the content server to download from."); - Console.WriteLine("\t-max-servers <#>\t\t- maximum number of content servers to use. (default: 20)."); - Console.WriteLine("\t-max-downloads <#>\t\t- maximum number of chunks to download concurrently. (default: 8)."); - Console.WriteLine("\t-loginid <#>\t\t- a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently."); + Console.WriteLine(" -manifest-only - downloads a human readable manifest for any depots that would be downloaded."); + Console.WriteLine(" -cellid <#> - the overridden CellID of the content server to download from."); + Console.WriteLine(" -max-servers <#> - maximum number of content servers to use. (default: 20)."); + Console.WriteLine(" -max-downloads <#> - maximum number of chunks to download concurrently. (default: 8)."); + Console.WriteLine(" -loginid <#> - a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently."); } } } From 1620090c1992f9c6d0695f304bc9f4fabfc3e126 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 18:58:07 +0300 Subject: [PATCH 20/38] Make extens stricter --- DepotDownloader/PlatformUtilities.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/DepotDownloader/PlatformUtilities.cs b/DepotDownloader/PlatformUtilities.cs index 31d26e9..d3d184f 100644 --- a/DepotDownloader/PlatformUtilities.cs +++ b/DepotDownloader/PlatformUtilities.cs @@ -147,11 +147,13 @@ namespace DepotDownloader } #region WIN32_CONSOLE_STUFF - [DllImport("kernel32.dll", SetLastError = true)] + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] static extern uint GetConsoleProcessList(uint[] processList, uint processCount); - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] - static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); + [DllImport("user32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type); const uint MB_OK = 0x0; const uint MB_ICONWARNING = 0x30; @@ -167,7 +169,7 @@ namespace DepotDownloader return; } - _ = MessageBox( + _ = MessageBoxW( IntPtr.Zero, "Depot Downloader is a console application; there is no GUI.\n\nIf you do not pass any command line parameters, it prints usage info and exits.\n\nYou must use this from a terminal/console.", "DepotDownloader", From 48c00e53231d1c4cb1a874a273c840dd4aaec73b Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 21:38:33 +0300 Subject: [PATCH 21/38] Add trim_trailing_whitespace --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index c982ab2..717c772 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,6 +6,7 @@ indent_style = space charset = utf-8 end_of_line = lf insert_final_newline = true +trim_trailing_whitespace = true # Code files [*.{cs, csx, vb, vbx}] From 653506ac145f5fc5fd82a9e01148809550ef29f2 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 21:47:59 +0300 Subject: [PATCH 22/38] Automatically upload all artifacts to releases --- .editorconfig | 4 ++++ .github/workflows/build.yml | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/.editorconfig b/.editorconfig index 717c772..017bf61 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,10 @@ trim_trailing_whitespace = true [*.{cs, csx, vb, vbx}] indent_size = 4 +# Github yaml files +[*.yml] +indent_size = 2 + # XML project files [*.{csproj, vbproj, vcxproj, vcxproj.filters, proj, projitems, shproj}] indent_size = 2 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0725e4..fbb4ad7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -126,3 +126,47 @@ jobs: name: DepotDownloader-macos-arm64 path: selfcontained-osx-arm64 if-no-files-found: error + + release: + if: startsWith(github.ref, 'refs/tags/') + needs: build + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Display artifacts folder structure + run: ls -Rl + working-directory: artifacts + + - name: Create release files + run: | + set -eux + mkdir release + chmod +x artifacts/DepotDownloader-linux-x64/DepotDownloader + chmod +x artifacts/DepotDownloader-linux-arm/DepotDownloader + chmod +x artifacts/DepotDownloader-linux-arm64/DepotDownloader + chmod +x artifacts/DepotDownloader-macos-x64/DepotDownloader + chmod +x artifacts/DepotDownloader-macos-arm64/DepotDownloader + zip -9j release/DepotDownloader-framework.zip artifacts/DepotDownloader-framework/* + zip -9j release/DepotDownloader-windows-x64.zip artifacts/DepotDownloader-windows-x64/* + zip -9j release/DepotDownloader-windows-arm64.zip artifacts/DepotDownloader-windows-arm64/* + zip -9j release/DepotDownloader-linux-x64.zip artifacts/DepotDownloader-linux-x64/* + zip -9j release/DepotDownloader-linux-arm.zip artifacts/DepotDownloader-linux-arm/* + zip -9j release/DepotDownloader-linux-arm64.zip artifacts/DepotDownloader-linux-arm64/* + zip -9j release/DepotDownloader-macos-x64.zip artifacts/DepotDownloader-macos-x64/* + zip -9j release/DepotDownloader-macos-arm64.zip artifacts/DepotDownloader-macos-arm64/* + + - name: Display structure of archived files + run: ls -Rl + working-directory: release + + - name: Release + uses: softprops/action-gh-release@v2 + with: + draft: true + files: release/* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 785b0fe0b0d8dff7e98e96501344925d7d8132dc Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 22:11:12 +0300 Subject: [PATCH 23/38] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 68fdaff..2ce7635 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ DepotDownloader Steam depot downloader utilizing the SteamKit2 library. Supports .NET 8.0 +Download a binary from [the releases page](https://github.com/SteamRE/DepotDownloader/releases/latest). + +This program must be run from a console, it has no GUI. + ### Downloading one or all depots for an app ``` dotnet DepotDownloader.dll -app [-depot [-manifest ]] From 240f78987266ede805236229efb97b3e108491d8 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 22:17:03 +0300 Subject: [PATCH 24/38] Update README.md --- README.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 2ce7635..b5f8338 100644 --- a/README.md +++ b/README.md @@ -8,26 +8,31 @@ Download a binary from [the releases page](https://github.com/SteamRE/DepotDownl This program must be run from a console, it has no GUI. ### Downloading one or all depots for an app -``` -dotnet DepotDownloader.dll -app [-depot [-manifest ]] - [-username [-password ]] [other options] +```powershell +./DepotDownloader -app [-depot [-manifest ]] + [-username [-password ]] [other options] ``` -For example: `dotnet DepotDownloader.dll -app 730 -depot 731 -manifest 7617088375292372759` +For example: `./DepotDownloader -app 730 -depot 731 -manifest 7617088375292372759` + +By default it will use anonymous account ([view which apps are available on it here](https://steamdb.info/sub/17906/)). + +To use your account, specify the `-username ` parameter. Password will be asked interactively if you do +not use specify the `-password` parameter. ### Downloading a workshop item using pubfile id -``` -dotnet DepotDownloader.dll -app -pubfile [-username [-password ]] +```powershell +./DepotDownloader -app -pubfile [-username [-password ]] ``` -For example: `dotnet DepotDownloader.dll -app 730 -pubfile 1885082371` +For example: `./DepotDownloader -app 730 -pubfile 1885082371` ### Downloading a workshop item using ugc id -``` -dotnet DepotDownloader.dll -app -ugc [-username [-password ]] +```powershell +./DepotDownloader -app -ugc [-username [-password ]] ``` -For example: `dotnet DepotDownloader.dll -app 730 -ugc 770604181014286929` +For example: `./DepotDownloader -app 730 -ugc 770604181014286929` ## Parameters From a1f981168cae95eb0c6d95587242b98cf44a223a Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 22:19:11 +0300 Subject: [PATCH 25/38] Wrap parameters in code block --- README.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index b5f8338..b906eed 100644 --- a/README.md +++ b/README.md @@ -36,32 +36,32 @@ For example: `./DepotDownloader -app 730 -ugc 770604181014286929` ## Parameters -Parameter | Description ---------- | ----------- --app \<#> | the AppID to download. --depot \<#> | the DepotID to download. --manifest \ | manifest id of content to download (requires -depot, default: current for branch). --ugc \<#> | the UGC ID to download. --beta \ | download from specified branch if available (default: Public). --betapassword \ | branch password if applicable. --all-platforms | downloads all platform-specific depots when -app is used. --os \ | the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on) --osarch \ | the architecture for which to download the game (32 or 64, default: the host's architecture) --all-languages | download all language-specific depots when -app is used. --language \ | the language for which to download the game (default: english) --lowviolence | download low violence depots when -app is used. --pubfile \<#> | the PublishedFileId to download. (Will automatically resolve to UGC id) --username \ | the username of the account to login to for restricted content. --password \ | the password of the account to login to for restricted content. --remember-password | if set, remember the password for subsequent logins of this user. (Use -username -remember-password as login credentials) --dir \ | the directory in which to place downloaded files. --filelist \ | a list of files to download (from the manifest). Prefix file path with `regex:` if you want to match with regex. --validate | Include checksum verification of files already downloaded --manifest-only | downloads a human readable manifest for any depots that would be downloaded. --cellid \<#> | the overridden CellID of the content server to download from. --max-servers \<#> | maximum number of content servers to use. (default: 20). --max-downloads \<#> | maximum number of chunks to download concurrently. (default: 8). --loginid \<#> | a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently. +Parameter | Description +----------------------- | ----------- +`-app <#>` | the AppID to download. +`-depot <#>` | the DepotID to download. +`-manifest ` | manifest id of content to download (requires `-depot`, default: current for branch). +`-ugc <#>` | the UGC ID to download. +`-beta ` | download from specified branch if available (default: Public). +`-betapassword ` | branch password if applicable. +`-all-platforms` | downloads all platform-specific depots when `-app` is used. +`-os ` | the operating system for which to download the game (windows, macos or linux, default: OS the program is currently running on) +`-osarch ` | the architecture for which to download the game (32 or 64, default: the host's architecture) +`-all-languages` | download all language-specific depots when `-app` is used. +`-language ` | the language for which to download the game (default: english) +`-lowviolence` | download low violence depots when `-app` is used. +`-pubfile <#>` | the PublishedFileId to download. (Will automatically resolve to UGC id) +`-username ` | the username of the account to login to for restricted content. +`-password ` | the password of the account to login to for restricted content. +`-remember-password` | if set, remember the password for subsequent logins of this user. (Use `-username -remember-password` as login credentials) +`-dir ` | the directory in which to place downloaded files. +`-filelist ` | a list of files to download (from the manifest). Prefix file path with `regex:` if you want to match with regex. +`-validate` | Include checksum verification of files already downloaded +`-manifest-only` | downloads a human readable manifest for any depots that would be downloaded. +`-cellid <#>` | the overridden CellID of the content server to download from. +`-max-servers <#>` | maximum number of content servers to use. (default: 20). +`-max-downloads <#>` | maximum number of chunks to download concurrently. (default: 8). +`-loginid <#>` | a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently. ## Frequently Asked Questions From 0ce116fdcee7b189756c71facd824f3cb0901aac Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 27 Aug 2024 09:56:10 +0300 Subject: [PATCH 26/38] Use CsWin32 --- DepotDownloader/DepotDownloader.csproj | 4 ++++ DepotDownloader/NativeMethods.txt | 2 ++ DepotDownloader/PlatformUtilities.cs | 25 +++++++------------------ 3 files changed, 13 insertions(+), 18 deletions(-) create mode 100644 DepotDownloader/NativeMethods.txt diff --git a/DepotDownloader/DepotDownloader.csproj b/DepotDownloader/DepotDownloader.csproj index 0b060b1..319e281 100644 --- a/DepotDownloader/DepotDownloader.csproj +++ b/DepotDownloader/DepotDownloader.csproj @@ -19,6 +19,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DepotDownloader/NativeMethods.txt b/DepotDownloader/NativeMethods.txt new file mode 100644 index 0000000..8f2bb82 --- /dev/null +++ b/DepotDownloader/NativeMethods.txt @@ -0,0 +1,2 @@ +GetConsoleProcessList +MessageBox diff --git a/DepotDownloader/PlatformUtilities.cs b/DepotDownloader/PlatformUtilities.cs index d3d184f..01c75a3 100644 --- a/DepotDownloader/PlatformUtilities.cs +++ b/DepotDownloader/PlatformUtilities.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace DepotDownloader { @@ -146,36 +147,24 @@ namespace DepotDownloader } } - #region WIN32_CONSOLE_STUFF - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - static extern uint GetConsoleProcessList(uint[] processList, uint processCount); - - [DllImport("user32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Unicode)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type); - - const uint MB_OK = 0x0; - const uint MB_ICONWARNING = 0x30; - + [SupportedOSPlatform("windows5.0")] public static void VerifyConsoleLaunch() { // Reference: https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922 var processList = new uint[2]; - var processCount = GetConsoleProcessList(processList, (uint)processList.Length); + var processCount = Windows.Win32.PInvoke.GetConsoleProcessList(processList); if (processCount != 1) { return; } - _ = MessageBoxW( - IntPtr.Zero, + _ = Windows.Win32.PInvoke.MessageBox( + Windows.Win32.Foundation.HWND.Null, "Depot Downloader is a console application; there is no GUI.\n\nIf you do not pass any command line parameters, it prints usage info and exits.\n\nYou must use this from a terminal/console.", - "DepotDownloader", - MB_OK | MB_ICONWARNING + "Depot Downloader", + Windows.Win32.UI.WindowsAndMessaging.MESSAGEBOX_STYLE.MB_OK | Windows.Win32.UI.WindowsAndMessaging.MESSAGEBOX_STYLE.MB_ICONWARNING ); } - #endregion } } From fec2fe746a94414ef5037fe07a953bdbc745242c Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 27 Aug 2024 10:10:52 +0300 Subject: [PATCH 27/38] Use File.SetUnixFileMode --- DepotDownloader/PlatformUtilities.cs | 130 ++------------------------- 1 file changed, 6 insertions(+), 124 deletions(-) diff --git a/DepotDownloader/PlatformUtilities.cs b/DepotDownloader/PlatformUtilities.cs index 2386eae..c5fd155 100644 --- a/DepotDownloader/PlatformUtilities.cs +++ b/DepotDownloader/PlatformUtilities.cs @@ -2,133 +2,13 @@ // in file 'LICENSE', which is part of this source code package. using System; +using System.IO; using System.Runtime.InteropServices; namespace DepotDownloader { static class PlatformUtilities { - private const int ModeExecuteOwner = 0x0040; - private const int ModeExecuteGroup = 0x0008; - private const int ModeExecuteOther = 0x0001; - private const int ModeExecute = ModeExecuteOwner | ModeExecuteGroup | ModeExecuteOther; - - [StructLayout(LayoutKind.Explicit, Size = 144)] - private readonly struct StatLinuxX64 - { - [FieldOffset(24)] public readonly uint st_mode; - } - - [StructLayout(LayoutKind.Explicit, Size = 104)] - private readonly struct StatLinuxArm32 - { - [FieldOffset(16)] public readonly uint st_mode; - } - - [StructLayout(LayoutKind.Explicit, Size = 128)] - private readonly struct StatLinuxArm64 - { - [FieldOffset(16)] public readonly uint st_mode; - } - - [StructLayout(LayoutKind.Explicit, Size = 144)] - private readonly struct StatOSX - { - [FieldOffset(4)] public readonly ushort st_mode; - } - - [StructLayout(LayoutKind.Explicit, Size = 224)] - private readonly struct StatFBSDX64 - { - [FieldOffset(24)] public readonly ushort st_mode; - } - - [DllImport("libc", EntryPoint = "__xstat", SetLastError = true)] - private static extern int statLinuxX64(int version, string path, out StatLinuxX64 statLinux); - - [DllImport("libc", EntryPoint = "__xstat", SetLastError = true)] - private static extern int statLinuxArm32(int version, string path, out StatLinuxArm32 statLinux); - - [DllImport("libc", EntryPoint = "__xstat", SetLastError = true)] - private static extern int statLinuxArm64(int version, string path, out StatLinuxArm64 statLinux); - - [DllImport("libc", EntryPoint = "stat", SetLastError = true)] - private static extern int statOSX(string path, out StatOSX stat); - - [DllImport("libc", EntryPoint = "stat$INODE64", SetLastError = true)] - private static extern int statOSXCompat(string path, out StatOSX stat); - - [DllImport("libc", EntryPoint = "stat", SetLastError = true)] - private static extern int statFBSDX64(string path, out StatFBSDX64 stat); - - [DllImport("libc", SetLastError = true)] - private static extern int chmod(string path, uint mode); - - [DllImport("libc", CallingConvention = CallingConvention.Cdecl, SetLastError = true)] - private static extern IntPtr strerror(int errno); - - private static void ThrowIf(int i) - { - if (i == -1) - { - var errno = Marshal.GetLastWin32Error(); - throw new Exception(Marshal.PtrToStringAnsi(strerror(errno))); - } - } - - private static uint GetFileMode(string path) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - switch (RuntimeInformation.ProcessArchitecture) - { - case Architecture.X64: - { - ThrowIf(statLinuxX64(1, path, out var stat)); - return stat.st_mode; - } - case Architecture.Arm: - { - ThrowIf(statLinuxArm32(3, path, out var stat)); - return stat.st_mode; - } - case Architecture.Arm64: - { - ThrowIf(statLinuxArm64(0, path, out var stat)); - return stat.st_mode; - } - } - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) - { - switch (RuntimeInformation.ProcessArchitecture) - { - case Architecture.X64: - { - ThrowIf(statFBSDX64(path, out var stat)); - return stat.st_mode; - } - } - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - switch (RuntimeInformation.ProcessArchitecture) - { - case Architecture.X64: - { - ThrowIf(statOSXCompat(path, out var stat)); - return stat.st_mode; - } - case Architecture.Arm64: - { - ThrowIf(statOSX(path, out var stat)); - return stat.st_mode; - } - } - } - throw new PlatformNotSupportedException(); - } - public static void SetExecutable(string path, bool value) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -136,13 +16,15 @@ namespace DepotDownloader return; } - var mode = GetFileMode(path); + const UnixFileMode ModeExecute = UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; + + var mode = File.GetUnixFileMode(path); var hasExecuteMask = (mode & ModeExecute) == ModeExecute; if (hasExecuteMask != value) { - ThrowIf(chmod(path, (uint)(value + File.SetUnixFileMode(path, value ? mode | ModeExecute - : mode & ~ModeExecute))); + : mode & ~ModeExecute); } } } From b5d4926f1ea6792e3118e407a141c20ef54ea98e Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 27 Aug 2024 11:37:17 +0300 Subject: [PATCH 28/38] Update bug-report.yml --- .github/ISSUE_TEMPLATE/bug-report.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index d9e2ae9..941391c 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -32,17 +32,24 @@ body: validations: required: true - type: input - id: dotnet-version + id: version attributes: label: Version - description: What version of DepotDownloader are you running on? + description: What version of DepotDownloader are using? + validations: + required: true + - type: input + id: command + attributes: + label: Command + description: Specify the full command you used (except for username and password) validations: required: true - type: textarea id: logs attributes: label: Relevant log output - description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. + description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. Run with `-debug` parameter to get additional output. render: shell - type: textarea id: additional-info From 0772448cd9e7e99e48a44e98a44a21f79ca9c88f Mon Sep 17 00:00:00 2001 From: Yaakov Date: Tue, 27 Aug 2024 20:22:16 +1000 Subject: [PATCH 29/38] Add ContinuousIntegrationBuild to CI --- .github/workflows/build.yml | 2 +- .github/workflows/sk2-ci.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fbb4ad7..686a2cb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: dotnet-version: 8.0.x - name: Build - run: dotnet publish DepotDownloader/DepotDownloader.csproj -c ${{ matrix.configuration }} -o artifacts + run: dotnet publish DepotDownloader/DepotDownloader.csproj -c ${{ matrix.configuration }} -o artifacts /p:ContinuousIntegrationBuild=true - name: Upload artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/sk2-ci.yml b/.github/workflows/sk2-ci.yml index 405b31d..b7463d9 100644 --- a/.github/workflows/sk2-ci.yml +++ b/.github/workflows/sk2-ci.yml @@ -30,7 +30,7 @@ jobs: dotnet add DepotDownloader/DepotDownloader.csproj package SteamKit2 --prerelease - name: Build - run: dotnet publish DepotDownloader/DepotDownloader.csproj -c ${{ matrix.configuration }} -o artifacts + run: dotnet publish DepotDownloader/DepotDownloader.csproj -c ${{ matrix.configuration }} -o artifacts /p:ContinuousIntegrationBuild=true - name: Upload artifact uses: actions/upload-artifact@v4 From b1a36462cc85af50deaee0ead3cf86724ed9c372 Mon Sep 17 00:00:00 2001 From: Yaakov Date: Tue, 27 Aug 2024 20:24:31 +1000 Subject: [PATCH 30/38] Enable warnings as errors in CI --- DepotDownloader/DepotDownloader.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/DepotDownloader/DepotDownloader.csproj b/DepotDownloader/DepotDownloader.csproj index 319e281..7d16e69 100644 --- a/DepotDownloader/DepotDownloader.csproj +++ b/DepotDownloader/DepotDownloader.csproj @@ -10,6 +10,7 @@ Copyright © SteamRE Team 2024 ..\Icon\DepotDownloader.ico true + true From fa486d1e5fb69c20064828202c597106c6f449b3 Mon Sep 17 00:00:00 2001 From: Yaakov Date: Tue, 27 Aug 2024 20:26:19 +1000 Subject: [PATCH 31/38] even though we will probably never run on XP, this silences CA1416 --- DepotDownloader/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index a71a0fb..91b8790 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -22,7 +22,7 @@ namespace DepotDownloader { PrintUsage(); - if (OperatingSystem.IsWindows()) + if (OperatingSystem.IsWindowsVersionAtLeast(5, 0)) { PlatformUtilities.VerifyConsoleLaunch(); } From 831bdb6001ee176128c9c94ecd84ac37dfd278d3 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 22:09:56 +0300 Subject: [PATCH 32/38] Create github action to automatically submit new versions to winget --- .github/workflows/winget.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/winget.yml diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml new file mode 100644 index 0000000..0241c1c --- /dev/null +++ b/.github/workflows/winget.yml @@ -0,0 +1,29 @@ +name: WinGet submission on release + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + winget: + name: Publish winget package + runs-on: windows-latest + steps: + - name: Submit package to Windows Package Manager Community Repository + run: | + $wingetPackage = "SteamRE.DepotDownloader" + + $github = Invoke-RestMethod -uri "https://api.github.com/repos/SteamRE/DepotDownloader/releases" + + $targetRelease = $github | Where-Object -Property name -match '^DepotDownloader' | Select -First 1 + $assets = $targetRelease | Select -ExpandProperty assets -First 1 + $zipX64Url = $assets | Where-Object -Property name -match 'DepotDownloader-windows-x64.zip' | Select -ExpandProperty browser_download_url + $zipArm64Url = $assets | Where-Object -Property name -match 'DepotDownloader-windows-arm64.zip' | Select -ExpandProperty browser_download_url + $ver = $targetRelease.tag_name -ireplace '^(DepotDownloader[ _])?v?' + + # getting latest wingetcreate file + iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe + + # how to create a token: https://github.com/microsoft/winget-create?tab=readme-ov-file#github-personal-access-token-classic-permissions + .\wingetcreate.exe update $wingetPackage --submit --version $ver --urls "$zipX64Url" "$zipArm64Url" --token "${{ secrets.PT_WINGET }}" From 317122f4aa287631386ea04e6d2a69eba2ae9e55 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 26 Aug 2024 22:13:51 +0300 Subject: [PATCH 33/38] Update README.md --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b906eed..0de2031 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,23 @@ DepotDownloader Steam depot downloader utilizing the SteamKit2 library. Supports .NET 8.0 +This program must be run from a console, it has no GUI. + +## Installation + Download a binary from [the releases page](https://github.com/SteamRE/DepotDownloader/releases/latest). -This program must be run from a console, it has no GUI. +### Via Windows Package Manager CLI (aka winget) + +[winget](https://github.com/microsoft/winget-cli) users can download and install +the latest Terminal release by installing the `SteamRE.DepotDownloader` +package: + +```powershell +winget install --exact --id SteamRE.DepotDownloader +``` + +## Usage ### Downloading one or all depots for an app ```powershell From 83e2dbb03f7b3b906b4bc32e5244420764da4d8a Mon Sep 17 00:00:00 2001 From: Yaakov Date: Wed, 28 Aug 2024 18:36:44 +1000 Subject: [PATCH 34/38] Tweak installation instructions and add Homebrew --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0de2031..b706db0 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ This program must be run from a console, it has no GUI. Download a binary from [the releases page](https://github.com/SteamRE/DepotDownloader/releases/latest). -### Via Windows Package Manager CLI (aka winget) +### via Windows Package Manager CLI (aka winget) -[winget](https://github.com/microsoft/winget-cli) users can download and install +On Windows, [winget](https://github.com/microsoft/winget-cli) users can download and install the latest Terminal release by installing the `SteamRE.DepotDownloader` package: @@ -19,6 +19,15 @@ package: winget install --exact --id SteamRE.DepotDownloader ``` +### via Homebrew + +On macOS, [Homebrew](https://brew.sh) users can download and install that latest release by running the following commands: + +```shell +brew tap steamre/tools +brew install depotdownloader +``` + ## Usage ### Downloading one or all depots for an app From b9895121a42231120b8dfbb92dbf8eba85b985bc Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Wed, 28 Aug 2024 12:51:50 +0300 Subject: [PATCH 35/38] Add header --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b706db0..a264259 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ This program must be run from a console, it has no GUI. ## Installation +### Directly from GitHub + Download a binary from [the releases page](https://github.com/SteamRE/DepotDownloader/releases/latest). ### via Windows Package Manager CLI (aka winget) From 53fefe6da24b50d245e91ab16517715510326478 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Wed, 28 Aug 2024 12:52:37 +0300 Subject: [PATCH 36/38] Change exit code when using no params to zero --- DepotDownloader/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index 91b8790..af90cdf 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -27,7 +27,7 @@ namespace DepotDownloader PlatformUtilities.VerifyConsoleLaunch(); } - return 1; + return 0; } Ansi.Init(); From 8117d8f6c20249930e44775c38a8bd836d00b10e Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Wed, 28 Aug 2024 12:59:28 +0300 Subject: [PATCH 37/38] Add -V/--version Fixes #531 --- DepotDownloader/Program.cs | 26 +++++++++++++++++++++++--- README.md | 2 +- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index af90cdf..b44b79c 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -20,6 +20,7 @@ namespace DepotDownloader { if (args.Length == 0) { + PrintVersion(); PrintUsage(); if (OperatingSystem.IsWindowsVersionAtLeast(5, 0)) @@ -38,8 +39,17 @@ namespace DepotDownloader #region Common Options + // Not using HasParameter because it is case insensitive + if (args.Length == 1 && (args[0] == "-V" || args[0] == "--version")) + { + PrintVersion(true); + return 0; + } + if (HasParameter(args, "-debug")) { + PrintVersion(true); + DebugLog.Enabled = true; DebugLog.AddListener((category, message) => { @@ -47,9 +57,6 @@ namespace DepotDownloader }); var httpEventListener = new HttpDiagnosticEventListener(); - - DebugLog.WriteLine("DepotDownloader", "Version: {0}", Assembly.GetExecutingAssembly().GetName().Version); - DebugLog.WriteLine("DepotDownloader", "Runtime: {0}", RuntimeInformation.FrameworkDescription); } var username = GetParameter(args, "-username") ?? GetParameter(args, "-user"); @@ -417,5 +424,18 @@ namespace DepotDownloader Console.WriteLine(" -max-downloads <#> - maximum number of chunks to download concurrently. (default: 8)."); Console.WriteLine(" -loginid <#> - a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently."); } + + static void PrintVersion(bool printExtra = false) + { + var version = typeof(Program).Assembly.GetCustomAttribute().InformationalVersion; + Console.WriteLine($"DepotDownloader v{version}"); + + if (!printExtra) + { + return; + } + + Console.WriteLine($"Runtime: {RuntimeInformation.FrameworkDescription} on {RuntimeInformation.OSDescription}"); + } } } diff --git a/README.md b/README.md index a264259..67de48b 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Parameter | Description `-max-servers <#>` | maximum number of content servers to use. (default: 20). `-max-downloads <#>` | maximum number of chunks to download concurrently. (default: 8). `-loginid <#>` | a unique 32-bit integer Steam LogonID in decimal, required if running multiple instances of DepotDownloader concurrently. - +`-V` or `--version` | print version and runtime ## Frequently Asked Questions From 907b3360ccec0bace6810bca32c8ff0b74ad3273 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Wed, 28 Aug 2024 13:07:38 +0300 Subject: [PATCH 38/38] Forgot to bump version --- DepotDownloader/DepotDownloader.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DepotDownloader/DepotDownloader.csproj b/DepotDownloader/DepotDownloader.csproj index 7d16e69..16a814e 100644 --- a/DepotDownloader/DepotDownloader.csproj +++ b/DepotDownloader/DepotDownloader.csproj @@ -4,7 +4,7 @@ net8.0 true LatestMajor - 2.6.0 + 2.7.1 Steam Downloading Utility SteamRE Team Copyright © SteamRE Team 2024