Merge remote-traking branch SteamRE/DepotDownloader/master

pull/503/head
Iluha 2 years ago
commit fae706be90

@ -0,0 +1,53 @@
name: Bug Report
description: File a bug report
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report!
- type: textarea
id: what-should-have-happened
attributes:
label: What did you expect to happen?
placeholder: I expected that...
validations:
required: true
- type: textarea
id: what-actually-happened
attributes:
label: Instead of that, what actually happened?
placeholder: ... but instead, what happened was...
validations:
required: true
- type: dropdown
id: operating-system
attributes:
label: Which operating system are you running on?
options:
- Linux
- macOS
- Windows
- Other
validations:
required: true
- type: input
id: dotnet-version
attributes:
label: Version
description: What version of DepotDownloader are you running on?
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.
render: shell
- type: textarea
id: additional-info
attributes:
label: Additional Information
description: Is there anything else that you think we should know?
validations:
required: false

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Discussions
url: https://github.com/SteamRE/DepotDownloader/discussions/new
about: Please ask and answer questions here.

@ -0,0 +1,36 @@
name: Feature Request
description: Suggest an idea for this project
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
Thanks, we appreciate good ideas!
- type: textarea
id: problem-area
attributes:
label: What problem is this feature trying to solve?
placeholder: I'm really frustrated when...
validations:
required: true
- type: textarea
id: proposed-solution
attributes:
label: How would you like it to be solved?
placeholder: I think that it could be solved by...
validations:
required: true
- type: textarea
id: alternative-solutions
attributes:
label: Have you considered any alternative solutions
placeholder: I did think that that it also could be solved by ..., but...
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Information
description: Is there anything else that you think we should know?
validations:
required: false

@ -15,28 +15,114 @@ on:
jobs:
build:
name: .NET on ${{ matrix.os }} (${{ matrix.configuration }})
runs-on: ${{ matrix.os }}
name: .NET on ${{ matrix.runs-on }} (${{ matrix.configuration }})
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: [macos-latest, ubuntu-latest, windows-latest]
configuration: [Release, Debug]
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'
dotnet-version: 6.0.x
- name: Build
run: dotnet publish -c ${{ matrix.configuration }} -o artifacts
- name: Upload artifact
uses: actions/upload-artifact@v2
if: matrix.configuration == 'Release'
uses: actions/upload-artifact@v3
if: matrix.configuration == 'Release' && matrix.runs-on == 'windows-latest'
with:
name: DepotDownloader-${{ runner.os }}
name: DepotDownloader-framework
path: artifacts
if-no-files-found: error
- name: Publish Windows-x64
if: matrix.configuration == 'Release' && matrix.runs-on == 'windows-latest'
run: dotnet publish --configuration Release -p:PublishSingleFile=true -p:DebugType=embedded --self-contained --runtime win-x64 --output selfcontained-win-x64
- name: Publish Windows-arm64
if: matrix.configuration == 'Release' && matrix.runs-on == 'windows-latest'
run: dotnet publish --configuration Release -p:PublishSingleFile=true -p:DebugType=embedded --self-contained --runtime win-arm64 --output selfcontained-win-arm64
- name: Publish Linux-x64
if: matrix.configuration == 'Release' && matrix.runs-on == 'ubuntu-latest'
run: dotnet publish --configuration Release -p:PublishSingleFile=true -p:DebugType=embedded --self-contained --runtime linux-x64 --output selfcontained-linux-x64
- name: Publish Linux-arm
if: matrix.configuration == 'Release' && matrix.runs-on == 'ubuntu-latest'
run: dotnet publish --configuration Release -p:PublishSingleFile=true -p:DebugType=embedded --self-contained --runtime linux-arm --output selfcontained-linux-arm
- name: Publish Linux-arm64
if: matrix.configuration == 'Release' && matrix.runs-on == 'ubuntu-latest'
run: dotnet publish --configuration Release -p:PublishSingleFile=true -p:DebugType=embedded --self-contained --runtime linux-arm64 --output selfcontained-linux-arm64
- name: Publish macOS-x64
if: matrix.configuration == 'Release' && matrix.runs-on == 'macos-latest'
run: dotnet publish --configuration Release -p:PublishSingleFile=true -p:DebugType=embedded --self-contained --runtime osx-x64 --output selfcontained-osx-x64
- name: Publish macOS-arm64
if: matrix.configuration == 'Release' && matrix.runs-on == 'macos-latest'
run: dotnet publish --configuration Release -p:PublishSingleFile=true -p:DebugType=embedded --self-contained --runtime osx-arm64 --output selfcontained-osx-arm64
- name: Upload Windows-x64
uses: actions/upload-artifact@v3
if: matrix.configuration == 'Release' && matrix.runs-on == 'windows-latest'
with:
name: DepotDownloader-windows-x64
path: selfcontained-win-x64
if-no-files-found: error
- name: Upload Windows-arm64
uses: actions/upload-artifact@v3
if: matrix.configuration == 'Release' && matrix.runs-on == 'windows-latest'
with:
name: DepotDownloader-windows-arm64
path: selfcontained-win-arm64
if-no-files-found: error
- name: Upload Linux-x64
uses: actions/upload-artifact@v3
if: matrix.configuration == 'Release' && matrix.runs-on == 'ubuntu-latest'
with:
name: DepotDownloader-linux-x64
path: selfcontained-linux-x64
if-no-files-found: error
- name: Upload Linux-arm
uses: actions/upload-artifact@v3
if: matrix.configuration == 'Release' && matrix.runs-on == 'ubuntu-latest'
with:
name: DepotDownloader-linux-arm
path: selfcontained-linux-arm
if-no-files-found: error
- name: Upload Linux-arm64
uses: actions/upload-artifact@v3
if: matrix.configuration == 'Release' && matrix.runs-on == 'ubuntu-latest'
with:
name: DepotDownloader-linux-arm64
path: selfcontained-linux-arm64
if-no-files-found: error
- name: Upload macOS-x64
uses: actions/upload-artifact@v3
if: matrix.configuration == 'Release' && matrix.runs-on == 'macos-latest'
with:
name: DepotDownloader-macos-x64
path: selfcontained-osx-x64
if-no-files-found: error
- name: Upload macOS-arm64
uses: actions/upload-artifact@v3
if: matrix.configuration == 'Release' && matrix.runs-on == 'macos-latest'
with:
name: DepotDownloader-macos-arm64
path: selfcontained-osx-arm64
if-no-files-found: error

