This handler is used for interacting with apps and packages on the Steam network.
Inheritance: ClientMsgHandler
Esempio n. 1
0
        private void OnFreeLicenseCallback(SteamApps.FreeLicenseCallback callback)
        {
            JobManager.TryRemoveJob(callback.JobID);

            var packageIDs = callback.GrantedPackages;
            var appIDs = callback.GrantedApps;

            Log.WriteDebug("FreeLicense", "Received free license: {0} ({1} apps: {2}, {3} packages: {4})",
                callback.Result, appIDs.Count, string.Join(", ", appIDs), packageIDs.Count, string.Join(", ", packageIDs));

            if (appIDs.Any())
            {
                JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appIDs, Enumerable.Empty<uint>()));
            }

            if (packageIDs.Any())
            {
                JobManager.AddJob(() => Steam.Instance.Apps.PICSGetProductInfo(Enumerable.Empty<uint>(), packageIDs));

                TaskManager.Run(() =>
                {
                    RefreshPackageNames();

                    foreach (var subID in packageIDs)
                    {
                        IRC.Instance.SendMain("New free license granted: {0}{1}{2} -{3} {4}",
                            Colors.BLUE, Steam.GetPackageName(subID), Colors.NORMAL,
                            Colors.DARKBLUE, SteamDB.GetPackageURL(subID)
                        );
                    }
                });
            }
        }
        public SteamCdnClientPool(SteamContentClient steamContentClient, SteamContentServerQualityProvider steamContentServerQualityProvider)
        {
            _steamApps          = steamContentClient.SteamClient.InternalClient.GetHandler <SteamKit2.SteamApps>();
            _steamContentClient = steamContentClient;
            _steamContentServerQualityProvider = steamContentServerQualityProvider;

            _steamContentServerQualities = _steamContentServerQualityProvider.Load() ?? new List <SteamContentServerQuality>();
        }
        private void OnPICSChangesFullRun(SteamApps.PICSChangesCallback callback)
        {
            PreviousChangeNumber = 2;

            Log.WriteInfo("PICSChanges", "Requesting info for {0} apps and {1} packages", callback.AppChanges.Count, callback.PackageChanges.Count);

            JobManager.AddJob(() => Steam.Instance.Apps.PICSGetProductInfo(Enumerable.Empty<SteamApps.PICSRequest>(), callback.PackageChanges.Keys.Select(package => Utils.NewPICSRequest(package))));
            JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(callback.AppChanges.Keys, Enumerable.Empty<uint>()));
        }
Esempio n. 4
0
        static void Main( string[] args )
        {
            if ( args.Length < 2 )
            {
                Console.WriteLine( "Sample9: No username and password specified!" );
                return;
            }

            // save our logon details
            user = args[ 0 ];
            pass = args[ 1 ];

            // create our steamclient instance
            steamClient = new SteamClient();
            // create the callback manager which will route callbacks to function calls
            manager = new CallbackManager( steamClient );

            // get the steamuser handler, which is used for logging on after successfully connecting
            steamUser = steamClient.GetHandler<SteamUser>();

            // get our steamapps handler, we'll use this as an example of how async jobs can be handled
            steamApps = steamClient.GetHandler<SteamApps>();

            // register a few callbacks we're interested in
            // these are registered upon creation to a callback manager, which will then route the callbacks
            // to the functions specified
            manager.Subscribe<SteamClient.ConnectedCallback>( OnConnected );
            manager.Subscribe<SteamClient.DisconnectedCallback>( OnDisconnected );

            manager.Subscribe<SteamUser.LoggedOnCallback>( OnLoggedOn );
            manager.Subscribe<SteamUser.LoggedOffCallback>( OnLoggedOff );

            // notice that we're not subscribing to the SteamApps.PICSProductInfoCallback callback here (or other SteamApps callbacks)
            // since this sample is using the async job directly, we no longer need to subscribe to the callback.
            // however, if we still wish to use callbacks (or have existing code which subscribes to callbacks, they will
            // continue to operate alongside direct async job handling. (i.e.: steamclient will still post callbacks for
            // any async jobs that are completed)

            isRunning = true;

            Console.WriteLine( "Connecting to Steam..." );

            // initiate the connection
            steamClient.Connect();

            // create our callback handling loop
            while ( isRunning )
            {
                // in order for the callbacks to get routed, they need to be handled by the manager
                manager.RunWaitCallbacks( TimeSpan.FromSeconds( 1 ) );
            }
        }
Esempio n. 5
0
        public Steam3Session( SteamUser.LogOnDetails details )
        {
            this.logonDetails = details;

            this.authenticatedUser = details.Username != null;
            this.credentials = new Credentials();
            this.bConnected = false;
            this.bConnecting = false;
            this.bAborted = false;
            this.seq = 0;

            this.AppTickets = new Dictionary<uint, byte[]>();
            this.AppTokens = new Dictionary<uint, ulong>();
            this.DepotKeys = new Dictionary<uint, byte[]>();
            this.CDNAuthTokens = new Dictionary<Tuple<uint, string>, SteamApps.CDNAuthTokenCallback>();
            this.AppInfo = new Dictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo>();
            this.PackageInfo = new Dictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo>();

            this.steamClient = new SteamClient();

            this.steamUser = this.steamClient.GetHandler<SteamUser>();
            this.steamApps = this.steamClient.GetHandler<SteamApps>();

            this.callbacks = new CallbackManager(this.steamClient);

            this.callbacks.Subscribe<SteamClient.ConnectedCallback>(ConnectedCallback);
            this.callbacks.Subscribe<SteamClient.DisconnectedCallback>(DisconnectedCallback);
            this.callbacks.Subscribe<SteamUser.LoggedOnCallback>(LogOnCallback);
            this.callbacks.Subscribe<SteamUser.SessionTokenCallback>(SessionTokenCallback);
            this.callbacks.Subscribe<SteamApps.LicenseListCallback>(LicenseListCallback);
            this.callbacks.Subscribe<SteamUser.UpdateMachineAuthCallback>(UpdateMachineAuthCallback);

            Console.Write( "Connecting to Steam3..." );

            if ( authenticatedUser )
            {
                FileInfo fi = new FileInfo(String.Format("{0}.sentryFile", logonDetails.Username));
                if (ConfigStore.TheConfig.SentryData != null && ConfigStore.TheConfig.SentryData.ContainsKey(logonDetails.Username))
                {
                    logonDetails.SentryFileHash = Util.SHAHash(ConfigStore.TheConfig.SentryData[logonDetails.Username]);
                }
                else if (fi.Exists && fi.Length > 0)
                {
                    var sentryData = File.ReadAllBytes(fi.FullName);
                    logonDetails.SentryFileHash = Util.SHAHash(sentryData);
                    ConfigStore.TheConfig.SentryData[logonDetails.Username] = sentryData;
                    ConfigStore.Save();
                }
            }

            Connect();
        }
        private void OnPICSChanges(SteamApps.PICSChangesCallback callback)
        {
            if (PreviousChangeNumber == callback.CurrentChangeNumber)
            {
                return;
            }

            if (Application.ProcessorPool.IsIdle)
            {
                Log.WriteDebug("PICSChanges", "Cleaning processed {0} apps and {1} subs", Application.ProcessedApps.Count, Application.ProcessedSubs.Count);

                // TODO: Do we really need to clear? Find a better solution for this
                Application.ProcessedApps.Clear();
                Application.ProcessedSubs.Clear();
            }

            var packageChangesCount = callback.PackageChanges.Count;
            var appChangesCount = callback.AppChanges.Count;

            Log.WriteInfo("PICSChanges", "Changelist {0} -> {1} ({2} apps, {3} packages)", PreviousChangeNumber, callback.CurrentChangeNumber, appChangesCount, packageChangesCount);

            PreviousChangeNumber = callback.CurrentChangeNumber;

            DbWorker.ExecuteNonQuery("INSERT INTO `Changelists` (`ChangeID`) VALUES (@ChangeID) ON DUPLICATE KEY UPDATE `Date` = CURRENT_TIMESTAMP()", new MySqlParameter("@ChangeID", callback.CurrentChangeNumber));

            if (appChangesCount == 0 && packageChangesCount == 0)
            {
                IRC.Instance.SendAnnounce("{0}»{1} Changelist {2}{3}{4} (empty)", Colors.RED, Colors.NORMAL, Colors.OLIVE, PreviousChangeNumber, Colors.DARKGRAY);

                return;
            }

            if (appChangesCount > 0)
            {
                JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(callback.AppChanges.Keys, Enumerable.Empty<uint>()));

                Application.SecondaryPool.QueueWorkItem(HandleApps, callback, WorkItemPriority.AboveNormal);
            }

            if (packageChangesCount > 0)
            {
                JobManager.AddJob(() => Steam.Instance.Apps.PICSGetProductInfo(Enumerable.Empty<SteamApps.PICSRequest>(), callback.PackageChanges.Keys.Select(package => Utils.NewPICSRequest(package))));

                Application.SecondaryPool.QueueWorkItem(HandlePackages, callback, WorkItemPriority.AboveNormal);
            }

            Application.SecondaryPool.QueueWorkItem(SendChangelistsToIRC, callback);

            PrintImportants(callback);
        }
