Represents a Steam3 depot manifest.
Example #1
0
        // Validate a file against Steam3 Chunk data
        public static DepotManifest.ChunkData[] ValidateSteam3FileChecksums(FileStream fs, DepotManifest.ChunkData[] chunkdata)
        {
            var neededChunks = new List<DepotManifest.ChunkData>();
            int read;

            foreach (DepotManifest.ChunkData data in chunkdata)
            {
                byte[] chunk = new byte[data.UncompressedLength];
                fs.Seek((long)data.Offset, SeekOrigin.Begin);
                read = fs.Read(chunk, 0, (int)data.UncompressedLength);

                byte[] tempchunk;
                if (read < data.UncompressedLength)
                {
                    tempchunk = new byte[read];
                    Array.Copy(chunk, 0, tempchunk, 0, read);
                }
                else
                {
                    tempchunk = chunk;
                }

                byte[] adler = AdlerHash(tempchunk);
                if (!adler.SequenceEqual(data.Checksum))
                {
                    neededChunks.Add(data);
                }
            }

            return neededChunks.ToArray();
        }
 public FileData(DepotManifest.FileData sourceData) : this()
 {
     FileName = sourceData.FileName;
     sourceData.Chunks.ForEach(c => Chunks.Add(new ChunkData(c)));
     Flags = sourceData.Flags;
     TotalSize = sourceData.TotalSize;
     FileHash = sourceData.FileHash;
 }
 public ChunkData(DepotManifest.ChunkData sourceChunk)
 {
     ChunkID = sourceChunk.ChunkID;
     Checksum = sourceChunk.Checksum;
     Offset = sourceChunk.Offset;
     CompressedLength = sourceChunk.CompressedLength;
     UncompressedLength = sourceChunk.UncompressedLength;
 }
Example #4
0
        /// <summary>
        /// Downloads the depot manifest specified by the given manifest ID, and optionally decrypts the manifest's filenames if the depot decryption key has been provided.
        /// </summary>
        /// <param name="manifestId">The unique identifier of the manifest to be downloaded.</param>
        /// <returns>A <see cref="DepotManifest"/> instance that contains information about the files present within a depot.</returns>
        public DepotManifest DownloadManifest(ulong manifestId)
        {
            byte[] compressedManifest = DoRawCommand(connectedServer, "depot", doAuth: true, args: string.Format("{0}/manifest/{1}", depotId, manifestId));

            byte[] manifestData = ZipUtil.Decompress(compressedManifest);

            var depotManifest = new DepotManifest(manifestData);

            if (depotKey != null)
            {
                // if we have the depot key, decrypt the manifest filenames
                depotManifest.DecryptFilenames(depotKey);
            }

            return(depotManifest);
        }
Example #5
0
        async Task <DepotManifest> DownloadManifestCoreAsync(uint depotId, ulong manifestId, Server server, string cdnAuthToken, byte[] depotKey)
        {
            var manifestData = await DoRawCommandAsync(server, HttpMethod.Get, "depot", doAuth : true, args : string.Format("{0}/manifest/{1}/5", depotId, manifestId), authtoken : cdnAuthToken).ConfigureAwait(false);

            manifestData = ZipUtil.Decompress(manifestData);

            var depotManifest = new DepotManifest(manifestData);

            if (depotKey != null)
            {
                // if we have the depot key, decrypt the manifest filenames
                depotManifest.DecryptFilenames(depotKey);
            }

            return(depotManifest);
        }
Example #6
0
        DepotManifest DownloadManifestCore(uint depotId, ulong manifestId, Server server, string cdnAuthToken, byte[] depotKey)
        {
            byte[] manifestData = DoRawCommand(server, "depot", doAuth: true, args: string.Format("{0}/manifest/{1}/5", depotId, manifestId), authtoken: cdnAuthToken);

            manifestData = ZipUtil.Decompress(manifestData);

            var depotManifest = new DepotManifest(manifestData);

            if (depotKey != null)
            {
                // if we have the depot key, decrypt the manifest filenames
                depotManifest.DecryptFilenames(depotKey);
            }

            return(depotManifest);
        }
