Ejemplo n.º 1
0
        private static async Task GetPackage(HttpListenerContext context)
        {
            var subid = context.Request.QueryString.Get("subid");

            if (subid == null)
            {
                throw new MissingFieldException("subid parameter is missing");
            }

            var result = await PackageCommand.GetPackageData(uint.Parse(subid));

            if (result == null)
            {
                context.Response.StatusCode = (int)HttpStatusCode.NotFound;
                await WriteJsonResponse("App not found", context.Response);

                return;
            }

            context.Response.ContentType = "text/vdf; charset=utf-8";
            context.Response.Headers[HttpResponseHeader.ETag] = $"\"{Utils.ByteArrayToString(result.SHAHash)}\"";

            await using var kvMemory = new MemoryStream();
            result.KeyValues.SaveToStream(kvMemory, false);
            kvMemory.Position = 0;
            await kvMemory.CopyToAsync(context.Response.OutputStream);
        }
Ejemplo n.º 2
0
        public override async Task OnCommand(CommandArguments command)
        {
            if (command.Message.Length == 0 || !uint.TryParse(command.Message, out var subID))
            {
                command.Reply($"Usage:{Colors.OLIVE} sub <subid>");

                return;
            }

            var info = await GetPackageData(subID);

            if (info == null)
            {
                command.Reply($"Unknown SubID: {Colors.BLUE}{subID}{(LicenseList.OwnedSubs.ContainsKey(subID) ? SteamDB.StringCheckmark : string.Empty)}");

                return;
            }

            if (!info.KeyValues.Children.Any())
            {
                command.Reply($"No package info returned for SubID: {Colors.BLUE}{subID}{(info.MissingToken ? SteamDB.StringNeedToken : string.Empty)}{(LicenseList.OwnedSubs.ContainsKey(subID) ? SteamDB.StringCheckmark : string.Empty)}");

                return;
            }

            var filename = $"{Utils.ByteArrayToString(info.SHAHash)}.vdf";

            info.KeyValues.SaveToFile(Path.Combine(Application.Path, "sub", filename), false);

            command.Reply($"{Colors.BLUE}{Steam.GetPackageName(info.ID)}{Colors.NORMAL} -{Colors.DARKBLUE} <{SteamDB.GetPackageUrl(info.ID)}>{Colors.NORMAL} - Dump:{Colors.DARKBLUE} <{SteamDB.GetRawPackageUrl(filename)}>{Colors.NORMAL}{(info.MissingToken ? SteamDB.StringNeedToken : string.Empty)}{(LicenseList.OwnedSubs.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty)}");
        }
Ejemplo n.º 3
0
        private void OnDepotKeyCallback(SteamApps.DepotKeyCallback callback)
        {
            JobAction job;

            if (!JobManager.TryRemoveJob(callback.JobID, out job))
            {
                RemoveLock(callback.DepotID);

                return;
            }

            var request = job.ManifestJob;

            if (callback.Result != EResult.OK)
            {
                if (callback.Result != EResult.AccessDenied || FileDownloader.IsImportantDepot(request.DepotID))
                {
                    Log.WriteError("Depot Processor", "Failed to get depot key for depot {0} (parent {1}) - {2}", callback.DepotID, request.ParentAppID, callback.Result);
                }

                RemoveLock(request.DepotID);

                return;
            }

            request.DepotKey = callback.DepotKey;
            request.Tries    = CDNServers.Count;
            request.Server   = GetContentServer();

            JobManager.AddJob(() => Steam.Instance.Apps.GetCDNAuthToken(request.DepotID, request.Server), request);

            var decryptionKey = Utils.ByteArrayToString(callback.DepotKey);

            using (var db = Database.GetConnection())
            {
                var currentDecryptionKey = db.ExecuteScalar <string>("SELECT `Key` FROM `DepotsKeys` WHERE `DepotID` = @DepotID", new { callback.DepotID });

                if (decryptionKey != currentDecryptionKey)
                {
                    if (currentDecryptionKey != null)
                    {
                        Log.WriteInfo("Depot Processor", "Decryption key for {0} changed: {1} -> {2}", callback.DepotID, currentDecryptionKey, decryptionKey);

                        IRC.Instance.SendOps("Decryption key for {0} changed: {1} -> {2}", callback.DepotID, currentDecryptionKey, decryptionKey);
                    }

                    db.Execute("INSERT INTO `DepotsKeys` (`DepotID`, `Key`) VALUES (@DepotID, @Key) ON DUPLICATE KEY UPDATE `Key` = VALUES(`Key`)", new { callback.DepotID, Key = decryptionKey });
                }
            }
        }