@ -0,0 +1,41 @@
name: SteamKit2 Continuous Integration
on:
schedule:
- cron: '0 1 * * SUN'
workflow_dispatch:
jobs:
build:
name: .NET on ${{ matrix.runs-on }} (${{ matrix.configuration }})
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
runs-on: [ macos-latest, ubuntu-latest, windows-latest ]
configuration: [ Release, Debug ]
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
steps:
- uses: actions/checkout@v3
- name: Setup .NET Core
uses: actions/setup-dotnet@v2
with:
dotnet-version: 6.0.x
- name: Configure NuGet
run: |
dotnet nuget add source --username USERNAME --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/SteamRE/index.json"
dotnet add DepotDownloader/DepotDownloader.csproj package SteamKit2 --prerelease
- name: Build
run: dotnet publish -c ${{ matrix.configuration }} -o artifacts
- name: Upload artifact
uses: actions/upload-artifact@v3
if: matrix.configuration == 'Release'
with:
name: DepotDownloader-${{ runner.os }}
path: artifacts
if-no-files-found: error

@ -17,8 +17,10 @@ namespace DepotDownloader
[ProtoMember(2, IsRequired = false)]
public ConcurrentDictionary<string, int> ContentServerPenalty { get; private set; }
[ProtoMember(3, IsRequired = false)]
public Dictionary<string, string> LoginKeys { get; private set; }
// Member 3 was a Dictionary<string, string> for LoginKeys.
[ProtoMember(4, IsRequired = false)]
public Dictionary<string, string> LoginTokens { get; private set; }
string FileName;
@ -26,7 +28,7 @@ namespace DepotDownloader
{
SentryData = new Dictionary<string, byte[]>();
ContentServerPenalty = new ConcurrentDictionary<string, int>();
LoginKeys = new Dictionary<string, string>();
LoginTokens = new Dictionary<string, string>();
}
static bool Loaded