Example #7
0
        /// <summary>
        /// Downloads the depot manifest specified by the given manifest ID, and optionally decrypts the manifest's filenames if the depot decryption key has been provided.
        /// </summary>
        /// <param name="depotId">The id of the depot being accessed.</param>
        /// <param name="manifestId">The unique identifier of the manifest to be downloaded.</param>
        /// <returns>A <see cref="DepotManifest"/> instance that contains information about the files present within a depot.</returns>
        public DepotManifest DownloadManifest(uint depotId, ulong manifestId)
        {
            string cdnToken = null;

            depotCdnAuthKeys.TryGetValue(depotId, out cdnToken);

            byte[] compressedManifest = DoRawCommand(connectedServer, "depot", doAuth: true, args: string.Format("{0}/manifest/{1}/5", depotId, manifestId), authtoken: cdnToken);

            byte[] manifestData = ZipUtil.Decompress(compressedManifest);

            var depotManifest = new DepotManifest(manifestData);

            byte[] depotKey;
            if (depotKeys.TryGetValue(depotId, out depotKey))
            {
                // if we have the depot key, decrypt the manifest filenames
                depotManifest.DecryptFilenames(depotKey);
            }

            return(depotManifest);
        }
Example #8
0
        /// <summary>
        /// Downloads the depot manifest specified by the given manifest ID, and optionally decrypts the manifest's filenames if the depot decryption key has been provided.
        /// </summary>
        /// <param name="depotId">The id of the depot being accessed.</param>
        /// <param name="manifestId">The unique identifier of the manifest to be downloaded.</param>
        /// <param name="server">The content server to connect to.</param>
        /// <param name="cdnAuthToken">CDN auth token for CDN content server endpoints.</param>
        /// <param name="depotKey">
        /// The depot decryption key for the depot that will be downloaded.
        /// This is used for decrypting filenames (if needed) in depot manifests, and processing depot chunks.
        /// </param>
        /// <param name="proxyServer">Optional content server marked as UseAsProxy which transforms the request.</param>
        /// <returns>A <see cref="DepotManifest"/> instance that contains information about the files present within a depot.</returns>
        /// <exception cref="System.ArgumentNullException"><see ref="server"/> was null.</exception>
        /// <exception cref="HttpRequestException">An network error occurred when performing the request.</exception>
        /// <exception cref="SteamKitWebRequestException">A network error occurred when performing the request.</exception>
        public async Task <DepotManifest> DownloadManifestAsync(uint depotId, ulong manifestId, Server server, string?cdnAuthToken, byte[]?depotKey, Server?proxyServer = null)
        {
            if (server == null)
            {
                throw new ArgumentNullException(nameof(server));
            }

            var manifestData = await DoRawCommandAsync(server, string.Format("depot/{0}/manifest/{1}/5", depotId, manifestId), cdnAuthToken, proxyServer).ConfigureAwait(false);

            manifestData = ZipUtil.Decompress(manifestData);

            var depotManifest = new DepotManifest(manifestData);

            if (depotKey != null)
            {
                // if we have the depot key, decrypt the manifest filenames
                depotManifest.DecryptFilenames(depotKey);
            }

            return(depotManifest);
        }
        /*
         * Here be dragons.
         */
        public static void DownloadFilesFromDepot(DepotProcessor.ManifestJob job, DepotManifest depotManifest)
        {
            var randomGenerator = new Random();
            var files = depotManifest.Files.Where(x => IsFileNameMatching(job.DepotID, x.FileName)).ToList();
            var filesUpdated = false;
            var filesAnyFailed = false;

            var hashesFile = Path.Combine(Application.Path, "files", ".support", "hashes", string.Format("{0}.json", job.DepotID));
            var hashes = new Dictionary<string, byte[]>();

            if (File.Exists(hashesFile))
            {
                hashes = JsonConvert.DeserializeObject<Dictionary<string, byte[]>>(File.ReadAllText(hashesFile));
            }

            Log.WriteDebug("FileDownloader", "Will download {0} files from depot {1}", files.Count, job.DepotID);

            Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 2 }, (file, state2) =>
            {
                string directory    = Path.Combine(Application.Path, "files", job.DepotID.ToString(), Path.GetDirectoryName(file.FileName));
                string finalPath    = Path.Combine(directory, Path.GetFileName(file.FileName));
                string downloadPath = Path.Combine(Path.GetTempPath(), Path.ChangeExtension(Path.GetRandomFileName(), ".steamdb_tmp"));

                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }
                else if (file.TotalSize == 0)
                {
                    if (File.Exists(finalPath))
                    {
                        var f = new FileInfo(finalPath);

                        if(f.Length == 0)
                        {
            #if DEBUG
                            Log.WriteDebug("FileDownloader", "{0} is already empty", file.FileName);
            #endif

                            return;
                        }
                    }
                    else
                    {
                        File.Create(finalPath);

                        Log.WriteInfo("FileDownloader", "{0} created an empty file", file.FileName);

                        return;
                    }
                }
                else if(hashes.ContainsKey(file.FileName) && file.FileHash.SequenceEqual(hashes[file.FileName]))
                {
            #if DEBUG
                    Log.WriteDebug("FileDownloader", "{0} already matches the file we have", file.FileName);
            #endif

                    return;
                }

                var chunks = file.Chunks.OrderBy(x => x.Offset).ToList();

                Log.WriteInfo("FileDownloader", "Downloading {0} ({1} bytes, {2} chunks)", file.FileName, file.TotalSize, chunks.Count);

                uint count = 0;
                byte[] checksum;
                string lastError = "or checksum failed";
                string oldChunksFile;

                using (var sha = new SHA1Managed())
                {
                    oldChunksFile = Path.Combine(Application.Path, "files", ".support", "chunks",
                        string.Format("{0}-{1}.json", job.DepotID, BitConverter.ToString(sha.ComputeHash(Encoding.UTF8.GetBytes(file.FileName))))
                    );
                }

                using (var fs = File.Open(downloadPath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                {
                    fs.SetLength((long)file.TotalSize);

                    var lockObject = new object();
                    var neededChunks = new List<DepotManifest.ChunkData>();

                    if (File.Exists(oldChunksFile) && File.Exists(finalPath))
                    {
                        var oldChunks = JsonConvert.DeserializeObject<List<DepotManifest.ChunkData>>(
                                            File.ReadAllText(oldChunksFile),
                                            new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All }
                                        );

                        using (var fsOld = File.Open(finalPath, FileMode.Open, FileAccess.Read))
                        {
                            foreach (var chunk in chunks)
                            {
                                var oldChunk = oldChunks.FirstOrDefault(c => c.ChunkID.SequenceEqual(chunk.ChunkID));

                                if (oldChunk != null)
                                {
                                    var oldData = new byte[oldChunk.UncompressedLength];
                                    fsOld.Seek((long)oldChunk.Offset, SeekOrigin.Begin);
                                    fsOld.Read(oldData, 0, oldData.Length);

                                    var existingChecksum = Utils.AdlerHash(oldData);

                                    if (existingChecksum.SequenceEqual(chunk.Checksum))
                                    {
                                        fs.Seek((long)chunk.Offset, SeekOrigin.Begin);
                                        fs.Write(oldData, 0, oldData.Length);

            #if DEBUG
                                        Log.WriteDebug("FileDownloader", "{0} Found chunk ({1}), not downloading ({2}/{3})", file.FileName, chunk.Offset, ++count, chunks.Count);
            #endif
                                    }
                                    else
                                    {
                                        neededChunks.Add(chunk);

            #if DEBUG
                                        Log.WriteDebug("FileDownloader", "{0} Found chunk ({1}), but checksum differs", file.FileName, chunk.Offset);
            #endif
                                    }
                                }
                                else
                                {
                                    neededChunks.Add(chunk);
                                }
                            }
                        }
                    }
                    else
                    {
                        neededChunks = chunks;
                    }

                    Parallel.ForEach(neededChunks, new ParallelOptions { MaxDegreeOfParallelism = 3 }, (chunk, state) =>
                    {
                        var downloaded = false;

                        for (var i = 0; i <= 5; i++)
                        {
                            try
                            {
                                var chunkData = CDNClient.DownloadDepotChunk(job.DepotID, chunk, job.Server, job.CDNToken, job.DepotKey);

                                lock (lockObject)
                                {
                                    fs.Seek((long)chunk.Offset, SeekOrigin.Begin);
                                    fs.Write(chunkData.Data, 0, chunkData.Data.Length);

                                    Log.WriteDebug("FileDownloader", "Downloaded {0} ({1}/{2})", file.FileName, ++count, chunks.Count);
                                }

                                downloaded = true;

                                break;
                            }
                            catch (Exception e)
                            {
                                lastError = e.Message;
                            }

                            // See https://developers.google.com/drive/web/handle-errors
                            Task.Delay((1 << i) * 1000 + randomGenerator.Next(1001)).Wait();
                        }

                        if (!downloaded)
                        {
                            state.Stop();
                        }
                    });

                    fs.Seek(0, SeekOrigin.Begin);

                    using (var sha = new SHA1Managed())
                    {
                        checksum = sha.ComputeHash(fs);
                    }
                }

                if (file.FileHash.SequenceEqual(checksum))
                {
                    Log.WriteInfo("FileDownloader", "Downloaded {0} from {1}", file.FileName, Steam.GetAppName(job.ParentAppID));

                    hashes[file.FileName] = checksum;

                    if (File.Exists(finalPath))
                    {
                        File.Delete(finalPath);
                    }

                    File.Move(downloadPath, finalPath);

                    if (chunks.Count > 1)
                    {
                        File.WriteAllText(oldChunksFile,
                            JsonConvert.SerializeObject(
                                chunks,
                                Formatting.None,
                                new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All }
                            )
                        );
                    }
                    else if (File.Exists(oldChunksFile))
                    {
                        File.Delete(oldChunksFile);
                    }

                    filesUpdated = true;
                }
                else
                {
                    filesAnyFailed = true;

                    IRC.Instance.SendOps("{0}[{1}]{2} Failed to download {3}: Only {4} out of {5} chunks downloaded ({6})",
                        Colors.OLIVE, Steam.GetAppName(job.ParentAppID), Colors.NORMAL, file.FileName, count, chunks.Count, lastError);

                    Log.WriteError("FileDownloader", "Failed to download {0}: Only {1} out of {2} chunks downloaded from {3} ({4})",
                        file.FileName, count, chunks.Count, job.Server, lastError);

                    File.Delete(downloadPath);
                }
            });

            if (filesUpdated)
            {
                if (filesAnyFailed)
                {
                    IRC.Instance.SendOps("{0}[{1}]{2} Failed to download some files, not running update script to prevent broken diffs.",
                        Colors.OLIVE, Steam.GetAppName(job.ParentAppID), Colors.NORMAL);

                    return;
                }

                File.WriteAllText(hashesFile, JsonConvert.SerializeObject(hashes));

                var updateScript = Path.Combine(Application.Path, "files", "update.sh");

                if (File.Exists(updateScript))
                {
                    lock (updateLock)
                    {
                        // YOLO
                        Process.Start(updateScript, job.DepotID.ToString());
                    }
                }
            }
        }
        private static void ProcessDepotAfterDownload(IDbConnection db, ManifestJob request, DepotManifest depotManifest)
        {
            var filesOld = db.Query<DepotFile>("SELECT `ID`, `File`, `Hash`, `Size`, `Flags` FROM `DepotsFiles` WHERE `DepotID` = @DepotID", new { request.DepotID }).ToDictionary(x => x.File, x => x);
            var filesNew = new List<DepotFile>();
            var filesAdded = new List<DepotFile>();
            var shouldHistorize = filesOld.Any(); // Don't historize file additions if we didn't have any data before

            foreach (var file in depotManifest.Files)
            {
                var name = file.FileName.Replace('\\', '/');

                // safe guard
                if (name.Length > 255)
                {
                    ErrorReporter.Notify(new OverflowException(string.Format("File \"{0}\" in depot {1} is too long", name, request.DepotID)));

                    continue;
                }

                var depotFile = new DepotFile
                {
                    DepotID = request.DepotID,
                    File    = name,
                    Size    = file.TotalSize,
                    Flags   = file.Flags
                };

                if (file.FileHash.Length > 0 && !file.Flags.HasFlag(EDepotFileFlag.Directory))
                {
                    depotFile.Hash = Utils.ByteArrayToString(file.FileHash);
                }
                else
                {
                    depotFile.Hash = "0000000000000000000000000000000000000000";
                }

                filesNew.Add(depotFile);
            }

            foreach (var file in filesNew)
            {
                if (filesOld.ContainsKey(file.File))
                {
                    var oldFile = filesOld[file.File];
                    var updateFile = false;

                    if (oldFile.Size != file.Size || !file.Hash.Equals(oldFile.Hash))
                    {
                        MakeHistory(db, request, file.File, "modified", oldFile.Size, file.Size);

                        updateFile = true;
                    }

                    if (oldFile.Flags != file.Flags)
                    {
                        MakeHistory(db, request, file.File, "modified_flags", (ulong)oldFile.Flags, (ulong)file.Flags);

                        updateFile = true;
                    }

                    if (updateFile)
                    {
                        file.ID = oldFile.ID;

                        db.Execute("UPDATE `DepotsFiles` SET `Hash` = @Hash, `Size` = @Size, `Flags` = @Flags WHERE `DepotID` = @DepotID AND `ID` = @ID", file);
                    }

                    filesOld.Remove(file.File);
                }
                else
                {
                    // We want to historize modifications first, and only then deletions and additions
                    filesAdded.Add(file);
                }
            }

            if (filesOld.Any())
            {
                db.Execute("DELETE FROM `DepotsFiles` WHERE `DepotID` = @DepotID AND `ID` IN @Files", new { request.DepotID, Files = filesOld.Select(x => x.Value.ID) });

                db.Execute(GetHistoryQuery(), filesOld.Select(x => new DepotHistory
                {
                    DepotID  = request.DepotID,
                    ChangeID = request.ChangeNumber,
                    Action   = "removed",
                    File     = x.Value.File
                }));
            }

            if (filesAdded.Any())
            {
                db.Execute("INSERT INTO `DepotsFiles` (`DepotID`, `File`, `Hash`, `Size`, `Flags`) VALUES (@DepotID, @File, @Hash, @Size, @Flags)", filesAdded);

                if (shouldHistorize)
                {
                    db.Execute(GetHistoryQuery(), filesAdded.Select(x => new DepotHistory
                    {
                        DepotID  = request.DepotID,
                        ChangeID = request.ChangeNumber,
                        Action   = "added",
                        File     = x.File
                    }));
                }
            }

            db.Execute("UPDATE `Depots` SET `LastManifestID` = @ManifestID WHERE `DepotID` = @DepotID", new { request.DepotID, request.ManifestID });
        }