Ejemplo n.º 4
0
        public override async Task OnCommand(CommandArguments command)
        {
            if (string.IsNullOrWhiteSpace(command.Message))
            {
                command.Reply($"Usage:{Colors.OLIVE} app <appid or partial game name>");

                return;
            }

            if (!uint.TryParse(command.Message, out var appID))
            {
                appID = await TrySearchAppId(command);

                if (appID == 0)
                {
                    return;
                }
            }

            var info = await GetAppData(appID);

            if (info == null)
            {
                command.Reply($"Unknown AppID: {Colors.BLUE}{appID}{(LicenseList.OwnedApps.ContainsKey(appID) ? SteamDB.StringCheckmark : string.Empty)}");

                return;
            }

            string name;

            if (info.KeyValues["common"]["name"].Value != null)
            {
                name = Utils.LimitStringLength(Utils.RemoveControlCharacters(info.KeyValues["common"]["name"].AsString()));
            }
            else
            {
                name = Steam.GetAppName(info.ID);
            }

            var filename = $"{Utils.ByteArrayToString(info.SHAHash)}.vdf";

            info.KeyValues.SaveToFile(Path.Combine(Application.Path, "app", filename), false);

            command.Reply($"{Colors.BLUE}{name}{Colors.NORMAL} -{Colors.DARKBLUE} <{SteamDB.GetAppUrl(info.ID)}>{Colors.NORMAL} - Dump:{Colors.DARKBLUE} <{SteamDB.GetRawAppUrl(filename)}>{Colors.NORMAL}{(info.MissingToken ? SteamDB.StringNeedToken : string.Empty)}{(LicenseList.OwnedApps.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty)}");

            if (command.Recipient == Settings.Current.IRC.Channel.Ops && !LicenseList.OwnedApps.ContainsKey(info.ID))
            {
                JobManager.AddJob(() => Steam.Instance.Apps.RequestFreeLicense(info.ID));
            }
        }
Ejemplo n.º 5
0
        private async Task <byte[]> GetDepotDecryptionKey(SteamApps instance, uint depotID, uint appID)
        {
            using (var db = Database.Get())
            {
                var currentDecryptionKey = await db.ExecuteScalarAsync <string>("SELECT `Key` FROM `DepotsKeys` WHERE `DepotID` = @DepotID", new { depotID });

                if (currentDecryptionKey != null)
                {
                    return(Utils.StringToByteArray(currentDecryptionKey));
                }
            }

            var task = instance.GetDepotDecryptionKey(depotID, appID);

            task.Timeout = TimeSpan.FromMinutes(15);

            SteamApps.DepotKeyCallback callback;

            try
            {
                callback = await task;
            }
            catch (TaskCanceledException)
            {
                Log.WriteError("Depot Processor", "Decryption key timed out for {0}", depotID);

                return(null);
            }

            if (callback.Result != EResult.OK)
            {
                if (callback.Result != EResult.AccessDenied)
                {
                    Log.WriteError("Depot Processor", "No access to depot {0} ({1})", depotID, callback.Result);
                }

                return(null);
            }

            Log.WriteDebug("Depot Downloader", "Got a new depot key for depot {0}", depotID);

            using (var db = Database.Get())
            {
                await db.ExecuteAsync("INSERT INTO `DepotsKeys` (`DepotID`, `Key`) VALUES (@DepotID, @Key) ON DUPLICATE KEY UPDATE `Key` = VALUES(`Key`)", new { depotID, Key = Utils.ByteArrayToString(callback.DepotKey) });
            }

            return(callback.DepotKey);
        }