Esempio n. 7
0
        void OnAppChanges( SteamApps.AppChangesCallback callback )
        {
            if ( lastChangeNumber == callback.CurrentChangeNumber )
                return;

            lastChangeNumber = callback.CurrentChangeNumber;

            IRC.Instance.SendAnnounce( "Got AppInfo changelist {0} with info for {1} apps! (fullupdate? {2})",
                lastChangeNumber, callback.AppIDs.Count, callback.ForceFullUpdate );

            if ( callback.AppIDs.Count > 0 )
            {
                IRC.Instance.SendAnnounce( "AppInfo Apps: {0}", string.Join( ", ", callback.AppIDs ) );
            }
        }
Esempio n. 8
0
        private static void OnPICSTokens(SteamApps.PICSTokensCallback callback)
        {
            if (!JobManager.TryRemoveJob(callback.JobID))
            {
                Log.WriteDebug("PICSTokens", "Got tokens, but we had no job");

                return;
            }

            Log.WriteDebug("PICSTokens", "Tokens granted: {0} - Tokens denied: {1}", callback.AppTokens.Count, callback.AppTokensDenied.Count);

            var apps = callback.AppTokensDenied
                .Select(app => Utils.NewPICSRequest(app))
                .Concat(callback.AppTokens.Select(app => Utils.NewPICSRequest(app.Key, app.Value)));
            
            JobManager.AddJob(() => Steam.Instance.Apps.PICSGetProductInfo(apps, Enumerable.Empty<SteamApps.PICSRequest>()));
        }
        private static void OnLicenseListCallback(SteamApps.LicenseListCallback licenseList)
        {
            if (licenseList.Result != EResult.OK)
            {
                Log.WriteError("LicenseList", "Failed: {0}", licenseList.Result);

                return;
            }

            Log.WriteInfo("LicenseList", "Received {0} licenses from Steam", licenseList.LicenseList.Count);

            if (!licenseList.LicenseList.Any())
            {
                Application.OwnedSubs.Clear();
                Application.OwnedApps.Clear();

                return;
            }

            var ownedSubs = new Dictionary<uint, byte>();
            var ownedApps = new Dictionary<uint, byte>();

            foreach (var license in licenseList.LicenseList)
            {
                // For some obscure reason license list can contain duplicates
                if (ownedSubs.ContainsKey(license.PackageID))
                {
                    Log.WriteError("LicenseList", "Already contains {0} ({1})", license.PackageID, license.PaymentMethod);

                    continue;
                }

                ownedSubs.Add(license.PackageID, (byte)license.PaymentMethod);
            }

            using (MySqlDataReader Reader = DbWorker.ExecuteReader(string.Format("SELECT DISTINCT `AppID` FROM `SubsApps` WHERE `SubID` IN ({0})", string.Join(", ", ownedSubs.Keys))))
            {
                while (Reader.Read())
                {
                    ownedApps.Add(Reader.GetUInt32("AppID"), 1);
                }
            }

            Application.OwnedSubs = ownedSubs;
            Application.OwnedApps = ownedApps;
        }
Esempio n. 10
0
        public SteamKit(ARKUpdater p, AutoResetEvent r)
        {
            this._Parent = p;
            this._ResetEvent = r;

            this.Ready = false;
            this.Failed = false;
            this._ThreadRunning = true;

            this._Client = new SteamClient();
            this._CManager = new CallbackManager(this._Client);

            this._User = this._Client.GetHandler<SteamUser>();
            this._Apps = this._Client.GetHandler<SteamApps>();

            this.SubscribeCallbacks();
            this._Client.Connect();
        }
Esempio n. 11
0
        public Steam3Session( SteamUser.LogOnDetails details )
        {
            this.logonDetails = details;

            this.authenticatedUser = details.Username != null;
            this.credentials = new Credentials();
            this.bConnected = false;
            this.bAborted = false;

            this.AppTickets = new Dictionary<uint, byte[]>();
            this.AppTokens = new Dictionary<uint, ulong>();
            this.DepotKeys = new Dictionary<uint, byte[]>();
            this.AppInfo = new Dictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo>();
            this.PackageInfo = new Dictionary<uint, SteamApps.PICSProductInfoCallback.PICSProductInfo>();
            this.AppInfoOverridesCDR = new Dictionary<uint, bool>();

            this.steamClient = new SteamClient();

            this.steamUser = this.steamClient.GetHandler<SteamUser>();
            this.steamApps = this.steamClient.GetHandler<SteamApps>();

            this.callbacks = new CallbackManager(this.steamClient);

            this.callbacks.Register(new Callback<SteamClient.ConnectedCallback>(ConnectedCallback));
            this.callbacks.Register(new Callback<SteamClient.DisconnectedCallback>(DisconnectedCallback));
            this.callbacks.Register(new Callback<SteamUser.LoggedOnCallback>(LogOnCallback));
            this.callbacks.Register(new Callback<SteamUser.SessionTokenCallback>(SessionTokenCallback));
            this.callbacks.Register(new Callback<SteamApps.LicenseListCallback>(LicenseListCallback));
            this.callbacks.Register(new JobCallback<SteamUser.UpdateMachineAuthCallback>(UpdateMachineAuthCallback));

            Console.Write( "Connecting to Steam3..." );

            if ( authenticatedUser )
            {
                FileInfo fi = new FileInfo(String.Format("{0}.sentryFile", logonDetails.Username));
                if (fi.Exists && fi.Length > 0)
                {
                    logonDetails.SentryFileHash = Util.SHAHash(File.ReadAllBytes(fi.FullName));
                }
            }

            Connect();
        }
Esempio n. 12
0
        public void Process(SteamApps.PICSProductInfoCallback.PICSProductInfo productInfo)
        {
            ChangeNumber = productInfo.ChangeNumber;

            #if !DEBUG
            if (Settings.Current.FullRun > 0)
            #endif
            {
                Log.WriteDebug("App Processor", "AppID: {0}", AppID);
            }

            try
            {
                TryProcess(productInfo);
            }
            catch (Exception e)
            {
                Log.WriteError("App Processor", "Caught exception while processing app {0}: {1}\n{2}", AppID, e.Message, e.StackTrace);
            }
        }
Esempio n. 13
0
        private void InitializeClient()
        {
            // client/manager creation
            Client  = new SteamClient();
            Manager = new CallbackManager(Client);

            _user = Client.GetHandler <SteamUser>();
            _apps = Client.GetHandler <SteamApps>();

            Client.AddHandler(new SteamTicketAuth());

            // subscriptions
            Manager.Subscribe <SteamClient.DisconnectedCallback>(OnDisconnected);
            Manager.Subscribe <SteamClient.ConnectedCallback>(OnConnected);
            Manager.Subscribe <SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
            Manager.Subscribe <SteamUser.LoggedOnCallback>(OnLoggedOn);


            // internal subs
            Manager.Subscribe <SteamApps.GameConnectTokensCallback>(OnGcTokens);
        }
        private static void OnPICSTokens(SteamApps.PICSTokensCallback callback)
        {
            Log.WriteDebug("Steam", "Tokens granted: {0} - Tokens denied: {1}", callback.AppTokens.Count, callback.AppTokensDenied.Count);

            var apps = callback.AppTokensDenied
                .Select(app => Utils.NewPICSRequest(app))
                .Concat(callback.AppTokens.Select(app => Utils.NewPICSRequest(app.Key, app.Value)));

            Func<JobID> func = () => Steam.Instance.Apps.PICSGetProductInfo(apps, Enumerable.Empty<SteamApps.PICSRequest>());

            JobAction job;

            // We have to preserve CommandRequest between jobs
            if (JobManager.TryRemoveJob(callback.JobID, out job) && job.IsCommand)
            {
                JobManager.AddJob(func, job.CommandRequest);

                return;
            }

            JobManager.AddJob(func);
        }
Esempio n. 15
0
        private static void OnLicenseListCallback(SteamApps.LicenseListCallback licenseList)
        {
            if (licenseList.Result != EResult.OK)
            {
                Log.WriteError("LicenseList", "Failed: {0}", licenseList.Result);

                return;
            }

            Log.WriteInfo("LicenseList", "Received {0} licenses from Steam", licenseList.LicenseList.Count);

            if (!licenseList.LicenseList.Any())
            {
                OwnedSubs.Clear();
                OwnedApps.Clear();

                return;
            }

            var ownedSubs = new Dictionary<uint, byte>();

            foreach (var license in licenseList.LicenseList)
            {
                // For some obscure reason license list can contain duplicates
                if (ownedSubs.ContainsKey(license.PackageID))
                {
                    Log.WriteWarn("LicenseList", "Already contains {0} ({1})", license.PackageID, license.PaymentMethod);

                    continue;
                }

                ownedSubs.Add(license.PackageID, (byte)license.PaymentMethod);
            }


            OwnedSubs = ownedSubs;

            RefreshApps();
        }