Example #11
0
        // Ambiguous reference in cref attribute: 'CDNClient.DownloadManifest'. Assuming 'SteamKit2.CDNClient.DownloadManifest(uint, ulong)',
        // but could have also matched other overloads including 'SteamKit2.CDNClient.DownloadManifest(uint, ulong, string, string, byte[])'.
#pragma warning disable 0419

        /// <summary>
        /// Downloads the specified depot chunk, and optionally processes the chunk and verifies the checksum if the depot decryption key has been provided.
        /// </summary>
        /// <remarks>
        /// This function will also validate the length of the downloaded chunk with the value of <see cref="DepotManifest.ChunkData.CompressedLength"/>,
        /// if it has been assigned a value.
        /// </remarks>
        /// <param name="depotId">The id of the depot being accessed.</param>
        /// <param name="chunk">
        /// A <see cref="DepotManifest.ChunkData"/> instance that represents the chunk to download.
        /// This value should come from a manifest downloaded with <see cref="CDNClient.DownloadManifest"/>.
        /// </param>
        /// <returns>A <see cref="DepotChunk"/> instance that contains the data for the given chunk.</returns>
        /// <param name="host">CDN hostname.</param>
        /// <param name="cdnAuthToken">CDN auth token for CDN content server endpoints.</param>
        /// <param name="depotKey">
        /// The depot decryption key for the depot that will be downloaded.
        /// This is used for decrypting filenames (if needed) in depot manifests, and processing depot chunks.
        /// </param>
        /// <exception cref="System.ArgumentNullException">chunk's <see cref="DepotManifest.ChunkData.ChunkID"/> was null.</exception>
        public DepotChunk DownloadDepotChunk( uint depotId, DepotManifest.ChunkData chunk, string host, string cdnAuthToken, byte[] depotKey = null )