Ejemplo n.º 6
0
        private async Task <EPurchaseResultDetail> ActivateKey(string key)
        {
            var msg = new ClientMsgProtobuf <CMsgClientRegisterKey>(EMsg.ClientRegisterKey)
            {
                SourceJobID = Steam.Instance.Client.GetNextJobID(),
                Body        =
                {
                    key = key
                }
            };

            Steam.Instance.Client.Send(msg);

            PurchaseResponseCallback job;

            try
            {
                job = await new AsyncJob <PurchaseResponseCallback>(Steam.Instance.Client, msg.SourceJobID);
            }
            catch (Exception)
            {
                return(EPurchaseResultDetail.Timeout);
            }

            await using (var db = await Database.GetConnectionAsync())
            {
                using var sha = new SHA1CryptoServiceProvider();
                await db.ExecuteAsync("UPDATE `SteamKeys` SET `SteamKey` = @HashedKey, `SubID` = @SubID, `Result` = @PurchaseResultDetail WHERE `SteamKey` = @SteamKey OR `SteamKey` = @HashedKey",
                                      new
                {
                    job.PurchaseResultDetail,
                    SubID     = job.Packages.Count > 0 ? (int)job.Packages.First().Key : -1,
                    SteamKey  = key,
                    HashedKey = Utils.ByteArrayToString(sha.ComputeHash(Encoding.ASCII.GetBytes(key)))
                });
            }

            if (job.Packages.Count == 0)
            {
                if (job.PurchaseResultDetail != EPurchaseResultDetail.BadActivationCode)
                {
                    IRC.Instance.SendOps($"{Colors.GREEN}[Keys]{Colors.NORMAL} Key not activated:{Colors.OLIVE} {job.Result} - {job.PurchaseResultDetail}");
                }

                return(job.PurchaseResultDetail);
            }

            if (job.PurchaseResultDetail != EPurchaseResultDetail.AlreadyPurchased &&
                job.PurchaseResultDetail != EPurchaseResultDetail.DuplicateActivationCode &&
                job.PurchaseResultDetail != EPurchaseResultDetail.DoesNotOwnRequiredApp)
            {
                var response = job.PurchaseResultDetail == EPurchaseResultDetail.NoDetail ?
                               $"{Colors.GREEN}Key activated" : $"{Colors.BLUE}{job.PurchaseResultDetail}";

                IRC.Instance.SendOps($"{Colors.GREEN}[Keys]{Colors.NORMAL} {response}{Colors.NORMAL}. Packages:{Colors.OLIVE} {string.Join(", ", job.Packages.Select(x => $"{x.Key}: {x.Value}"))}");
            }

            JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(Enumerable.Empty <uint>(), job.Packages.Keys));

            await using (var db = await Database.GetConnectionAsync())
            {
                var apps = await db.QueryAsync <uint>("SELECT `AppID` FROM `SubsApps` WHERE `Type` = \"app\" AND `SubID` IN @Ids", new { Ids = job.Packages.Keys });

                JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(apps, Enumerable.Empty <uint>()));

                foreach (var package in job.Packages)
                {
                    var databaseName = (await db.QueryAsync <string>("SELECT `LastKnownName` FROM `Subs` WHERE `SubID` = @SubID", new { SubID = package.Key })).FirstOrDefault() ?? string.Empty;

                    if (databaseName.Equals(package.Value, StringComparison.CurrentCultureIgnoreCase))
                    {
                        continue;
                    }

                    await db.ExecuteAsync("UPDATE `Subs` SET `LastKnownName` = @Name WHERE `SubID` = @SubID", new { SubID = package.Key, Name = package.Value });

                    await db.ExecuteAsync(SubProcessor.HistoryQuery,
                                          new PICSHistory
                    {
                        ID       = package.Key,
                        Key      = SteamDB.DATABASE_NAME_TYPE,
                        OldValue = "key activation",
                        NewValue = package.Value,
                        Action   = "created_info"
                    }
                                          );
                }
            }

            return(job.PurchaseResultDetail);
        }
