public static void OnMarketingMessage(SteamUser.MarketingMessageCallback callback)
        {
            foreach (var message in callback.Messages)
            {
                // TODO: Move this query outside this loop
                using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `ID` FROM `MarketingMessages` WHERE `ID` = @ID", new MySqlParameter("ID", message.ID)))
                {
                    if (Reader.Read())
                    {
                        continue;
                    }
                }

                if (message.Flags == EMarketingMessageFlags.None)
                {
                    IRC.SendMain("New marketing message:{0} {1}", Colors.DARK_BLUE, message.URL);
                }
                else
                {
                    IRC.SendMain("New marketing message:{0} {1} {2}({3})", Colors.DARK_BLUE, message.URL, Colors.DARK_GRAY, message.Flags.ToString().Replace("Platform", string.Empty));
                }

                DbWorker.ExecuteNonQuery("INSERT INTO `MarketingMessages` (`ID`, `Flags`) VALUES (@ID, @Flags)",
                                         new MySqlParameter("@ID", message.ID),
                                         new MySqlParameter("@Flags", message.Flags)
                                         );
            }
        }
Exemplo n.º 2
0
        private void OnLicenseListCallback(SteamApps.LicenseListCallback licenseList)
        {
            if (licenseList.Result != EResult.OK)
            {
                Log.WriteError("Steam", "Unable to get license list: {0}", licenseList.Result);

                return;
            }

            Log.WriteInfo("Steam", "{0} Licenses: {1}", licenseList.LicenseList.Count, string.Join(", ", licenseList.LicenseList.Select(lic => lic.PackageID)));

            var timeNow = DateTime.Now;

            foreach (var license in licenseList.LicenseList)
            {
                if (license.PackageID != 0 && !OwnedPackages.Contains(license.PackageID) && timeNow.Subtract(license.TimeCreated).TotalSeconds < 600)
                {
                    IRC.SendMain("New license granted: {0}{1}{2} -{3} {4} {5}({6}, {7})",
                                 Colors.OLIVE, SteamProxy.GetPackageName(license.PackageID), Colors.NORMAL,
                                 Colors.DARK_BLUE, SteamDB.GetPackageURL(license.PackageID), Colors.NORMAL,
                                 license.LicenseType, license.PaymentMethod
                                 );
                }

                OwnedPackages.Add(license.PackageID);
            }

            // TODO: Probably should handle deletions too, for OwnedPackages
        }
        private void OnSystemMessage(IPacketGCMsg packetMsg)
        {
            var msg = new ClientGCMsgProtobuf <CMsgSystemBroadcast>(packetMsg).Body;

            Log.WriteInfo(Name, "Message: {0}", msg.message);

            IRC.SendMain("{0}{1}{2} system message:{3} {4}", Colors.OLIVE, SteamProxy.GetAppName(AppID), Colors.NORMAL, Colors.OLIVE, msg.message);
        }
        private void OnVersionUpdate(IPacketGCMsg packetMsg)
        {
            var msg = new ClientGCMsgProtobuf <CMsgGCServerVersionUpdated>(packetMsg).Body;

            Log.WriteInfo(Name, "GC version changed ({0} -> {1})", LastVersion, msg.server_version);

            IRC.SendMain("{0}{1}{2} server version changed:{3} {4} {5}(from {6})", Colors.OLIVE, SteamProxy.GetAppName(AppID), Colors.NORMAL, Colors.OLIVE, msg.server_version, Colors.DARK_GRAY, LastVersion);

            LastVersion = (int)msg.server_version;
        }
Exemplo n.º 5
0
        private void OnLoggedOff(SteamUser.LoggedOffCallback callback)
        {
            Timer.Stop();

            Log.WriteInfo("Steam", "Logged out of Steam: {0}", callback.Result);

            IRC.SendMain("Logged out of Steam: {0}{1}{2}. See{3} http://steamstat.us", Colors.OLIVE, callback.Result, Colors.NORMAL, Colors.DARK_BLUE);
            IRC.SendEmoteAnnounce("logged out of Steam: {0}", callback.Result);

            GameCoordinator.UpdateStatus(0, callback.Result.ToString());
        }