Esempio n. 16
0
        public SteamBot()
        {
            reconnectBackoff = new ExponentialBackoff();

            // create our steamclient instance
            steamClient = new SteamClient();
            // create the callback manager which will route callbacks to function calls
            manager = new CallbackManager(steamClient);

            // get the steamuser handler, which is used for logging on after successfully connecting
            steamUser = steamClient.GetHandler<SteamUser>();
            // get the steam friends handler, which is used for interacting with friends on the network after logging on
            steamFriends = steamClient.GetHandler<SteamFriends>();
            steamApps = steamClient.GetHandler<SteamApps>();

            steamClient.AddHandler(new CustomHandler());

            // register a few callbacks we're interested in
            // these are registered upon creation to a callback manager, which will then route the callbacks
            // to the functions specified
            manager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
            manager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);

            manager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
            manager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
            //manager.Subscribe<SteamUser.MarketingMessageCallback>(OnMarketing);

            // we use the following callbacks for friends related activities
            manager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
            manager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
            manager.Subscribe<SteamFriends.PersonaStateCallback>(OnPersonaState);
            manager.Subscribe<SteamFriends.FriendAddedCallback>(OnFriendAdded);
            manager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
            manager.Subscribe<SteamFriends.FriendMsgEchoCallback>(EchoMsg);
            manager.Subscribe<SteamFriends.FriendMsgHistoryCallback>(ch_OnOfflineMessage2);
            manager.Subscribe<SteamFriends.ChatInviteCallback>(OnChatInvite);
            manager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
        }
        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 });
                }
            }
        }
        private void OnCDNAuthTokenCallback(SteamApps.CDNAuthTokenCallback callback)
        {
            JobAction job;

            if (!JobManager.TryRemoveJob(callback.JobID, out job))
            {
                return;
            }

            var request = job.ManifestJob;

            if (callback.Result != EResult.OK)
            {
                if (FileDownloader.IsImportantDepot(request.DepotID))
                {
                    Log.WriteError("Depot Processor", "Failed to get CDN auth token for depot {0} (parent {1} - server {2}) - {3} (#{4})",
                        request.DepotID, request.ParentAppID, request.Server, callback.Result, request.Tries);
                }

                if (--request.Tries >= 0)
                {
                    request.Server = GetContentServer(request.Tries);

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

                    return;
                }

                RemoveLock(request.DepotID);

                return;
            }

            request.CDNToken = callback.Token;

            // TODO: Using tasks makes every manifest download timeout
            // TODO: which seems to be bug with mono's threadpool implementation
            /*TaskManager.Run(() => DownloadManifest(request)).ContinueWith(task =>
            {
                RemoveLock(request.DepotID);

                Log.WriteDebug("Depot Processor", "Processed depot {0} ({1} depot locks left)", request.DepotID, DepotLocks.Count);
            });*/

            try
            {
                DownloadManifest(request);
            }
            catch (Exception)
            {
                RemoveLock(request.DepotID);
            }
        }
Esempio n. 19
0
        public void Process(SteamApps.PICSProductInfoCallback.PICSProductInfo productInfo)
        {
            ChangeNumber = productInfo.ChangeNumber;

            if (Settings.IsFullRun)
            {
                Log.WriteDebug("App Processor", "AppID: {0}", AppID);

                DbConnection.Execute("INSERT INTO `Changelists` (`ChangeID`) VALUES (@ChangeNumber) ON DUPLICATE KEY UPDATE `Date` = `Date`", new { productInfo.ChangeNumber });
                DbConnection.Execute("INSERT INTO `ChangelistsApps` (`ChangeID`, `AppID`) VALUES (@ChangeNumber, @AppID) ON DUPLICATE KEY UPDATE `AppID` = `AppID`", new { AppID, productInfo.ChangeNumber });
            }

            var app = DbConnection.Query<App>("SELECT `Name`, `AppType` FROM `Apps` WHERE `AppID` = @AppID LIMIT 1", new { AppID }).SingleOrDefault();

            var newAppName = productInfo.KeyValues["common"]["name"].AsString();

            if (newAppName != null)
            {
                int newAppType = -1;
                string currentType = productInfo.KeyValues["common"]["type"].AsString().ToLower();

                using (var reader = DbConnection.ExecuteReader("SELECT `AppType` FROM `AppsTypes` WHERE `Name` = @Type LIMIT 1", new { Type = currentType }))
                {
                    if (reader.Read())
                    {
                        newAppType = reader.GetInt32(reader.GetOrdinal("AppType"));
                    }
                }

                if (newAppType == -1)
                {
                    DbConnection.Execute("INSERT INTO `AppsTypes` (`Name`, `DisplayName`) VALUES(@Name, @DisplayName)",
                        new { Name = currentType, DisplayName = productInfo.KeyValues["common"]["type"].AsString() }); // We don't need to lower display name

                    Log.WriteInfo("App Processor", "Creating new apptype \"{0}\" (AppID {1})", currentType, AppID);

                    IRC.Instance.SendOps("New app type: {0}{1}{2} for app {3}{4}{5}", Colors.BLUE, currentType, Colors.NORMAL, Colors.BLUE, AppID, Colors.NORMAL);

                    newAppType = DbConnection.ExecuteScalar<int>("SELECT `AppType` FROM `AppsTypes` WHERE `Name` = @Type LIMIT 1", new { Type = currentType });
                }

                if (string.IsNullOrEmpty(app.Name) || app.Name.StartsWith(SteamDB.UNKNOWN_APP, StringComparison.Ordinal))
                {
                    DbConnection.Execute("INSERT INTO `Apps` (`AppID`, `AppType`, `Name`, `LastKnownName`) VALUES (@AppID, @Type, @AppName, @AppName) ON DUPLICATE KEY UPDATE `Name` = VALUES(`Name`), `LastKnownName` = VALUES(`LastKnownName`), `AppType` = VALUES(`AppType`)",
                        new { AppID, Type = newAppType, AppName = newAppName }
                    );

                    MakeHistory("created_app");
                    MakeHistory("created_info", SteamDB.DATABASE_NAME_TYPE, string.Empty, newAppName);

                    // TODO: Testy testy
                    if (!Settings.IsFullRun && Settings.Current.ChatRooms.Count > 0)
                    {
                        Steam.Instance.Friends.SendChatRoomMessage(Settings.Current.ChatRooms[0], EChatEntryType.ChatMsg,
                            string.Format(
                                "New {0} was published: {1}\nSteamDB: {2}\nSteam: https://store.steampowered.com/app/{3}/",
                                currentType,
                                newAppName,
                                SteamDB.GetAppURL(AppID),
                                AppID
                            )
                        );
                    }

                    if ((newAppType > 9 && newAppType != 13) || Triggers.Any(newAppName.Contains))
                    {
                        IRC.Instance.SendOps("New {0}: {1}{2}{3} -{4} {5}",
                            currentType,
                            Colors.BLUE, newAppName, Colors.NORMAL,
                            Colors.DARKBLUE, SteamDB.GetAppURL(AppID, "history"));
                    }
                }
                else if (!app.Name.Equals(newAppName))
                {
                    DbConnection.Execute("UPDATE `Apps` SET `Name` = @AppName, `LastKnownName` = @AppName WHERE `AppID` = @AppID", new { AppID, AppName = newAppName });

                    MakeHistory("modified_info", SteamDB.DATABASE_NAME_TYPE, app.Name, newAppName);
                }

                if (app.AppType == 0 || app.AppType != newAppType)
                {
                    DbConnection.Execute("UPDATE `Apps` SET `AppType` = @Type WHERE `AppID` = @AppID", new { AppID, Type = newAppType });

                    if (app.AppType == 0)
                    {
                        MakeHistory("created_info", SteamDB.DATABASE_APPTYPE, string.Empty, newAppType.ToString());
                    }
                    else
                    {
                        MakeHistory("modified_info", SteamDB.DATABASE_APPTYPE, app.AppType.ToString(), newAppType.ToString());
                    }
                }
            }

            foreach (var section in productInfo.KeyValues.Children)
            {
                string sectionName = section.Name.ToLower();

                if (sectionName == "appid" || sectionName == "public_only")
                {
                    continue;
                }

                if (sectionName == "change_number") // Carefully handle change_number
                {
                    sectionName = "root_change_number";

                    // TODO: Remove this key, move it to Apps table itself
                    ProcessKey(sectionName, "change_number", productInfo.ChangeNumber.ToString()); //section.AsString());
                }
                else if (sectionName == "common" || sectionName == "extended")
                {
                    string keyName;

                    foreach (KeyValue keyvalue in section.Children)
                    {
                        keyName = string.Format("{0}_{1}", sectionName, keyvalue.Name);

                        if (keyName.Equals("common_type") || keyName.Equals("common_gameid") || keyName.Equals("common_name") || keyName.Equals("extended_order"))
                        {
                            // Ignore common keys that are either duplicated or serve no real purpose
                            continue;
                        }

                        if (keyvalue.Children.Count > 0)
                        {
                            ProcessKey(keyName, keyvalue.Name, Utils.JsonifyKeyValue(keyvalue), true);
                        }
                        else if (!string.IsNullOrEmpty(keyvalue.Value))
                        {
                            ProcessKey(keyName, keyvalue.Name, keyvalue.Value);
                        }
                    }
                }
                else
                {
                    sectionName = string.Format("root_{0}", sectionName);

                    if (ProcessKey(sectionName, sectionName, Utils.JsonifyKeyValue(section), true) && sectionName.Equals("root_depots"))
                    {
                        DbConnection.Execute("UPDATE `Apps` SET `LastDepotUpdate` = CURRENT_TIMESTAMP() WHERE `AppID` = @AppID", new { AppID });
                    }
                }
            }
           
            foreach (var data in CurrentData.Values)
            {
                if (!data.Processed && !data.KeyName.StartsWith("website", StringComparison.Ordinal))
                {
                    DbConnection.Execute("DELETE FROM `AppsInfo` WHERE `AppID` = @AppID AND `Key` = @Key", new { AppID, data.Key });

                    MakeHistory("removed_key", data.Key, data.Value);

                    if (newAppName != null && data.Key.Equals("common_section_type") && data.Value.Equals("ownersonly"))
                    {
                        IRC.Instance.SendMain("Removed ownersonly from: {0}{1}{2} -{3} {4}", Colors.BLUE, app.Name, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetAppURL(AppID, "history"));
                    }
                }
            }

            if (newAppName == null)
            {
                if (string.IsNullOrEmpty(app.Name)) // We don't have the app in our database yet
                {
                    DbConnection.Execute("INSERT INTO `Apps` (`AppID`, `Name`) VALUES (@AppID, @AppName) ON DUPLICATE KEY UPDATE `AppType` = `AppType`", new { AppID, AppName = string.Format("{0} {1}", SteamDB.UNKNOWN_APP, AppID) });
                }
                else if (!app.Name.StartsWith(SteamDB.UNKNOWN_APP, StringComparison.Ordinal)) // We do have the app, replace it with default name
                {
                    IRC.Instance.SendMain("App deleted: {0}{1}{2} -{3} {4}", Colors.BLUE, app.Name, Colors.NORMAL, Colors.DARKBLUE, SteamDB.GetAppURL(AppID, "history"));

                    DbConnection.Execute("UPDATE `Apps` SET `Name` = @AppName, `AppType` = 0 WHERE `AppID` = @AppID", new { AppID, AppName = string.Format("{0} {1}", SteamDB.UNKNOWN_APP, AppID) });

                    MakeHistory("deleted_app", 0, app.Name);
                }
            }

            if (productInfo.KeyValues["depots"] != null)
            {
                Steam.Instance.DepotProcessor.Process(AppID, ChangeNumber, productInfo.KeyValues["depots"]);
            }
        }