Ejemplo n.º 7
0
        private async Task <EResult> ProcessDepotAfterDownload(IDbConnection db, IDbTransaction transaction, ManifestJob request, DepotManifest depotManifest)
        {
            var filesOld        = (await db.QueryAsync <DepotFile>("SELECT `ID`, `File`, `Hash`, `Size`, `Flags` FROM `DepotsFiles` WHERE `DepotID` = @DepotID", new { request.DepotID }, transaction: transaction)).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("Depot Processor", 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))
                    {
                        await MakeHistory(db, transaction, request, file.File, "modified", oldFile.Size, file.Size);

                        updateFile = true;
                    }

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

                        updateFile = true;
                    }

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

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

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

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

                await db.ExecuteAsync(HistoryQuery, filesOld.Select(x => new DepotHistory
                {
                    DepotID  = request.DepotID,
                    ChangeID = request.ChangeNumber,
                    Action   = "removed",
                    File     = x.Value.File
                }), transaction : transaction);
            }

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

                if (shouldHistorize)
                {
                    await db.ExecuteAsync(HistoryQuery, filesAdded.Select(x => new DepotHistory
                    {
                        DepotID  = request.DepotID,
                        ChangeID = request.ChangeNumber,
                        Action   = "added",
                        File     = x.File
                    }), transaction : transaction);
                }
            }

            await db.ExecuteAsync("UPDATE `Depots` SET `LastManifestID` = @ManifestID, `LastUpdated` = CURRENT_TIMESTAMP() WHERE `DepotID` = @DepotID", new { request.DepotID, request.ManifestID }, transaction : transaction);

            return(EResult.OK);
        }
Ejemplo n.º 8
0
        private async Task <EPurchaseResultDetail> ActivateKey(string key, uint id = 0)
        {
            var msg = new ClientMsgProtobuf <CMsgClientRegisterKey>(EMsg.ClientRegisterKey)
            {
                SourceJobID = Steam.Instance.Client.GetNextJobID(),
                Body        =
                {
                    key = key
                }
            };

            Steam.Instance.Client.Send(msg);

            PurchaseResponseCallback job;

            try
            {
                job = await new AsyncJob <PurchaseResponseCallback>(Steam.Instance.Client, msg.SourceJobID);
            }
            catch (Exception)
            {
                return(EPurchaseResultDetail.Timeout);
            }

            await using var db = await Database.GetConnectionAsync();

            if (id > 0)
            {
                using var sha = SHA1.Create();
                await db.ExecuteAsync(
                    "UPDATE `SteamKeys` SET `SteamKey` = @HashedKey, `SubID` = @SubID, `Result` = @Result WHERE `ID` = @ID",
                    new
                {
                    ID        = id,
                    Result    = job.PurchaseResultDetail,
                    SubID     = job.Packages.Count > 0 ? (int)job.Packages.First().Key : -1,
                    HashedKey = Utils.ByteArrayToString(sha.ComputeHash(Encoding.ASCII.GetBytes(key)))
                });
            }

            if (job.Packages.Count == 0)
            {
                if (job.PurchaseResultDetail != EPurchaseResultDetail.BadActivationCode &&
                    job.PurchaseResultDetail != EPurchaseResultDetail.DuplicateActivationCode &&
                    job.PurchaseResultDetail != EPurchaseResultDetail.RestrictedCountry)
                {
                    IRC.Instance.SendOps($"{Colors.GREEN}[Keys]{Colors.NORMAL} Key not activated:{Colors.OLIVE} {job.Result} - {job.PurchaseResultDetail}");
                }

                return(job.PurchaseResultDetail);
            }

            if (job.PurchaseResultDetail == EPurchaseResultDetail.NoDetail)
            {
                _ = TaskManager.Run(async() => await Utils.SendWebhook(new
                {
                    Type = "KeyActivated",
                    Key  = id,
                    job.Packages,
                }));
            }

            foreach (var(subid, name) in job.Packages)
            {
                var databaseName = (await db.QueryAsync <string>("SELECT `LastKnownName` FROM `Subs` WHERE `SubID` = @SubID", new { SubID = subid })).FirstOrDefault() ?? string.Empty;

                if (databaseName.Equals(name, StringComparison.CurrentCultureIgnoreCase))
                {
                    continue;
                }

                await db.ExecuteAsync("UPDATE `Subs` SET `LastKnownName` = @Name WHERE `SubID` = @SubID", new { SubID = subid, Name = name });

                await db.ExecuteAsync(SubProcessor.HistoryQuery,
                                      new PICSHistory
                {
                    ID       = subid,
                    Key      = SteamDB.DatabaseNameType,
                    OldValue = "key activation",
                    NewValue = name,
                    Action   = "created_info"
                }
                                      );
            }

            return(job.PurchaseResultDetail);
        }