#pragma warning restore 0419
        {
            if (chunk.ChunkID == null)
                throw new ArgumentNullException( "chunk.ChunkID" );

            var server = new Server
            {
                Host = host,
                Port = 80
            };

            return DownloadDepotChunkCore( depotId, chunk, server, cdnAuthToken, depotKey );
        }
Example #12
0
        // Ambiguous reference in cref attribute: 'CDNClient.DownloadManifest'. Assuming 'SteamKit2.CDNClient.DownloadManifest(uint, ulong)',
        // but could have also matched other overloads including 'SteamKit2.CDNClient.DownloadManifest(uint, ulong, string, string, byte[])'.
#pragma warning disable 0419

        /// <summary>
        /// Downloads the specified depot chunk, and optionally processes the chunk and verifies the checksum if the depot decryption key has been provided.
        /// </summary>
        /// <remarks>
        /// This function will also validate the length of the downloaded chunk with the value of <see cref="DepotManifest.ChunkData.CompressedLength"/>,
        /// if it has been assigned a value.
        /// </remarks>
        /// <param name="depotId">The id of the depot being accessed.</param>
        /// <param name="chunk">
        /// A <see cref="DepotManifest.ChunkData"/> instance that represents the chunk to download.
        /// This value should come from a manifest downloaded with <see cref="CDNClient.DownloadManifest"/>.
        /// </param>
        /// <returns>A <see cref="DepotChunk"/> instance that contains the data for the given chunk.</returns>
        /// <exception cref="System.ArgumentNullException">chunk's <see cref="DepotManifest.ChunkData.ChunkID"/> was null.</exception>
        public DepotChunk DownloadDepotChunk( uint depotId, DepotManifest.ChunkData chunk )