Esempio n. 20
0
        public void Process(SteamApps.PICSProductInfoCallback.PICSProductInfo productInfo)
        {
            ChangeNumber = productInfo.ChangeNumber;

            #if !DEBUG
            if (Settings.Current.FullRun > 0)
            #endif
            {
                Log.WriteDebug("App Processor", "AppID: {0}", AppID);
            }

            if (productInfo.KeyValues == null)
            {
                Log.WriteWarn("App Processor", "AppID {0} is empty, wot do I do?", AppID);
                return;
            }

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

            string appName = string.Empty;
            string appType = "0";

            using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `Name`, `AppType` FROM `Apps` WHERE `AppID` = @AppID LIMIT 1", new MySqlParameter("AppID", AppID)))
            {
                if (Reader.Read())
                {
                    appName = DbWorker.GetString("Name", Reader);
                    appType = DbWorker.GetString("AppType", Reader);
                }
            }

            if (productInfo.KeyValues["common"]["name"].Value != null)
            {
                string newAppType = "0";
                string currentType = productInfo.KeyValues["common"]["type"].AsString().ToLower();

                using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `AppType` FROM `AppsTypes` WHERE `Name` = @Type LIMIT 1", new MySqlParameter("Type", currentType)))
                {
                    if (Reader.Read())
                    {
                        newAppType = DbWorker.GetString("AppType", Reader);
                    }
                    else
                    {
                        // TODO: Create it?
                        Log.WriteError("App Processor", "AppID {0} - unknown app type: {1}", AppID, currentType);

                        // TODO: This is debuggy just so we are aware of new app types
                        IRC.SendAnnounce("Unknown app type \"{0}\" for appid {1}, cc Alram and xPaw", currentType, AppID);
                    }
                }

                if (string.IsNullOrEmpty(appName) || appName.StartsWith(SteamDB.UNKNOWN_APP, StringComparison.Ordinal))
                {
                    DbWorker.ExecuteNonQuery("INSERT INTO `Apps` (`AppID`, `AppType`, `Name`) VALUES (@AppID, @Type, @AppName) ON DUPLICATE KEY UPDATE `Name` = @AppName, `AppType` = @Type",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@Type", newAppType),
                                             new MySqlParameter("@AppName", productInfo.KeyValues["common"]["name"].Value)
                    );

                    MakeHistory("created_app");
                    MakeHistory("created_info", DATABASE_NAME_TYPE, string.Empty, productInfo.KeyValues["common"]["name"].Value);
                }
                else if (!appName.Equals(productInfo.KeyValues["common"]["name"].Value))
                {
                    DbWorker.ExecuteNonQuery("UPDATE `Apps` SET `Name` = @AppName WHERE `AppID` = @AppID",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@AppName", productInfo.KeyValues["common"]["name"].Value)
                    );

                    MakeHistory("modified_info", DATABASE_NAME_TYPE, appName, productInfo.KeyValues["common"]["name"].Value);
                }

                if (appType.Equals("0"))
                {
                    DbWorker.ExecuteNonQuery("UPDATE `Apps` SET `AppType` = @Type WHERE `AppID` = @AppID",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@Type", newAppType)
                    );

                    MakeHistory("created_info", DATABASE_APPTYPE, string.Empty, newAppType);
                }
                else if (!appType.Equals(newAppType))
                {
                    DbWorker.ExecuteNonQuery("UPDATE `Apps` SET `AppType` = @Type WHERE `AppID` = @AppID",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@Type", newAppType)
                    );

                    MakeHistory("modified_info", DATABASE_APPTYPE, appType, newAppType);
                }
            }

            // If we are full running with unknowns, process depots too
            bool depotsSectionModified = Settings.Current.FullRun >= 2 && productInfo.KeyValues["depots"].Children.Count > 0;

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

                if (sectionName == "appid" || sectionName == "public_only")
                {
                    continue;
                }

                if (sectionName == "change_number") // Carefully handle change_number
                {
                    sectionName = "root_change_number";

                    ProcessKey(sectionName, "change_number", section.AsString());
                }
                else if (sectionName == "common" || sectionName == "extended")
                {
                    string keyName;

                    foreach (KeyValue keyvalue in section.Children)
                    {
                        keyName = string.Format("{0}_{1}", sectionName, keyvalue.Name);

                        if (keyName.Equals("common_type") || keyName.Equals("common_gameid") || keyName.Equals("common_name") || keyName.Equals("extended_order"))
                        {
                            // Ignore common keys that are either duplicated or serve no real purpose
                            continue;
                        }

                        // TODO: This is godlike hackiness
                        if (keyName.Equals("extended_de") ||
                                 keyName.Equals("extended_jp") ||
                                 keyName.Equals("extended_cn") ||
                                 keyName.Equals("extended_us") ||
                                 keyName.StartsWith("extended_us ", StringComparison.Ordinal) ||
                                 keyName.StartsWith("extended_im ", StringComparison.Ordinal) ||
                                 keyName.StartsWith("extended_af ax al dz as ad ao ai aq ag ", StringComparison.Ordinal)
                        )
                        {
                            Log.WriteWarn("App Processor", "Dammit Valve, why these long keynames: {0} - {1} ", AppID, keyName);

                            continue;
                        }

                        if (keyvalue.Children.Count > 0)
                        {
                            if (keyName.Equals("common_languages"))
                            {
                                ProcessKey(keyName, keyvalue.Name, string.Join(",", keyvalue.Children.Select(x => x.Name)));
                            }
                            else
                            {
                                ProcessKey(keyName, keyvalue.Name, DbWorker.JsonifyKeyValue(keyvalue), true);
                            }
                        }
                        else if (!string.IsNullOrEmpty(keyvalue.Value))
                        {
                            ProcessKey(keyName, keyvalue.Name, keyvalue.Value);
                        }
                    }
                }
                else
                {
                    sectionName = string.Format("root_{0}", sectionName);

                    if (ProcessKey(sectionName, sectionName, DbWorker.JsonifyKeyValue(section), true) && sectionName.Equals("root_depots"))
                    {
                        depotsSectionModified = true;
                    }
                }
            }

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

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

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

            if (productInfo.KeyValues["common"]["name"].Value == null)
            {
                if (string.IsNullOrEmpty(appName)) // We don't have the app in our database yet
                {
                    DbWorker.ExecuteNonQuery("INSERT INTO `Apps` (`AppID`, `Name`) VALUES (@AppID, @AppName)",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@AppName", string.Format("{0} {1}", SteamDB.UNKNOWN_APP, AppID))
                    );
                }
                else if (!appName.StartsWith(SteamDB.UNKNOWN_APP, StringComparison.Ordinal)) // We do have the app, replace it with default name
                {
                    DbWorker.ExecuteNonQuery("UPDATE `Apps` SET `Name` = @AppName, `AppType` = 0 WHERE `AppID` = @AppID",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@AppName", string.Format("{0} {1}", SteamDB.UNKNOWN_APP, AppID))
                    );

                    MakeHistory("deleted_app", 0, appName);
                }
            }

            if (depotsSectionModified)
            {
                DepotProcessor.Process(AppID, ChangeNumber, productInfo.KeyValues["depots"]);
            }
        }