Exemplo n.º 6
0
        private static void OnSillyCrashHandler(object sender, UnhandledExceptionEventArgs args)
        {
            var e = (Exception)args.ExceptionObject;

            Log.WriteError("Unhandled Exception", "{0} (is terminating: {1})\n{2}", e.Message, args.IsTerminating, e.StackTrace);

            if (args.IsTerminating)
            {
                IRC.SendMain("Hey, xPaw and Alram, I'm crashing over here!!");

                Cleanup();
            }
        }
        private void OnItemSchemaUpdate(IPacketGCMsg packetMsg)
        {
            var msg = new ClientGCMsgProtobuf <CMsgUpdateItemSchema>(packetMsg).Body;

            if (LastSchemaVersion != 0 && LastSchemaVersion != msg.item_schema_version)
            {
                Log.WriteInfo(Name, "Schema change from {0} to {1}", LastSchemaVersion, msg.item_schema_version);

                IRC.SendMain("{0}{1}{2} item schema updated: {3}{4}{5} -{6} {7}", Colors.OLIVE, SteamProxy.GetAppName(AppID), Colors.NORMAL, Colors.DARK_GRAY, msg.item_schema_version.ToString("X4"), Colors.NORMAL, Colors.DARK_BLUE, msg.items_game_url);
            }

            LastSchemaVersion = msg.item_schema_version;

#if DEBUG
            Log.WriteDebug(Name, msg.items_game_url);
#endif
        }
        private void OnWelcome(IPacketGCMsg packetMsg)
        {
            Timer.Stop();

            int version = -1;

            // TF2 GC is not in sync
            if (AppID == 440)
            {
                var msg = new ClientGCMsgProtobuf <SteamKit2.GC.TF2.Internal.CMsgServerWelcome>(packetMsg).Body;

                version = (int)msg.active_version;
            }
            else
            {
                var msg = new ClientGCMsgProtobuf <CMsgClientWelcome>(packetMsg).Body;

                version = (int)msg.version;
            }

            Log.WriteInfo(Name, "New GC session ({0} -> {1})", LastVersion, version);

            string message = string.Format("New {0}{1}{2} GC session", Colors.OLIVE, SteamProxy.GetAppName(AppID), Colors.NORMAL);

            if (LastVersion == -1 || LastVersion == version)
            {
                message += string.Format(" {0}(version {1})", Colors.DARK_GRAY, version);
            }
            else
            {
                message += string.Format(" {0}(version changed from {1} to {2})", Colors.DARK_GRAY, LastVersion, version);
            }

            if (LastVersion != -1 && (LastVersion != version || LastStatus != GCConnectionStatus.GCConnectionStatus_HAVE_SESSION))
            {
                IRC.SendMain(message);
            }

            IRC.SendAnnounce(message);

            LastVersion = version;
            LastStatus  = GCConnectionStatus.GCConnectionStatus_HAVE_SESSION;

            UpdateStatus(AppID, LastStatus.ToString());
        }
Exemplo n.º 9
0
        private void OnLoggedOn(SteamUser.LoggedOnCallback callback)
        {
            GameCoordinator.UpdateStatus(0, callback.Result.ToString());

            if (callback.Result == EResult.AccountLogonDenied)
            {
                Console.Write("STEAM GUARD! Please enter the auth code sent to the email at {0}: ", callback.EmailDomain);

                AuthCode = Console.ReadLine().Trim();

                return;
            }

            if (callback.Result != EResult.OK)
            {
                Log.WriteInfo("Steam", "Failed to login: {0}", callback.Result);

                IRC.SendEmoteAnnounce("failed to log in: {0}", callback.Result);

                Thread.Sleep(TimeSpan.FromSeconds(2));

                return;
            }

            Log.WriteInfo("Steam", "Logged in, current Valve time is {0}", callback.ServerTime.ToString("R"));

            IRC.SendMain("Logged in to Steam. Valve time: {0}{1}", Colors.DARK_GRAY, callback.ServerTime.ToString("R"));
            IRC.SendEmoteAnnounce("logged in.");

            if (Settings.IsFullRun)
            {
                if (PreviousChange == 1)
                {
                    GetPICSChanges();
                }
            }
            else
            {
                Timer.Start();
            }
        }