Ejemplo n.º 9
0
        private static async void OnClanState(SteamFriends.ClanStateCallback callback)
        {
            if (callback.Events.Count == 0 && callback.Announcements.Count == 0)
            {
                return;
            }

            var groupName   = callback.ClanName ?? Steam.Instance.Friends.GetClanName(callback.ClanID);
            var groupAvatar = Utils.ByteArrayToString(callback.AvatarHash ?? Steam.Instance.Friends.GetClanAvatar(callback.ClanID) ?? System.Array.Empty <byte>()).ToLowerInvariant();

            if (string.IsNullOrEmpty(groupName))
            {
                groupName = "Group";
            }

            foreach (var announcement in callback.Announcements)
            {
                var url = $"https://steamcommunity.com/gid/{callback.ClanID.AccountID}/announcements/detail/{announcement.ID}";

                IRC.Instance.SendAnnounce($"{Colors.BLUE}{groupName}{Colors.NORMAL} announcement: {Colors.OLIVE}{announcement.Headline}{Colors.NORMAL} -{Colors.DARKBLUE} {url}");

                _ = TaskManager.Run(async() => await Utils.SendWebhook(new
                {
                    Type    = "GroupAnnouncement",
                    Title   = announcement.Headline,
                    Group   = groupName,
                    Avatar  = groupAvatar,
                    Url     = url,
                    GroupID = callback.ClanID.AccountID,
                }));

                Log.WriteInfo(nameof(ClanState), $"{groupName} \"{announcement.Headline}\"");
            }

            await using var db = await Database.GetConnectionAsync();

            foreach (var groupEvent in callback.Events)
            {
                var link = $"https://steamcommunity.com/gid/{callback.ClanID.AccountID}/events/{groupEvent.ID}";
                var id   = await db.ExecuteScalarAsync <int>("SELECT `ID` FROM `RSS` WHERE `Link` = @Link", new { Link = link });

                if (id > 0)
                {
                    continue;
                }

                IRC.Instance.SendAnnounce(
                    $"{Colors.BLUE}{groupName}{Colors.NORMAL} event: {Colors.OLIVE}{groupEvent.Headline}{Colors.NORMAL} -{Colors.DARKBLUE} {link} {Colors.DARKGRAY}({groupEvent.EventTime.ToString("s", CultureInfo.InvariantCulture).Replace("T", " ")})"
                    );

                Log.WriteInfo(nameof(ClanState), $"{groupName} Event \"{groupEvent.Headline}\" {link}");

                await db.ExecuteAsync("INSERT INTO `RSS` (`Link`, `Title`, `Date`) VALUES(@Link, @Title, @EventTime)", new
                {
                    Link  = link,
                    Title = groupEvent.Headline,
                    groupEvent.EventTime,
                });

                _ = TaskManager.Run(async() => await Utils.SendWebhook(new
                {
                    Type    = "GroupAnnouncement",
                    Title   = groupEvent.Headline,
                    Group   = groupName,
                    Avatar  = groupAvatar,
                    Url     = link,
                    GroupID = callback.ClanID.AccountID,
                }));
            }
        }