@ -22,7 +22,7 @@ namespace DepotDownloader
public const uint INVALID_APP_ID = uint.MaxValue;
public const uint INVALID_DEPOT_ID = uint.MaxValue;
public const ulong INVALID_MANIFEST_ID = ulong.MaxValue;
public const string DEFAULT_BRANCH = "Public";
public const string DEFAULT_BRANCH = "public";
public static DownloadConfig Config = new DownloadConfig();
@ -42,21 +42,18 @@ namespace DepotDownloader
public string branch { get; private set; }
public string installDir { get; private set; }
public string contentName { get; private set; }
public byte[] depotKey { get; private set; }
public DepotDownloadInfo(
uint depotid, uint appId, ulong manifestId, string branch,
string installDir, string contentName,
byte[] depotKey)
string installDir, byte[] depotKey)
{
this.id = depotid;
this.appId = appId;
this.manifestId = manifestId;
this.branch = branch;
this.installDir = installDir;
this.contentName = contentName;
this.depotKey = depotKey;
}
}
@ -212,20 +209,6 @@ namespace DepotDownloader
return uint.Parse(buildid.Value);
}
static uint GetSteam3DepotProxyAppId(uint depotId, uint appId)
{
var depots = GetSteam3AppSection(appId, EAppInfoSection.Depots);
var depotChild = depots[depotId.ToString()];
if (depotChild == KeyValue.Invalid)
return INVALID_APP_ID;
if (depotChild["depotfromapp"] == KeyValue.Invalid)
return INVALID_APP_ID;
return depotChild["depotfromapp"].AsUnsignedInteger();
}
static ulong GetSteam3DepotManifest(uint depotId, uint appId, string branch)
{
var depots = GetSteam3AppSection(appId, EAppInfoSection.Depots);
@ -259,9 +242,9 @@ namespace DepotDownloader
if (manifests.Children.Count == 0 && manifests_encrypted.Children.Count == 0)
return INVALID_MANIFEST_ID;
var node = manifests[branch];
var node = manifests[branch]["gid"];
if (branch != "Public" && node == KeyValue.Invalid)
if (node == KeyValue.Invalid && !string.Equals(branch, DEFAULT_BRANCH, StringComparison.OrdinalIgnoreCase))
{
var node_encrypted = manifests_encrypted[branch];
if (node_encrypted != KeyValue.Invalid)
@ -273,24 +256,9 @@ namespace DepotDownloader
Config.BetaPassword = password = Console.ReadLine();
}
var encrypted_v1 = node_encrypted["encrypted_gid"];
var encrypted_v2 = node_encrypted["encrypted_gid_2"];
if (encrypted_v1 != KeyValue.Invalid)
{
var input = Util.DecodeHexString(encrypted_v1.Value);
var manifest_bytes = CryptoHelper.VerifyAndDecryptPassword(input, password);
if (manifest_bytes == null)
{
Console.WriteLine("[Error]|[Password]|Password was invalid for branch {0}", branch);
return INVALID_MANIFEST_ID;
}
return BitConverter.ToUInt64(manifest_bytes, 0);
}
var encrypted_gid = node_encrypted["gid"];
if (encrypted_v2 != KeyValue.Invalid)
if (encrypted_gid != KeyValue.Invalid)
{
// Submit the password to Steam now to get encryption keys
steam3.CheckAppBetaPassword(appId, Config.BetaPassword);
@ -301,7 +269,7 @@ namespace DepotDownloader
return INVALID_MANIFEST_ID;
}
var input = Util.DecodeHexString(encrypted_v2.Value);
var input = Util.DecodeHexString(encrypted_gid.Value);
byte[] manifest_bytes;
try
{
@ -329,47 +297,31 @@ namespace DepotDownloader
return UInt64.Parse(node.Value);
}
static string GetAppOrDepotName(uint depotId, uint appId)
static string GetAppName(uint appId)
{
if (depotId == INVALID_DEPOT_ID)
{
var info = GetSteam3AppSection(appId, EAppInfoSection.Common);
if (info == null)
return String.Empty;
return info["name"].AsString();
}
var depots = GetSteam3AppSection(appId, EAppInfoSection.Depots);
if (depots == null)
return String.Empty;
var depotChild = depots[depotId.ToString()];
if (depotChild == null)
var info = GetSteam3AppSection(appId, EAppInfoSection.Common);
if (info == null)
return String.Empty;
return depotChild["name"].AsString();
return info["name"].AsString();
}
public static bool InitializeSteam3(string username, string password)
{
string loginKey = null;
string loginToken = null;
if (username != null && Config.RememberPassword)
{
_ = AccountSettingsStore.Instance.LoginKeys.TryGetValue(username, out loginKey);
_ = AccountSettingsStore.Instance.LoginTokens.TryGetValue(username, out loginToken);
}
steam3 = new Steam3Session(
new SteamUser.LogOnDetails
{
Username = username,
Password = loginKey == null ? password : null,
Password = loginToken == null ? password : null,
ShouldRememberPassword = Config.RememberPassword,
LoginKey = loginKey,
AccessToken = loginToken,
LoginID = Config.LoginID ?? 0x534B32, // "SK2"
}
);
@ -396,7 +348,6 @@ namespace DepotDownloader
if (steam3 == null)
return;
steam3.TryWaitForLoginKey();
steam3.Disconnect();
}
@ -501,7 +452,7 @@ namespace DepotDownloader
}
else
{
var contentName = GetAppOrDepotName(INVALID_DEPOT_ID, appId);
var contentName = GetAppName(appId);
throw new ContentDownloaderException(String.Format("[Error]|[NotAvailableApp]|App {0} ({1}) is not available from this account.", appId, contentName));
}
}
@ -624,11 +575,9 @@ namespace DepotDownloader
if (steam3 != null && appId != INVALID_APP_ID)
steam3.RequestAppInfo(appId);
var contentName = GetAppOrDepotName(depotId, appId);
if (!AccountHasAccess(depotId))
{
Console.WriteLine("[Error]|[NotAvailableApp]||Depot {0} ({1}) is not available from this account.", depotId, contentName);
Console.WriteLine("[Error]|[NotAvailableApp]|Depot {0}) is not available from this account.", depotId);
return null;
}
@ -636,26 +585,21 @@ namespace DepotDownloader
if (manifestId == INVALID_MANIFEST_ID)
{
manifestId = GetSteam3DepotManifest(depotId, appId, branch);
if (manifestId == INVALID_MANIFEST_ID && branch != "public")
if (manifestId == INVALID_MANIFEST_ID && !string.Equals(branch, DEFAULT_BRANCH, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("Warning: Depot {0} does not have branch named \"{1}\". Trying public branch.", depotId, branch);
branch = "public";
Console.WriteLine("Warning: Depot {0} does not have branch named \"{1}\". Trying {2} branch.", depotId, branch, DEFAULT_BRANCH);
branch = DEFAULT_BRANCH;
manifestId = GetSteam3DepotManifest(depotId, appId, branch);
}
if (manifestId == INVALID_MANIFEST_ID)
{
Console.WriteLine("[Error]|[InvalidManifest]|Depot {0} ({1}) missing public subsection or manifest section.", depotId, contentName);
Console.WriteLine("[Error]|[InvalidManifest]|Depot {0} missing public subsection or manifest section.", depotId);
return null;
}
}
// For depots that are proxied through depotfromapp, we still need to resolve the proxy app id
var containingAppId = appId;
var proxyAppId = GetSteam3DepotProxyAppId(depotId, appId);
if (proxyAppId != INVALID_APP_ID) containingAppId = proxyAppId;
steam3.RequestDepotKey(depotId, containingAppId);
steam3.RequestDepotKey(depotId, appId);
if (!steam3.DepotKeys.ContainsKey(depotId))
{
Console.WriteLine("[Error]|[NoValidKey]|No valid depot key for {0}, unable to download.", depotId);
@ -673,7 +617,7 @@ namespace DepotDownloader
var depotKey = steam3.DepotKeys[depotId];
return new DepotDownloadInfo(depotId, containingAppId, manifestId, branch, installDir, contentName, depotKey);
return new DepotDownloadInfo(depotId, appId, manifestId, branch, installDir, depotKey);
}
private class ChunkMatch
@ -774,7 +718,7 @@ namespace DepotDownloader
{
var depotCounter = new DepotDownloadCounter();
Console.WriteLine("Processing depot {0} - {1}", depot.id, depot.contentName);
Console.WriteLine("Processing depot {0}", depot.id);
ProtoManifest oldProtoManifest = null;
ProtoManifest newProtoManifest = null;
@ -1013,7 +957,7 @@ namespace DepotDownloader
var depot = depotFilesData.depotDownloadInfo;
var depotCounter = depotFilesData.depotCounter;
Console.WriteLine("Downloading depot {0} - {1}", depot.id, depot.contentName);
Console.WriteLine("Downloading depot {0}", depot.id);
var files = depotFilesData.filteredFiles.Where(f => !f.Flags.HasFlag(EDepotFileFlag.Directory)).ToArray();
var networkChunkQueue = new ConcurrentQueue<(FileStreamData fileStreamData, ProtoManifest.FileData fileData, ProtoManifest.ChunkData chunk)>();

@ -4,14 +4,16 @@
<TargetFramework>net6.0</TargetFramework>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<RollForward>LatestMajor</RollForward>
<Version>2.4.6</Version>
<Version>2.5.0</Version>
<Description>Steam Downloading Utility</Description>
<Authors>SteamRE Team</Authors>
<Copyright>Copyright © SteamRE Team 2021</Copyright>
<ApplicationIcon>..\Icon\DepotDownloader.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="protobuf-net" Version="3.0.101" />
<PackageReference Include="SteamKit2" Version="2.4.1" />
<PackageReference Include="protobuf-net" Version="3.2.16" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="SteamKit2" Version="2.5.0-Beta.1" />
</ItemGroup>
</Project>

@ -22,10 +22,11 @@ namespace DepotDownloader
public int MaxServers { get; set; }
public int MaxDownloads { get; set; }
public string SuppliedPassword { get; set; }
public bool RememberPassword { get; set; }
// A Steam LoginID to allow multiple concurrent connections
public uint? LoginID { get; set; }
public bool UseQrCode { get; set; }
}
}

