PageRenderTime 54ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/Projects/DepotDownloader/DepotDownloader/ContentDownloader.cs

https://bitbucket.org/VoiDeD/steamre/
C# | 1157 lines | 923 code | 219 blank | 15 comment | 272 complexity | 09af1748f76e02f25c36d99e1564b42f MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, Apache-2.0, BSD-3-Clause
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Net.Sockets;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Threading;
  10. using SteamKit2;
  11. namespace DepotDownloader
  12. {
  13. static class ContentDownloader
  14. {
  15. const string DEFAULT_DIR = "depots";
  16. const int MAX_STORAGE_RETRIES = 500;
  17. const int MAX_CONNECT_RETRIES = 10;
  18. const int NUM_STEAM3_CONNECTIONS = 4;
  19. const int STEAM2_CONNECT_TIMEOUT_SECONDS = 5;
  20. public static DownloadConfig Config = new DownloadConfig();
  21. private static Steam3Session steam3;
  22. private static Steam3Session.Credentials steam3Credentials;
  23. private static IPEndPoint lastSteam2ContentServer;
  24. private abstract class IDepotDownloadInfo
  25. {
  26. public int id { get; protected set; }
  27. public string installDir { get; protected set; }
  28. public string contentName { get; protected set; }
  29. public abstract DownloadSource GetDownloadType();
  30. }
  31. private sealed class DepotDownloadInfo2 : IDepotDownloadInfo
  32. {
  33. public int version { get; private set; }
  34. public IPEndPoint[] contentServers = null;
  35. public Steam2Manifest manifest = null;
  36. public List<int> NodesToDownload = new List<int>();
  37. public byte[] cryptKey = null;
  38. public override DownloadSource GetDownloadType() { return DownloadSource.Steam2; }
  39. public DepotDownloadInfo2(int id, int version, string installDir, string contentName)
  40. {
  41. this.id = id;
  42. this.version = version;
  43. this.installDir = installDir;
  44. this.contentName = contentName;
  45. }
  46. }
  47. private sealed class DepotDownloadInfo3 : IDepotDownloadInfo
  48. {
  49. public ulong manifestId { get; private set; }
  50. public byte[] depotKey;
  51. public override DownloadSource GetDownloadType() { return DownloadSource.Steam3; }
  52. public DepotDownloadInfo3(int depotid, ulong manifestId, string installDir, string contentName)
  53. {
  54. this.id = depotid;
  55. this.manifestId = manifestId;
  56. this.installDir = installDir;
  57. this.contentName = contentName;
  58. }
  59. }
  60. static bool CreateDirectories( int depotId, uint depotVersion, DownloadSource source, out string installDir )
  61. {
  62. installDir = null;
  63. try
  64. {
  65. if (ContentDownloader.Config.InstallDirectory == null || ContentDownloader.Config.InstallDirectory == "")
  66. {
  67. Directory.CreateDirectory( DEFAULT_DIR );
  68. string depotPath = Path.Combine( DEFAULT_DIR, depotId.ToString() );
  69. Directory.CreateDirectory( depotPath );
  70. installDir = Path.Combine(depotPath, depotVersion.ToString());
  71. Directory.CreateDirectory(installDir);
  72. }
  73. else
  74. {
  75. Directory.CreateDirectory(ContentDownloader.Config.InstallDirectory);
  76. installDir = ContentDownloader.Config.InstallDirectory;
  77. if (source == DownloadSource.Steam2)
  78. {
  79. string serverFolder = CDRManager.GetDedicatedServerFolder(depotId);
  80. if (serverFolder != null && serverFolder != "")
  81. {
  82. installDir = Path.Combine(ContentDownloader.Config.InstallDirectory, serverFolder);
  83. Directory.CreateDirectory(installDir);
  84. }
  85. }
  86. }
  87. }
  88. catch
  89. {
  90. return false;
  91. }
  92. return true;
  93. }
  94. static string[] GetExcludeList( ContentServerClient.StorageSession session, Steam2Manifest manifest )
  95. {
  96. string[] excludeList = null;
  97. for ( int x = 0 ; x < manifest.Nodes.Count ; ++x )
  98. {
  99. var dirEntry = manifest.Nodes[ x ];
  100. if ( dirEntry.Name == "exclude.lst" &&
  101. dirEntry.FullName.StartsWith( "reslists" + Path.DirectorySeparatorChar ) &&
  102. ( dirEntry.Attributes & Steam2Manifest.Node.Attribs.EncryptedFile ) == 0 )
  103. {
  104. string excludeFile = Encoding.UTF8.GetString( session.DownloadFile( dirEntry ) );
  105. if ( Environment.OSVersion.Platform == PlatformID.Win32NT )
  106. excludeFile = excludeFile.Replace( '/', Path.DirectorySeparatorChar );
  107. else
  108. excludeFile = excludeFile.Replace( '\\', Path.DirectorySeparatorChar );
  109. excludeList = excludeFile.Split( new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries );
  110. break;
  111. }
  112. }
  113. return excludeList;
  114. }
  115. static bool IsFileExcluded( string file, string[] excludeList )
  116. {
  117. if ( excludeList == null || file.Length < 1 )
  118. return false;
  119. foreach ( string e in excludeList )
  120. {
  121. int wildPos = e.IndexOf( "*" );
  122. if ( wildPos == -1 )
  123. {
  124. if ( file.StartsWith( e ) )
  125. return true;
  126. continue;
  127. }
  128. if ( wildPos == 0 )
  129. {
  130. if ( e.Length == 1 || file.EndsWith( e.Substring( 1 ) ) )
  131. return true;
  132. continue;
  133. }
  134. string start = e.Substring( 0, wildPos );
  135. string end = e.Substring( wildPos + 1, e.Length - wildPos - 1 );
  136. if ( file.StartsWith( start ) && file.EndsWith( end ) )
  137. return true;
  138. }
  139. return false;
  140. }
  141. static bool TestIsFileIncluded(string filename)
  142. {
  143. if (!Config.UsingFileList)
  144. return true;
  145. foreach (string fileListEntry in Config.FilesToDownload)
  146. {
  147. if (fileListEntry.Equals(filename, StringComparison.OrdinalIgnoreCase))
  148. return true;
  149. }
  150. foreach (Regex rgx in Config.FilesToDownloadRegex)
  151. {
  152. Match m = rgx.Match(filename);
  153. if (m.Success)
  154. return true;
  155. }
  156. return false;
  157. }
  158. static bool AccountHasAccess( int depotId, bool appId=false )
  159. {
  160. if ( steam3 == null || (steam3.Licenses == null && steam3.steamUser.SteamID.AccountType != EAccountType.AnonUser) )
  161. return CDRManager.SubHasDepot( 0, depotId );
  162. IEnumerable<uint> licenseQuery;
  163. if ( steam3.steamUser.SteamID.AccountType == EAccountType.AnonUser )
  164. {
  165. licenseQuery = new List<uint>() { 17906 };
  166. }
  167. else
  168. {
  169. licenseQuery = steam3.Licenses.Select( x => x.PackageID );
  170. }
  171. steam3.RequestPackageInfo( licenseQuery );
  172. foreach ( var license in licenseQuery )
  173. {
  174. SteamApps.PICSProductInfoCallback.PICSProductInfo package;
  175. if ( steam3.PackageInfo.TryGetValue( license, out package ) || package == null )
  176. {
  177. KeyValue root = package.KeyValues[license.ToString()];
  178. KeyValue subset = (appId == true ? root["appids"] : root["depotids"]);
  179. foreach ( var child in subset.Children )
  180. {
  181. if ( child.AsInteger() == depotId )
  182. return true;
  183. }
  184. }
  185. if ( CDRManager.SubHasDepot( ( int )license, depotId ) )
  186. return true;
  187. }
  188. return false;
  189. }
  190. static bool AppIsSteam3(int appId)
  191. {
  192. if (steam3 == null || steam3.AppInfoOverridesCDR == null)
  193. {
  194. return false;
  195. }
  196. steam3.RequestAppInfo((uint)appId);
  197. bool app_override;
  198. if(!steam3.AppInfoOverridesCDR.TryGetValue((uint)appId, out app_override))
  199. return false;
  200. KeyValue depots = GetSteam3AppSection(appId, EAppInfoSection.Depots);
  201. foreach (KeyValue depotChild in depots.Children)
  202. {
  203. if (depotChild == null)
  204. return false;
  205. int id;
  206. if (!int.TryParse(depotChild.Name, out id))
  207. continue;
  208. var nodes = depotChild["manifests"].Children;
  209. var nodes_encrypted = depotChild["encryptedmanifests"].Children;
  210. if (nodes.Count == 0 && nodes_encrypted.Count == 0)
  211. return false;
  212. }
  213. return true;
  214. }
  215. internal static KeyValue GetSteam3AppSection( int appId, EAppInfoSection section )
  216. {
  217. if (steam3 == null || steam3.AppInfo == null)
  218. {
  219. return null;
  220. }
  221. SteamApps.PICSProductInfoCallback.PICSProductInfo app;
  222. if ( !steam3.AppInfo.TryGetValue( (uint)appId, out app ) || app == null )
  223. {
  224. return null;
  225. }
  226. KeyValue appinfo = app.KeyValues;
  227. string section_key;
  228. switch (section)
  229. {
  230. case EAppInfoSection.Common:
  231. section_key = "common";
  232. break;
  233. case EAppInfoSection.Extended:
  234. section_key = "extended";
  235. break;
  236. case EAppInfoSection.Config:
  237. section_key = "config";
  238. break;
  239. case EAppInfoSection.Depots:
  240. section_key = "depots";
  241. break;
  242. default:
  243. throw new NotImplementedException();
  244. }
  245. KeyValue section_kv = appinfo.Children.Where(c => c.Name == section_key).FirstOrDefault();
  246. return section_kv;
  247. }
  248. enum DownloadSource
  249. {
  250. Steam2,
  251. Steam3
  252. }
  253. static DownloadSource GetAppDownloadSource(int appId)
  254. {
  255. if (appId == -1 || !AppIsSteam3(appId))
  256. return DownloadSource.Steam2;
  257. KeyValue config = GetSteam3AppSection(appId, EAppInfoSection.Config);
  258. int contenttype = config["contenttype"].AsInteger(0);
  259. // EContentDownloadSourceType?
  260. if (contenttype != 3)
  261. {
  262. Console.WriteLine("Warning: App {0} does not advertise contenttype as steam3, but has steam3 depots", appId);
  263. }
  264. return DownloadSource.Steam3;
  265. }
  266. static uint GetSteam3AppChangeNumber(int appId)
  267. {
  268. if (steam3 == null || steam3.AppInfo == null)
  269. {
  270. return 0;
  271. }
  272. SteamApps.PICSProductInfoCallback.PICSProductInfo app;
  273. if (!steam3.AppInfo.TryGetValue((uint)appId, out app) || app == null)
  274. {
  275. return 0;
  276. }
  277. return app.ChangeNumber;
  278. }
  279. static uint GetSteam3AppBuildNumber(int appId, string branch)
  280. {
  281. if (appId == -1 || !AppIsSteam3(appId))
  282. return 0;
  283. KeyValue depots = ContentDownloader.GetSteam3AppSection(appId, EAppInfoSection.Depots);
  284. KeyValue branches = depots["branches"];
  285. KeyValue node = branches[branch];
  286. if (node == null)
  287. return 0;
  288. KeyValue buildid = node["buildid"];
  289. if (buildid == null)
  290. return 0;
  291. return uint.Parse(buildid.Value);
  292. }
  293. static ulong GetSteam3DepotManifest(int depotId, int appId, string branch)
  294. {
  295. if (appId == -1 || !AppIsSteam3(appId))
  296. return 0;
  297. KeyValue depots = GetSteam3AppSection(appId, EAppInfoSection.Depots);
  298. KeyValue depotChild = depots[depotId.ToString()];
  299. if (depotChild == null)
  300. return 0;
  301. var manifests = depotChild["manifests"];
  302. var manifests_encrypted = depotChild["encryptedmanifests"];
  303. if (manifests.Children.Count == 0 && manifests_encrypted.Children.Count == 0)
  304. return 0;
  305. var node = manifests[branch];
  306. if (branch != "Public" && node == KeyValue.Invalid)
  307. {
  308. var node_encrypted = manifests_encrypted[branch];
  309. if (node_encrypted != KeyValue.Invalid)
  310. {
  311. string password = Config.BetaPassword;
  312. if (password == null)
  313. {
  314. Console.Write("Please enter the password for branch {0}: ", branch);
  315. Config.BetaPassword = password = Console.ReadLine();
  316. }
  317. byte[] input = Util.DecodeHexString(node_encrypted["encrypted_gid"].Value);
  318. byte[] manifest_bytes = CryptoHelper.VerifyAndDecryptPassword(input, password);
  319. if (manifest_bytes == null)
  320. {
  321. Console.WriteLine("Password was invalid for branch {0}", branch);
  322. return 0;
  323. }
  324. return BitConverter.ToUInt64(manifest_bytes, 0);
  325. }
  326. Console.WriteLine("Invalid branch {0} for appId {1}", branch, appId);
  327. return 0;
  328. }
  329. if (node.Value == null)
  330. return 0;
  331. return UInt64.Parse(node.Value);
  332. }
  333. static string GetAppOrDepotName(int depotId, int appId)
  334. {
  335. if (appId == -1 || !AppIsSteam3(appId))
  336. {
  337. return CDRManager.GetDepotName(depotId);
  338. }
  339. else if (depotId == -1)
  340. {
  341. KeyValue info = GetSteam3AppSection(appId, EAppInfoSection.Common);
  342. if (info == null)
  343. return String.Empty;
  344. return info["name"].AsString();
  345. }
  346. else
  347. {
  348. KeyValue depots = GetSteam3AppSection(appId, EAppInfoSection.Depots);
  349. if (depots == null)
  350. return String.Empty;
  351. KeyValue depotChild = depots[depotId.ToString()];
  352. if (depotChild == null)
  353. return String.Empty;
  354. return depotChild["name"].AsString();
  355. }
  356. }
  357. public static void InitializeSteam3(string username, string password)
  358. {
  359. steam3 = new Steam3Session(
  360. new SteamUser.LogOnDetails()
  361. {
  362. Username = username,
  363. Password = password,
  364. RequestSteam2Ticket = true,
  365. }
  366. );
  367. steam3Credentials = steam3.WaitForCredentials();
  368. if (!steam3Credentials.IsValid)
  369. {
  370. Console.WriteLine("Unable to get steam3 credentials.");
  371. return;
  372. }
  373. }
  374. public static void ShutdownSteam3()
  375. {
  376. if (steam3 == null)
  377. return;
  378. steam3.Disconnect();
  379. }
  380. private static ContentServerClient.Credentials GetSteam2Credentials(uint appId)
  381. {
  382. if (steam3 == null || !steam3Credentials.IsValid)
  383. {
  384. return null;
  385. }
  386. return new ContentServerClient.Credentials()
  387. {
  388. Steam2Ticket = steam3Credentials.Steam2Ticket,
  389. AppTicket = steam3.AppTickets[appId],
  390. SessionToken = steam3Credentials.SessionToken,
  391. };
  392. }
  393. public static void DownloadApp(int appId, int depotId, string branch, bool bListOnly=false)
  394. {
  395. if(steam3 != null)
  396. steam3.RequestAppInfo((uint)appId);
  397. if (!AccountHasAccess(appId, true))
  398. {
  399. string contentName = GetAppOrDepotName(-1, appId);
  400. Console.WriteLine("App {0} ({1}) is not available from this account.", appId, contentName);
  401. return;
  402. }
  403. List<int> depotIDs = null;
  404. if (AppIsSteam3(appId))
  405. {
  406. depotIDs = new List<int>();
  407. KeyValue depots = GetSteam3AppSection(appId, EAppInfoSection.Depots);
  408. if (depots != null)
  409. {
  410. foreach (var child in depots.Children)
  411. {
  412. int id = -1;
  413. if (int.TryParse(child.Name, out id) && child.Children.Count > 0 && (depotId == -1 || id == depotId))
  414. {
  415. depotIDs.Add(id);
  416. }
  417. }
  418. }
  419. }
  420. else
  421. {
  422. // steam2 path
  423. depotIDs = CDRManager.GetDepotIDsForApp(appId, Config.DownloadAllPlatforms);
  424. }
  425. if (depotIDs == null || (depotIDs.Count == 0 && depotId == -1))
  426. {
  427. Console.WriteLine("Couldn't find any depots to download for app {0}", appId);
  428. return;
  429. }
  430. else if (depotIDs.Count == 0)
  431. {
  432. Console.WriteLine("Depot {0} not listed for app {1}", depotId, appId);
  433. return;
  434. }
  435. if ( bListOnly )
  436. {
  437. Console.WriteLine( "\n {0} Depots:", appId );
  438. foreach ( var depot in depotIDs )
  439. {
  440. var depotName = CDRManager.GetDepotName( depot );
  441. Console.WriteLine( "{0} - {1}", depot, depotName );
  442. }
  443. return;
  444. }
  445. var infos2 = new List<DepotDownloadInfo2>();
  446. var infos3 = new List<DepotDownloadInfo3>();
  447. foreach (var depot in depotIDs)
  448. {
  449. int depotVersion = 0;
  450. if ( !AppIsSteam3( appId ) )
  451. {
  452. // Steam2 dependency
  453. depotVersion = CDRManager.GetLatestDepotVersion(depot, Config.PreferBetaVersions);
  454. if (depotVersion == -1)
  455. {
  456. Console.WriteLine("Error: Unable to find DepotID {0} in the CDR!", depot);
  457. return;
  458. }
  459. }
  460. IDepotDownloadInfo info = GetDepotInfo(depot, depotVersion, appId, branch);
  461. if (info != null)
  462. {
  463. if (info.GetDownloadType() == DownloadSource.Steam2)
  464. infos2.Add((DepotDownloadInfo2)info);
  465. else if (info.GetDownloadType() == DownloadSource.Steam3)
  466. infos3.Add((DepotDownloadInfo3)info);
  467. }
  468. }
  469. if( infos2.Count() > 0 )
  470. DownloadSteam2( infos2 );
  471. if( infos3.Count() > 0 )
  472. DownloadSteam3( infos3 );
  473. }
  474. public static void DownloadDepotsForGame(string game, string branch)
  475. {
  476. var infos2 = new List<DepotDownloadInfo2>();
  477. var infos3 = new List<DepotDownloadInfo3>();
  478. List<int> depotIDs = CDRManager.GetDepotIDsForGameserver(game, ContentDownloader.Config.DownloadAllPlatforms);
  479. foreach (var depot in depotIDs)
  480. {
  481. int depotVersion = CDRManager.GetLatestDepotVersion(depot, ContentDownloader.Config.PreferBetaVersions);
  482. if (depotVersion == -1)
  483. {
  484. Console.WriteLine("Error: Unable to find DepotID {0} in the CDR!", depot);
  485. ContentDownloader.ShutdownSteam3();
  486. continue;
  487. }
  488. IDepotDownloadInfo info = GetDepotInfo(depot, depotVersion, 0, branch);
  489. if (info.GetDownloadType() == DownloadSource.Steam2)
  490. {
  491. infos2.Add((DepotDownloadInfo2)info);
  492. }
  493. else if (info.GetDownloadType() == DownloadSource.Steam3)
  494. {
  495. infos3.Add((DepotDownloadInfo3)info);
  496. }
  497. }
  498. if (infos2.Count() > 0)
  499. DownloadSteam2(infos2);
  500. if (infos3.Count() > 0)
  501. DownloadSteam3(infos3);
  502. }
  503. public static void DownloadDepot(int depotId, int depotVersion, string branch, int appId = 0)
  504. {
  505. IDepotDownloadInfo info = GetDepotInfo(depotId, depotVersion, appId, branch);
  506. if (info.GetDownloadType() == DownloadSource.Steam2)
  507. {
  508. var infos = new List<DepotDownloadInfo2>();
  509. infos.Add((DepotDownloadInfo2)info);
  510. DownloadSteam2(infos);
  511. }
  512. else if (info.GetDownloadType() == DownloadSource.Steam3)
  513. {
  514. var infos = new List<DepotDownloadInfo3>();
  515. infos.Add((DepotDownloadInfo3)info);
  516. DownloadSteam3(infos);
  517. }
  518. }
  519. static IDepotDownloadInfo GetDepotInfo(int depotId, int depotVersion, int appId, string branch)
  520. {
  521. if(steam3 != null && appId > 0)
  522. steam3.RequestAppInfo((uint)appId);
  523. string contentName = GetAppOrDepotName(depotId, appId);
  524. if (!AccountHasAccess(depotId, false))
  525. {
  526. Console.WriteLine("Depot {0} ({1}) is not available from this account.", depotId, contentName);
  527. return null;
  528. }
  529. DownloadSource source = GetAppDownloadSource(appId);
  530. uint uVersion = (uint)depotVersion;
  531. if (source == DownloadSource.Steam3)
  532. {
  533. uVersion = GetSteam3AppBuildNumber(appId, branch);
  534. }
  535. string installDir;
  536. if (!CreateDirectories(depotId, uVersion, source, out installDir))
  537. {
  538. Console.WriteLine("Error: Unable to create install directories!");
  539. return null;
  540. }
  541. if(steam3 != null)
  542. steam3.RequestAppTicket((uint)depotId);
  543. if (source == DownloadSource.Steam3)
  544. {
  545. ulong manifestID = GetSteam3DepotManifest(depotId, appId, branch);
  546. if (manifestID == 0)
  547. {
  548. Console.WriteLine("Depot {0} ({1}) missing public subsection or manifest section.", depotId, contentName);
  549. return null;
  550. }
  551. steam3.RequestDepotKey( ( uint )depotId, ( uint )appId );
  552. if (!steam3.DepotKeys.ContainsKey((uint)depotId))
  553. {
  554. Console.WriteLine("No valid depot key for {0}, unable to download.", depotId);
  555. return null;
  556. }
  557. byte[] depotKey = steam3.DepotKeys[(uint)depotId];
  558. var info = new DepotDownloadInfo3( depotId, manifestID, installDir, contentName );
  559. info.depotKey = depotKey;
  560. return info;
  561. }
  562. else
  563. {
  564. // steam2 path
  565. var info = new DepotDownloadInfo2(depotId, depotVersion, installDir, contentName);
  566. return info;
  567. }
  568. }
  569. private static void DownloadSteam3( List<DepotDownloadInfo3> depots )
  570. {
  571. foreach (var depot in depots)
  572. {
  573. int depotId = depot.id;
  574. ulong depot_manifest = depot.manifestId;
  575. byte[] depotKey = depot.depotKey;
  576. string installDir = depot.installDir;
  577. Console.WriteLine("Downloading depot {0} - {1}", depot.id, depot.contentName);
  578. Console.Write("Finding content servers...");
  579. List<IPEndPoint> serverList = steam3.steamClient.GetServersOfType(EServerType.CS);
  580. List<CDNClient.ClientEndPoint> cdnServers = null;
  581. int counterDeferred = 0;
  582. for (int i = 0; ; i++)
  583. {
  584. IPEndPoint endpoint = serverList[i % serverList.Count];
  585. cdnServers = CDNClient.FetchServerList(new CDNClient.ClientEndPoint(endpoint.Address.ToString(), endpoint.Port), Config.CellID);
  586. if (cdnServers == null) counterDeferred++;
  587. if (cdnServers != null && cdnServers.Count((ep) => { return ep.Type == "CS"; }) > 0)
  588. break;
  589. if (((i + 1) % serverList.Count) == 0)
  590. {
  591. Console.WriteLine("Unable to find any Steam3 content servers");
  592. return;
  593. }
  594. }
  595. Console.WriteLine(" Done!");
  596. Console.Write("Downloading depot manifest...");
  597. List<CDNClient.ClientEndPoint> cdnEndpoints = cdnServers.Where((ep) => { return ep.Type == "CDN"; }).ToList();
  598. List<CDNClient.ClientEndPoint> csEndpoints = cdnServers.Where((ep) => { return ep.Type == "CS"; }).ToList();
  599. List<CDNClient> cdnClients = new List<CDNClient>();
  600. byte[] appTicket = steam3.AppTickets[(uint)depotId];
  601. foreach (var server in csEndpoints)
  602. {
  603. CDNClient client;
  604. if (appTicket == null)
  605. client = new CDNClient(server, (uint)depotId, steam3.steamUser.SteamID);
  606. else
  607. client = new CDNClient(server, appTicket);
  608. if (client.Connect())
  609. {
  610. cdnClients.Add(client);
  611. if (cdnClients.Count >= NUM_STEAM3_CONNECTIONS)
  612. break;
  613. }
  614. }
  615. if (cdnClients.Count == 0)
  616. {
  617. Console.WriteLine("\nCould not initialize connection with CDN.");
  618. return;
  619. }
  620. DepotManifest depotManifest = cdnClients[0].DownloadDepotManifest( depotId, depot_manifest );
  621. if ( depotManifest == null )
  622. {
  623. // TODO: check for 401s
  624. for (int i = 1; i < cdnClients.Count && depotManifest == null; i++)
  625. {
  626. depotManifest = cdnClients[i].DownloadDepotManifest( depotId, depot_manifest );
  627. }
  628. if (depotManifest == null)
  629. {
  630. Console.WriteLine("\nUnable to download manifest {0} for depot {1}", depot_manifest, depotId);
  631. return;
  632. }
  633. }
  634. if (!depotManifest.DecryptFilenames(depotKey))
  635. {
  636. Console.WriteLine("\nUnable to decrypt manifest for depot {0}", depotId);
  637. return;
  638. }
  639. Console.WriteLine(" Done!");
  640. ulong complete_download_size = 0;
  641. ulong size_downloaded = 0;
  642. depotManifest.Files.Sort((x, y) => { return x.FileName.CompareTo(y.FileName); });
  643. if (Config.DownloadManifestOnly)
  644. {
  645. StringBuilder manifestBuilder = new StringBuilder();
  646. string txtManifest = Path.Combine(depot.installDir, string.Format("manifest_{0}.txt", depot.id));
  647. foreach (var file in depotManifest.Files)
  648. {
  649. if (file.Flags.HasFlag(EDepotFileFlag.Directory))
  650. continue;
  651. manifestBuilder.Append(string.Format("{0}\n", file.FileName));
  652. }
  653. File.WriteAllText(txtManifest, manifestBuilder.ToString());
  654. continue;
  655. }
  656. depotManifest.Files.RemoveAll((x) => !TestIsFileIncluded(x.FileName));
  657. foreach (var file in depotManifest.Files)
  658. {
  659. complete_download_size += file.TotalSize;
  660. }
  661. foreach (var file in depotManifest.Files)
  662. {
  663. string download_path = Path.Combine(installDir, file.FileName);
  664. if (file.Flags.HasFlag(EDepotFileFlag.Directory))
  665. {
  666. if (!Directory.Exists(download_path))
  667. Directory.CreateDirectory(download_path);
  668. continue;
  669. }
  670. string dir_path = Path.GetDirectoryName(download_path);
  671. if (!Directory.Exists(dir_path))
  672. Directory.CreateDirectory(dir_path);
  673. FileStream fs;
  674. DepotManifest.ChunkData[] neededChunks;
  675. FileInfo fi = new FileInfo(download_path);
  676. if (!fi.Exists)
  677. {
  678. // create new file. need all chunks
  679. fs = File.Create(download_path);
  680. neededChunks = file.Chunks.ToArray();
  681. }
  682. else
  683. {
  684. // open existing
  685. fs = File.Open(download_path, FileMode.Open);
  686. if ((ulong)fi.Length != file.TotalSize)
  687. {
  688. fs.SetLength((long)file.TotalSize);
  689. }
  690. // find which chunks we need, in order so that we aren't seeking every which way
  691. neededChunks = Util.ValidateSteam3FileChecksums(fs, file.Chunks.OrderBy(x => x.Offset).ToArray());
  692. if (neededChunks.Count() == 0)
  693. {
  694. size_downloaded += file.TotalSize;
  695. Console.WriteLine("{0,6:#00.00}% {1}", ((float)size_downloaded / (float)complete_download_size) * 100.0f, download_path);
  696. fs.Close();
  697. continue;
  698. }
  699. else
  700. {
  701. size_downloaded += (file.TotalSize - (ulong)neededChunks.Select(x => (int)x.UncompressedLength).Sum());
  702. }
  703. }
  704. Console.Write("{0,6:#00.00}% {1}", ((float)size_downloaded / (float)complete_download_size) * 100.0f, download_path);
  705. foreach (var chunk in neededChunks)
  706. {
  707. string chunkID = EncodeHexString(chunk.ChunkID);
  708. byte[] encrypted_chunk = cdnClients[0].DownloadDepotChunk(depotId, chunkID);
  709. if (encrypted_chunk == null)
  710. {
  711. for (int i = 1; i < cdnClients.Count && encrypted_chunk == null; i++)
  712. {
  713. encrypted_chunk = cdnClients[i].DownloadDepotChunk(depotId, chunkID);
  714. }
  715. if (encrypted_chunk == null)
  716. {
  717. Console.WriteLine("Unable to download chunk id {0} for depot {1}", chunkID, depotId);
  718. fs.Close();
  719. return;
  720. }
  721. }
  722. byte[] chunk_data = CDNClient.ProcessChunk(encrypted_chunk, depotKey);
  723. fs.Seek((long)chunk.Offset, SeekOrigin.Begin);
  724. fs.Write(chunk_data, 0, chunk_data.Length);
  725. size_downloaded += chunk.UncompressedLength;
  726. Console.CursorLeft = 0;
  727. Console.Write("{0,6:#00.00}%", ((float)size_downloaded / (float)complete_download_size) * 100.0f);
  728. }
  729. fs.Close();
  730. Console.WriteLine();
  731. }
  732. }
  733. }
  734. private static ContentServerClient.StorageSession GetSteam2StorageSession(IPEndPoint [] contentServers, ContentServerClient csClient, int depotId, int depotVersion)
  735. {
  736. ContentServerClient.StorageSession session = null;
  737. if (csClient.IsConnected && contentServers.Contains(lastSteam2ContentServer))
  738. {
  739. try
  740. {
  741. session = csClient.OpenStorage( (uint)depotId, (uint)depotVersion, (uint)Config.CellID, null, false );
  742. return session;
  743. }
  744. catch ( Steam2Exception )
  745. {
  746. csClient.Disconnect();
  747. }
  748. }
  749. int tries = 0;
  750. int counterSocket = 0, counterSteam2 = 0;
  751. for (int i = 0; ; i++)
  752. {
  753. IPEndPoint endpoint = contentServers[i % contentServers.Length];
  754. try
  755. {
  756. csClient.Connect( endpoint );
  757. session = csClient.OpenStorage( (uint)depotId, (uint)depotVersion, (uint)Config.CellID, GetSteam2Credentials( (uint)depotId ), true );
  758. lastSteam2ContentServer = endpoint;
  759. break;
  760. }
  761. catch ( SocketException )
  762. {
  763. counterSocket++;
  764. }
  765. catch ( Steam2Exception )
  766. {
  767. csClient.Disconnect();
  768. counterSteam2++;
  769. }
  770. if (((i + 1) % contentServers.Length) == 0)
  771. {
  772. if (++tries > MAX_CONNECT_RETRIES)
  773. {
  774. Console.WriteLine("\nGiving up finding Steam2 content server.");
  775. return null;
  776. }
  777. Console.WriteLine("\nSearching for content servers... (socket error: {0}, steam2 error: {1})", counterSocket, counterSteam2);
  778. counterSocket = 0;
  779. counterSteam2 = 0;
  780. Thread.Sleep(1000);
  781. }
  782. }
  783. return session;
  784. }
  785. private static void DownloadSteam2( List<DepotDownloadInfo2> depots )
  786. {
  787. Console.WriteLine("Found depots:");
  788. foreach (var depot in depots)
  789. {
  790. Console.WriteLine("- {0}\t{1} (version {2})", depot.id, depot.contentName, depot.version);
  791. }
  792. Console.Write("Finding content servers...");
  793. foreach( var depot in depots )
  794. {
  795. depot.contentServers = GetStorageServer(depot.id, depot.version, Config.CellID);
  796. if (depot.contentServers == null || depot.contentServers.Length == 0)
  797. {
  798. Console.WriteLine("\nError: Unable to find any Steam2 content servers for depot {0}, version {1}", depot.id, depot.version);
  799. return;
  800. }
  801. }
  802. Console.WriteLine(" Done!");
  803. ContentServerClient csClient = new ContentServerClient();
  804. csClient.ConnectionTimeout = TimeSpan.FromSeconds(STEAM2_CONNECT_TIMEOUT_SECONDS);
  805. ContentServerClient.StorageSession session;
  806. foreach (var depot in depots)
  807. {
  808. session = GetSteam2StorageSession(depot.contentServers, csClient, depot.id, depot.version);
  809. if (session == null)
  810. continue;
  811. Console.Write(String.Format("Downloading manifest for depot {0}...", depot.id));
  812. string txtManifest = Path.Combine(depot.installDir, string.Format("manifest_{0}.txt", depot.id));
  813. Steam2ChecksumData checksums = null;
  814. StringBuilder manifestBuilder = new StringBuilder();
  815. string[] excludeList = null;
  816. depot.cryptKey = CDRManager.GetDepotEncryptionKey(depot.id, depot.version);
  817. depot.manifest = session.DownloadManifest();
  818. Console.WriteLine(" Done!");
  819. if (Config.UsingExclusionList)
  820. excludeList = GetExcludeList(session, depot.manifest);
  821. // Build a list of files that need downloading.
  822. for (int x = 0; x < depot.manifest.Nodes.Count; ++x)
  823. {
  824. var dirEntry = depot.manifest.Nodes[x];
  825. string downloadPath = Path.Combine(depot.installDir, dirEntry.FullName.ToLower());
  826. if (Config.DownloadManifestOnly)
  827. {
  828. if (dirEntry.FileID == -1)
  829. continue;
  830. manifestBuilder.Append(string.Format("{0}\n", dirEntry.FullName));
  831. continue;
  832. }
  833. if (Config.UsingExclusionList && IsFileExcluded(dirEntry.FullName, excludeList))
  834. continue;
  835. if (!TestIsFileIncluded(dirEntry.FullName))
  836. continue;
  837. string path = Path.GetDirectoryName(downloadPath);
  838. if (path != "" && !Directory.Exists(path))
  839. Directory.CreateDirectory(path);
  840. if ( dirEntry.FileID == -1 )
  841. {
  842. if ( !Directory.Exists( downloadPath ) )
  843. {
  844. // this is a directory, so lets just create it
  845. Directory.CreateDirectory( downloadPath );
  846. }
  847. continue;
  848. }
  849. if (checksums == null)
  850. {
  851. // Skip downloading checksums if we're only interested in manifests.
  852. Console.Write(String.Format("Downloading checksums for depot {0}...", depot.id));
  853. checksums = session.DownloadChecksums();
  854. Console.WriteLine(" Done!");
  855. }
  856. FileInfo fi = new FileInfo(downloadPath);
  857. if (fi.Exists)
  858. {
  859. // Similar file, let's check checksums
  860. if (fi.Length == dirEntry.SizeOrCount &&
  861. Util.ValidateSteam2FileChecksums(fi, checksums.GetFileChecksums(dirEntry.FileID)))
  862. {
  863. // checksums OK
  864. float perc = ((float)x / (float)depot.manifest.Nodes.Count) * 100.0f;
  865. Console.WriteLine("{0,6:#00.00}%\t{1}", perc, downloadPath);
  866. continue;
  867. }
  868. // Unlink the current file before we download a new one.
  869. // This will keep symbolic/hard link targets from being overwritten.
  870. fi.Delete();
  871. }
  872. depot.NodesToDownload.Add(x);
  873. }
  874. if (Config.DownloadManifestOnly)
  875. {
  876. File.WriteAllText(txtManifest, manifestBuilder.ToString());
  877. return;
  878. }
  879. }
  880. foreach( var depot in depots )
  881. {
  882. Console.Write("Downloading requested files from depot {0}... ", depot.id);
  883. if ( depot.NodesToDownload.Count == 0 )
  884. Console.WriteLine("none needed.");
  885. else
  886. Console.WriteLine();
  887. session = GetSteam2StorageSession(depot.contentServers, csClient, depot.id, depot.version);
  888. if(session == null)
  889. continue;
  890. for ( int x = 0 ; x < depot.NodesToDownload.Count ; ++x )
  891. {
  892. var dirEntry = depot.manifest.Nodes[ depot.NodesToDownload[ x ] ];
  893. string downloadPath = Path.Combine( depot.installDir, dirEntry.FullName.ToLower() );
  894. float perc = ( ( float )x / ( float )depot.NodesToDownload.Count ) * 100.0f;
  895. Console.WriteLine("{0,6:#00.00}%\t{1}", perc, downloadPath);
  896. using (var fs = new FileStream(downloadPath, FileMode.Create))
  897. {
  898. session.DownloadFileToStream(dirEntry, fs, ContentServerClient.StorageSession.DownloadPriority.High, depot.cryptKey);
  899. }
  900. }
  901. }
  902. csClient.Disconnect();
  903. }
  904. static IPEndPoint[] GetStorageServer( int depotId, int depotVersion, int cellId )
  905. {
  906. foreach ( IPEndPoint csdServer in ServerCache.CSDSServers )
  907. {
  908. ContentServer[] servers;
  909. try
  910. {
  911. ContentServerDSClient csdsClient = new ContentServerDSClient();
  912. csdsClient.Connect( csdServer );
  913. servers = csdsClient.GetContentServerList( (uint)depotId, (uint)depotVersion, (uint)cellId );
  914. }
  915. catch ( SocketException )
  916. {
  917. servers = null;
  918. continue;
  919. }
  920. if ( servers == null )
  921. {
  922. Console.WriteLine( "Warning: CSDS {0} rejected the given depotid or version!", csdServer );
  923. continue;
  924. }
  925. if ( servers.Length == 0 )
  926. continue;
  927. return servers.OrderBy(x => x.Load).Select(x => x.StorageServer).ToArray();
  928. }
  929. return null;
  930. }
  931. static string EncodeHexString( byte[] input )
  932. {
  933. return input.Aggregate( new StringBuilder(),
  934. ( sb, v ) => sb.Append( v.ToString( "x2" ) )
  935. ).ToString();
  936. }
  937. }
  938. }