Exemplo n.º 10
0
        private void OnDisconnected(SteamClient.DisconnectedCallback callback)
        {
            if (!IsRunning)
            {
                Timer.Stop();

                Log.WriteInfo("Steam", "Disconnected from Steam");

                return;
            }

            if (Timer.Enabled)
            {
                IRC.SendMain("Disconnected from Steam. See{0} http://steamstat.us", Colors.DARK_BLUE);
            }

            Timer.Stop();

            GameCoordinator.UpdateStatus(0, EResult.NoConnection.ToString());

            if (SteamProxy.Instance.IRCRequests.Count > 0)
            {
                foreach (var request in SteamProxy.Instance.IRCRequests)
                {
                    CommandHandler.ReplyToCommand(request.Command, "{0}{1}{2}: Your request failed.", Colors.OLIVE, request.Command.Nickname, Colors.NORMAL);
                }

                SteamProxy.Instance.IRCRequests.Clear();
            }

            const uint RETRY_DELAY = 15;

            Log.WriteInfo("Steam", "Disconnected from Steam. Retrying in {0} seconds...", RETRY_DELAY);

            IRC.SendEmoteAnnounce("disconnected from Steam. Retrying in {0} seconds...", RETRY_DELAY);

            Thread.Sleep(TimeSpan.FromSeconds(RETRY_DELAY));

            Client.Connect();
        }
Exemplo n.º 11
0
        public void OnPICSChanges(SteamApps.PICSChangesCallback callback)
        {
            // Print any apps importants first
            var important = callback.AppChanges.Keys.Intersect(ImportantApps);

            if (important.Count() > 5)
            {
                IRC.SendMain("{0}{1}{2} important apps updated -{3} {4}", Colors.OLIVE, important.Count(), Colors.NORMAL, Colors.DARK_BLUE, SteamDB.GetChangelistURL(callback.CurrentChangeNumber));
            }
            else
            {
                foreach (var app in important)
                {
                    IRC.SendMain("Important app update: {0}{1}{2} -{3} {4}", Colors.OLIVE, GetAppName(app), Colors.NORMAL, Colors.DARK_BLUE, SteamDB.GetAppURL(app, "history"));
                }
            }

            // And then important packages
            important = callback.PackageChanges.Keys.Intersect(ImportantSubs);

            if (important.Count() > 5)
            {
                IRC.SendMain("{0}{1}{2} important packages updated -{3} {4}", Colors.OLIVE, important.Count(), Colors.NORMAL, Colors.DARK_BLUE, SteamDB.GetChangelistURL(callback.CurrentChangeNumber));
            }
            else
            {
                foreach (var package in important)
                {
                    IRC.SendMain("Important package update: {0}{1}{2} -{3} {4}", Colors.OLIVE, GetPackageName(package), Colors.NORMAL, Colors.DARK_BLUE, SteamDB.GetPackageURL(package, "history"));
                }
            }

            // Group apps and package changes by changelist, this will seperate into individual changelists
            var appGrouping     = callback.AppChanges.Values.GroupBy(a => a.ChangeNumber);
            var packageGrouping = callback.PackageChanges.Values.GroupBy(p => p.ChangeNumber);

            // Join apps and packages back together based on changelist number
            var changeLists = Utils.FullOuterJoin(appGrouping, packageGrouping, a => a.Key, p => p.Key, (a, p, key) => new
            {
                ChangeNumber = key,

                Apps     = a.ToList(),
                Packages = p.ToList(),
            },
                                                  new EmptyGrouping <uint, SteamApps.PICSChangesCallback.PICSChangeData>(),
                                                  new EmptyGrouping <uint, SteamApps.PICSChangesCallback.PICSChangeData>())
                              .OrderBy(c => c.ChangeNumber);

            foreach (var changeList in changeLists)
            {
                var appCount     = changeList.Apps.Count;
                var packageCount = changeList.Packages.Count;

                string Message = string.Format("Changelist {0}{1}{2} {3}({4:N0} apps and {5:N0} packages){6} -{7} {8}",
                                               Colors.OLIVE, changeList.ChangeNumber, Colors.NORMAL,
                                               Colors.DARK_GRAY, appCount, packageCount, Colors.NORMAL,
                                               Colors.DARK_BLUE, SteamDB.GetChangelistURL(changeList.ChangeNumber)
                                               );

                var changesCount = appCount + packageCount;

                if (changesCount >= 50)
                {
                    IRC.SendMain(Message);
                }

                IRC.SendAnnounce("{0}»{1} {2}", Colors.RED, Colors.NORMAL, Message);

                // If this changelist is very big, freenode will hate us forever if we decide to print all that stuff
                if (changesCount > 500)
                {
                    IRC.SendAnnounce("{0}  This changelist is too big to be printed in IRC, please view it on our website", Colors.RED);

                    continue;
                }

                string name;

                if (appCount > 0)
                {
                    foreach (var app in changeList.Apps)
                    {
                        name = GetAppName(app.ID, true);

                        if (string.IsNullOrEmpty(name))
                        {
                            name = string.Format("{0}{1}{2}", Colors.GREEN, app.ID, Colors.NORMAL);
                        }
                        else
                        {
                            name = string.Format("{0}{1}{2} - {3}", Colors.LIGHT_GRAY, app.ID, Colors.NORMAL, name);
                        }

                        IRC.SendAnnounce("  App: {0}{1}", name, app.NeedsToken ? StringNeedToken : string.Empty);
                    }
                }

                if (packageCount > 0)
                {
                    foreach (var package in changeList.Packages)
                    {
                        name = GetPackageName(package.ID, true);

                        if (string.IsNullOrEmpty(name))
                        {
                            name = string.Format("{0}{1}{2}", Colors.GREEN, package.ID, Colors.NORMAL);
                        }
                        else
                        {
                            name = string.Format("{0}{1}{2} - {3}", Colors.LIGHT_GRAY, package.ID, Colors.NORMAL, name);
                        }

                        IRC.SendAnnounce("  Package: {0}{1}{2}",
                                         name,
                                         package.NeedsToken ? StringNeedToken : string.Empty,
                                         Steam.Instance.OwnedPackages.Contains(package.ID) ? StringCheckmark : string.Empty
                                         );
                    }
                }
            }
        }
