mirror of
https://github.com/SteamRE/DepotDownloader.git
synced 2026-10-20 01:46:17 +00:00
Convert to .NET Core.
Works on macOS, haven't tried Windows / .NET Framework yet.
This commit is contained in:
1 parent
35aa666ea5
commit
c0b6d0ce3c
6 files changed
+281
-422
No files matched your search
@@ -25,7 +25,7 @@ namespace DepotDownloader
|
||||
private BlockingCollection<CDNClient.Server> availableServerEndpoints;
|
||||
|
||||
private AutoResetEvent populatePoolEvent;
|
||||
private Thread monitorThread;
|
||||
private Task monitorTask;
|
||||
|
||||
public CDNClientPool(Steam3Session steamSession)
|
||||
{
|
||||
@@ -37,13 +37,10 @@ namespace DepotDownloader
|
||||
|
||||
populatePoolEvent = new AutoResetEvent(true);
|
||||
|
||||
monitorThread = new Thread(ConnectionPoolMonitor);
|
||||
monitorThread.Name = "CDNClient Pool Monitor";
|
||||
monitorThread.IsBackground = true;
|
||||
monitorThread.Start();
|
||||
monitorTask = Task.Factory.StartNew(ConnectionPoolMonitorAsync).Unwrap();
|
||||
}
|
||||
|
||||
private List<CDNClient.Server> FetchBootstrapServerList()
|
||||
private async Task<IList<CDNClient.Server>> FetchBootstrapServerListAsync()
|
||||
{
|
||||
CDNClient bootstrap = new CDNClient(steamSession.steamClient);
|
||||
|
||||
@@ -51,7 +48,7 @@ namespace DepotDownloader
|
||||
{
|
||||
try
|
||||
{
|
||||
var cdnServers = bootstrap.FetchServerList(cellId: (uint)ContentDownloader.Config.CellID);
|
||||
var cdnServers = await bootstrap.FetchServerListAsync(cellId: (uint)ContentDownloader.Config.CellID).ConfigureAwait(false);
|
||||
if (cdnServers != null)
|
||||
{
|
||||
return cdnServers;
|
||||
@@ -64,7 +61,7 @@ namespace DepotDownloader
|
||||
}
|
||||
}
|
||||
|
||||
private void ConnectionPoolMonitor()
|
||||
private async Task ConnectionPoolMonitorAsync()
|
||||
{
|
||||
while(true)
|
||||
{
|
||||
@@ -75,7 +72,7 @@ namespace DepotDownloader
|
||||
steamSession.steamClient.IsConnected &&
|
||||
steamSession.steamClient.GetServersOfType(EServerType.CS).Count > 0)
|
||||
{
|
||||
var servers = FetchBootstrapServerList();
|
||||
var servers = await FetchBootstrapServerListAsync();
|
||||
|
||||
var weightedCdnServers = servers.Select(x =>
|
||||
{
|
||||
@@ -101,7 +98,7 @@ namespace DepotDownloader
|
||||
activeClientAuthed.TryRemove(client, out authData);
|
||||
}
|
||||
|
||||
private CDNClient BuildConnection(uint appId, uint depotId, byte[] depotKey, CDNClient.Server serverSeed, CancellationToken token)
|
||||
private async Task<CDNClient> BuildConnectionAsync(uint appId, uint depotId, byte[] depotKey, CDNClient.Server serverSeed, CancellationToken token)
|
||||
{
|
||||
CDNClient.Server server = null;
|
||||
CDNClient client = null;
|
||||
@@ -147,8 +144,8 @@ namespace DepotDownloader
|
||||
}
|
||||
}
|
||||
|
||||
client.Connect(server);
|
||||
client.AuthenticateDepot(depotId, depotKey, cdnAuthToken);
|
||||
await client.ConnectAsync(server).ConfigureAwait(false);
|
||||
await client.AuthenticateDepotAsync(depotId, depotKey, cdnAuthToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -168,7 +165,7 @@ namespace DepotDownloader
|
||||
return client;
|
||||
}
|
||||
|
||||
private bool ReauthConnection(CDNClient client, CDNClient.Server server, uint appId, uint depotId, byte[] depotKey)
|
||||
private async Task<bool> ReauthConnectionAsync(CDNClient client, CDNClient.Server server, uint appId, uint depotId, byte[] depotKey)
|
||||
{
|
||||
DebugLog.Assert(server.Type == "CDN" || steamSession.AppTickets[depotId] == null, "CDNClientPool", "Re-authing a CDN or anonymous connection");
|
||||
|
||||
@@ -193,7 +190,7 @@ namespace DepotDownloader
|
||||
}
|
||||
}
|
||||
|
||||
client.AuthenticateDepot(depotId, depotKey, cdnAuthToken);
|
||||
await client.AuthenticateDepotAsync(depotId, depotKey, cdnAuthToken).ConfigureAwait(false);
|
||||
activeClientAuthed[client] = Tuple.Create(depotId, server);
|
||||
return true;
|
||||
}
|
||||
@@ -205,7 +202,7 @@ namespace DepotDownloader
|
||||
return false;
|
||||
}
|
||||
|
||||
public CDNClient GetConnectionForDepot(uint appId, uint depotId, byte[] depotKey, CancellationToken token)
|
||||
public async Task<CDNClient> GetConnectionForDepotAsync(uint appId, uint depotId, byte[] depotKey, CancellationToken token)
|
||||
{
|
||||
CDNClient client = null;
|
||||
|
||||
@@ -216,24 +213,24 @@ namespace DepotDownloader
|
||||
// if we couldn't find a connection, make one now
|
||||
if (client == null)
|
||||
{
|
||||
client = BuildConnection(appId, depotId, depotKey, null, token);
|
||||
client = await BuildConnectionAsync(appId, depotId, depotKey, null, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// if we couldn't find the authorization data or it's not authed to this depotid, re-initialize
|
||||
if (!activeClientAuthed.TryGetValue(client, out authData) || authData.Item1 != depotId)
|
||||
{
|
||||
if (authData.Item2.Type == "CDN" && ReauthConnection(client, authData.Item2, appId, depotId, depotKey))
|
||||
if (authData.Item2.Type == "CDN" && await ReauthConnectionAsync(client, authData.Item2, appId, depotId, depotKey).ConfigureAwait(false))
|
||||
{
|
||||
Console.WriteLine("Re-authed CDN connection to content server {0} from {1} to {2}", authData.Item2, authData.Item1, depotId);
|
||||
}
|
||||
else if (authData.Item2.Type == "CS" && steamSession.AppTickets[depotId] == null && ReauthConnection(client, authData.Item2, appId, depotId, depotKey))
|
||||
else if (authData.Item2.Type == "CS" && steamSession.AppTickets[depotId] == null && await ReauthConnectionAsync(client, authData.Item2, appId, depotId, depotKey).ConfigureAwait(false))
|
||||
{
|
||||
Console.WriteLine("Re-authed anonymous connection to content server {0} from {1} to {2}", authData.Item2, authData.Item1, depotId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReleaseConnection(client);
|
||||
client = BuildConnection(appId, depotId, depotKey, authData.Item2, token);
|
||||
client = await BuildConnectionAsync(appId, depotId, depotKey, authData.Item2, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -238,62 +238,62 @@ namespace DepotDownloader
|
||||
if (branch != "Public" && node == KeyValue.Invalid)
|
||||
{
|
||||
var node_encrypted = manifests_encrypted[branch];
|
||||
if (node_encrypted != KeyValue.Invalid)
|
||||
{
|
||||
string password = Config.BetaPassword;
|
||||
if (password == null)
|
||||
{
|
||||
Console.Write("Please enter the password for branch {0}: ", branch);
|
||||
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)
|
||||
{
|
||||
byte[] input = Util.DecodeHexString(encrypted_v1.Value);
|
||||
byte[] manifest_bytes = CryptoHelper.VerifyAndDecryptPassword(input, password);
|
||||
|
||||
if (manifest_bytes == null)
|
||||
{
|
||||
Console.WriteLine("Password was invalid for branch {0}", branch);
|
||||
return INVALID_MANIFEST_ID;
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt64(manifest_bytes, 0);
|
||||
}
|
||||
else if (encrypted_v2 != KeyValue.Invalid)
|
||||
{
|
||||
// Submit the password to Steam now to get encryption keys
|
||||
steam3.CheckAppBetaPassword(appId, Config.BetaPassword);
|
||||
|
||||
if (!steam3.AppBetaPasswords.ContainsKey(branch))
|
||||
{
|
||||
Console.WriteLine("Password was invalid for branch {0}", branch);
|
||||
return INVALID_MANIFEST_ID;
|
||||
}
|
||||
|
||||
byte[] input = Util.DecodeHexString(encrypted_v2.Value);
|
||||
byte[] manifest_bytes;
|
||||
try
|
||||
{
|
||||
manifest_bytes = CryptoHelper.SymmetricDecryptECB(input, steam3.AppBetaPasswords[branch]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Failed to decrypt branch {0}: {1}", branch, e.Message);
|
||||
return INVALID_MANIFEST_ID;
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt64(manifest_bytes, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Unhandled depot encryption for depotId {0}", depotId);
|
||||
return INVALID_MANIFEST_ID;
|
||||
}
|
||||
|
||||
if (node_encrypted != KeyValue.Invalid)
|
||||
{
|
||||
string password = Config.BetaPassword;
|
||||
if (password == null)
|
||||
{
|
||||
Console.Write("Please enter the password for branch {0}: ", branch);
|
||||
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)
|
||||
{
|
||||
byte[] input = Util.DecodeHexString(encrypted_v1.Value);
|
||||
byte[] manifest_bytes = CryptoHelper.VerifyAndDecryptPassword(input, password);
|
||||
|
||||
if (manifest_bytes == null)
|
||||
{
|
||||
Console.WriteLine("Password was invalid for branch {0}", branch);
|
||||
return INVALID_MANIFEST_ID;
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt64(manifest_bytes, 0);
|
||||
}
|
||||
else if (encrypted_v2 != KeyValue.Invalid)
|
||||
{
|
||||
// Submit the password to Steam now to get encryption keys
|
||||
steam3.CheckAppBetaPassword(appId, Config.BetaPassword);
|
||||
|
||||
if (!steam3.AppBetaPasswords.ContainsKey(branch))
|
||||
{
|
||||
Console.WriteLine("Password was invalid for branch {0}", branch);
|
||||
return INVALID_MANIFEST_ID;
|
||||
}
|
||||
|
||||
byte[] input = Util.DecodeHexString(encrypted_v2.Value);
|
||||
byte[] manifest_bytes;
|
||||
try
|
||||
{
|
||||
manifest_bytes = CryptoHelper.SymmetricDecryptECB(input, steam3.AppBetaPasswords[branch]);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Failed to decrypt branch {0}: {1}", branch, e.Message);
|
||||
return INVALID_MANIFEST_ID;
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt64(manifest_bytes, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Unhandled depot encryption for depotId {0}", depotId);
|
||||
return INVALID_MANIFEST_ID;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return INVALID_MANIFEST_ID;
|
||||
@@ -362,22 +362,22 @@ namespace DepotDownloader
|
||||
steam3.Disconnect();
|
||||
}
|
||||
|
||||
public static void DownloadApp(uint appId, uint depotId, string branch, bool forceDepot = false)
|
||||
public static async Task DownloadAppAsync(uint appId, uint depotId, string branch, bool forceDepot = false)
|
||||
{
|
||||
if(steam3 != null)
|
||||
steam3.RequestAppInfo(appId);
|
||||
|
||||
if (!AccountHasAccess(appId))
|
||||
{
|
||||
if (steam3.RequestFreeAppLicense(appId))
|
||||
{
|
||||
Console.WriteLine("Obtained FreeOnDemand license for app {0}", appId);
|
||||
if (steam3.RequestFreeAppLicense(appId))
|
||||
{
|
||||
Console.WriteLine("Obtained FreeOnDemand license for app {0}", appId);
|
||||
}
|
||||
else
|
||||
{
|
||||
string contentName = GetAppOrDepotName(INVALID_DEPOT_ID, appId);
|
||||
Console.WriteLine("App {0} ({1}) is not available from this account.", appId, contentName);
|
||||
return;
|
||||
else
|
||||
{
|
||||
string contentName = GetAppOrDepotName(INVALID_DEPOT_ID, appId);
|
||||
Console.WriteLine("App {0} ({1}) is not available from this account.", appId, contentName);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ namespace DepotDownloader
|
||||
|
||||
try
|
||||
{
|
||||
DownloadSteam3(appId, infos);
|
||||
await DownloadSteam3Async(appId, infos).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -524,7 +524,7 @@ namespace DepotDownloader
|
||||
public ProtoManifest.ChunkData NewChunk { get; private set; }
|
||||
}
|
||||
|
||||
private static void DownloadSteam3( uint appId, List<DepotDownloadInfo> depots )
|
||||
private static async Task DownloadSteam3Async( uint appId, List<DepotDownloadInfo> depots )
|
||||
{
|
||||
ulong TotalBytesCompressed = 0;
|
||||
ulong TotalBytesUncompressed = 0;
|
||||
@@ -583,9 +583,9 @@ namespace DepotDownloader
|
||||
{
|
||||
CDNClient client = null;
|
||||
try {
|
||||
client = cdnPool.GetConnectionForDepot(appId, depot.id, depot.depotKey, CancellationToken.None);
|
||||
client = await cdnPool.GetConnectionForDepotAsync(appId, depot.id, depot.depotKey, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
depotManifest = client.DownloadManifest(depot.id, depot.manifestId);
|
||||
depotManifest = await client.DownloadManifestAsync(depot.id, depot.manifestId).ConfigureAwait(false);
|
||||
|
||||
cdnPool.ReturnConnection(client);
|
||||
}
|
||||
@@ -677,200 +677,210 @@ namespace DepotDownloader
|
||||
}
|
||||
});
|
||||
|
||||
var semaphore = new SemaphoreSlim(Config.MaxDownloads);
|
||||
filesAfterExclusions.Where(f => !f.Flags.HasFlag(EDepotFileFlag.Directory))
|
||||
.AsParallel().WithCancellation(cts.Token).WithDegreeOfParallelism(Config.MaxDownloads)
|
||||
.ForAll(file =>
|
||||
.ForAll(async file =>
|
||||
{
|
||||
string fileFinalPath = Path.Combine(depot.installDir, file.FileName);
|
||||
string fileStagingPath = Path.Combine(stagingDir, file.FileName);
|
||||
try
|
||||
{
|
||||
await semaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
// This may still exist if the previous run exited before cleanup
|
||||
if (File.Exists(fileStagingPath))
|
||||
{
|
||||
File.Delete(fileStagingPath);
|
||||
}
|
||||
string fileFinalPath = Path.Combine(depot.installDir, file.FileName);
|
||||
string fileStagingPath = Path.Combine(stagingDir, file.FileName);
|
||||
|
||||
FileStream fs = null;
|
||||
List<ProtoManifest.ChunkData> neededChunks;
|
||||
FileInfo fi = new FileInfo(fileFinalPath);
|
||||
if (!fi.Exists)
|
||||
{
|
||||
// create new file. need all chunks
|
||||
fs = File.Create(fileFinalPath);
|
||||
fs.SetLength((long)file.TotalSize);
|
||||
neededChunks = new List<ProtoManifest.ChunkData>(file.Chunks);
|
||||
}
|
||||
else
|
||||
{
|
||||
// open existing
|
||||
ProtoManifest.FileData oldManifestFile = null;
|
||||
if (oldProtoManifest != null)
|
||||
// This may still exist if the previous run exited before cleanup
|
||||
if (File.Exists(fileStagingPath))
|
||||
{
|
||||
oldManifestFile = oldProtoManifest.Files.SingleOrDefault(f => f.FileName == file.FileName);
|
||||
File.Delete(fileStagingPath);
|
||||
}
|
||||
|
||||
if (oldManifestFile != null)
|
||||
FileStream fs = null;
|
||||
List<ProtoManifest.ChunkData> neededChunks;
|
||||
FileInfo fi = new FileInfo(fileFinalPath);
|
||||
if (!fi.Exists)
|
||||
{
|
||||
neededChunks = new List<ProtoManifest.ChunkData>();
|
||||
|
||||
if (Config.VerifyAll || !oldManifestFile.FileHash.SequenceEqual(file.FileHash))
|
||||
// create new file. need all chunks
|
||||
fs = File.Create(fileFinalPath);
|
||||
fs.SetLength((long)file.TotalSize);
|
||||
neededChunks = new List<ProtoManifest.ChunkData>(file.Chunks);
|
||||
}
|
||||
else
|
||||
{
|
||||
// open existing
|
||||
ProtoManifest.FileData oldManifestFile = null;
|
||||
if (oldProtoManifest != null)
|
||||
{
|
||||
// we have a version of this file, but it doesn't fully match what we want
|
||||
oldManifestFile = oldProtoManifest.Files.SingleOrDefault(f => f.FileName == file.FileName);
|
||||
}
|
||||
|
||||
var matchingChunks = new List<ChunkMatch>();
|
||||
if (oldManifestFile != null)
|
||||
{
|
||||
neededChunks = new List<ProtoManifest.ChunkData>();
|
||||
|
||||
foreach (var chunk in file.Chunks)
|
||||
if (Config.VerifyAll || !oldManifestFile.FileHash.SequenceEqual(file.FileHash))
|
||||
{
|
||||
var oldChunk = oldManifestFile.Chunks.FirstOrDefault(c => c.ChunkID.SequenceEqual(chunk.ChunkID));
|
||||
if (oldChunk != null)
|
||||
// we have a version of this file, but it doesn't fully match what we want
|
||||
|
||||
var matchingChunks = new List<ChunkMatch>();
|
||||
|
||||
foreach (var chunk in file.Chunks)
|
||||
{
|
||||
matchingChunks.Add(new ChunkMatch(oldChunk, chunk));
|
||||
}
|
||||
else
|
||||
{
|
||||
neededChunks.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
File.Move(fileFinalPath, fileStagingPath);
|
||||
|
||||
fs = File.Open(fileFinalPath, FileMode.Create);
|
||||
fs.SetLength((long)file.TotalSize);
|
||||
|
||||
using (var fsOld = File.Open(fileStagingPath, FileMode.Open))
|
||||
{
|
||||
foreach (var match in matchingChunks)
|
||||
{
|
||||
fsOld.Seek((long)match.OldChunk.Offset, SeekOrigin.Begin);
|
||||
|
||||
byte[] tmp = new byte[match.OldChunk.UncompressedLength];
|
||||
fsOld.Read(tmp, 0, tmp.Length);
|
||||
|
||||
byte[] adler = Util.AdlerHash(tmp);
|
||||
if (!adler.SequenceEqual(match.OldChunk.Checksum))
|
||||
var oldChunk = oldManifestFile.Chunks.FirstOrDefault(c => c.ChunkID.SequenceEqual(chunk.ChunkID));
|
||||
if (oldChunk != null)
|
||||
{
|
||||
neededChunks.Add(match.NewChunk);
|
||||
matchingChunks.Add(new ChunkMatch(oldChunk, chunk));
|
||||
}
|
||||
else
|
||||
{
|
||||
fs.Seek((long)match.NewChunk.Offset, SeekOrigin.Begin);
|
||||
fs.Write(tmp, 0, tmp.Length);
|
||||
neededChunks.Add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
File.Move(fileFinalPath, fileStagingPath);
|
||||
|
||||
fs = File.Open(fileFinalPath, FileMode.Create);
|
||||
fs.SetLength((long)file.TotalSize);
|
||||
|
||||
using (var fsOld = File.Open(fileStagingPath, FileMode.Open))
|
||||
{
|
||||
foreach (var match in matchingChunks)
|
||||
{
|
||||
fsOld.Seek((long)match.OldChunk.Offset, SeekOrigin.Begin);
|
||||
|
||||
byte[] tmp = new byte[match.OldChunk.UncompressedLength];
|
||||
fsOld.Read(tmp, 0, tmp.Length);
|
||||
|
||||
byte[] adler = Util.AdlerHash(tmp);
|
||||
if (!adler.SequenceEqual(match.OldChunk.Checksum))
|
||||
{
|
||||
neededChunks.Add(match.NewChunk);
|
||||
}
|
||||
else
|
||||
{
|
||||
fs.Seek((long)match.NewChunk.Offset, SeekOrigin.Begin);
|
||||
fs.Write(tmp, 0, tmp.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File.Delete(fileStagingPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No old manifest or file not in old manifest. We must validate.
|
||||
|
||||
fs = File.Open(fileFinalPath, FileMode.Open);
|
||||
if ((ulong)fi.Length != file.TotalSize)
|
||||
{
|
||||
fs.SetLength((long)file.TotalSize);
|
||||
}
|
||||
|
||||
File.Delete(fileStagingPath);
|
||||
neededChunks = Util.ValidateSteam3FileChecksums(fs, file.Chunks.OrderBy(x => x.Offset).ToArray());
|
||||
}
|
||||
|
||||
if (neededChunks.Count() == 0)
|
||||
{
|
||||
size_downloaded += file.TotalSize;
|
||||
Console.WriteLine("{0,6:#00.00}% {1}", ((float)size_downloaded / (float)complete_download_size) * 100.0f, fileFinalPath);
|
||||
if (fs != null)
|
||||
fs.Dispose();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_downloaded += (file.TotalSize - (ulong)neededChunks.Select(x => (long)x.UncompressedLength).Sum());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
foreach (var chunk in neededChunks)
|
||||
{
|
||||
// No old manifest or file not in old manifest. We must validate.
|
||||
if (cts.IsCancellationRequested) break;
|
||||
|
||||
fs = File.Open(fileFinalPath, FileMode.Open);
|
||||
if ((ulong)fi.Length != file.TotalSize)
|
||||
string chunkID = Util.EncodeHexString(chunk.ChunkID);
|
||||
CDNClient.DepotChunk chunkData = null;
|
||||
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
fs.SetLength((long)file.TotalSize);
|
||||
}
|
||||
|
||||
neededChunks = Util.ValidateSteam3FileChecksums(fs, file.Chunks.OrderBy(x => x.Offset).ToArray());
|
||||
}
|
||||
|
||||
if (neededChunks.Count() == 0)
|
||||
{
|
||||
size_downloaded += file.TotalSize;
|
||||
Console.WriteLine("{0,6:#00.00}% {1}", ((float)size_downloaded / (float)complete_download_size) * 100.0f, fileFinalPath);
|
||||
if (fs != null)
|
||||
fs.Close();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_downloaded += (file.TotalSize - (ulong)neededChunks.Select(x => (long)x.UncompressedLength).Sum());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var chunk in neededChunks)
|
||||
{
|
||||
if (cts.IsCancellationRequested) break;
|
||||
|
||||
string chunkID = Util.EncodeHexString(chunk.ChunkID);
|
||||
CDNClient.DepotChunk chunkData = null;
|
||||
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
CDNClient client;
|
||||
try
|
||||
{
|
||||
client = cdnPool.GetConnectionForDepot(appId, depot.id, depot.depotKey, cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
DepotManifest.ChunkData data = new DepotManifest.ChunkData();
|
||||
data.ChunkID = chunk.ChunkID;
|
||||
data.Checksum = chunk.Checksum;
|
||||
data.Offset = chunk.Offset;
|
||||
data.CompressedLength = chunk.CompressedLength;
|
||||
data.UncompressedLength = chunk.UncompressedLength;
|
||||
|
||||
try
|
||||
{
|
||||
chunkData = client.DownloadDepotChunk(depot.id, data);
|
||||
cdnPool.ReturnConnection(client);
|
||||
break;
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
cdnPool.ReturnBrokenConnection(client);
|
||||
|
||||
if (e.Status == WebExceptionStatus.ProtocolError)
|
||||
CDNClient client;
|
||||
try
|
||||
{
|
||||
var response = e.Response as HttpWebResponse;
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
|
||||
client = await cdnPool.GetConnectionForDepotAsync(appId, depot.id, depot.depotKey, cts.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
DepotManifest.ChunkData data = new DepotManifest.ChunkData();
|
||||
data.ChunkID = chunk.ChunkID;
|
||||
data.Checksum = chunk.Checksum;
|
||||
data.Offset = chunk.Offset;
|
||||
data.CompressedLength = chunk.CompressedLength;
|
||||
data.UncompressedLength = chunk.UncompressedLength;
|
||||
|
||||
try
|
||||
{
|
||||
chunkData = await client.DownloadDepotChunkAsync(depot.id, data).ConfigureAwait(false);
|
||||
cdnPool.ReturnConnection(client);
|
||||
break;
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
cdnPool.ReturnBrokenConnection(client);
|
||||
|
||||
if (e.Status == WebExceptionStatus.ProtocolError)
|
||||
{
|
||||
Console.WriteLine("Encountered 401 for chunk {0}. Aborting.", chunkID);
|
||||
cts.Cancel();
|
||||
break;
|
||||
var response = e.Response as HttpWebResponse;
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
Console.WriteLine("Encountered 401 for chunk {0}. Aborting.", chunkID);
|
||||
cts.Cancel();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Encountered error downloading chunk {0}: {1}", chunkID, response.StatusCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Encountered error downloading chunk {0}: {1}", chunkID, response.StatusCode);
|
||||
Console.WriteLine("Encountered error downloading chunk {0}: {1}", chunkID, e.Status);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("Encountered error downloading chunk {0}: {1}", chunkID, e.Status);
|
||||
cdnPool.ReturnBrokenConnection(client);
|
||||
Console.WriteLine("Encountered unexpected error downloading chunk {0}: {1}", chunkID, e.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
if (chunkData == null)
|
||||
{
|
||||
cdnPool.ReturnBrokenConnection(client);
|
||||
Console.WriteLine("Encountered unexpected error downloading chunk {0}: {1}", chunkID, e.Message);
|
||||
Console.WriteLine("Failed to find any server with chunk {0} for depot {1}. Aborting.", chunkID, depot.id);
|
||||
return;
|
||||
}
|
||||
|
||||
TotalBytesCompressed += chunk.CompressedLength;
|
||||
DepotBytesCompressed += chunk.CompressedLength;
|
||||
TotalBytesUncompressed += chunk.UncompressedLength;
|
||||
DepotBytesUncompressed += chunk.UncompressedLength;
|
||||
|
||||
fs.Seek((long)chunk.Offset, SeekOrigin.Begin);
|
||||
fs.Write(chunkData.Data, 0, chunkData.Data.Length);
|
||||
|
||||
size_downloaded += chunk.UncompressedLength;
|
||||
}
|
||||
|
||||
if (chunkData == null)
|
||||
{
|
||||
Console.WriteLine("Failed to find any server with chunk {0} for depot {1}. Aborting.", chunkID, depot.id);
|
||||
return;
|
||||
}
|
||||
fs.Dispose();
|
||||
|
||||
TotalBytesCompressed += chunk.CompressedLength;
|
||||
DepotBytesCompressed += chunk.CompressedLength;
|
||||
TotalBytesUncompressed += chunk.UncompressedLength;
|
||||
DepotBytesUncompressed += chunk.UncompressedLength;
|
||||
|
||||
fs.Seek((long)chunk.Offset, SeekOrigin.Begin);
|
||||
fs.Write(chunkData.Data, 0, chunkData.Data.Length);
|
||||
|
||||
size_downloaded += chunk.UncompressedLength;
|
||||
Console.WriteLine("{0,6:#00.00}% {1}", ((float)size_downloaded / (float)complete_download_size) * 100.0f, fileFinalPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
|
||||
fs.Close();
|
||||
|
||||
Console.WriteLine("{0,6:#00.00}% {1}", ((float)size_downloaded / (float)complete_download_size) * 100.0f, fileFinalPath);
|
||||
});
|
||||
|
||||
ConfigStore.TheConfig.LastManifests[depot.id] = depot.manifestId;
|
||||
|
||||
@@ -1,124 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>9.0.30729</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{39159C47-ACD3-449F-96CA-4F30C8ED147A}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>DepotDownloader</RootNamespace>
|
||||
<AssemblyName>DepotDownloader</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<FileUpgradeFlags>
|
||||
</FileUpgradeFlags>
|
||||
<OldToolsVersion>3.5</OldToolsVersion>
|
||||
<UpgradeBackupLocation />
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
<TargetFrameworkProfile />
|
||||
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
|
||||
<RestorePackages>true</RestorePackages>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="protobuf-net, Version=2.0.0.668, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
|
||||
<SpecificVersion>True</SpecificVersion>
|
||||
<HintPath>..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SteamKit2, Version=1.8.1.0, Culture=neutral, PublicKeyToken=ed3ce47ed5aad940, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SteamKit2.1.8.1\lib\net45\SteamKit2.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.DataSetExtensions">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CDNClientPool.cs" />
|
||||
<Compile Include="ContentDownloader.cs" />
|
||||
<Compile Include="ConfigStore.cs" />
|
||||
<Compile Include="DownloadConfig.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ProtoManifest.cs" />
|
||||
<Compile Include="Steam3Session.cs" />
|
||||
<Compile Include="Util.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Windows Installer 3.1</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>netcoreapp1.1;net46</TargetFrameworks>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\SteamKit\SteamKit2\SteamKit2\SteamKit2.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="protobuf-net" Version="2.1.0" />
|
||||
<PackageReference Include="system.componentmodel.typeconverter" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -4,12 +4,16 @@ using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using SteamKit2;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DepotDownloader
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void Main( string[] args )
|
||||
=> MainAsync(args).GetAwaiter().GetResult();
|
||||
|
||||
static async Task MainAsync( string[] args )
|
||||
{
|
||||
if ( args.Length == 0 )
|
||||
{
|
||||
@@ -19,7 +23,7 @@ namespace DepotDownloader
|
||||
|
||||
DebugLog.Enabled = false;
|
||||
|
||||
ConfigStore.LoadFromFile(Path.Combine(Environment.CurrentDirectory, "DepotDownloader.config"));
|
||||
ConfigStore.LoadFromFile(Path.Combine(Directory.GetCurrentDirectory(), "DepotDownloader.config"));
|
||||
|
||||
bool bDumpManifest = HasParameter( args, "-manifest-only" );
|
||||
uint appId = GetParameter<uint>( args, "-app", ContentDownloader.INVALID_APP_ID );
|
||||
@@ -110,7 +114,7 @@ namespace DepotDownloader
|
||||
|
||||
if (ContentDownloader.InitializeSteam3(username, password))
|
||||
{
|
||||
ContentDownloader.DownloadApp(appId, depotId, branch, forceDepot);
|
||||
await ContentDownloader.DownloadAppAsync(appId, depotId, branch, forceDepot).ConfigureAwait(false);
|
||||
ContentDownloader.ShutdownSteam3();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,4 +31,4 @@ using System.Runtime.InteropServices;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("2.2.1.*")]
|
||||
[assembly: AssemblyVersion("2.2.2.0")]
|
||||
+15
-57
@@ -11,60 +11,19 @@ namespace DepotDownloader
|
||||
{
|
||||
static class Util
|
||||
{
|
||||
[DllImport( "libc" )]
|
||||
static extern int uname( IntPtr buf );
|
||||
|
||||
static int _isMacOSX = -1;
|
||||
|
||||
// Environment.OSVersion.Platform returns PlatformID.Unix under Mono on OS X
|
||||
// Code adapted from Mono: mcs/class/Managed.Windows.Forms/System.Windows.Forms/XplatUI.cs
|
||||
private static bool IsMacOSX()
|
||||
{
|
||||
if ( _isMacOSX != -1 )
|
||||
return _isMacOSX == 1;
|
||||
|
||||
IntPtr buf = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
// The size of the utsname struct varies from system to system, but this _seems_ more than enough
|
||||
buf = Marshal.AllocHGlobal( 4096 );
|
||||
|
||||
if ( uname( buf ) == 0 )
|
||||
{
|
||||
string sys = Marshal.PtrToStringAnsi( buf );
|
||||
if ( sys == "Darwin" )
|
||||
{
|
||||
_isMacOSX = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Do nothing?
|
||||
}
|
||||
finally
|
||||
{
|
||||
if ( buf != IntPtr.Zero )
|
||||
Marshal.FreeHGlobal( buf );
|
||||
}
|
||||
|
||||
_isMacOSX = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string GetSteamOS()
|
||||
{
|
||||
switch (Environment.OSVersion.Platform)
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
case PlatformID.Win32NT:
|
||||
case PlatformID.Win32Windows:
|
||||
return "windows";
|
||||
case PlatformID.MacOSX:
|
||||
return "macos";
|
||||
case PlatformID.Unix:
|
||||
return IsMacOSX() ? "macos" : "linux";
|
||||
return "windows";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
return "macos";
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return "linux";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
@@ -141,13 +100,12 @@ namespace DepotDownloader
|
||||
|
||||
public static byte[] SHAHash( byte[] input )
|
||||
{
|
||||
SHA1Managed sha = new SHA1Managed();
|
||||
using (var sha = SHA1.Create())
|
||||
{
|
||||
var output = sha.ComputeHash( input );
|
||||
|
||||
byte[] output = sha.ComputeHash( input );
|
||||
|
||||
sha.Clear();
|
||||
|
||||
return output;
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] DecodeHexString( string hex )
|
||||
|
||||
Reference in new issue
Block a user