#pragma warning restore 0419
        {
            if ( chunk.ChunkID == null )
                throw new ArgumentNullException( "chunk.ChunkID" );

            string cdnToken = null;
            depotCdnAuthKeys.TryGetValue( depotId, out cdnToken );

            byte[] depotKey = null;
            depotKeys.TryGetValue( depotId, out depotKey );

            return DownloadDepotChunkCore( depotId, chunk, connectedServer, cdnToken, depotKey );
        }
 public ProtoManifest(DepotManifest sourceManifest, ulong id) : this()
 {
     sourceManifest.Files.ForEach(f => Files.Add(new FileData(f)));
     ID = id;
 }
Example #14
0
        /// <summary>
        /// Downloads the specified depot chunk, and optionally processes the chunk and verifies the checksum if the depot decryption key has been provided.
        /// </summary>
        /// <remarks>
        /// This function will also validate the length of the downloaded chunk with the value of <see cref="DepotManifest.ChunkData.CompressedLength"/>,
        /// if it has been assigned a value.
        /// </remarks>
        /// <param name="depotId">The id of the depot being accessed.</param>
        /// <param name="chunk">
        /// A <see cref="DepotManifest.ChunkData"/> instance that represents the chunk to download.
        /// This value should come from a manifest downloaded with <see cref="CDNClient.DownloadManifest"/>.
        /// </param>
        /// <returns>A <see cref="DepotChunk"/> instance that contains the data for the given chunk.</returns>
        /// <exception cref="System.ArgumentNullException">chunk's <see cref="DepotManifest.ChunkData.ChunkID"/> was null.</exception>
        public DepotChunk DownloadDepotChunk( uint depotId, DepotManifest.ChunkData chunk )
        {
            if ( chunk.ChunkID == null )
                throw new ArgumentNullException( "chunk.ChunkID" );

            string chunkId = Utils.EncodeHexString( chunk.ChunkID );
            string cdnToken = null;
            depotCdnAuthKeys.TryGetValue(depotId, out cdnToken);

            byte[] chunkData = DoRawCommand( connectedServer, "depot", doAuth: true, args: string.Format( "{0}/chunk/{1}", depotId, chunkId ), authtoken: cdnToken );

            if ( chunk.CompressedLength != default( uint ) )
            {
                // assert that lengths match only if the chunk has a length assigned.
                DebugLog.Assert( chunkData.Length == chunk.CompressedLength, "CDNClient", "Length mismatch after downloading depot chunk!" );
            }

            var depotChunk = new DepotChunk
            {
                ChunkInfo = chunk,
                Data = chunkData,
            };

            byte[] depotKey;
            if ( depotKeys.TryGetValue( depotId, out depotKey ) )
            {
                // if we have the depot key, we can process the chunk immediately
                depotChunk.Process( depotKey );
            }

            return depotChunk;
        }