Esempio n. 21
0
        private void TryProcess(SteamApps.PICSProductInfoCallback.PICSProductInfo productInfo)
        {
            using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `Name`, `Value` FROM `AppsInfo` INNER JOIN `KeyNames` ON `AppsInfo`.`Key` = `KeyNames`.`ID` WHERE `AppID` = @AppID", new MySqlParameter("AppID", AppID)))
            {
                while (Reader.Read())
                {
                    CurrentData.Add(DbWorker.GetString("Name", Reader), DbWorker.GetString("Value", Reader));
                }
            }

            string appName = string.Empty;
            string appType = "0";

            using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `Name`, `AppType` FROM `Apps` WHERE `AppID` = @AppID LIMIT 1", new MySqlParameter("AppID", AppID)))
            {
                if (Reader.Read())
                {
                    appName = DbWorker.GetString("Name", Reader);
                    appType = DbWorker.GetString("AppType", Reader);
                }
            }

            if (productInfo.KeyValues["common"]["name"].Value != null)
            {
                string newAppType = "0";
                string currentType = productInfo.KeyValues["common"]["type"].AsString().ToLower();

                using (MySqlDataReader Reader = DbWorker.ExecuteReader("SELECT `AppType` FROM `AppsTypes` WHERE `Name` = @Type LIMIT 1", new MySqlParameter("Type", currentType)))
                {
                    if (Reader.Read())
                    {
                        newAppType = DbWorker.GetString("AppType", Reader);
                    }
                    else
                    {
                        // TODO: Create it?
                        Log.WriteError("App Processor", "AppID {0} - unknown app type: {1}", AppID, currentType);

                        // TODO: This is debuggy just so we are aware of new app types
                        IRC.SendAnnounce("Unknown app type \"{0}\" for appid {1}, cc Alram and xPaw", currentType, AppID);
                    }
                }

                if (string.IsNullOrEmpty(appName) || appName.StartsWith(SteamDB.UNKNOWN_APP, StringComparison.Ordinal))
                {
                    DbWorker.ExecuteNonQuery("INSERT INTO `Apps` (`AppID`, `AppType`, `Name`) VALUES (@AppID, @Type, @AppName) ON DUPLICATE KEY UPDATE `Name` = @AppName, `AppType` = @Type",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@Type", newAppType),
                                             new MySqlParameter("@AppName", productInfo.KeyValues["common"]["name"].Value)
                    );

                    MakeHistory("created_app");
                    MakeHistory("created_info", DATABASE_NAME_TYPE, string.Empty, productInfo.KeyValues["common"]["name"].Value);

                    // TODO: Testy testy
                    if (Settings.Current.FullRun == 0
                    &&  Settings.Current.ChatRooms.Count > 0
                    &&  !appName.StartsWith("SteamApp", StringComparison.Ordinal)
                    &&  !appName.StartsWith("ValveTest", StringComparison.Ordinal))
                    {
                        Steam.Instance.Friends.SendChatRoomMessage(Settings.Current.ChatRooms[0], EChatEntryType.ChatMsg, string.Format(":retreat: New {0} was published: {1} - {2}", currentType, productInfo.KeyValues["common"]["name"].AsString(), SteamDB.GetAppURL(AppID)));
                    }
                }
                else if (!appName.Equals(productInfo.KeyValues["common"]["name"].Value))
                {
                    string newAppName = productInfo.KeyValues["common"]["name"].AsString();

                    DbWorker.ExecuteNonQuery("UPDATE `Apps` SET `Name` = @AppName WHERE `AppID` = @AppID",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@AppName", newAppName)
                    );

                    MakeHistory("modified_info", DATABASE_NAME_TYPE, appName, newAppName);

                    // TODO: Testy testy
                    if (Settings.Current.FullRun == 0
                    &&  Settings.Current.ChatRooms.Count > 0
                    &&  !string.Equals(appName, newAppName, StringComparison.OrdinalIgnoreCase)
                    &&  !newAppName.StartsWith("SteamApp", StringComparison.Ordinal)
                    &&  !newAppName.StartsWith("ValveTest", StringComparison.Ordinal))
                    {
                        Steam.Instance.Friends.SendChatRoomMessage(Settings.Current.ChatRooms[0], EChatEntryType.ChatMsg, string.Format(":retreat: {0} name was changed - {1}\n« {2}\n» {3}", currentType, SteamDB.GetAppURL(AppID, "history"), appName, newAppName));
                    }
                }

                if (appType.Equals("0"))
                {
                    DbWorker.ExecuteNonQuery("UPDATE `Apps` SET `AppType` = @Type WHERE `AppID` = @AppID",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@Type", newAppType)
                    );

                    MakeHistory("created_info", DATABASE_APPTYPE, string.Empty, newAppType);
                }
                else if (!appType.Equals(newAppType))
                {
                    DbWorker.ExecuteNonQuery("UPDATE `Apps` SET `AppType` = @Type WHERE `AppID` = @AppID",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@Type", newAppType)
                    );

                    MakeHistory("modified_info", DATABASE_APPTYPE, appType, newAppType);
                }
            }

            // If we are full running with unknowns, process depots too
            bool depotsSectionModified = Settings.Current.FullRun >= 2 && productInfo.KeyValues["depots"].Children.Count > 0;

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

                if (sectionName == "appid" || sectionName == "public_only")
                {
                    continue;
                }

                if (sectionName == "change_number") // Carefully handle change_number
                {
                    sectionName = "root_change_number";

                    // TODO: Remove this key, move it to Apps table itself
                    ProcessKey(sectionName, "change_number", productInfo.ChangeNumber.ToString()); //section.AsString());
                }
                else if (sectionName == "common" || sectionName == "extended")
                {
                    string keyName;

                    foreach (KeyValue keyvalue in section.Children)
                    {
                        keyName = string.Format("{0}_{1}", sectionName, keyvalue.Name);

                        if (keyName.Equals("common_type") || keyName.Equals("common_gameid") || keyName.Equals("common_name") || keyName.Equals("extended_order"))
                        {
                            // Ignore common keys that are either duplicated or serve no real purpose
                            continue;
                        }

                        if (keyvalue.Children.Count > 0)
                        {
                            if (keyName.Equals("common_languages"))
                            {
                                ProcessKey(keyName, keyvalue.Name, string.Join(",", keyvalue.Children.Select(x => x.Name)));
                            }
                            else
                            {
                                ProcessKey(keyName, keyvalue.Name, DbWorker.JsonifyKeyValue(keyvalue), true);
                            }
                        }
                        else if (!string.IsNullOrEmpty(keyvalue.Value))
                        {
                            ProcessKey(keyName, keyvalue.Name, keyvalue.Value);
                        }
                    }
                }
                else
                {
                    sectionName = string.Format("root_{0}", sectionName);

                    if (ProcessKey(sectionName, sectionName, DbWorker.JsonifyKeyValue(section), true) && sectionName.Equals("root_depots"))
                    {
                        depotsSectionModified = true;
                    }
                }
            }

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

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

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

            if (productInfo.KeyValues["common"]["name"].Value == null)
            {
                if (string.IsNullOrEmpty(appName)) // We don't have the app in our database yet
                {
                    DbWorker.ExecuteNonQuery("INSERT INTO `Apps` (`AppID`, `Name`) VALUES (@AppID, @AppName) ON DUPLICATE KEY UPDATE `AppType` = `AppType`",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@AppName", string.Format("{0} {1}", SteamDB.UNKNOWN_APP, AppID))
                    );
                }
                else if (!appName.StartsWith(SteamDB.UNKNOWN_APP, StringComparison.Ordinal)) // We do have the app, replace it with default name
                {
                    DbWorker.ExecuteNonQuery("UPDATE `Apps` SET `Name` = @AppName, `AppType` = 0 WHERE `AppID` = @AppID",
                                             new MySqlParameter("@AppID", AppID),
                                             new MySqlParameter("@AppName", string.Format("{0} {1}", SteamDB.UNKNOWN_APP, AppID))
                    );

                    MakeHistory("deleted_app", 0, appName);
                }
            }

            if (depotsSectionModified)
            {
                DepotProcessor.Process(AppID, ChangeNumber, productInfo.KeyValues["depots"]);
            }
        }
        private static void OnFreeLicenseCallback(SteamApps.FreeLicenseCallback callback)
        {
            JobAction job;
            JobManager.TryRemoveJob(callback.JobID, out job);

            var packageIDs = callback.GrantedPackages;
            var appIDs = callback.GrantedApps;

            Log.WriteDebug("FreeLicense", "Received free license: {0} ({1} apps: {2}, {3} packages: {4})",
                callback.Result, appIDs.Count, string.Join(", ", appIDs), packageIDs.Count, string.Join(", ", packageIDs));

            if (appIDs.Count > 0)
            {
                JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(appIDs, Enumerable.Empty<uint>()));
            }

            if (packageIDs.Count > 0)
            {
                JobManager.AddJob(() => Steam.Instance.Apps.PICSGetProductInfo(Enumerable.Empty<uint>(), packageIDs));

                // We don't want to block our main thread with web requests
                TaskManager.Run(() =>
                {
                    string data = null;

                    try
                    {
                        var response = WebAuth.PerformRequest("GET", "https://store.steampowered.com/account/licenses/");

                        using (var responseStream = response.GetResponseStream())
                        {
                            using (var reader = new StreamReader(responseStream))
                            {
                                data = reader.ReadToEnd();
                            }
                        }
                    }
                    catch (WebException e)
                    {
                        Log.WriteError("FreeLicense", "Failed to fetch account details page: {0}", e.Message);
                    }

                    using (var db = Database.GetConnection())
                    {
                        foreach (var package in packageIDs)
                        {
                            var packageData = db.Query<Package>("SELECT `SubID`, `Name`, `LastKnownName` FROM `Subs` WHERE `SubID` = @SubID", new { SubID = package }).FirstOrDefault();

                            if (!string.IsNullOrEmpty(data))
                            {
                                // Tell me all about using regex
                                var match = Regex.Match(data, string.Format("RemoveFreeLicense\\( ?{0}, ?'(.+)' ?\\)", package));

                                if (match.Success)
                                {
                                    var grantedName = Encoding.UTF8.GetString(Convert.FromBase64String(match.Groups[1].Value));

                                    // Update last known name if we can
                                    if (packageData.SubID > 0 && (string.IsNullOrEmpty(packageData.LastKnownName) || packageData.LastKnownName.StartsWith("Steam Sub ", StringComparison.Ordinal)))
                                    {
                                        db.Execute("UPDATE `Subs` SET `LastKnownName` = @Name WHERE `SubID` = @SubID", new { SubID = package, Name = grantedName });

                                        db.Execute(SubProcessor.GetHistoryQuery(),
                                            new PICSHistory
                                            {
                                                ID       = package,
                                                Key      = SteamDB.DATABASE_NAME_TYPE,
                                                OldValue = "free on demand; account page",
                                                NewValue = grantedName,
                                                Action   = "created_info"
                                            }
                                        );

                                        // Add a app comment on each app in this package
                                        var comment = string.Format("This app is in a free on demand package called <b>{0}</b>", SecurityElement.Escape(grantedName));
                                        var apps = db.Query<PackageApp>("SELECT `AppID` FROM `SubsApps` WHERE `SubID` = @SubID", new { SubID = package }).ToList();
                                        var types = db.Query<App>("SELECT `AppID` FROM `Apps` WHERE `AppType` > 0 AND `AppID` IN @Ids", new { Ids = apps.Select(x => x.AppID) }).ToDictionary(x => x.AppID, x => true);
                                        var key = db.ExecuteScalar<uint>("SELECT `ID` FROM `KeyNames` WHERE `Name` = 'website_comment'");

                                        foreach (var app in apps)
                                        {
                                            if (types.ContainsKey(app.AppID))
                                            {
                                                continue;
                                            }

                                            db.Execute("INSERT INTO `AppsInfo` VALUES (@AppID, @Key, @Value) ON DUPLICATE KEY UPDATE `Key` = `Key`", new {app.AppID, Key = key, value = comment });
                                        }
                                    }

                                    packageData.LastKnownName = grantedName;
                                }
                            }

                            IRC.Instance.SendMain("New free license granted: {0}{1}{2} -{3} {4}",
                                Colors.BLUE, Steam.FormatPackageName(package, packageData), Colors.NORMAL,
                                Colors.DARKBLUE, SteamDB.GetPackageURL(package)
                            );
                        }
                    }
                });
            }
        }
