diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 7f75c57e8..897651a6c 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -58,15 +58,15 @@ namespace ArchiSteamFarm { private const string SteamStoreURL = "http://" + SteamStoreHost; - private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1); + private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1, 1); private static int Timeout = GlobalConfig.DefaultConnectionTimeout * 1000; // This must be int type - private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1); + private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1, 1); private readonly Bot Bot; - private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1); - private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1); - private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1); + private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1, 1); private readonly WebBrowser WebBrowser; private string CachedApiKey; diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index a7beadffd..8c9fabb34 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -50,12 +50,11 @@ namespace ArchiSteamFarm { private const ushort MaxSteamMessageLength = 2048; private const byte MaxTwoFactorCodeFailures = 3; private const byte MinHeartBeatTTL = GlobalConfig.DefaultConnectionTimeout; // Assume client is responsive for at least that amount of seconds - private const byte PICSCooldownInMiliseconds = 200; // We might need to tune this further internal static readonly ConcurrentDictionary Bots = new ConcurrentDictionary(); - private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1); - private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1); + private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1); + private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1); private static readonly SteamConfiguration SteamConfiguration = new SteamConfiguration(); internal readonly ArchiLogger ArchiLogger; @@ -74,16 +73,16 @@ namespace ArchiSteamFarm { private readonly BotDatabase BotDatabase; private readonly string BotName; private readonly CallbackManager CallbackManager; - private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1); + private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1, 1); [JsonProperty] private readonly CardsFarmer CardsFarmer; private readonly ConcurrentHashSet HandledGifts = new ConcurrentHashSet(); private readonly Timer HeartBeatTimer; - private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1); - private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1); - private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1); + private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1, 1); private readonly Statistics Statistics; private readonly SteamApps SteamApps; private readonly SteamClient SteamClient; @@ -361,10 +360,7 @@ namespace ArchiSteamFarm { ArchiLogger.LogGenericException(e); return (0, DateTime.MinValue); } finally { - Task.Run(async () => { - await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false); - PICSSemaphore.Release(); - }).Forget(); + PICSSemaphore.Release(); } // ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619 @@ -471,10 +467,7 @@ namespace ArchiSteamFarm { ArchiLogger.LogGenericException(e); return null; } finally { - Task.Run(async () => { - await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false); - PICSSemaphore.Release(); - }).Forget(); + PICSSemaphore.Release(); } Dictionary> result = new Dictionary>(); @@ -1871,6 +1864,7 @@ namespace ArchiSteamFarm { Statistics?.OnLoggedOn().Forget(); Trading.OnNewTrade().Forget(); + ResponseOwns(GetFirstSteamMasterID(), "*").Forget(); // TODO: DEBUG break; case EResult.InvalidPassword: case EResult.NoConnection: @@ -3084,6 +3078,8 @@ namespace ArchiSteamFarm { if (query.Equals("*")) { foreach (KeyValuePair ownedGame in ownedGames) { + ArchiLogger.LogGenericDebug("Asking for " + ownedGame.Key); + await GetAppDataForIdling(ownedGame.Key).ConfigureAwait(false); // TODO: DEBUG response.Append(FormatBotResponse(string.Format(Strings.BotOwnedAlreadyWithName, ownedGame.Key, ownedGame.Value))); } } else { diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 37673313f..53a6c0584 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -58,9 +58,9 @@ namespace ArchiSteamFarm { ); private readonly Bot Bot; - private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1); - private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1); - private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false); + private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim FarmingResetSemaphore = new SemaphoreSlim(0, 1); private readonly Timer IdleFarmingTimer; [JsonProperty] @@ -87,8 +87,8 @@ namespace ArchiSteamFarm { public void Dispose() { // Those are objects that are always being created if constructor doesn't throw exception EventSemaphore.Dispose(); - FarmingSemaphore.Dispose(); - FarmResetEvent.Dispose(); + FarmingInitializationSemaphore.Dispose(); + FarmingResetSemaphore.Dispose(); // Those are objects that might be null and the check should be in-place IdleFarmingTimer?.Dispose(); @@ -140,7 +140,7 @@ namespace ArchiSteamFarm { internal async Task OnNewItemsNotification() { if (NowFarming) { - FarmResetEvent.Set(); + FarmingResetSemaphore.Release(); return; } @@ -188,7 +188,7 @@ namespace ArchiSteamFarm { return; } - await FarmingSemaphore.WaitAsync().ConfigureAwait(false); + await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false); try { if (NowFarming || Paused || !Bot.IsPlayingPossible) { @@ -231,7 +231,7 @@ namespace ArchiSteamFarm { KeepFarming = NowFarming = true; Utilities.StartBackgroundFunction(Farm); } finally { - FarmingSemaphore.Release(); + FarmingInitializationSemaphore.Release(); } } @@ -240,7 +240,7 @@ namespace ArchiSteamFarm { return; } - await FarmingSemaphore.WaitAsync().ConfigureAwait(false); + await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false); try { if (!NowFarming) { @@ -248,7 +248,7 @@ namespace ArchiSteamFarm { } KeepFarming = false; - FarmResetEvent.Set(); + FarmingResetSemaphore.Release(); for (byte i = 0; (i < 5) && NowFarming; i++) { await Task.Delay(1000).ConfigureAwait(false); @@ -261,7 +261,7 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericInfo(Strings.IdlingStopped); Bot.OnFarmingStopped(); } finally { - FarmingSemaphore.Release(); + FarmingInitializationSemaphore.Release(); } } @@ -556,65 +556,68 @@ namespace ArchiSteamFarm { // If we have restricted card drops, we use complex algorithm Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex")); while (GamesToFarm.Count > 0) { - HashSet playableGamesToFarmSolo = new HashSet(); - foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) { - if (await IsPlayableGame(game).ConfigureAwait(false)) { - playableGamesToFarmSolo.Add(game); + HashSet gamesToCheck = new HashSet(GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)); + + foreach (Game game in gamesToCheck) { + if (!await IsPlayableGame(game).ConfigureAwait(false)) { + GamesToFarm.Remove(game); + continue; + } + + if (await FarmSolo(game).ConfigureAwait(false)) { + continue; + } + + NowFarming = false; + return; + } + + gamesToCheck = new HashSet(GamesToFarm.OrderByDescending(game => game.HoursPlayed)); + HashSet playableGamesToFarmMultiple = new HashSet(); + + foreach (Game game in gamesToCheck) { + if (!await IsPlayableGame(game).ConfigureAwait(false)) { + GamesToFarm.Remove(game); + continue; + } + + playableGamesToFarmMultiple.Add(game); + if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) { + break; } } - if (playableGamesToFarmSolo.Count > 0) { - while (playableGamesToFarmSolo.Count > 0) { - Game playableGame = playableGamesToFarmSolo.First(); - if (await FarmSolo(playableGame).ConfigureAwait(false)) { - playableGamesToFarmSolo.Remove(playableGame); - } else { - NowFarming = false; - return; - } - } + if (playableGamesToFarmMultiple.Count == 0) { + break; + } + + if (await FarmMultiple(playableGamesToFarmMultiple).ConfigureAwait(false)) { + Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID)))); } else { - HashSet playableGamesToFarmMultiple = new HashSet(); - foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed < HoursToBump).OrderByDescending(game => game.HoursPlayed)) { - if (await IsPlayableGame(game).ConfigureAwait(false)) { - playableGamesToFarmMultiple.Add(game); - } - - if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) { - break; - } - } - - if (FarmMultiple(playableGamesToFarmMultiple)) { - Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID)))); - } else { - NowFarming = false; - return; - } + NowFarming = false; + return; } } } else { // If we have unrestricted card drops, we use simple algorithm Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple")); + while (GamesToFarm.Count > 0) { - Game playableGame = null; - foreach (Game game in GamesToFarm) { + HashSet gamesToCheck = new HashSet(GamesToFarm); + + foreach (Game game in gamesToCheck) { if (!await IsPlayableGame(game).ConfigureAwait(false)) { + GamesToFarm.Remove(game); continue; } - playableGame = game; - break; - } - - if (playableGame != null) { - if (await FarmSolo(playableGame).ConfigureAwait(false)) { + if (await FarmSolo(game).ConfigureAwait(false)) { continue; } - } - NowFarming = false; - return; + NowFarming = false; + return; + } } } } while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault()); @@ -646,8 +649,7 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName)); DateTime startFarmingPeriod = DateTime.UtcNow; - if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { - FarmResetEvent.Reset(); + if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) { success = KeepFarming; } @@ -665,7 +667,7 @@ namespace ArchiSteamFarm { return success; } - private bool FarmHours(ConcurrentHashSet games) { + private async Task FarmHours(ConcurrentHashSet games) { if ((games == null) || (games.Count == 0)) { Bot.ArchiLogger.LogNullError(nameof(games)); return false; @@ -689,8 +691,7 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID)))); DateTime startFarmingPeriod = DateTime.UtcNow; - if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { - FarmResetEvent.Reset(); + if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) { success = KeepFarming; } @@ -711,7 +712,7 @@ namespace ArchiSteamFarm { return success; } - private bool FarmMultiple(IEnumerable games) { + private async Task FarmMultiple(IEnumerable games) { if (games == null) { Bot.ArchiLogger.LogNullError(nameof(games)); return false; @@ -721,7 +722,7 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdlingList, string.Join(", ", CurrentGamesFarming.Select(game => game.AppID)))); - bool result = FarmHours(CurrentGamesFarming); + bool result = await FarmHours(CurrentGamesFarming).ConfigureAwait(false); CurrentGamesFarming.Clear(); return result; } diff --git a/ArchiSteamFarm/ConcurrentSortedHashSet.cs b/ArchiSteamFarm/ConcurrentSortedHashSet.cs index 17ac7951d..fbeb7ad80 100644 --- a/ArchiSteamFarm/ConcurrentSortedHashSet.cs +++ b/ArchiSteamFarm/ConcurrentSortedHashSet.cs @@ -44,7 +44,7 @@ namespace ArchiSteamFarm { public bool IsReadOnly => false; private readonly HashSet BackingCollection = new HashSet(); - private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1); + private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1); public bool Add(T item) { SemaphoreSlim.Wait(); diff --git a/ArchiSteamFarm/GlobalConfig.cs b/ArchiSteamFarm/GlobalConfig.cs index 6c0321561..7275e7042 100644 --- a/ArchiSteamFarm/GlobalConfig.cs +++ b/ArchiSteamFarm/GlobalConfig.cs @@ -102,7 +102,7 @@ namespace ArchiSteamFarm { internal readonly bool Statistics = true; [JsonProperty(Required = Required.DisallowNull)] - internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.All; + internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.Tcp; [JsonProperty(Required = Required.DisallowNull)] internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable; diff --git a/ArchiSteamFarm/GlobalDatabase.cs b/ArchiSteamFarm/GlobalDatabase.cs index 4219a5d53..4987f62e3 100644 --- a/ArchiSteamFarm/GlobalDatabase.cs +++ b/ArchiSteamFarm/GlobalDatabase.cs @@ -44,7 +44,7 @@ namespace ArchiSteamFarm { private readonly object FileLock = new object(); - private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1); + private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1); internal uint CellID { get => _CellID; diff --git a/ArchiSteamFarm/MobileAuthenticator.cs b/ArchiSteamFarm/MobileAuthenticator.cs index 8c4a0453e..95f1ed762 100644 --- a/ArchiSteamFarm/MobileAuthenticator.cs +++ b/ArchiSteamFarm/MobileAuthenticator.cs @@ -42,14 +42,14 @@ namespace ArchiSteamFarm { private const byte CodeInterval = 30; private static readonly char[] CodeCharacters = { '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y' }; - private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1); + private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1, 1); private static int? SteamTimeDifference; // "ERROR" is being used by SteamDesktopAuthenticator internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); - private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1); + private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1, 1); #pragma warning disable 649 [JsonProperty(PropertyName = "identity_secret", Required = Required.Always)] diff --git a/ArchiSteamFarm/Statistics.cs b/ArchiSteamFarm/Statistics.cs index e1f224841..1c9a28e06 100644 --- a/ArchiSteamFarm/Statistics.cs +++ b/ArchiSteamFarm/Statistics.cs @@ -40,7 +40,7 @@ namespace ArchiSteamFarm { private const string URL = "https://" + SharedInfo.StatisticsServer; private readonly Bot Bot; - private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1); + private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1); private DateTime LastAnnouncementCheck = DateTime.MinValue; private DateTime LastHeartBeat = DateTime.MinValue; diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 976099fc3..82fc988d3 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -37,7 +37,7 @@ namespace ArchiSteamFarm { private readonly Bot Bot; private readonly ConcurrentHashSet IgnoredTrades = new ConcurrentHashSet(); - private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1); + private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1, 1); private bool ParsingScheduled; diff --git a/ArchiSteamFarm/config/ASF.json b/ArchiSteamFarm/config/ASF.json index c33993c5e..aa1ffdec3 100644 --- a/ArchiSteamFarm/config/ASF.json +++ b/ArchiSteamFarm/config/ASF.json @@ -18,6 +18,6 @@ "OptimizationMode": 0, "Statistics": true, "SteamOwnerID": 0, - "SteamProtocols": 7, + "SteamProtocols": 1, "UpdateChannel": 1 } \ No newline at end of file