Example #15
0
        /// <summary>
        /// Downloads the depot manifest specified by the given manifest ID, and optionally decrypts the manifest's filenames if the depot decryption key has been provided.
        /// </summary>
        /// <param name="depotId">The id of the depot being accessed.</param>
        /// <param name="manifestId">The unique identifier of the manifest to be downloaded.</param>
        /// <returns>A <see cref="DepotManifest"/> instance that contains information about the files present within a depot.</returns>
        public DepotManifest DownloadManifest( uint depotId, ulong manifestId )
        {
            string cdnToken = null;
            depotCdnAuthKeys.TryGetValue( depotId, out cdnToken );

            byte[] compressedManifest = DoRawCommand( connectedServer, "depot", doAuth: true, args: string.Format( "{0}/manifest/{1}/5", depotId, manifestId ), authtoken: cdnToken );

            byte[] manifestData = ZipUtil.Decompress( compressedManifest );

            var depotManifest = new DepotManifest( manifestData );

            byte[] depotKey;
            if ( depotKeys.TryGetValue( depotId, out depotKey ) )
            {
                // if we have the depot key, decrypt the manifest filenames
                depotManifest.DecryptFilenames( depotKey );
            }

            return depotManifest;
        }
Example #16
0
        /// <summary>
        /// Downloads the depot manifest specified by the given manifest ID, and optionally decrypts the manifest's filenames if the depot decryption key has been provided.
        /// </summary>
        /// <param name="manifestId">The unique identifier of the manifest to be downloaded.</param>
        /// <returns>A <see cref="DepotManifest"/> instance that contains information about the files present within a depot.</returns>
        public DepotManifest DownloadManifest( ulong manifestId )
        {
            byte[] compressedManifest = DoRawCommand( connectedServer, "depot", doAuth: true, args: string.Format( "{0}/manifest/{1}", depotId, manifestId ) );

            byte[] manifestData = ZipUtil.Decompress( compressedManifest );

            var depotManifest = new DepotManifest( manifestData );

            if ( depotKey != null )
            {
                // if we have the depot key, decrypt the manifest filenames
                depotManifest.DecryptFilenames( depotKey );
            }

            return depotManifest;
        }