Esempio n. 23
0
        private static void OnPICSProductInfo(SteamApps.PICSProductInfoCallback callback)
        {
            JobAction job;

            if (!JobManager.TryRemoveJob(callback.JobID, out job) || !job.IsCommand)
            {
                return;
            }

            var request = job.CommandRequest;

            if (request.Type == JobManager.IRCRequestType.TYPE_SUB)
            {
                if (!callback.Packages.ContainsKey(request.Target))
                {
                    CommandHandler.ReplyToCommand(request.Command, "Unknown SubID: {0}{1}{2}", Colors.BLUE, request.Target, LicenseList.OwnedSubs.ContainsKey(request.Target) ? SteamDB.StringCheckmark : string.Empty);

                    return;
                }

                var info = callback.Packages[request.Target];
                var kv = info.KeyValues.Children.FirstOrDefault();
                string name;

                if (kv["name"].Value != null)
                {
                    name = Utils.RemoveControlCharacters(kv["name"].AsString());
                }
                else
                {
                    name = Steam.GetPackageName(info.ID);
                }

                try
                {
                    kv.SaveToFile(Path.Combine(Application.Path, "sub", string.Format("{0}.vdf", info.ID)), false);
                }
                catch (Exception e)
                {
                    CommandHandler.ReplyToCommand(request.Command, "Unable to save file for {0}: {1}", name, e.Message);

                    return;
                }

                CommandHandler.ReplyToCommand(request.Command, "{0}{1}{2} -{3} {4}{5} - Dump:{6} {7}{8}{9}{10}",
                    Colors.BLUE, name, Colors.NORMAL,
                    Colors.DARKBLUE, SteamDB.GetPackageURL(info.ID), Colors.NORMAL,
                    Colors.DARKBLUE, SteamDB.GetRawPackageURL(info.ID), Colors.NORMAL,
                    info.MissingToken ? SteamDB.StringNeedToken : string.Empty,
                    LicenseList.OwnedSubs.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty
                );
            }
            else if (request.Type == JobManager.IRCRequestType.TYPE_APP)
            {
                if (!callback.Apps.ContainsKey(request.Target))
                {
                    CommandHandler.ReplyToCommand(request.Command, "Unknown AppID: {0}{1}{2}", Colors.BLUE, request.Target, LicenseList.OwnedApps.ContainsKey(request.Target) ? SteamDB.StringCheckmark : string.Empty);

                    return;
                }

                var info = callback.Apps[request.Target];
                string name;

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

                try
                {
                    info.KeyValues.SaveToFile(Path.Combine(Application.Path, "app", string.Format("{0}.vdf", info.ID)), false);
                }
                catch (Exception e)
                {
                    CommandHandler.ReplyToCommand(request.Command, "Unable to save file for {0}: {1}", name, e.Message);

                    return;
                }

                CommandHandler.ReplyToCommand(request.Command, "{0}{1}{2} -{3} {4}{5} - Dump:{6} {7}{8}{9}{10}",
                    Colors.BLUE, name, Colors.NORMAL,
                    Colors.DARKBLUE, SteamDB.GetAppURL(info.ID), Colors.NORMAL,
                    Colors.DARKBLUE, SteamDB.GetRawAppURL(info.ID), Colors.NORMAL,
                    info.MissingToken ? SteamDB.StringNeedToken : string.Empty,
                    LicenseList.OwnedApps.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty
                );
            }
            else
            {
                CommandHandler.ReplyToCommand(request.Command, "I have no idea what happened here!");
            }
        }
        private static void OnPICSProductInfo(SteamApps.PICSProductInfoCallback callback)
        {
            JobAction job;

            if (JobManager.TryRemoveJob(callback.JobID, out job) && job.IsCommand)
            {
                OnProductInfoForIRC(job.CommandRequest, callback);
            }

            foreach (var app in callback.Apps)
            {
                Log.WriteInfo("PICSProductInfo", "AppID: {0}", app.Key);

                var workaround = app;

                IWorkItemResult mostRecentItem;
                Application.ProcessedApps.TryGetValue(workaround.Key, out mostRecentItem);

                var workerItem = Application.ProcessorPool.QueueWorkItem(delegate
                {
                    if (mostRecentItem != null && !mostRecentItem.IsCompleted)
                    {
                        Log.WriteDebug("PICSProductInfo", "Waiting for app {0} to finish processing", workaround.Key);

                        SmartThreadPool.WaitAll(new IWaitableResult[] { mostRecentItem });
                    }

                    new AppProcessor(workaround.Key).Process(workaround.Value);
                });

                if (!Settings.IsFullRun)
                {
                    Application.ProcessedApps.AddOrUpdate(app.Key, workerItem, (key, oldValue) => workerItem);
                }
            }

            foreach (var package in callback.Packages)
            {
                Log.WriteInfo("PICSProductInfo", "SubID: {0}", package.Key);

                var workaround = package;

                IWorkItemResult mostRecentItem;
                Application.ProcessedSubs.TryGetValue(workaround.Key, out mostRecentItem);

                var workerItem = Application.ProcessorPool.QueueWorkItem(delegate
                {
                    if (mostRecentItem != null && !mostRecentItem.IsCompleted)
                    {
                        Log.WriteDebug("PICSProductInfo", "Waiting for package {0} to finish processing", workaround.Key);

                        SmartThreadPool.WaitAll(new IWaitableResult[] { mostRecentItem });
                    }

                    new SubProcessor(workaround.Key).Process(workaround.Value);
                });

                if (!Settings.IsFullRun)
                {
                    Application.ProcessedSubs.AddOrUpdate(package.Key, workerItem, (key, oldValue) => workerItem);
                }
            }

            foreach (uint app in callback.UnknownApps)
            {
                Log.WriteInfo("PICSProductInfo", "Unknown AppID: {0}", app);

                uint workaround = app;

                IWorkItemResult mostRecentItem;
                Application.ProcessedApps.TryGetValue(workaround, out mostRecentItem);

                var workerItem = Application.ProcessorPool.QueueWorkItem(delegate
                {
                    if (mostRecentItem != null && !mostRecentItem.IsCompleted)
                    {
                        Log.WriteDebug("PICSProductInfo", "Waiting for app {0} to finish processing (unknown)", workaround);

                        SmartThreadPool.WaitAll(new IWaitableResult[] { mostRecentItem });
                    }

                    new AppProcessor(workaround).ProcessUnknown();
                });

                if (!Settings.IsFullRun)
                {
                    Application.ProcessedApps.AddOrUpdate(app, workerItem, (key, oldValue) => workerItem);
                }
            }

            foreach (uint package in callback.UnknownPackages)
            {
                Log.WriteInfo("PICSProductInfo", "Unknown SubID: {0}", package);

                uint workaround = package;

                IWorkItemResult mostRecentItem;
                Application.ProcessedSubs.TryGetValue(workaround, out mostRecentItem);

                var workerItem = Application.ProcessorPool.QueueWorkItem(delegate
                {
                    if (mostRecentItem != null && !mostRecentItem.IsCompleted)
                    {
                        Log.WriteDebug("PICSProductInfo", "Waiting for package {0} to finish processing (unknown)", workaround);

                        SmartThreadPool.WaitAll(new IWaitableResult[] { mostRecentItem });
                    }

                    new SubProcessor(workaround).ProcessUnknown();
                });

                if (!Settings.IsFullRun)
                {
                    Application.ProcessedSubs.AddOrUpdate(package, workerItem, (key, oldValue) => workerItem);
                }
            }
        }