Ejemplo n.º 10
0
        private static async Task <EResult> ProcessDepotAfterDownload(IDbConnection db, IDbTransaction transaction, ManifestJob request, DepotManifest depotManifest)
        {
            var filesOld        = (await db.QueryAsync <DepotFile>("SELECT `File`, `Hash`, `Size`, `Flags` FROM `DepotsFiles` WHERE `DepotID` = @DepotID", new { request.DepotID }, transaction)).ToDictionary(x => x.File, x => x);
            var filesAdded      = new List <DepotFile>();
            var shouldHistorize = filesOld.Count > 0 && !depotManifest.FilenamesEncrypted; // Don't historize file additions if we didn't have any data before

            if (request.StoredFilenamesEncrypted && !depotManifest.FilenamesEncrypted)
            {
                Log.WriteInfo(nameof(DepotProcessor), $"Depot {request.DepotID} will decrypt stored filenames");

                var decryptedFilesOld = new Dictionary <string, DepotFile>();

                foreach (var file in filesOld.Values)
                {
                    var oldFile = file.File;
                    file.File = DecryptFilename(oldFile, request.DepotKey);

                    decryptedFilesOld.Add(file.File, file);

                    await db.ExecuteAsync("UPDATE `DepotsFiles` SET `File` = @File WHERE `DepotID` = @DepotID AND `File` = @OldFile", new
                    {
                        request.DepotID,
                        file.File,
                        OldFile = oldFile
                    }, transaction);
                }

                await MakeHistory(db, transaction, request, string.Empty, "files_decrypted");

                filesOld = decryptedFilesOld;
            }

            foreach (var file in depotManifest.Files.OrderByDescending(x => x.FileName))
            {
                var name = depotManifest.FilenamesEncrypted ? file.FileName.Replace("\n", "") : file.FileName.Replace('\\', '/');

                byte[] hash = null;

                // Store empty hashes as NULL (e.g. an empty file)
                if ((file.Flags & EDepotFileFlag.Directory) == 0 && file.FileHash.Length > 0 && file.FileHash.Any(c => c != 0))
                {
                    hash = file.FileHash;
                }

                // Limit path names to 260 characters (default windows max length)
                // File column is varchar(260) and not higher to prevent reducing performance
                // See https://stackoverflow.com/questions/1962310/importance-of-varchar-length-in-mysql-table/1962329#1962329
                // Until 2019 there hasn't been a single file that went over this limit, so far there has been only one
                // game with a big node_modules path, so we're safeguarding by limiting it.
                if (name.Length > 260)
                {
                    if (depotManifest.FilenamesEncrypted)
                    {
                        continue;
                    }

                    using var sha = SHA1.Create();
                    var nameHash = Utils.ByteArrayToString(sha.ComputeHash(Encoding.UTF8.GetBytes(name)));
                    name = $"{{SteamDB file name is too long}}/{nameHash}/...{name.Substring(name.Length - 150)}";
                }

                if (filesOld.ContainsKey(name))
                {
                    var oldFile    = filesOld[name];
                    var updateFile = false;

                    if (oldFile.Size != file.TotalSize || !Utils.ByteArrayEquals(hash, oldFile.Hash))
                    {
                        await MakeHistory(db, transaction, request, name, "modified", oldFile.Size, file.TotalSize);

                        updateFile = true;
                    }

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

                        updateFile = true;
                    }

                    if (updateFile)
                    {
                        await db.ExecuteAsync("UPDATE `DepotsFiles` SET `Hash` = @Hash, `Size` = @Size, `Flags` = @Flags WHERE `DepotID` = @DepotID AND `File` = @File", new DepotFile
                        {
                            DepotID = request.DepotID,
                            File    = name,
                            Hash    = hash,
                            Size    = file.TotalSize,
                            Flags   = file.Flags
                        }, transaction);
                    }

                    filesOld.Remove(name);
                }
                else
                {
                    // We want to historize modifications first, and only then deletions and additions
                    filesAdded.Add(new DepotFile
                    {
                        DepotID = request.DepotID,
                        Hash    = hash,
                        File    = name,
                        Size    = file.TotalSize,
                        Flags   = file.Flags
                    });
                }
            }

            if (filesOld.Count > 0)
            {
                // Chunk file deletion queries so it doesn't go over max_allowed_packet
                var filesOldChunks = filesOld.Select(x => x.Value.File).Split(1000);

                foreach (var filesOldChunk in filesOldChunks)
                {
                    await db.ExecuteAsync("DELETE FROM `DepotsFiles` WHERE `DepotID` = @DepotID AND `File` IN @Files",
                                          new
                    {
                        request.DepotID,
                        Files = filesOldChunk,
                    }, transaction);
                }

                if (shouldHistorize)
                {
                    await db.ExecuteAsync(HistoryQuery, filesOld.Select(x => new DepotHistory
                    {
                        DepotID    = request.DepotID,
                        ManifestID = request.ManifestID,
                        ChangeID   = request.ChangeNumber,
                        Action     = "removed",
                        File       = x.Value.File,
                        OldValue   = x.Value.Size
                    }), transaction);
                }
            }

            if (filesAdded.Count > 0)
            {
                await db.ExecuteAsync("INSERT INTO `DepotsFiles` (`DepotID`, `File`, `Hash`, `Size`, `Flags`) VALUES (@DepotID, @File, @Hash, @Size, @Flags)", filesAdded, transaction);

                if (shouldHistorize)
                {
                    await db.ExecuteAsync(HistoryQuery, filesAdded.Select(x => new DepotHistory
                    {
                        DepotID    = request.DepotID,
                        ManifestID = request.ManifestID,
                        ChangeID   = request.ChangeNumber,
                        Action     = "added",
                        File       = x.File,
                        NewValue   = x.Size
                    }), transaction);
                }
            }

            await db.ExecuteAsync(
                request.LastManifestID == request.ManifestID?
                "UPDATE `Depots` SET `LastManifestID` = @ManifestID, `ManifestDate` = @ManifestDate, `FilenamesEncrypted` = @FilenamesEncrypted, `SizeOriginal` = @SizeOriginal, `SizeCompressed` = @SizeCompressed WHERE `DepotID` = @DepotID" :
                "UPDATE `Depots` SET `LastManifestID` = @ManifestID, `ManifestDate` = @ManifestDate, `FilenamesEncrypted` = @FilenamesEncrypted, `SizeOriginal` = @SizeOriginal, `SizeCompressed` = @SizeCompressed, `LastUpdated` = CURRENT_TIMESTAMP() WHERE `DepotID` = @DepotID",
                new
            {
                request.DepotID,
                request.ManifestID,
                depotManifest.FilenamesEncrypted,
                ManifestDate   = depotManifest.CreationTime,
                SizeOriginal   = depotManifest.TotalUncompressedSize,
                SizeCompressed = depotManifest.TotalCompressedSize,
            }, transaction);

            return(EResult.OK);
        }
