diff --git a/DepotDownloader/CDNClientPool.cs b/DepotDownloader/CDNClientPool.cs index 2ed0a086..de8e9010 100644 --- a/DepotDownloader/CDNClientPool.cs +++ b/DepotDownloader/CDNClientPool.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading; using System.Threading.Tasks; using SteamKit2.CDN; @@ -20,12 +21,12 @@ namespace DepotDownloader public Client CDNClient { get; } public Server ProxyServer { get; private set; } - private readonly ConcurrentStack activeConnectionPool = []; - private readonly BlockingCollection availableServerEndpoints = []; + private readonly ConcurrentStack activeConnectionPool = new ConcurrentStack(); + private readonly BlockingCollection availableServerEndpoints = new BlockingCollection(); - private readonly AutoResetEvent populatePoolEvent = new(true); + private readonly AutoResetEvent populatePoolEvent = new AutoResetEvent(true); private readonly Task monitorTask; - private readonly CancellationTokenSource shutdownToken = new(); + private readonly CancellationTokenSource shutdownToken = new CancellationTokenSource(); public CancellationTokenSource ExhaustedToken { get; set; } public CDNClientPool(Steam3Session steamSession, uint appId) @@ -34,7 +35,7 @@ namespace DepotDownloader this.appId = appId; CDNClient = new Client(steamSession.steamClient); - monitorTask = Task.Factory.StartNew(ConnectionPoolMonitorAsync).Unwrap(); + monitorTask = Task.Factory.StartNew(ConnectionPoolMonitorAsync, TaskCreationOptions.LongRunning); } public void Shutdown() @@ -47,18 +48,36 @@ namespace DepotDownloader { try { - var cdnServers = await this.steamSession.steamContent.GetServersForSteamPipe(); - if (cdnServers != null) - { - return cdnServers; - } + var cdnServers = await steamSession.steamContent.GetServersForSteamPipe().ConfigureAwait(false); + return cdnServers; } catch (Exception ex) { Console.WriteLine("Failed to retrieve content server list: {0}", ex.Message); + return null; } + } - return null; + private async Task ResolveLancacheIpAsync(string hostname) + { + try + { + var hostEntry = await Dns.GetHostEntryAsync(hostname).ConfigureAwait(false); + return hostEntry.AddressList.FirstOrDefault(ip => IsPrivateIp(ip)); + } + catch (Exception ex) + { + Console.WriteLine("Failed to resolve Lancache IP: {0}", ex.Message); + return null; + } + } + + private bool IsPrivateIp(IPAddress ip) + { + byte[] bytes = ip.GetAddressBytes(); + return bytes[0] == 10 || + (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) || + (bytes[0] == 192 && bytes[1] == 168); } private async Task ConnectionPoolMonitorAsync() @@ -69,7 +88,6 @@ namespace DepotDownloader { populatePoolEvent.WaitOne(TimeSpan.FromSeconds(1)); - // We want the Steam session so we can take the CellID from the session and pass it through to the ContentServer Directory Service if (availableServerEndpoints.Count < ServerEndpointMinimumSize && steamSession.steamClient.IsConnected) { var servers = await FetchBootstrapServerListAsync().ConfigureAwait(false); @@ -80,27 +98,40 @@ namespace DepotDownloader return; } - ProxyServer = servers.Where(x => x.UseAsProxy).FirstOrDefault(); - - var weightedCdnServers = servers - .Where(server => - { - var isEligibleForApp = server.AllowedAppIds.Length == 0 || server.AllowedAppIds.Contains(appId); - return isEligibleForApp && (server.Type == "SteamCache" || server.Type == "CDN"); - }) - .Select(server => - { - AccountSettingsStore.Instance.ContentServerPenalty.TryGetValue(server.Host, out var penalty); - - return (server, penalty); - }) - .OrderBy(pair => pair.penalty).ThenBy(pair => pair.server.WeightedLoad); + ProxyServer = servers.FirstOrDefault(x => x.UseAsProxy); - foreach (var (server, weight) in weightedCdnServers) + foreach (var server in servers) { - for (var i = 0; i < server.NumEntries; i++) + if (server.Type == "SteamCache") { - availableServerEndpoints.Add(server); + var lancacheIp = await ResolveLancacheIpAsync(server.Host).ConfigureAwait(false); + if (lancacheIp != null) + { + var lancacheServer = new Server + { + Host = lancacheIp.ToString(), + Type = server.Type, + NumEntries = server.NumEntries, + WeightedLoad = server.WeightedLoad, + AllowedAppIds = server.AllowedAppIds.ToArray() + }; + + // Downgrade connection to HTTP if Lancache server is found + lancacheServer.Protocol = Server.ConnectionProtocol.HTTP; + Console.WriteLine($"Found Lancache Server: {lancacheServer.Host}. Downgrading connection to HTTP."); + availableServerEndpoints.Add(lancacheServer); + } + } + else + { + var isEligibleForApp = server.AllowedAppIds.Length == 0 || server.AllowedAppIds.Contains(appId); + if (isEligibleForApp && (server.Type == "SteamCache" || server.Type == "CDN")) + { + for (var i = 0; i < server.NumEntries; i++) + { + availableServerEndpoints.Add(server); + } + } } } diff --git a/DepotDownloader/Program.cs b/DepotDownloader/Program.cs index 14f7f88d..d3dba8f2 100644 --- a/DepotDownloader/Program.cs +++ b/DepotDownloader/Program.cs @@ -20,13 +20,16 @@ namespace DepotDownloader static async Task MainAsync(string[] args) { + // Add the SteamKitLogger as a listener to DebugLog + DebugLog.AddListener(new SteamKitLogger()); + if (args.Length == 0) { PrintUsage(); return 1; } - DebugLog.Enabled = false; + DebugLog.Enabled = true; AccountSettingsStore.LoadFromFile("account.config"); diff --git a/DepotDownloader/SteamKitLogger.cs b/DepotDownloader/SteamKitLogger.cs new file mode 100644 index 00000000..db617bee --- /dev/null +++ b/DepotDownloader/SteamKitLogger.cs @@ -0,0 +1,14 @@ +using SteamKit2; +using System; + +public class SteamKitLogger : IDebugListener +{ + public void WriteLine(string category, string msg) + { + Console.WriteLine("[{0}] {1}", category, msg); + } +} + +public interface ILog +{ +}