Exemplo n.º 12
0
        public void OnClanState(SteamFriends.ClanStateCallback callback)
        {
            if (callback.Events.Count == 0 && callback.Announcements.Count == 0)
            {
                return;
            }

            string groupName = callback.ClanName;
            string message;

            if (string.IsNullOrEmpty(groupName))
            {
                groupName = Steam.Instance.Friends.GetClanName(callback.ClanID);

                // Check once more, because that can fail too
                if (string.IsNullOrEmpty(groupName))
                {
                    groupName = "Group";

                    Log.WriteError("IRC Proxy", "ClanID: {0} - no group name", callback.ClanID);
                }
            }

            foreach (var announcement in callback.Announcements)
            {
                message = string.Format("{0}{1}{2} announcement: {3}{4}{5} -{6} http://steamcommunity.com/gid/{7}/announcements/detail/{8}",
                                        Colors.OLIVE, groupName, Colors.NORMAL,
                                        Colors.GREEN, announcement.Headline, Colors.NORMAL,
                                        Colors.DARK_BLUE, callback.ClanID, announcement.ID
                                        );

                IRC.SendMain(message);

                // Additionally send announcements to steamlug channel
                if (callback.ClanID.Equals(SteamLUG))
                {
                    IRC.SendSteamLUG(message);
                }

                Log.WriteInfo("Group Announcement", "{0} \"{1}\"", groupName, announcement.Headline);
            }

            foreach (var groupEvent in callback.Events)
            {
                if (groupEvent.JustPosted)
                {
                    message = string.Format("{0}{1}{2} event: {3}{4}{5} -{6} http://steamcommunity.com/gid/{7}/events/{8} {9}({10})",
                                            Colors.OLIVE, groupName, Colors.NORMAL,
                                            Colors.GREEN, groupEvent.Headline, Colors.NORMAL,
                                            Colors.DARK_BLUE, callback.ClanID, groupEvent.ID,
                                            Colors.DARK_GRAY, groupEvent.EventTime.ToString()
                                            );

                    // Send events only to steamlug channel
                    if (callback.ClanID.Equals(SteamLUG))
                    {
                        IRC.SendSteamLUG(message);
                    }
                    else
                    {
                        IRC.SendMain(message);
                    }

                    Log.WriteInfo("Group Announcement", "{0} Event \"{1}\"", groupName, groupEvent.Headline);
                }
            }
        }