@ -11,11 +11,23 @@ namespace DepotDownloader
private const int ModeExecute = ModeExecuteOwner | ModeExecuteGroup | ModeExecuteOther;
[StructLayout(LayoutKind.Explicit, Size = 144)]
private readonly struct StatLinux
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
{
@ -23,7 +35,13 @@ namespace DepotDownloader
}
[DllImport("libc", EntryPoint = "__xstat", SetLastError = true)]
private static extern int statLinux(int version, string path, out StatLinux statLinux);
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);
@ -34,9 +52,6 @@ namespace DepotDownloader
[DllImport("libc", SetLastError = true)]
private static extern int chmod(string path, uint mode);
[DllImport("libc", SetLastError = true)]
private static extern int chmod(string path, ushort mode);
[DllImport("libc", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
private static extern IntPtr strerror(int errno);
@ -49,36 +64,63 @@ namespace DepotDownloader
}
}
public static void SetExecutable(string path, bool value)
private static uint GetFileMode(string path)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
ThrowIf(statLinux(1, path, out var stat));
var hasExecuteMask = (stat.st_mode & ModeExecute) == ModeExecute;
if (hasExecuteMask != value)
switch (RuntimeInformation.ProcessArchitecture)
{
ThrowIf(chmod(path, (uint)(value
? stat.st_mode | ModeExecute
: stat.st_mode & ~ModeExecute)));
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.OSX))
{
StatOSX stat;
ThrowIf(RuntimeInformation.ProcessArchitecture == Architecture.Arm64
? statOSX(path, out stat)
: statOSXCompat(path, out stat));
var hasExecuteMask = (stat.st_mode & ModeExecute) == ModeExecute;
if (hasExecuteMask != value)
switch (RuntimeInformation.ProcessArchitecture)
{
ThrowIf(chmod(path, (ushort)(value
? stat.st_mode | ModeExecute
: stat.st_mode & ~ModeExecute)));
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))
{
return;
}
var mode = GetFileMode(path);
var hasExecuteMask = (mode & ModeExecute) == ModeExecute;
if (hasExecuteMask != value)
{
ThrowIf(chmod(path, (uint)(value
? mode | ModeExecute
: mode & ~ModeExecute)));
}
}
}
}