Example #17
0
        /// <summary>
        /// Downloads the specified depot chunk, and optionally processes the chunk and verifies the checksum if the depot decryption key has been provided.
        /// </summary>
        /// <param name="chunk">
        /// A <see cref="DepotManifest.ChunkData"/> instance that represents the chunk to download.
        /// This value should come from a manifest downloaded with <see cref="CDNClient.DownloadManifest"/>.
        /// </param>
        /// <returns>A <see cref="DepotChunk"/> instance that contains the data for the given chunk.</returns>
        public DepotChunk DownloadDepotChunk( DepotManifest.ChunkData chunk )
        {
            string chunkId = Utils.EncodeHexString( chunk.ChunkID );

            byte[] chunkData = DoRawCommand( connectedServer, "depot", doAuth: true, args: string.Format( "{0}/chunk/{1}", depotId, chunkId ) );

            DebugLog.Assert( chunkData.Length == chunk.CompressedLength, "CDNClient", "Length mismatch after downloading depot chunk!" );

            var depotChunk = new DepotChunk
            {
                ChunkInfo = chunk,
                Data = chunkData,
            };

            if ( depotKey != null )
            {
                // if we have the depot key, we can process the chunk immediately
                depotChunk.Process( depotKey );
            }

            return depotChunk;
        }
Example #18
0
        DepotManifest DownloadManifestCore( uint depotId, ulong manifestId, Server server, string cdnAuthToken, byte[] depotKey )
        {

            byte[] manifestData = DoRawCommand( server, "depot", doAuth: true, args: string.Format( "{0}/manifest/{1}/5", depotId, manifestId ), authtoken: cdnAuthToken );

            manifestData = ZipUtil.Decompress( manifestData );

            var depotManifest = new DepotManifest( manifestData );

            if ( depotKey != null )
            {
                // if we have the depot key, decrypt the manifest filenames
                depotManifest.DecryptFilenames( depotKey );
            }

            return depotManifest;
        }
