From 969a394fdd5e42c7ce56fac2b96280553bd2df1c Mon Sep 17 00:00:00 2001 From: Ryan Kistner Date: Tue, 22 Mar 2022 18:58:56 -0600 Subject: [PATCH] Implemented manifest request codes --- DepotDownloader/CDNClientPool.cs | 16 ----- DepotDownloader/ContentDownloader.cs | 91 ++++++++++++++++++++++------ DepotDownloader/Steam3Session.cs | 47 +++----------- 3 files changed, 81 insertions(+), 73 deletions(-) diff --git a/DepotDownloader/CDNClientPool.cs b/DepotDownloader/CDNClientPool.cs index aab33c9e..d40d4b2a 100644 --- a/DepotDownloader/CDNClientPool.cs +++ b/DepotDownloader/CDNClientPool.cs @@ -140,22 +140,6 @@ namespace DepotDownloader return connection; } - public async Task AuthenticateConnection(uint appId, uint depotId, Server server) - { - var host = steamSession.ResolveCDNTopLevelHost(server.Host); - var cdnKey = $"{depotId:D}:{host}"; - - steamSession.RequestCDNAuthToken(appId, depotId, host, cdnKey); - - if (steamSession.CDNAuthTokens.TryGetValue(cdnKey, out var authTokenCallbackPromise)) - { - var result = await authTokenCallbackPromise.Task; - return result.Token; - } - - throw new Exception($"Failed to retrieve CDN token for server {server.Host} depot {depotId}"); - } - public void ReturnConnection(Server server) { if (server == null) return; diff --git a/DepotDownloader/ContentDownloader.cs b/DepotDownloader/ContentDownloader.cs index cbd771bf..7db9b10f 100644 --- a/DepotDownloader/ContentDownloader.cs +++ b/DepotDownloader/ContentDownloader.cs @@ -36,18 +36,27 @@ namespace DepotDownloader private sealed class DepotDownloadInfo { public uint id { get; private set; } + public uint appId { get; private set; } + public ulong manifestId { get; private set; } + public string branch { get; private set; } + public string installDir { get; private set; } public string contentName { get; private set; } - public ulong manifestId { get; private set; } - public byte[] depotKey; + public byte[] depotKey { get; private set; } - public DepotDownloadInfo(uint depotid, ulong manifestId, string installDir, string contentName) + public DepotDownloadInfo( + uint depotid, uint appId, ulong manifestId, string branch, + string installDir, string contentName, + byte[] depotKey) { this.id = depotid; + this.appId = appId; this.manifestId = manifestId; + this.branch = branch; this.installDir = installDir; this.contentName = contentName; + this.depotKey = depotKey; } } @@ -202,6 +211,20 @@ 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); @@ -626,27 +649,30 @@ namespace DepotDownloader } } - var uVersion = GetSteam3AppBuildNumber(appId, branch); + // 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; - string installDir; - if (!CreateDirectories(depotId, uVersion, out installDir)) + steam3.RequestDepotKey(depotId, containingAppId); + if (!steam3.DepotKeys.ContainsKey(depotId)) { - Console.WriteLine("Error: Unable to create install directories!"); + Console.WriteLine("No valid depot key for {0}, unable to download.", depotId); return null; } - steam3.RequestDepotKey(depotId, appId); - if (!steam3.DepotKeys.ContainsKey(depotId)) + var uVersion = GetSteam3AppBuildNumber(appId, branch); + + string installDir; + if (!CreateDirectories(depotId, uVersion, out installDir)) { - Console.WriteLine("No valid depot key for {0}, unable to download.", depotId); + Console.WriteLine("Error: Unable to create install directories!"); return null; } var depotKey = steam3.DepotKeys[depotId]; - var info = new DepotDownloadInfo(depotId, manifestId, installDir, contentName); - info.depotKey = depotKey; - return info; + return new DepotDownloadInfo(depotId, containingAppId, manifestId, branch, installDir, contentName, depotKey); } private class ChunkMatch @@ -837,9 +863,34 @@ namespace DepotDownloader { connection = cdnPool.GetConnection(cts.Token); - DebugLog.WriteLine("ContentDownloader", "Downloading manifest {0} from {1} with {2}", depot.manifestId, connection, cdnPool.ProxyServer != null ? cdnPool.ProxyServer : "no proxy"); - depotManifest = await cdnPool.CDNClient.DownloadManifestAsync(depot.id, depot.manifestId, manifestRequestCode: 0, - connection, depot.depotKey, cdnPool.ProxyServer).ConfigureAwait(false); + // In order to download this manifest, we need the current manifest request code + // The manifest request code is time limited, so it must be retrieved on demand + // TODO: for retried requests, is there enough leeway to use a cached code? + var manifestRequestCode = await steam3.GetDepotManifestRequestCodeAsync( + depot.id, + depot.appId, + depot.manifestId, + depot.branch); + + if (manifestRequestCode == 0) + { + DebugLog.WriteLine("ContentDownloader", + "No manifest request code was returned for {0} {1}", + depot.id, depot.manifestId); + } + + DebugLog.WriteLine("ContentDownloader", + "Downloading manifest {0} from {1} with {2}", + depot.manifestId, + connection, + cdnPool.ProxyServer != null ? cdnPool.ProxyServer : "no proxy"); + depotManifest = await cdnPool.CDNClient.DownloadManifestAsync( + depot.id, + depot.manifestId, + manifestRequestCode, + connection, + depot.depotKey, + cdnPool.ProxyServer).ConfigureAwait(false); cdnPool.ReturnConnection(connection); } @@ -1236,8 +1287,12 @@ namespace DepotDownloader 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.id, data, - connection, depot.depotKey, cdnPool.ProxyServer).ConfigureAwait(false); + chunkData = await cdnPool.CDNClient.DownloadDepotChunkAsync( + depot.id, + data, + connection, + depot.depotKey, + cdnPool.ProxyServer).ConfigureAwait(false); cdnPool.ReturnConnection(connection); } diff --git a/DepotDownloader/Steam3Session.cs b/DepotDownloader/Steam3Session.cs index c84cdc13..071981fb 100644 --- a/DepotDownloader/Steam3Session.cs +++ b/DepotDownloader/Steam3Session.cs @@ -317,50 +317,19 @@ namespace DepotDownloader }, () => { return completed; }); } - public string ResolveCDNTopLevelHost(string host) - { - // SteamPipe CDN shares tokens with all hosts - if (host.EndsWith(".steampipe.steamcontent.com")) - { - return "steampipe.steamcontent.com"; - } - - if (host.EndsWith(".steamcontent.com")) - { - return "steamcontent.com"; - } - return host; - } - - public void RequestCDNAuthToken(uint appid, uint depotid, string host, string cdnKey) + public async Task GetDepotManifestRequestCodeAsync(uint depotId, uint appId, ulong manifestId, string branch) { - if (CDNAuthTokens.ContainsKey(cdnKey) || bAborted) - return; - - if (!CDNAuthTokens.TryAdd(cdnKey, new TaskCompletionSource())) - return; + if (bAborted) + return 0; - var completed = false; - var timeoutDate = DateTime.Now.AddSeconds(10); - Action cbMethod = cdnAuth => - { - completed = true; - Console.WriteLine("Got CDN auth token for {0} result: {1} (expires {2})", host, cdnAuth.Result, cdnAuth.Expiration); + var requestCode = await steamContent.GetManifestRequestCode(depotId, appId, manifestId, branch); - if (cdnAuth.Result != EResult.OK) - { - Abort(); - return; - } - - CDNAuthTokens[cdnKey].TrySetResult(cdnAuth); - }; + Console.WriteLine("Got manifest request code for {0} {1} result: {2}", + depotId, manifestId, + requestCode); - WaitUntilCallback(() => - { - callbacks.Subscribe(steamApps.GetCDNAuthToken(appid, depotid, host), cbMethod); - }, () => { return completed || DateTime.Now >= timeoutDate; }); + return requestCode; } public void CheckAppBetaPassword(uint appid, string password)