@ -47,6 +47,7 @@ namespace DepotDownloader
var username = GetParameter<string>(args, "-username") ?? GetParameter<string>(args, "-user");
var password = GetParameter<string>(args, "-password") ?? GetParameter<string>(args, "-pass");
ContentDownloader.Config.RememberPassword = HasParameter(args, "-remember-password");
ContentDownloader.Config.UseQrCode = HasParameter(args, "-qr");
ContentDownloader.Config.DownloadManifestOnly = HasParameter(args, "-manifest-only");
@ -268,32 +269,32 @@ namespace DepotDownloader
static bool InitializeSteam(string username, string password)
{
if (username != null && password == null && (!ContentDownloader.Config.RememberPassword || !AccountSettingsStore.Instance.LoginKeys.ContainsKey(username)))
if (!ContentDownloader.Config.UseQrCode)
{
do
if (username != null && password == null && (!ContentDownloader.Config.RememberPassword || !AccountSettingsStore.Instance.LoginTokens.ContainsKey(username)))
{
Console.Write("[Password]|Enter account password for \"{0}\": ", username);
if (Console.IsInputRedirected)
do
{
password = Console.ReadLine();
}
else
{
// Avoid console echoing of password
password = Util.ReadPassword();
}
Console.Write("[Password]|Enter account password for \"{0}\": ", username);
if (Console.IsInputRedirected)
{
password = Console.ReadLine();
}
else
{
// Avoid console echoing of password
password = Util.ReadPassword();
}
Console.WriteLine();
} while (string.Empty == password);
}
else if (username == null)
{
Console.WriteLine("No username given. Using anonymous account with dedicated server subscription.");
Console.WriteLine();
} while (string.Empty == password);
}
else if (username == null)
{
Console.WriteLine("No username given. Using anonymous account with dedicated server subscription.");
}
}
// capture the supplied password in case we need to re-use it after checking the login key
ContentDownloader.Config.SuppliedPassword = password;
return ContentDownloader.InitializeSteam3(username, password);
}
@ -375,7 +376,7 @@ namespace DepotDownloader
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 <id>\t\t\t- manifest id of content to download (requires -depot, default: current for branch).");
Console.WriteLine("\t-beta <branchname>\t\t\t- download from specified branch if available (default: Public).");
Console.WriteLine($"\t-beta <branchname>\t\t\t- download from specified branch if available (default: {ContentDownloader.DEFAULT_BRANCH}).");
Console.WriteLine("\t-betapassword <pass>\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 <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)");