Esempio n. 25
0
		private void OnFreeLicense(SteamApps.FreeLicenseCallback callback) {
			if (callback == null) {
				Logging.LogNullError(nameof(callback), BotName);
			}
		}
Esempio n. 26
0
		private async void OnLicenseList(SteamApps.LicenseListCallback callback) {
			if (callback?.LicenseList == null) {
				Logging.LogNullError(nameof(callback) + " || " + nameof(callback.LicenseList), BotName);
				return;
			}

			OwnedPackageIDs.Clear();

			foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList) {
				OwnedPackageIDs.Add(license.PackageID);
			}

			OwnedPackageIDs.TrimExcess();

			await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback

			if (!ArchiWebHandler.Ready) {
				for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
					await Task.Delay(1000).ConfigureAwait(false);
				}

				if (!ArchiWebHandler.Ready) {
					return;
				}
			}

			await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
		}
Esempio n. 27
0
		internal Bot(string botName) {
			if (string.IsNullOrEmpty(botName)) {
				throw new ArgumentNullException(nameof(botName));
			}

			if (Bots.ContainsKey(botName)) {
				throw new ArgumentException("That bot is already defined!");
			}

			string botPath = Path.Combine(SharedInfo.ConfigDirectory, botName);

			BotName = botName;
			SentryFile = botPath + ".bin";

			string botConfigFile = botPath + ".json";

			BotConfig = BotConfig.Load(botConfigFile);
			if (BotConfig == null) {
				Logging.LogGenericError("Your bot config is invalid, please verify content of " + botConfigFile + " and try again!", botName);
				return;
			}

			// Register bot as available for ASF
			if (!Bots.TryAdd(botName, this)) {
				throw new ArgumentException("That bot is already defined!");
			}

			string botDatabaseFile = botPath + ".db";

			BotDatabase = BotDatabase.Load(botDatabaseFile);
			if (BotDatabase == null) {
				Logging.LogGenericError("Bot database could not be loaded, refusing to create this bot instance! In order to recreate it, remove " + botDatabaseFile + " and try again!", botName);
				return;
			}

			if (BotDatabase.MobileAuthenticator != null) {
				BotDatabase.MobileAuthenticator.Init(this);
			} else {
				// Support and convert SDA files
				string maFilePath = botPath + ".maFile";
				if (File.Exists(maFilePath)) {
					ImportAuthenticator(maFilePath);
				}
			}

			// Initialize
			SteamClient = new SteamClient(Program.GlobalConfig.SteamProtocol);

			if (Program.GlobalConfig.Debug && Directory.Exists(SharedInfo.DebugDirectory)) {
				string debugListenerPath = Path.Combine(SharedInfo.DebugDirectory, botName);

				try {
					Directory.CreateDirectory(debugListenerPath);
					SteamClient.DebugNetworkListener = new NetHookNetworkListener(debugListenerPath);
				} catch (Exception e) {
					Logging.LogGenericException(e, botName);
				}
			}

			ArchiHandler = new ArchiHandler(this);
			SteamClient.AddHandler(ArchiHandler);

			CallbackManager = new CallbackManager(SteamClient);
			CallbackManager.Subscribe<SteamClient.ConnectedCallback>(OnConnected);
			CallbackManager.Subscribe<SteamClient.DisconnectedCallback>(OnDisconnected);

			SteamApps = SteamClient.GetHandler<SteamApps>();
			CallbackManager.Subscribe<SteamApps.FreeLicenseCallback>(OnFreeLicense);
			CallbackManager.Subscribe<SteamApps.GuestPassListCallback>(OnGuestPassList);
			CallbackManager.Subscribe<SteamApps.LicenseListCallback>(OnLicenseList);

			SteamFriends = SteamClient.GetHandler<SteamFriends>();
			CallbackManager.Subscribe<SteamFriends.ChatInviteCallback>(OnChatInvite);
			CallbackManager.Subscribe<SteamFriends.ChatMsgCallback>(OnChatMsg);
			CallbackManager.Subscribe<SteamFriends.FriendsListCallback>(OnFriendsList);
			CallbackManager.Subscribe<SteamFriends.FriendMsgCallback>(OnFriendMsg);
			CallbackManager.Subscribe<SteamFriends.FriendMsgHistoryCallback>(OnFriendMsgHistory);
			CallbackManager.Subscribe<SteamFriends.PersonaStateCallback>(OnPersonaState);

			SteamUser = SteamClient.GetHandler<SteamUser>();
			CallbackManager.Subscribe<SteamUser.AccountInfoCallback>(OnAccountInfo);
			CallbackManager.Subscribe<SteamUser.LoggedOffCallback>(OnLoggedOff);
			CallbackManager.Subscribe<SteamUser.LoggedOnCallback>(OnLoggedOn);
			CallbackManager.Subscribe<SteamUser.LoginKeyCallback>(OnLoginKey);
			CallbackManager.Subscribe<SteamUser.UpdateMachineAuthCallback>(OnMachineAuth);
			CallbackManager.Subscribe<SteamUser.WebAPIUserNonceCallback>(OnWebAPIUserNonce);

			CallbackManager.Subscribe<ArchiHandler.NotificationsCallback>(OnNotifications);
			CallbackManager.Subscribe<ArchiHandler.OfflineMessageCallback>(OnOfflineMessage);
			CallbackManager.Subscribe<ArchiHandler.PlayingSessionStateCallback>(OnPlayingSessionState);
			CallbackManager.Subscribe<ArchiHandler.PurchaseResponseCallback>(OnPurchaseResponse);
			CallbackManager.Subscribe<ArchiHandler.SharedLibraryLockStatusCallback>(OnSharedLibraryLockStatus);

			ArchiWebHandler = new ArchiWebHandler(this);

			CardsFarmer = new CardsFarmer(this) {
				Paused = BotConfig.Paused
			};

			Trading = new Trading(this);

			HeartBeatTimer = new Timer(
				async e => await HeartBeat().ConfigureAwait(false),
				null,
				TimeSpan.FromMinutes(1) + TimeSpan.FromMinutes(0.2 * Bots.Count), // Delay
				TimeSpan.FromMinutes(1) // Period
			);

			Initialize().Forget();
		}
        private void OnCDNAuthTokenCallback(SteamApps.CDNAuthTokenCallback callback)
        {
            JobAction job;

            if (!JobManager.TryRemoveJob(callback.JobID, out job))
            {
                return;
            }

            var request = job.ManifestJob;

            if (callback.Result != EResult.OK)
            {
                RemoveLock(request.DepotID);

                return;
            }

            request.CDNToken = callback.Token;

            JobManager.AddJob(() => Steam.Instance.Apps.GetDepotDecryptionKey(request.DepotID, request.ParentAppID), request);
        }