Example #19
0
        DepotChunk DownloadDepotChunkCore( uint depotId, DepotManifest.ChunkData chunk, Server server, string cdnAuthToken, byte[] depotKey )
        {
            var chunkID = Utils.EncodeHexString( chunk.ChunkID );

            byte[] chunkData = DoRawCommand( server, "depot", doAuth: true, args: string.Format( "{0}/chunk/{1}", depotId, chunkID ), authtoken: cdnAuthToken );

            if ( chunk.CompressedLength != default( uint ) )
            {
                // assert that lengths match only if the chunk has a length assigned.
                DebugLog.Assert( chunkData.Length == chunk.CompressedLength, "CDNClient", "Length mismatch after downloading depot chunk!" );
            }

            var depotChunk = new DepotChunk
            {
                ChunkInfo = chunk,
                Data = chunkData,
            };

            if ( depotKey != null )
            {
                // if we have the depot key, we can process the chunk immediately
                depotChunk.Process( depotKey );
            }

            return depotChunk;
        }
        public static void DownloadFilesFromDepot(DepotProcessor.ManifestJob job, DepotManifest depotManifest)
        {
            var files = depotManifest.Files.Where(x => IsFileNameMatching(job.DepotID, x.FileName)).ToList();
            var filesUpdated = false;

            Log.WriteDebug("FileDownloader", "Will download {0} files from depot {1}", files.Count(), job.DepotID);

            foreach (var file in files)
            {
                string directory    = Path.Combine(Application.Path, FILES_DIRECTORY, job.DepotID.ToString(), Path.GetDirectoryName(file.FileName));
                string finalPath    = Path.Combine(directory, Path.GetFileName(file.FileName));
                string downloadPath = Path.GetTempFileName();

                if (!Directory.Exists(directory))
                {
                    Directory.CreateDirectory(directory);
                }
                else if (File.Exists(finalPath))
                {
                    using (var fs = File.Open(finalPath, FileMode.Open))
                    {
                        using (var sha = new SHA1Managed())
                        {
                            if (file.FileHash.SequenceEqual(sha.ComputeHash(fs)))
                            {
                                Log.WriteDebug("FileDownloader", "{0} already matches the file we have", file.FileName);

                                continue;
                            }
                        }
                    }
                }

                Log.WriteInfo("FileDownloader", "Downloading {0} ({1} bytes, {2} chunks)", file.FileName, file.TotalSize, file.Chunks.Count);

                uint count = 0;
                byte[] checksum;
                string lastError = "or checksum failed";

                using (var fs = File.Open(downloadPath, FileMode.OpenOrCreate))
                {
                    fs.SetLength((long)file.TotalSize);

                    var lockObject = new object();

                    // TODO: We *could* verify each chunk and only download needed ones
                    Parallel.ForEach(file.Chunks, (chunk, state) =>
                    {
                        var downloaded = false;

                        for (var i = 0; i <= 5; i++)
                        {
                            try
                            {
                                var chunkData = CDNClient.DownloadDepotChunk(job.DepotID, chunk, job.Server, job.CDNToken, job.DepotKey);

                                lock (lockObject)
                                {
                                    fs.Seek((long)chunk.Offset, SeekOrigin.Begin);
                                    fs.Write(chunkData.Data, 0, chunkData.Data.Length);

                                    Log.WriteDebug("FileDownloader", "Downloaded {0} ({1}/{2})", file.FileName, ++count, file.Chunks.Count);
                                }

                                downloaded = true;

                                break;
                            }
                            catch (Exception e)
                            {
                                lastError = e.Message;
                            }
                        }

                        if (!downloaded)
                        {
                            state.Stop();
                        }
                    });

                    fs.Seek(0, SeekOrigin.Begin);

                    using (var sha = new SHA1Managed())
                    {
                        checksum = sha.ComputeHash(fs);
                    }
                }

                if (file.Chunks.Count == 0 || file.FileHash.SequenceEqual(checksum))
                {
                    Log.WriteInfo("FileDownloader", "Downloaded {0} from {1}", file.FileName, Steam.GetAppName(job.ParentAppID));

                    if (File.Exists(finalPath))
                    {
                        File.Delete(finalPath);
                    }

                    File.Move(downloadPath, finalPath);

                    filesUpdated = true;
                }
                else
                {
                    IRC.Instance.SendOps("{0}[{1}]{2} Failed to download {3}: Only {4} out of {5} chunks downloaded ({6})",
                        Colors.OLIVE, Steam.GetAppName(job.ParentAppID), Colors.NORMAL, file.FileName, count, file.Chunks.Count, lastError);

                    Log.WriteError("FileDownloader", "Failed to download {0}: Only {1} out of {2} chunks downloaded from {3} ({4})",
                        file.FileName, count, file.Chunks.Count, job.Server, lastError);

                    File.Delete(downloadPath);
                }
            }

            if (filesUpdated)
            {
                var updateScript = Path.Combine(Application.Path, "files", "update.sh");

                if (File.Exists(updateScript))
                {
                    // YOLO
                    Process.Start(updateScript, job.DepotID.ToString());
                }
            }
        }