Ejemplo n.º 11
0
        private static async Task GetDepotDecryptionKey(SteamApps instance, ManifestJob depot, uint appID)
        {
            var task = instance.GetDepotDecryptionKey(depot.DepotID, appID);

            task.Timeout = TimeSpan.FromMinutes(15);

            SteamApps.DepotKeyCallback callback;

            try
            {
                callback = await task;
            }
            catch (TaskCanceledException)
            {
                Log.WriteWarn(nameof(DepotProcessor), $"Decryption key timed out for {depot.DepotID}");

                return;
            }

            if (callback.Result != EResult.OK)
            {
                if (callback.Result != EResult.AccessDenied)
                {
                    Log.WriteWarn(nameof(DepotProcessor), $"No access to depot {depot.DepotID} ({callback.Result})");
                }

                return;
            }

            Log.WriteDebug(nameof(DepotProcessor), $"Got a new depot key for depot {depot.DepotID}");

            await using (var db = await Database.GetConnectionAsync())
            {
                await db.ExecuteAsync("INSERT INTO `DepotsKeys` (`DepotID`, `Key`) VALUES (@DepotID, @Key) ON DUPLICATE KEY UPDATE `Key` = VALUES(`Key`)", new { depot.DepotID, Key = Utils.ByteArrayToString(callback.DepotKey) });
            }

            depot.DepotKey = callback.DepotKey;
        }