@ -6,7 +6,9 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using QRCoder;
using SteamKit2;
using SteamKit2.Authentication;
using SteamKit2.Internal;
namespace DepotDownloader
@ -53,11 +55,11 @@ namespace DepotDownloader
bool bAborted;
bool bExpectingDisconnectRemote;
bool bDidDisconnect;
bool bDidReceiveLoginKey;
bool bIsConnectionRecovery;
int connectionBackoff;
int seq; // more hack fixes
DateTime connectTime;
AuthSession authSession;
// input
readonly SteamUser.LogOnDetails logonDetails;
@ -72,14 +74,13 @@ namespace DepotDownloader
{
this.logonDetails = details;
this.authenticatedUser = details.Username != null;
this.authenticatedUser = details.Username != null || ContentDownloader.Config.UseQrCode;
this.credentials = new Credentials();
this.bConnected = false;
this.bConnecting = false;
this.bAborted = false;
this.bExpectingDisconnectRemote = false;
this.bDidDisconnect = false;
this.bDidReceiveLoginKey = false;
this.seq = 0;
this.AppTokens = new Dictionary<uint, ulong>();
@ -112,11 +113,10 @@ namespace DepotDownloader
this.callbacks.Subscribe<SteamUser.SessionTokenCallback>(SessionTokenCallback);
this.callbacks.Subscribe<SteamApps.LicenseListCallback>(LicenseListCallback);
this.callbacks.Subscribe<SteamUser.UpdateMachineAuthCallback>(UpdateMachineAuthCallback);
this.callbacks.Subscribe<SteamUser.LoginKeyCallback>(LoginKeyCallback);
Console.Write("Connecting to Steam3...");
if (authenticatedUser)
if (details.Username != null)
{
var fi = new FileInfo(String.Format("{0}.sentryFile", logonDetails.Username));
if (AccountSettingsStore.Instance.SentryData != null && AccountSettingsStore.Instance.SentryData.ContainsKey(logonDetails.Username))
@ -419,7 +419,6 @@ namespace DepotDownloader
bExpectingDisconnectRemote = false;
bDidDisconnect = false;
bIsConnectionRecovery = false;
bDidReceiveLoginKey = false;
}
void Connect()
@ -428,6 +427,7 @@ namespace DepotDownloader
bConnected = false;
bConnecting = true;
connectionBackoff = 0;
authSession = null;
ResetConnectionFlags();
@ -466,23 +466,6 @@ namespace DepotDownloader
steamClient.Disconnect();
}
public void TryWaitForLoginKey()
{
if (logonDetails.Username == null || !credentials.LoggedOn || !ContentDownloader.Config.RememberPassword) return;
var totalWaitPeriod = DateTime.Now.AddSeconds(3);
while (true)
{
var now = DateTime.Now;
if (now >= totalWaitPeriod) break;
if (bDidReceiveLoginKey) break;
callbacks.RunWaitAllCallbacks(TimeSpan.FromMilliseconds(100));
}
}
private void WaitForCallbacks()
{
callbacks.RunWaitCallbacks(TimeSpan.FromSeconds(1));
@ -496,11 +479,17 @@ namespace DepotDownloader
}
}
private void ConnectedCallback(SteamClient.ConnectedCallback connected)
private async void ConnectedCallback(SteamClient.ConnectedCallback connected)
{
Console.WriteLine(" Done!");
bConnecting = false;
bConnected = true;
// Update our tracking so that we don't time out, even if we need to reconnect multiple times,
// e.g. if the authentication phase takes a while and therefore multiple connections.
connectTime = DateTime.Now;
connectionBackoff = 0;
if (!authenticatedUser)
{
Console.Write("Logging anonymously into Steam3...");
@ -508,16 +497,103 @@ namespace DepotDownloader
}
else
{
Console.Write("Logging '{0}' into Steam3...", logonDetails.Username);
try
if (logonDetails.Username != null)
{
Console.WriteLine("Logging '{0}' into Steam3...", logonDetails.Username);
}
if (authSession is null)
{
steamUser.LogOn(logonDetails);
if (logonDetails.Username != null && logonDetails.Password != null && logonDetails.AccessToken is null)
{
try
{
authSession = await steamClient.Authentication.BeginAuthSessionViaCredentialsAsync(new SteamKit2.Authentication.AuthSessionDetails
{
Username = logonDetails.Username,
Password = logonDetails.Password,
IsPersistentSession = ContentDownloader.Config.RememberPassword,
Authenticator = new UserConsoleAuthenticator(),
});
}
catch (TaskCanceledException)
{
return;
}
catch (Exception ex)
{
Console.Error.WriteLine("[Error]|[SteamLib]|Failed to authenticate with Steam: " + ex.Message);
Abort(false);
return;
}
}
else if (logonDetails.AccessToken is null && ContentDownloader.Config.UseQrCode)
{
Console.WriteLine("Logging in with QR code...");
try
{
var session = await steamClient.Authentication.BeginAuthSessionViaQRAsync(new AuthSessionDetails
{
IsPersistentSession = ContentDownloader.Config.RememberPassword,
Authenticator = new UserConsoleAuthenticator(),
});
authSession = session;
// Steam will periodically refresh the challenge url, so we need a new QR code.
session.ChallengeURLChanged = () =>
{
Console.WriteLine();
Console.WriteLine("The QR code has changed:");
DisplayQrCode(session.ChallengeURL);
};
// Draw initial QR code immediately
DisplayQrCode(session.ChallengeURL);
}
catch (TaskCanceledException)
{
return;
}
catch (Exception ex)
{
Console.Error.WriteLine("Failed to authenticate with Steam: " + ex.Message);
Abort(false);
return;
}
}
}
catch (ArgumentException e)
if (authSession != null)
{
Console.WriteLine($"[Error]|[SteamLib]|{e.Message}");
throw e;
try
{
var result = await authSession.PollingWaitForResultAsync();
logonDetails.Username = result.AccountName;
logonDetails.Password = null;
logonDetails.AccessToken = result.RefreshToken;
AccountSettingsStore.Instance.LoginTokens[result.AccountName] = result.RefreshToken;
AccountSettingsStore.Save();
}
catch (TaskCanceledException)
{
return;
}
catch (Exception ex)
{
Console.Error.WriteLine("Failed to authenticate with Steam: " + ex.Message);
Abort(false);
return;
}
authSession = null;
}
steamUser.LogOn(logonDetails);
}
}
@ -525,6 +601,8 @@ namespace DepotDownloader
{
bDidDisconnect = true;
DebugLog.WriteLine(nameof(Steam3Session), $"Disconnected: bIsConnectionRecovery = {bIsConnectionRecovery}, UserInitiated = {disconnected.UserInitiated}, bExpectingDisconnectRemote = {bExpectingDisconnectRemote}");
// When recovering the connection, we want to reconnect even if the remote disconnects us
if (!bIsConnectionRecovery && (disconnected.UserInitiated || bExpectingDisconnectRemote))
{
@ -561,14 +639,14 @@ namespace DepotDownloader
{
var isSteamGuard = loggedOn.Result == EResult.AccountLogonDenied;
var is2FA = loggedOn.Result == EResult.AccountLoginDeniedNeedTwoFactor;
var isLoginKey = ContentDownloader.Config.RememberPassword && logonDetails.LoginKey != null && loggedOn.Result == EResult.InvalidPassword;
var isAccessToken = ContentDownloader.Config.RememberPassword && logonDetails.AccessToken != null && loggedOn.Result == EResult.InvalidPassword; // TODO: Get EResult for bad access token
if (isSteamGuard || is2FA || isLoginKey)
if (isSteamGuard || is2FA || isAccessToken)
{
bExpectingDisconnectRemote = true;
Abort(false);
if (!isLoginKey)
if (!isAccessToken)
{
Console.WriteLine("This account is protected by Steam Guard.");
}
@ -581,23 +659,15 @@ namespace DepotDownloader
logonDetails.TwoFactorCode = Console.ReadLine();
} while (String.Empty == logonDetails.TwoFactorCode);
}
else if (isLoginKey)
else if (isAccessToken)
{
AccountSettingsStore.Instance.LoginKeys.Remove(logonDetails.Username);
AccountSettingsStore.Instance.LoginTokens.Remove(logonDetails.Username);
AccountSettingsStore.Save();
logonDetails.LoginKey = null;
if (ContentDownloader.Config.SuppliedPassword != null)
{
Console.WriteLine("Login key was expired. Connecting with supplied password.");
logonDetails.Password = ContentDownloader.Config.SuppliedPassword;
}
else
{
Console.Write("[Password]|Login key was expired. Please enter your password: ");
logonDetails.Password = Util.ReadPassword();
}
// TODO: Handle gracefully by falling back to password prompt?
Console.WriteLine("Access token was rejected.");
Abort(false);
return;
}
else
{
@ -683,7 +753,7 @@ namespace DepotDownloader
private void UpdateMachineAuthCallback(SteamUser.UpdateMachineAuthCallback machineAuth)
{
var hash = Util.SHAHash(machineAuth.Data);
Console.WriteLine("Got Machine Auth: {0} {1} {2} {3}", machineAuth.FileName, machineAuth.Offset, machineAuth.BytesToWrite, machineAuth.Data.Length, hash);
Console.WriteLine("Got Machine Auth: {0} {1} {2} {3}", machineAuth.FileName, machineAuth.Offset, machineAuth.BytesToWrite, machineAuth.Data.Length);
AccountSettingsStore.Instance.SentryData[logonDetails.Username] = machineAuth.Data;
AccountSettingsStore.Save();
@ -709,16 +779,16 @@ namespace DepotDownloader
steamUser.SendMachineAuthResponse(authResponse);
}
private void LoginKeyCallback(SteamUser.LoginKeyCallback loginKey)
private static void DisplayQrCode(string challengeUrl)
{
Console.WriteLine("Accepted new login key for account {0}", logonDetails.Username);
AccountSettingsStore.Instance.LoginKeys[logonDetails.Username] = loginKey.LoginKey;
AccountSettingsStore.Save();
steamUser.AcceptNewLoginKey(loginKey);
bDidReceiveLoginKey = true;
// Encode the link as a QR code
using var qrGenerator = new QRCodeGenerator();
var qrCodeData = qrGenerator.CreateQrCode(challengeUrl, QRCodeGenerator.ECCLevel.L);
using var qrCode = new AsciiQRCode(qrCodeData);
var qrCodeAsAsciiArt = qrCode.GetGraphic(1, drawQuietZones: false);
Console.WriteLine("Use the Steam Mobile App to sign in with this QR code:");
Console.WriteLine(qrCodeAsAsciiArt);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 65 65" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g id="B" transform="matrix(1,0,0,1,0.5,0.5)">
<g>
<path d="M1.305,41.202C5.259,54.386 17.488,64 31.959,64C49.632,64 63.959,49.673 63.959,32C63.959,14.327 49.632,0 31.959,0C15.001,0 1.124,13.193 0.028,29.874C2.102,33.351 2.907,35.502 1.303,41.202L1.305,41.202Z" style="fill:rgb(56,61,72);fill-rule:nonzero;"/>
<g transform="matrix(-0.984183,-1.19698e-16,9.80712e-17,-0.795301,95.4776,96.4352)">
<g id="Arrow">
<path d="M45.613,41.896C45.811,41.658 46.125,41.658 46.322,41.896C48.069,44.002 55.776,53.297 58.421,56.487C58.569,56.666 58.615,56.938 58.537,57.174C58.459,57.411 58.273,57.566 58.067,57.566C55.616,57.566 50.417,57.566 50.417,57.566L50.417,80.833L41.519,90.628L41.519,57.566L33.869,57.566C33.662,57.566 33.476,57.411 33.399,57.174C33.321,56.938 33.367,56.666 33.515,56.487C36.16,53.297 43.867,44.002 45.613,41.896Z" style="fill:white;"/>
<path d="M44.904,40.996C43.158,43.102 35.45,52.397 32.805,55.587C32.361,56.122 32.224,56.937 32.457,57.648C32.691,58.359 33.249,58.824 33.869,58.824L40.502,58.824C40.502,58.824 40.502,90.628 40.502,90.628L42.194,91.567L51.433,81.398L51.433,58.824C51.433,58.824 58.067,58.824 58.067,58.824C58.687,58.824 59.245,58.359 59.478,57.648C59.712,56.937 59.574,56.122 59.13,55.587C56.485,52.397 48.778,43.102 47.032,40.996C46.44,40.282 45.496,40.282 44.904,40.996L44.904,40.996ZM45.613,41.896C45.811,41.658 46.125,41.658 46.322,41.896C48.069,44.002 55.776,53.297 58.421,56.487C58.569,56.666 58.615,56.938 58.537,57.174C58.459,57.411 58.273,57.566 58.067,57.566C55.616,57.566 50.417,57.566 50.417,57.566L50.417,80.833L41.519,90.628L41.519,57.566L33.869,57.566C33.662,57.566 33.476,57.411 33.399,57.174C33.321,56.938 33.367,56.666 33.515,56.487C36.16,53.297 43.867,44.002 45.613,41.896Z" style="fill:rgb(56,61,72);"/>
</g>
</g>
<path id="Steam" d="M30.31,23.985L30.313,24.143L22.483,35.518C21.215,35.46 19.943,35.683 18.735,36.18C18.209,36.394 17.707,36.662 17.237,36.98L0.042,29.893C0.042,29.893 -0.356,36.439 1.302,41.317L13.458,46.333C14.058,49.061 15.938,51.453 18.7,52.603C23.194,54.47 28.43,52.313 30.303,47.821C30.786,46.664 31.019,45.418 30.987,44.165L42.18,36.16L42.455,36.165C49.16,36.165 54.61,30.699 54.61,23.985C54.61,17.271 49.17,11.825 42.455,11.811C35.753,11.811 30.3,17.271 30.3,23.985L30.31,23.985ZM28.43,47.035C26.976,50.535 22.964,52.182 19.477,50.729C17.93,50.079 16.675,48.882 15.953,47.367L19.91,49.007C20.524,49.262 21.182,49.394 21.847,49.394C23.879,49.394 25.721,48.165 26.501,46.288C27.565,43.733 26.34,40.754 23.786,39.687L19.686,37.992C21.264,37.392 23.058,37.372 24.736,38.069C26.436,38.772 27.736,40.096 28.432,41.789C29.128,43.482 29.124,45.349 28.422,47.035M42.466,32.1C38.022,32.088 34.372,28.431 34.368,23.987C34.373,19.544 38.023,15.888 42.466,15.876C46.91,15.887 50.561,19.543 50.566,23.987C50.562,28.431 46.91,32.089 42.466,32.1M36.398,23.974C36.395,20.635 39.139,17.884 42.478,17.879C45.833,17.879 48.562,20.609 48.562,23.974C48.564,27.314 45.818,30.064 42.478,30.067C39.139,30.062 36.395,27.313 36.397,23.974L36.398,23.974Z" style="fill:rgb(175,212,255);fill-rule:nonzero;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -0,0 +1,6 @@
{
"sdk": {
"version": "6.0.100",
"rollForward": "latestMinor"
}
}
Loading…
Cancel
Save