Esempio n. 29
0
		private async void OnGuestPassList(SteamApps.GuestPassListCallback callback) {
			if (callback?.GuestPasses == null) {
				Logging.LogNullError(nameof(callback) + " || " + nameof(callback.GuestPasses), BotName);
				return;
			}

			if ((callback.CountGuestPassesToRedeem == 0) || (callback.GuestPasses.Count == 0) || !BotConfig.AcceptGifts) {
				return;
			}

			foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
				HandledGifts.Add(gid);

				Logging.LogGenericInfo("Accepting gift: " + gid + "...", BotName);
				await LimitGiftsRequestsAsync().ConfigureAwait(false);

				ArchiHandler.RedeemGuestPassResponseCallback response = await ArchiHandler.RedeemGuestPass(gid).ConfigureAwait(false);
				if (response != null) {
					if (response.Result == EResult.OK) {
						Logging.LogGenericInfo("Success!", BotName);
					} else {
						Logging.LogGenericWarning("Failed with error: " + response.Result, BotName);
					}
				} else {
					Logging.LogGenericWarning("Failed!", BotName);
				}
			}
		}
        private void OnDepotKeyCallback(SteamApps.DepotKeyCallback callback)
        {
            JobAction job;

            if (!JobManager.TryRemoveJob(callback.JobID, out job))
            {
                return;
            }

            var request = job.ManifestJob;

            if (callback.Result != EResult.OK)
            {
                if (callback.Result != EResult.Blocked)
                {
                    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;
            }

            Log.WriteInfo("Depot Processor", "DepotID: {0}", request.DepotID);

            request.DepotKey = callback.DepotKey;

            // In full run, process depots after everything else
            if (Settings.IsFullRun)
            {
                Application.ProcessorPool.QueueWorkItem(TryDownloadManifest, request, WorkItemPriority.Lowest);
            }
            else
            {
                Application.SecondaryPool.QueueWorkItem(TryDownloadManifest, request);
            }
        }
        public void Process(SteamApps.PICSProductInfoCallback.PICSProductInfo productInfo)
        {
            ChangeNumber = productInfo.ChangeNumber;

            #if !DEBUG
            if (Settings.IsFullRun)
            #endif
            {
                Log.WriteDebug("Sub Processor", "SubID: {0}", SubID);
            }

            var appAddedToThisPackage = false;
            var packageOwned = LicenseList.OwnedSubs.ContainsKey(SubID);
            var kv = productInfo.KeyValues.Children.FirstOrDefault();
            var newPackageName = kv["name"].AsString();
            var apps = DbConnection.Query<PackageApp>("SELECT `AppID`, `Type` FROM `SubsApps` WHERE `SubID` = @SubID", new { SubID }).ToDictionary(x => x.AppID, x => x.Type);

            // TODO: Ideally this should be SteamDB Unknown Package and proper checks like app processor does
            if (newPackageName == null)
            {
                newPackageName = string.Concat("Steam Sub ", SubID);
            }

            if (newPackageName != null)
            {
                if (string.IsNullOrEmpty(PackageName))
                {
                    DbConnection.Execute("INSERT INTO `Subs` (`SubID`, `Name`, `LastKnownName`) VALUES (@SubID, @Name, @Name) ON DUPLICATE KEY UPDATE `Name` = @Name", new { SubID, Name = newPackageName });

                    MakeHistory("created_sub");
                    MakeHistory("created_info", SteamDB.DATABASE_NAME_TYPE, string.Empty, newPackageName);
                }
                else if (!PackageName.Equals(newPackageName))
                {
                    if (newPackageName.StartsWith("Steam Sub ", StringComparison.Ordinal))
                    {
                        DbConnection.Execute("UPDATE `Subs` SET `Name` = @Name WHERE `SubID` = @SubID", new { SubID, Name = newPackageName });
                    }
                    else
                    {
                        DbConnection.Execute("UPDATE `Subs` SET `Name` = @Name, `LastKnownName` = @Name WHERE `SubID` = @SubID", new { SubID, Name = newPackageName });
                    }

                    MakeHistory("modified_info", SteamDB.DATABASE_NAME_TYPE, PackageName, newPackageName);
                }
            }

            foreach (var 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"))
                {
                    // Remove "ids", so we get "app" from appids and "depot" from depotids
                    string type = sectionName.Replace("ids", string.Empty);

                    var isAppSection = type.Equals("app");

                    var typeID = (uint)(isAppSection ? 0 : 1); // 0 = app, 1 = depot; can't store as string because it's in the `key` field

                    foreach (var 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)
                            {
                                DbConnection.Execute("UPDATE `SubsApps` SET `Type` = @Type WHERE `SubID` = @SubID AND `AppID` = @AppID", new { SubID, AppID = appID, Type = type });

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

                                appAddedToThisPackage = true;

                                // TODO: Log relevant add/remove history for depot/app?
                            }

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

                            MakeHistory("added_to_sub", typeID, string.Empty, childrenApp.Value);

                            if (isAppSection)
                            {
                                DbConnection.Execute(AppProcessor.GetHistoryQuery(),
                                    new PICSHistory
                                    {
                                        ID       = appID,
                                        ChangeID = ChangeNumber,
                                        NewValue = SubID.ToString(),
                                        Action   = "added_to_sub"
                                    }
                                );
                            }
                            else
                            {
                                DbConnection.Execute(DepotProcessor.GetHistoryQuery(),
                                    new DepotHistory
                                    {
                                        DepotID  = appID,
                                        ChangeID = ChangeNumber,
                                        NewValue = SubID,
                                        Action   = "added_to_sub"
                                    }
                                );
                            }

                            appAddedToThisPackage = true;

                            if (packageOwned && !LicenseList.OwnedApps.ContainsKey(appID))
                            {
                                LicenseList.OwnedApps.Add(appID, (byte)1);
                            }
                        }
                    }
                }
                else if (sectionName.Equals("extended"))
                {
                    string keyName;

                    foreach (var children in section.Children)
                    {
                        keyName = string.Format("{0}_{1}", sectionName, children.Name);

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

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

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

            foreach (var data in CurrentData.Values)
            {
                if (!data.Processed && !data.KeyName.StartsWith("website", StringComparison.Ordinal))
                {
                    DbConnection.Execute("DELETE FROM `SubsInfo` WHERE `SubID` = @SubID AND `Key` = @Key", new { SubID, data.Key });

                    MakeHistory("removed_key", data.Key, data.Value);
                }
            }

            var appsRemoved = apps.Any();

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

                var isAppSection = app.Value.Equals("app");

                var typeID = (uint)(isAppSection ? 0 : 1); // 0 = app, 1 = depot; can't store as string because it's in the `key` field

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

                if (isAppSection)
                {
                    DbConnection.Execute(AppProcessor.GetHistoryQuery(),
                        new PICSHistory
                        {
                            ID       = app.Key,
                            ChangeID = ChangeNumber,
                            OldValue = SubID.ToString(),
                            Action   = "removed_from_sub"
                        }
                    );
                }
                else
                {
                    DbConnection.Execute(DepotProcessor.GetHistoryQuery(),
                        new DepotHistory
                        {
                            DepotID  = app.Key,
                            ChangeID = ChangeNumber,
                            OldValue = SubID,
                            Action   = "removed_from_sub"
                        }
                    );
                }
            }

            if (appsRemoved)
            {
                LicenseList.RefreshApps();
            }

            if (kv["billingtype"].AsInteger() == 12 && !packageOwned) // 12 == free on demand
            {
                Log.WriteDebug("Sub Processor", "Requesting apps in SubID {0} as a free license", SubID);

                JobManager.AddJob(() => SteamDB.RequestFreeLicense(kv["appids"].Children.Select(appid => (uint)appid.AsInteger()).ToList()));
            }

            // Re-queue apps in this package so we can update depots and whatnot
            if (appAddedToThisPackage && !Settings.IsFullRun && !string.IsNullOrEmpty(PackageName))
            {
                JobManager.AddJob(() => Steam.Instance.Apps.PICSGetAccessTokens(kv["appids"].Children.Select(x => (uint)x.AsInteger()), Enumerable.Empty<uint>()));
            }
        }
        private static void OnProductInfoForIRC(JobManager.IRCRequest request, SteamApps.PICSProductInfoCallback callback)
        {
            if (request.Type == JobManager.IRCRequestType.TYPE_SUB)
            {
                if (!callback.Packages.ContainsKey(request.Target))
                {
                    CommandHandler.ReplyToCommand(request.Command, "Unknown SubID: {0}{1}", Colors.OLIVE, request.Target);

                    return;
                }

                var info = callback.Packages[request.Target];
                var kv = info.KeyValues.Children.FirstOrDefault();
                string name = string.Format("SubID {0}", info.ID);

                if (kv["name"].Value != null)
                {
                    name = kv["name"].AsString();
                }

                try
                {
                    kv.SaveToFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "sub", string.Format("{0}.vdf", info.ID)), false);
                }
                catch (Exception e)
                {
                    CommandHandler.ReplyToCommand(request.Command, "Unable to save file for {0}: {1}", name, e.Message);

                    return;
                }

                CommandHandler.ReplyToCommand(request.Command, "{0}{1}{2} -{3} {4}{5} - Dump:{6} {7}{8}{9}{10}",
                    Colors.OLIVE, name, Colors.NORMAL,
                    Colors.DARKBLUE, SteamDB.GetPackageURL(info.ID), Colors.NORMAL,
                    Colors.DARKBLUE, SteamDB.GetRawPackageURL(info.ID), Colors.NORMAL,
                    info.MissingToken ? SteamDB.StringNeedToken : string.Empty,
                    Application.OwnedSubs.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty
                );
            }
            else if (request.Type == JobManager.IRCRequestType.TYPE_APP)
            {
                if (!callback.Apps.ContainsKey(request.Target))
                {
                    CommandHandler.ReplyToCommand(request.Command, "Unknown AppID: {0}{1}", Colors.OLIVE, request.Target);

                    return;
                }

                var info = callback.Apps[request.Target];
                string name = string.Format("AppID {0}", info.ID);

                if (info.KeyValues["common"]["name"].Value != null)
                {
                    name = info.KeyValues["common"]["name"].AsString();
                }

                try
                {
                    info.KeyValues.SaveToFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app", string.Format("{0}.vdf", info.ID)), false);
                }
                catch (Exception e)
                {
                    CommandHandler.ReplyToCommand(request.Command, "Unable to save file for {0}: {1}", name, e.Message);

                    return;
                }

                CommandHandler.ReplyToCommand(request.Command, "{0}{1}{2} -{3} {4}{5} - Dump:{6} {7}{8}{9}{10}",
                    Colors.OLIVE, name, Colors.NORMAL,
                    Colors.DARKBLUE, SteamDB.GetAppURL(info.ID), Colors.NORMAL,
                    Colors.DARKBLUE, SteamDB.GetRawAppURL(info.ID), Colors.NORMAL,
                    info.MissingToken ? SteamDB.StringNeedToken : string.Empty,
                    Application.OwnedApps.ContainsKey(info.ID) ? SteamDB.StringCheckmark : string.Empty
                );
            }
            else
            {
                CommandHandler.ReplyToCommand(request.Command, "I have no idea what happened here!");
            }
        }