Ejemplo n.º 12
0
        private static async Task <EResult> ProcessDepotAfterDownload(IDbConnection db, IDbTransaction transaction, ManifestJob request, DepotManifest depotManifest)
        {
            var filesOld        = (await db.QueryAsync <DepotFile>("SELECT `ID`, `File`, `Hash`, `Size`, `Flags` FROM `DepotsFiles` WHERE `DepotID` = @DepotID", new { request.DepotID }, transaction: transaction)).ToDictionary(x => x.File, x => x);
            var filesAdded      = new List <DepotFile>();
            var shouldHistorize = filesOld.Count > 0; // Don't historize file additions if we didn't have any data before

            foreach (var file in depotManifest.Files.OrderByDescending(x => x.FileName))
            {
                var    name = file.FileName.Replace('\\', '/');
                byte[] hash = null;

                // Store empty hashes as NULL (e.g. an empty file)
                if (file.FileHash.Length > 0 && (file.Flags & EDepotFileFlag.Directory) == 0)
                {
                    for (var i = 0; i < file.FileHash.Length; ++i)
                    {
                        if (file.FileHash[i] != 0)
                        {
                            hash = file.FileHash;
                            break;
                        }
                    }
                }

                // Limit path names to 260 characters (default windows max length)
                // File column is varchar(260) and not higher to prevent reducing performance
                // See https://stackoverflow.com/questions/1962310/importance-of-varchar-length-in-mysql-table/1962329#1962329
                // Until 2019 there hasn't been a single file that went over this limit, so far there has been only one
                // game with a big node_modules path, so we're safeguarding by limiting it.
                if (name.Length > 260)
                {
                    using var sha = new System.Security.Cryptography.SHA1Managed();
                    var nameHash = Utils.ByteArrayToString(sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(name)));
                    name = $"{{SteamDB file name is too long}}/{nameHash}/...{name.Substring(name.Length - 150)}";
                }

                if (filesOld.ContainsKey(name))
                {
                    var oldFile    = filesOld[name];
                    var updateFile = false;

                    if (oldFile.Size != file.TotalSize || !Utils.IsEqualSHA1(hash, oldFile.Hash))
                    {
                        await MakeHistory(db, transaction, request, name, "modified", oldFile.Size, file.TotalSize);

                        updateFile = true;
                    }

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

                        updateFile = true;
                    }

                    if (updateFile)
                    {
                        await db.ExecuteAsync("UPDATE `DepotsFiles` SET `Hash` = @Hash, `Size` = @Size, `Flags` = @Flags WHERE `DepotID` = @DepotID AND `ID` = @ID", new DepotFile
                        {
                            ID      = oldFile.ID,
                            DepotID = request.DepotID,
                            Hash    = hash,
                            Size    = file.TotalSize,
                            Flags   = file.Flags
                        }, transaction : transaction);
                    }

                    filesOld.Remove(name);
                }
                else
                {
                    // We want to historize modifications first, and only then deletions and additions
                    filesAdded.Add(new DepotFile
                    {
                        DepotID = request.DepotID,
                        Hash    = hash,
                        File    = name,
                        Size    = file.TotalSize,
                        Flags   = file.Flags
                    });
                }
            }

            if (filesOld.Count > 0)
            {
                await db.ExecuteAsync("DELETE FROM `DepotsFiles` WHERE `DepotID` = @DepotID AND `ID` IN @Files", new { request.DepotID, Files = filesOld.Select(x => x.Value.ID) }, transaction : transaction);

                await db.ExecuteAsync(HistoryQuery, filesOld.Select(x => new DepotHistory
                {
                    DepotID    = request.DepotID,
                    ManifestID = request.ManifestID,
                    ChangeID   = request.ChangeNumber,
                    Action     = "removed",
                    File       = x.Value.File,
                    OldValue   = x.Value.Size
                }), transaction : transaction);
            }

            if (filesAdded.Count > 0)
            {
                await db.ExecuteAsync("INSERT INTO `DepotsFiles` (`DepotID`, `File`, `Hash`, `Size`, `Flags`) VALUES (@DepotID, @File, @Hash, @Size, @Flags)", filesAdded, transaction : transaction);

                if (shouldHistorize)
                {
                    await db.ExecuteAsync(HistoryQuery, filesAdded.Select(x => new DepotHistory
                    {
                        DepotID    = request.DepotID,
                        ManifestID = request.ManifestID,
                        ChangeID   = request.ChangeNumber,
                        Action     = "added",
                        File       = x.File,
                        NewValue   = x.Size
                    }), transaction : transaction);
                }
            }

            await db.ExecuteAsync("UPDATE `Depots` SET `LastManifestID` = @ManifestID, `LastUpdated` = CURRENT_TIMESTAMP() WHERE `DepotID` = @DepotID", new { request.DepotID, request.ManifestID }, transaction : transaction);

            return(EResult.OK);
        }