Exemplo n.º 13
0
        private static void DownloadManifest(ManifestJob request)
        {
            DepotManifest depotManifest = null;
            string        lastError     = string.Empty;

            // CDN is very random, just keep trying
            for (var i = 0; i <= 5; i++)
            {
                try
                {
                    depotManifest = CDNClient.DownloadManifest(request.DepotID, request.ManifestID, request.Server, request.DepotKey, request.CDNToken);

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

            if (depotManifest == null)
            {
                Log.WriteError("Depot Processor", "Failed to download depot manifest for depot {0} (jobs still in queue: {1}) ({2}: {3})", request.DepotID, ManifestJobs.Count - 1, request.Server, lastError);

                if (SteamProxy.Instance.ImportantApps.Contains(request.ParentAppID))
                {
                    IRC.SendMain("Important depot update: {0}{1}{2} -{3} failed to download depot manifest", Colors.OLIVE, request.DepotName, Colors.NORMAL, Colors.RED);
                }

                return;
            }

            if (SteamProxy.Instance.ImportantApps.Contains(request.ParentAppID))
            {
                IRC.SendMain("Important depot update: {0}{1}{2} -{3} {4}", Colors.OLIVE, request.DepotName, Colors.NORMAL, Colors.DARK_BLUE, SteamDB.GetDepotURL(request.DepotID, "history"));
            }

            var sortedFiles = depotManifest.Files.OrderBy(f => f.FileName, StringComparer.OrdinalIgnoreCase);

            bool shouldHistorize = false;
            var  filesNew        = new List <DepotFile>();
            var  filesOld        = new Dictionary <string, DepotFile>();

            foreach (var file in sortedFiles)
            {
                System.Text.Encoding.UTF8.GetString(file.FileHash);

                var depotFile = new DepotFile
                {
                    Name   = file.FileName.Replace('\\', '/'),
                    Size   = file.TotalSize,
                    Chunks = file.Chunks.Count,
                    Flags  = (int)file.Flags
                };

                // TODO: Ideally we would check if filehash is not empty
                if (!file.Flags.HasFlag(EDepotFileFlag.Directory))
                {
                    depotFile.Hash = string.Concat(Array.ConvertAll(file.FileHash, x => x.ToString("X2")));
                }

                filesNew.Add(depotFile);
            }

            using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `Files` FROM `Depots` WHERE `DepotID` = @DepotID LIMIT 1", new MySqlParameter("DepotID", request.DepotID)))
            {
                if (Reader.Read())
                {
                    string files = Reader.GetString("Files");

                    if (!string.IsNullOrEmpty(files))
                    {
                        shouldHistorize = true;

                        var _filesOld = JsonConvert.DeserializeObject <List <DepotFile> >(files);

                        filesOld = _filesOld.ToDictionary(x => x.Name);
                    }
                }
            }

            DbWorker.ExecuteNonQuery("UPDATE `Depots` SET `Files` = @Files WHERE `DepotID` = @DepotID",
                                     new MySqlParameter("@DepotID", request.DepotID),
                                     new MySqlParameter("@Files", JsonConvert.SerializeObject(filesNew, new JsonSerializerSettings {
                DefaultValueHandling = DefaultValueHandling.Ignore
            }))
                                     );

            if (shouldHistorize)
            {
                var filesAdded = new List <string>();

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

                        if (oldFile.Size != file.Size)
                        {
                            MakeHistory(request, file.Name, "modified", oldFile.Size, file.Size);
                        }
                        else if (file.Hash != null && oldFile.Hash != null && !file.Hash.Equals(oldFile.Hash))
                        {
                            MakeHistory(request, file.Name, "modified", oldFile.Size, file.Size);
                        }

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

                foreach (var file in filesOld)
                {
                    MakeHistory(request, file.Value.Name, "removed");
                }

                foreach (string file in filesAdded)
                {
                    MakeHistory(request, file, "added");
                }
            }

            lock (ManifestJobs)
            {
                Log.WriteDebug("Depot Processor", "DepotID: Processed {0} (jobs still in queue: {1})", request.DepotID, ManifestJobs.Count - 1);
            }
        }
Exemplo n.º 14
0
        private void TryProcess(SteamApps.PICSProductInfoCallback.PICSProductInfo productInfo)
        {
            string packageName = string.Empty;
            var    apps        = new Dictionary <uint, string>();

            using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `Name`, `Value` FROM `SubsInfo` INNER JOIN `KeyNamesSubs` ON `SubsInfo`.`Key` = `KeyNamesSubs`.`ID` WHERE `SubID` = @SubID", new MySqlParameter("@SubID", SubID)))
            {
                while (Reader.Read())
                {
                    CurrentData.Add(DbWorker.GetString("Name", Reader), DbWorker.GetString("Value", Reader));
                }
            }

            using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `Name` FROM `Subs` WHERE `SubID` = @SubID LIMIT 1", new MySqlParameter("@SubID", SubID)))
            {
                if (Reader.Read())
                {
                    packageName = DbWorker.GetString("Name", Reader);
                }
            }

            using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `AppID`, `Type` FROM `SubsApps` WHERE `SubID` = @SubID", new MySqlParameter("@SubID", SubID)))
            {
                while (Reader.Read())
                {
                    apps.Add(Reader.GetUInt32("AppID"), Reader.GetString("Type"));
                }
            }

            var kv = productInfo.KeyValues.Children.FirstOrDefault();

            if (kv["name"].Value != null)
            {
                if (string.IsNullOrEmpty(packageName))
                {
                    DbWorker.ExecuteNonQuery("INSERT INTO `Subs` (`SubID`, `Name`) VALUES (@SubID, @Name) ON DUPLICATE KEY UPDATE `Name` = @Name",
                                             new MySqlParameter("@SubID", SubID),
                                             new MySqlParameter("@Name", kv["name"].Value)
                                             );

                    MakeHistory("created_sub");
                    MakeHistory("created_info", DATABASE_NAME_TYPE, string.Empty, kv["name"].Value);
                }
                else if (!packageName.Equals(kv["name"].Value))
                {
                    DbWorker.ExecuteNonQuery("UPDATE `Subs` SET `Name` = @Name WHERE `SubID` = @SubID",
                                             new MySqlParameter("@SubID", SubID),
                                             new MySqlParameter("@Name", kv["name"].Value)
                                             );

                    MakeHistory("modified_info", DATABASE_NAME_TYPE, packageName, kv["name"].Value);
                }
            }

            foreach (KeyValue section in kv.Children)
            {
                string sectionName = section.Name.ToLower();

                if (string.IsNullOrEmpty(sectionName) || sectionName.Equals("packageid") || sectionName.Equals("name"))
                {
                    // Ignore common keys
                    continue;
                }

                if (sectionName.Equals("appids") || sectionName.Equals("depotids"))
                {
                    string type = sectionName.Replace("ids", string.Empty); // Remove "ids", so we get app from appids and depot from depotids

                    uint typeID = (uint)(type.Equals("app") ? 0 : 1);       // TODO: Remove legacy 0/1 and replace with type

                    foreach (KeyValue childrenApp in section.Children)
                    {
                        uint appID = uint.Parse(childrenApp.Value);

                        // Is this appid already in this package?
                        if (apps.ContainsKey(appID))
                        {
                            // Is this appid's type the same?
                            if (apps[appID] != type)
                            {
                                DbWorker.ExecuteNonQuery("UPDATE `SubsApps` SET `Type` = @Type WHERE `SubID` = @SubID AND `AppID` = @AppID",
                                                         new MySqlParameter("@SubID", SubID),
                                                         new MySqlParameter("@AppID", appID),
                                                         new MySqlParameter("@Type", type)
                                                         );

                                MakeHistory("added_to_sub", typeID, apps[appID].Equals("app") ? "0" : "1", childrenApp.Value);
                            }

                            apps.Remove(appID);
                        }
                        else
                        {
                            DbWorker.ExecuteNonQuery("INSERT INTO `SubsApps` (`SubID`, `AppID`, `Type`) VALUES(@SubID, @AppID, @Type) ON DUPLICATE KEY UPDATE `Type` = @Type",
                                                     new MySqlParameter("@SubID", SubID),
                                                     new MySqlParameter("@AppID", appID),
                                                     new MySqlParameter("@Type", type)
                                                     );

                            MakeHistory("added_to_sub", typeID, string.Empty, childrenApp.Value);
                            AppProcessor.MakeHistory(appID, ChangeNumber, "added_to_sub", typeID, string.Empty, SubID.ToString());

                            if (SteamProxy.Instance.ImportantApps.Contains(appID))
                            {
                                IRC.SendMain("Important app {0}{1}{2} was added to package {3}{4}{5} -{6} {7}",
                                             Colors.OLIVE, SteamProxy.GetAppName(appID), Colors.NORMAL,
                                             Colors.OLIVE, packageName, Colors.NORMAL,
                                             Colors.DARK_BLUE, SteamDB.GetPackageURL(SubID, "history")
                                             );
                            }
                        }
                    }
                }
                else if (sectionName.Equals("extended"))
                {
                    string keyName;

                    foreach (KeyValue children in section.Children)
                    {
                        if (children.Children.Count > 0)
                        {
                            Log.WriteError("Sub Processor", "SubID {0} has childen in extended section", SubID);
                        }

                        keyName = string.Format("{0}_{1}", sectionName, children.Name);

                        ProcessKey(keyName, children.Name, children.Value);
                    }
                }
                else if (section.Children.Count > 0)
                {
                    sectionName = string.Format("root_{0}", sectionName);

                    ProcessKey(sectionName, sectionName, DbWorker.JsonifyKeyValue(section), true);
                }
                else if (!string.IsNullOrEmpty(section.Value))
                {
                    string keyName = string.Format("root_{0}", sectionName);

                    ProcessKey(keyName, sectionName, section.Value);
                }
            }

            foreach (string keyName in CurrentData.Keys)
            {
                if (!keyName.StartsWith("website", StringComparison.Ordinal))
                {
                    uint ID = GetKeyNameID(keyName);

                    DbWorker.ExecuteNonQuery("DELETE FROM `SubsInfo` WHERE `SubID` = @SubID AND `Key` = @KeyNameID",
                                             new MySqlParameter("@SubID", SubID),
                                             new MySqlParameter("@KeyNameID", ID)
                                             );

                    MakeHistory("removed_key", ID, CurrentData[keyName]);
                }
            }

            foreach (var app in apps)
            {
                DbWorker.ExecuteNonQuery("DELETE FROM `SubsApps` WHERE `SubID` = @SubID AND `AppID` = @AppID AND `Type` = @Type",
                                         new MySqlParameter("@SubID", SubID),
                                         new MySqlParameter("@AppID", app.Key),
                                         new MySqlParameter("@Type", app.Value)
                                         );

                uint typeID = (uint)(app.Value.Equals("app") ? 0 : 1); // TODO: Remove legacy 0/1 and replace with type

                MakeHistory("removed_from_sub", typeID, app.Key.ToString());
                AppProcessor.MakeHistory(app.Key, ChangeNumber, "removed_from_sub", typeID, SubID.ToString());

                if (SteamProxy.Instance.ImportantApps.Contains(app.Key))
                {
                    IRC.SendMain("Important app {0}{1}{2} was removed from package {3}{4}{5} -{6} {7}",
                                 Colors.OLIVE, SteamProxy.GetAppName(app.Key), Colors.NORMAL,
                                 Colors.OLIVE, packageName, Colors.NORMAL,
                                 Colors.DARK_BLUE, SteamDB.GetPackageURL(SubID, "history")
                                 );
                }
            }

#if DEBUG
            if (kv["name"].Value == null)
            {
                if (string.IsNullOrEmpty(packageName)) // We don't have the package in our database yet
                {
                    // Don't do anything then
                    Log.WriteError("Sub Processor", "Got a package without a name, and we don't have it in our database: {0}", SubID);
                }
                else
                {
                    ////MakeHistory("deleted_sub", "0", packageName, "", true);

                    Log.WriteError("Sub Processor", "Got a package without a name, but we have it in our database: {0}", SubID);
                }
            }
#endif
        }