From df218074ad5fae31c7becc83449cd1f4485fa5a2 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Thu, 24 Nov 2016 07:32:16 +0100 Subject: [PATCH] Gigantic code cleanup Time to enforce some common file layout, as general mess started to annoying me. Sorry in advance for people using custom forks and having merge conflicts, this will help everybody in long-run --- ArchiSteamFarm.sln.DotSettings | 214 +- ArchiSteamFarm/ASF.cs | 55 +- ArchiSteamFarm/App.config | 11 +- ArchiSteamFarm/ArchiHandler.cs | 430 ++- ArchiSteamFarm/ArchiLogger.cs | 78 +- ArchiSteamFarm/ArchiServiceInstaller.cs | 7 +- ArchiSteamFarm/ArchiWebHandler.cs | 1100 ++++--- ArchiSteamFarm/Bot.cs | 2754 +++++++++--------- ArchiSteamFarm/BotConfig.cs | 180 +- ArchiSteamFarm/BotDatabase.cs | 52 +- ArchiSteamFarm/CardsFarmer.cs | 521 ++-- ArchiSteamFarm/ConcurrentEnumerator.cs | 10 +- ArchiSteamFarm/ConcurrentHashSet.cs | 67 +- ArchiSteamFarm/CryptoHelper.cs | 126 +- ArchiSteamFarm/Debugging.cs | 4 +- ArchiSteamFarm/Events.cs | 7 +- ArchiSteamFarm/GlobalConfig.cs | 86 +- ArchiSteamFarm/GlobalDatabase.cs | 63 +- ArchiSteamFarm/IPAddressConverter.cs | 2 +- ArchiSteamFarm/IPEndPointConverter.cs | 2 +- ArchiSteamFarm/InMemoryServerListProvider.cs | 7 +- ArchiSteamFarm/JSON/GitHub.cs | 8 +- ArchiSteamFarm/JSON/Steam.cs | 675 +++-- ArchiSteamFarm/Logging.cs | 51 +- ArchiSteamFarm/MobileAuthenticator.cs | 297 +- ArchiSteamFarm/Program.cs | 292 +- ArchiSteamFarm/Properties/AssemblyInfo.cs | 6 +- ArchiSteamFarm/Runtime.cs | 126 +- ArchiSteamFarm/SharedInfo.cs | 30 +- ArchiSteamFarm/Trading.cs | 67 +- ArchiSteamFarm/Utilities.cs | 2 +- ArchiSteamFarm/WCF.cs | 62 +- ArchiSteamFarm/WebBrowser.cs | 129 +- ArchiSteamFarm/config/ASF.json | 54 +- ArchiSteamFarm/config/example.json | 54 +- ArchiSteamFarm/config/minimal.json | 6 +- ArchiSteamFarm/packages.config | 1 + ConfigGenerator/ASFConfig.cs | 28 +- ConfigGenerator/App.config | 11 +- ConfigGenerator/BotConfig.cs | 233 +- ConfigGenerator/ConfigPage.cs | 2 +- ConfigGenerator/Debugging.cs | 2 +- ConfigGenerator/DialogBox.cs | 79 +- ConfigGenerator/EnhancedPropertyGrid.cs | 22 +- ConfigGenerator/FlagEnumEditor.cs | 129 +- ConfigGenerator/FodyWeavers.xml | 1 + ConfigGenerator/GlobalConfig.cs | 104 +- ConfigGenerator/Logging.cs | 20 +- ConfigGenerator/MainForm.cs | 62 +- ConfigGenerator/Program.cs | 32 +- ConfigGenerator/Properties/AssemblyInfo.cs | 6 +- ConfigGenerator/Properties/Settings.settings | 3 +- ConfigGenerator/RunTime.cs | 5 +- ConfigGenerator/Tutorial.cs | 30 +- ConfigGenerator/packages.config | 1 + GUI/App.config | 9 +- GUI/BotStatusForm.cs | 2 +- GUI/Events.cs | 3 +- GUI/FodyWeavers.xml | 1 + GUI/Logging.cs | 24 +- GUI/MainForm.cs | 89 +- GUI/Program.cs | 111 +- GUI/Properties/AssemblyInfo.cs | 6 +- GUI/Properties/Settings.settings | 3 +- GUI/packages.config | 1 + 65 files changed, 4299 insertions(+), 4356 deletions(-) diff --git a/ArchiSteamFarm.sln.DotSettings b/ArchiSteamFarm.sln.DotSettings index b084525c8..9a21d2744 100644 --- a/ArchiSteamFarm.sln.DotSettings +++ b/ArchiSteamFarm.sln.DotSettings @@ -90,10 +90,14 @@ END_OF_LINE True END_OF_LINE + 1 END_OF_LINE TOGETHER_SAME_LINE END_OF_LINE END_OF_LINE + 1 + 1 + False END_OF_LINE False False @@ -102,9 +106,216 @@ False LINE_BREAK True + True END_OF_LINE - 255 + 1000 False + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="ArchiPattern" Priority="150"> + <Entry DisplayName="Public (Events and Delegates)"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Delegate" /> + <Kind Is="Event" /> + </Or> + </And> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Kind Is="Member" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Constants"> + <Entry.Match> + <Kind Is="Constant" /> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Kind Is="Member" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Static (Fields, Properties and Indexers)"> + <Entry.Match> + <Or> + <And> + <Kind Is="Field" /> + <Static /> + </And> + <And> + <Kind Is="Autoproperty" /> + <Static /> + </And> + <And> + <Kind Is="Property" /> + <Static /> + </And> + <And> + <Kind Is="Indexer" /> + <Static /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Readonly /> + <Kind Is="Member" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Non-static (Fields, Properties and Indexers)"> + <Entry.Match> + <And> + <Not> + <Static /> + </Not> + <Or> + <Kind Is="Field" /> + <Kind Is="Autoproperty" /> + <Kind Is="Property" /> + <Kind Is="Indexer" /> + </Or> + </And> + </Entry.Match> + <Entry.SortBy> + <Readonly /> + <Access /> + <Kind Is="Member" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Constructors"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Kind Is="Member" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Interfaces"> + <Entry.Match> + <And> + <Kind Is="Member" /> + <ImplementsInterface /> + </And> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Kind Is="Member" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Everything else"> + <Entry.SortBy> + <Access /> + <Kind Is="Member" /> + <Name /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="Nested"> + <Entry.Match> + <Kind Is="Type" /> + </Entry.Match> + <Entry.SortBy> + <Access /> + <Kind Is="Member" /> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasMember> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" Inherited="True" /> + </And> + </HasMember> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <Or> + <Kind Is="Constructor" /> + <And> + <Kind Is="Method" /> + <ImplementsInterface Name="System.IDisposable" /> + </And> + </Or> + </Entry.Match> + <Entry.SortBy> + <Kind Order="Constructor" /> + </Entry.SortBy> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Test Methods" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="Xunit.FactAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry DisplayName="Test Methods" Priority="100"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> +</Patterns> UseExplicitType UseExplicitType UseExplicitType @@ -129,5 +340,6 @@ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="AaBb" /><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="AaBb" /><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> True + True True True \ No newline at end of file diff --git a/ArchiSteamFarm/ASF.cs b/ArchiSteamFarm/ASF.cs index 04a93e935..b2be71eaf 100644 --- a/ArchiSteamFarm/ASF.cs +++ b/ArchiSteamFarm/ASF.cs @@ -34,28 +34,6 @@ using ArchiSteamFarm.JSON; namespace ArchiSteamFarm { internal static class ASF { - internal sealed class BotConfigEventArgs : EventArgs { - internal readonly BotConfig BotConfig; - - internal BotConfigEventArgs(BotConfig botConfig = null) { - BotConfig = botConfig; - } - } - - internal enum EUserInputType : byte { - Unknown, - DeviceID, - Login, - Password, - PhoneNumber, - SMS, - SteamGuard, - SteamParentalPIN, - RevocationCode, - TwoFactorAuthentication, - WCFHostname - } - internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF); private static readonly ConcurrentDictionary LastWriteTimes = new ConcurrentDictionary(); @@ -90,10 +68,7 @@ namespace ArchiSteamFarm { } if ((AutoUpdatesTimer == null) && Program.GlobalConfig.AutoUpdates) { - AutoUpdatesTimer = new Timer( - async e => await CheckForUpdate().ConfigureAwait(false), - null, - TimeSpan.FromDays(1), // Delay + AutoUpdatesTimer = new Timer(async e => await CheckForUpdate().ConfigureAwait(false), null, TimeSpan.FromDays(1), // Delay TimeSpan.FromDays(1) // Period ); @@ -258,9 +233,7 @@ namespace ArchiSteamFarm { return; } - FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*.json") { - NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite - }; + FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*.json") { NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite }; FileSystemWatcher.Changed += OnChanged; FileSystemWatcher.Created += OnCreated; @@ -408,5 +381,27 @@ namespace ArchiSteamFarm { CreateBot(newBotName).Forget(); } + + internal sealed class BotConfigEventArgs : EventArgs { + internal readonly BotConfig BotConfig; + + internal BotConfigEventArgs(BotConfig botConfig = null) { + BotConfig = botConfig; + } + } + + internal enum EUserInputType : byte { + Unknown, + DeviceID, + Login, + Password, + PhoneNumber, + SMS, + SteamGuard, + SteamParentalPIN, + RevocationCode, + TwoFactorAuthentication, + WCFHostname + } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/App.config b/ArchiSteamFarm/App.config index fd9961fb2..efb4d02fd 100644 --- a/ArchiSteamFarm/App.config +++ b/ArchiSteamFarm/App.config @@ -1,6 +1,7 @@ - + + - - - - + + + + \ No newline at end of file diff --git a/ArchiSteamFarm/ArchiHandler.cs b/ArchiSteamFarm/ArchiHandler.cs index c2da0e33e..fea520c72 100644 --- a/ArchiSteamFarm/ArchiHandler.cs +++ b/ArchiSteamFarm/ArchiHandler.cs @@ -22,8 +22,6 @@ */ -using SteamKit2; -using SteamKit2.Internal; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; @@ -31,6 +29,8 @@ using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; +using SteamKit2; +using SteamKit2.Internal; namespace ArchiSteamFarm { internal sealed class ArchiHandler : ClientMsgHandler { @@ -44,187 +44,37 @@ namespace ArchiSteamFarm { ArchiLogger = archiLogger; } - /* - ____ _ _ _ _ - / ___| __ _ | || || |__ __ _ ___ | | __ ___ - | | / _` || || || '_ \ / _` | / __|| |/ // __| - | |___| (_| || || || |_) || (_| || (__ | < \__ \ - \____|\__,_||_||_||_.__/ \__,_| \___||_|\_\|___/ - - */ - - internal sealed class NotificationsCallback : CallbackMsg { - internal enum ENotification : byte { - [SuppressMessage("ReSharper", "UnusedMember.Global")] - Unknown = 0, - Trading = 1, - // Only custom below, different than ones available as user_notification_type - Items = 254 + public override void HandleMsg(IPacketMsg packetMsg) { + if (packetMsg == null) { + ArchiLogger.LogNullError(nameof(packetMsg)); + return; } - internal readonly HashSet Notifications; - - internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) { - if ((jobID == null) || (msg == null)) { - throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); - } - - JobID = jobID; - - if (msg.notifications.Count == 0) { - return; - } - - Notifications = new HashSet(msg.notifications.Select(notification => (ENotification) notification.user_notification_type)); - } - - internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) { - if ((jobID == null) || (msg == null)) { - throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); - } - - JobID = jobID; - - if (msg.count_new_items > 0) { - Notifications = new HashSet { ENotification.Items }; - } + switch (packetMsg.MsgType) { + case EMsg.ClientFSOfflineMessageNotification: + HandleFSOfflineMessageNotification(packetMsg); + break; + case EMsg.ClientItemAnnouncements: + HandleItemAnnouncements(packetMsg); + break; + case EMsg.ClientPlayingSessionState: + HandlePlayingSessionState(packetMsg); + break; + case EMsg.ClientPurchaseResponse: + HandlePurchaseResponse(packetMsg); + break; + case EMsg.ClientRedeemGuestPassResponse: + HandleRedeemGuestPassResponse(packetMsg); + break; + case EMsg.ClientSharedLibraryLockStatus: + HandleSharedLibraryLockStatus(packetMsg); + break; + case EMsg.ClientUserNotifications: + HandleUserNotifications(packetMsg); + break; } } - internal sealed class OfflineMessageCallback : CallbackMsg { - internal readonly uint OfflineMessagesCount; - - internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) { - if ((jobID == null) || (msg == null)) { - throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); - } - - JobID = jobID; - OfflineMessagesCount = msg.offline_messages; - } - } - - internal sealed class PlayingSessionStateCallback : CallbackMsg { - internal readonly bool PlayingBlocked; - - internal PlayingSessionStateCallback(JobID jobID, CMsgClientPlayingSessionState msg) { - if ((jobID == null) || (msg == null)) { - throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); - } - - JobID = jobID; - PlayingBlocked = msg.playing_blocked; - } - } - - internal sealed class PurchaseResponseCallback : CallbackMsg { - internal enum EPurchaseResult : sbyte { - [SuppressMessage("ReSharper", "UnusedMember.Global")] - Unknown = -2, - Timeout = -1, - OK = 0, - AlreadyOwned = 9, - RegionLocked = 13, - InvalidKey = 14, - DuplicatedKey = 15, - BaseGameRequired = 24, - SteamWalletCode = 50, - OnCooldown = 53 - } - - internal readonly Dictionary Items; - - internal EPurchaseResult PurchaseResult { get; set; } - - internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) { - if ((jobID == null) || (msg == null)) { - throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); - } - - JobID = jobID; - PurchaseResult = (EPurchaseResult) msg.purchase_result_details; - - if (msg.purchase_receipt_info == null) { - return; - } - - KeyValue receiptInfo = new KeyValue(); - using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) { - if (!receiptInfo.TryReadAsBinary(ms)) { - ASF.ArchiLogger.LogNullError(nameof(ms)); - return; - } - } - - List lineItems = receiptInfo["lineitems"].Children; - if (lineItems.Count == 0) { - return; - } - - Items = new Dictionary(lineItems.Count); - foreach (KeyValue lineItem in lineItems) { - uint packageID = lineItem["PackageID"].AsUnsignedInteger(); - if (packageID == 0) { - // Valid, coupons have PackageID of -1 (don't ask me why) - packageID = lineItem["ItemAppID"].AsUnsignedInteger(); - if (packageID == 0) { - ASF.ArchiLogger.LogNullError(nameof(packageID)); - return; - } - } - - string gameName = lineItem["ItemDescription"].Value; - if (string.IsNullOrEmpty(gameName)) { - ASF.ArchiLogger.LogNullError(nameof(gameName)); - return; - } - - gameName = WebUtility.HtmlDecode(gameName); // Apparently steam expects client to decode sent HTML - Items[packageID] = WebUtility.HtmlDecode(gameName); - } - } - } - - internal sealed class RedeemGuestPassResponseCallback : CallbackMsg { - internal readonly EResult Result; - - internal RedeemGuestPassResponseCallback(JobID jobID, CMsgClientRedeemGuestPassResponse msg) { - if ((jobID == null) || (msg == null)) { - throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); - } - - JobID = jobID; - Result = (EResult) msg.eresult; - } - } - - internal sealed class SharedLibraryLockStatusCallback : CallbackMsg { - internal readonly ulong LibraryLockedBySteamID; - - internal SharedLibraryLockStatusCallback(JobID jobID, CMsgClientSharedLibraryLockStatus msg) { - if ((jobID == null) || (msg == null)) { - throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); - } - - JobID = jobID; - - if (msg.own_library_locked_by == 0) { - return; - } - - LibraryLockedBySteamID = new SteamID(msg.own_library_locked_by, EUniverse.Public, EAccountType.Individual); - } - } - - /* - __ __ _ _ _ - | \/ | ___ | |_ | |__ ___ __| | ___ - | |\/| | / _ \| __|| '_ \ / _ \ / _` |/ __| - | | | || __/| |_ | | | || (_) || (_| |\__ \ - |_| |_| \___| \__||_| |_| \___/ \__,_||___/ - - */ - // TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed internal void LogOnWithoutMachineID(SteamUser.LogOnDetails details) { if (details == null) { @@ -276,7 +126,6 @@ namespace ArchiSteamFarm { logon.Body.sha_sentryfile = details.SentryFileHash; logon.Body.eresult_sentryfile = (int) (details.SentryFileHash != null ? EResult.OK : EResult.FileNotFound); - Client.Send(logon); } @@ -301,19 +150,11 @@ namespace ArchiSteamFarm { ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientGamesPlayed); if (!string.IsNullOrEmpty(gameName)) { - request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { - game_extra_info = gameName, - game_id = new GameID { - AppType = GameID.GameType.Shortcut, - ModID = uint.MaxValue - } - }); + request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_extra_info = gameName, game_id = new GameID { AppType = GameID.GameType.Shortcut, ModID = uint.MaxValue } }); } foreach (uint gameID in gameIDs.Where(gameID => gameID != 0)) { - request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { - game_id = new GameID(gameID) - }); + request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_id = new GameID(gameID) }); } Client.Send(request); @@ -329,9 +170,7 @@ namespace ArchiSteamFarm { return null; } - ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientRedeemGuestPass) { - SourceJobID = Client.GetNextJobID() - }; + ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientRedeemGuestPass) { SourceJobID = Client.GetNextJobID() }; request.Body.guest_pass_id = guestPassID; @@ -355,9 +194,7 @@ namespace ArchiSteamFarm { return null; } - ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientRegisterKey) { - SourceJobID = Client.GetNextJobID() - }; + ClientMsgProtobuf request = new ClientMsgProtobuf(EMsg.ClientRegisterKey) { SourceJobID = Client.GetNextJobID() }; request.Body.key = key; @@ -371,46 +208,6 @@ namespace ArchiSteamFarm { } } - /* - _ _ _ _ - | | | | __ _ _ __ __| || | ___ _ __ ___ - | |_| | / _` || '_ \ / _` || | / _ \| '__|/ __| - | _ || (_| || | | || (_| || || __/| | \__ \ - |_| |_| \__,_||_| |_| \__,_||_| \___||_| |___/ - - */ - - public override void HandleMsg(IPacketMsg packetMsg) { - if (packetMsg == null) { - ArchiLogger.LogNullError(nameof(packetMsg)); - return; - } - - switch (packetMsg.MsgType) { - case EMsg.ClientFSOfflineMessageNotification: - HandleFSOfflineMessageNotification(packetMsg); - break; - case EMsg.ClientItemAnnouncements: - HandleItemAnnouncements(packetMsg); - break; - case EMsg.ClientPlayingSessionState: - HandlePlayingSessionState(packetMsg); - break; - case EMsg.ClientPurchaseResponse: - HandlePurchaseResponse(packetMsg); - break; - case EMsg.ClientRedeemGuestPassResponse: - HandleRedeemGuestPassResponse(packetMsg); - break; - case EMsg.ClientSharedLibraryLockStatus: - HandleSharedLibraryLockStatus(packetMsg); - break; - case EMsg.ClientUserNotifications: - HandleUserNotifications(packetMsg); - break; - } - } - private void HandleFSOfflineMessageNotification(IPacketMsg packetMsg) { if (packetMsg == null) { ArchiLogger.LogNullError(nameof(packetMsg)); @@ -480,5 +277,168 @@ namespace ArchiSteamFarm { ClientMsgProtobuf response = new ClientMsgProtobuf(packetMsg); Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body)); } + + internal sealed class NotificationsCallback : CallbackMsg { + internal readonly HashSet Notifications; + + internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) { + if ((jobID == null) || (msg == null)) { + throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); + } + + JobID = jobID; + + if (msg.notifications.Count == 0) { + return; + } + + Notifications = new HashSet(msg.notifications.Select(notification => (ENotification) notification.user_notification_type)); + } + + internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) { + if ((jobID == null) || (msg == null)) { + throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); + } + + JobID = jobID; + + if (msg.count_new_items > 0) { + Notifications = new HashSet { ENotification.Items }; + } + } + + internal enum ENotification : byte { + [SuppressMessage("ReSharper", "UnusedMember.Global")] + Unknown = 0, + Trading = 1, + // Only custom below, different than ones available as user_notification_type + Items = 254 + } + } + + internal sealed class OfflineMessageCallback : CallbackMsg { + internal readonly uint OfflineMessagesCount; + + internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) { + if ((jobID == null) || (msg == null)) { + throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); + } + + JobID = jobID; + OfflineMessagesCount = msg.offline_messages; + } + } + + internal sealed class PlayingSessionStateCallback : CallbackMsg { + internal readonly bool PlayingBlocked; + + internal PlayingSessionStateCallback(JobID jobID, CMsgClientPlayingSessionState msg) { + if ((jobID == null) || (msg == null)) { + throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); + } + + JobID = jobID; + PlayingBlocked = msg.playing_blocked; + } + } + + internal sealed class PurchaseResponseCallback : CallbackMsg { + internal readonly Dictionary Items; + + internal EPurchaseResult PurchaseResult { get; set; } + + internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) { + if ((jobID == null) || (msg == null)) { + throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); + } + + JobID = jobID; + PurchaseResult = (EPurchaseResult) msg.purchase_result_details; + + if (msg.purchase_receipt_info == null) { + return; + } + + KeyValue receiptInfo = new KeyValue(); + using (MemoryStream ms = new MemoryStream(msg.purchase_receipt_info)) { + if (!receiptInfo.TryReadAsBinary(ms)) { + ASF.ArchiLogger.LogNullError(nameof(ms)); + return; + } + } + + List lineItems = receiptInfo["lineitems"].Children; + if (lineItems.Count == 0) { + return; + } + + Items = new Dictionary(lineItems.Count); + foreach (KeyValue lineItem in lineItems) { + uint packageID = lineItem["PackageID"].AsUnsignedInteger(); + if (packageID == 0) { + // Valid, coupons have PackageID of -1 (don't ask me why) + packageID = lineItem["ItemAppID"].AsUnsignedInteger(); + if (packageID == 0) { + ASF.ArchiLogger.LogNullError(nameof(packageID)); + return; + } + } + + string gameName = lineItem["ItemDescription"].Value; + if (string.IsNullOrEmpty(gameName)) { + ASF.ArchiLogger.LogNullError(nameof(gameName)); + return; + } + + gameName = WebUtility.HtmlDecode(gameName); // Apparently steam expects client to decode sent HTML + Items[packageID] = WebUtility.HtmlDecode(gameName); + } + } + + internal enum EPurchaseResult : sbyte { + [SuppressMessage("ReSharper", "UnusedMember.Global")] + Unknown = -2, + Timeout = -1, + OK = 0, + AlreadyOwned = 9, + RegionLocked = 13, + InvalidKey = 14, + DuplicatedKey = 15, + BaseGameRequired = 24, + SteamWalletCode = 50, + OnCooldown = 53 + } + } + + internal sealed class RedeemGuestPassResponseCallback : CallbackMsg { + internal readonly EResult Result; + + internal RedeemGuestPassResponseCallback(JobID jobID, CMsgClientRedeemGuestPassResponse msg) { + if ((jobID == null) || (msg == null)) { + throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); + } + + JobID = jobID; + Result = (EResult) msg.eresult; + } + } + + internal sealed class SharedLibraryLockStatusCallback : CallbackMsg { + internal readonly ulong LibraryLockedBySteamID; + + internal SharedLibraryLockStatusCallback(JobID jobID, CMsgClientSharedLibraryLockStatus msg) { + if ((jobID == null) || (msg == null)) { + throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); + } + + JobID = jobID; + + if (msg.own_library_locked_by == 0) { + return; + } + + LibraryLockedBySteamID = new SteamID(msg.own_library_locked_by, EUniverse.Public, EAccountType.Individual); + } + } } } \ No newline at end of file diff --git a/ArchiSteamFarm/ArchiLogger.cs b/ArchiSteamFarm/ArchiLogger.cs index 9ff07284f..56d88b3bc 100644 --- a/ArchiSteamFarm/ArchiLogger.cs +++ b/ArchiSteamFarm/ArchiLogger.cs @@ -40,24 +40,6 @@ namespace ArchiSteamFarm { Logger = LogManager.GetLogger(name); } - internal void LogGenericError(string message, [CallerMemberName] string previousMethodName = null) { - if (string.IsNullOrEmpty(message)) { - LogNullError(nameof(message)); - return; - } - - Logger.Error($"{previousMethodName}() {message}"); - } - - internal void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = null) { - if (exception == null) { - LogNullError(nameof(exception)); - return; - } - - Logger.Error(exception, $"{previousMethodName}()"); - } - [SuppressMessage("ReSharper", "LocalizableElement")] internal void LogFatalException(Exception exception, [CallerMemberName] string previousMethodName = null) { if (exception == null) { @@ -86,13 +68,31 @@ namespace ArchiSteamFarm { } } - internal void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) { + internal void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = null) { if (string.IsNullOrEmpty(message)) { LogNullError(nameof(message)); return; } - Logger.Warn($"{previousMethodName}() {message}"); + Logger.Debug($"{previousMethodName}() {message}"); + } + + internal void LogGenericError(string message, [CallerMemberName] string previousMethodName = null) { + if (string.IsNullOrEmpty(message)) { + LogNullError(nameof(message)); + return; + } + + Logger.Error($"{previousMethodName}() {message}"); + } + + internal void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = null) { + if (exception == null) { + LogNullError(nameof(exception)); + return; + } + + Logger.Error(exception, $"{previousMethodName}()"); } internal void LogGenericInfo(string message, [CallerMemberName] string previousMethodName = null) { @@ -104,24 +104,6 @@ namespace ArchiSteamFarm { Logger.Info($"{previousMethodName}() {message}"); } - [SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")] - internal void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = null) { - if (string.IsNullOrEmpty(nullObjectName)) { - return; - } - - LogGenericError(nullObjectName + " is null!", previousMethodName); - } - - internal void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = null) { - if (string.IsNullOrEmpty(message)) { - LogNullError(nameof(message)); - return; - } - - Logger.Debug($"{previousMethodName}() {message}"); - } - internal void LogGenericTrace(string message, [CallerMemberName] string previousMethodName = null) { if (string.IsNullOrEmpty(message)) { LogNullError(nameof(message)); @@ -130,5 +112,23 @@ namespace ArchiSteamFarm { Logger.Trace($"{previousMethodName}() {message}"); } + + internal void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) { + if (string.IsNullOrEmpty(message)) { + LogNullError(nameof(message)); + return; + } + + Logger.Warn($"{previousMethodName}() {message}"); + } + + [SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")] + internal void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = null) { + if (string.IsNullOrEmpty(nullObjectName)) { + return; + } + + LogGenericError(nullObjectName + " is null!", previousMethodName); + } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/ArchiServiceInstaller.cs b/ArchiSteamFarm/ArchiServiceInstaller.cs index 817193c37..aacbc6497 100644 --- a/ArchiSteamFarm/ArchiServiceInstaller.cs +++ b/ArchiSteamFarm/ArchiServiceInstaller.cs @@ -48,14 +48,11 @@ namespace ArchiSteamFarm { serviceInstaller.Installers.Clear(); - EventLogInstaller logInstaller = new EventLogInstaller { - Log = SharedInfo.EventLog, - Source = SharedInfo.EventLogSource - }; + EventLogInstaller logInstaller = new EventLogInstaller { Log = SharedInfo.EventLog, Source = SharedInfo.EventLogSource }; Installers.Add(serviceInstaller); Installers.Add(serviceProcessInstaller); Installers.Add(logInstaller); } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/ArchiWebHandler.cs b/ArchiSteamFarm/ArchiWebHandler.cs index 6c733c9fe..8f5897fce 100644 --- a/ArchiSteamFarm/ArchiWebHandler.cs +++ b/ArchiSteamFarm/ArchiWebHandler.cs @@ -22,29 +22,29 @@ */ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using HtmlAgilityPack; -using SteamKit2; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Xml; -using System.Threading; using ArchiSteamFarm.JSON; +using HtmlAgilityPack; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using SteamKit2; namespace ArchiSteamFarm { internal sealed class ArchiWebHandler : IDisposable { + private const byte MinSessionTTL = GlobalConfig.DefaultHttpTimeout/4; // Assume session is valid for at least that amount of seconds private const string SteamCommunityHost = "steamcommunity.com"; private const string SteamStoreHost = "store.steampowered.com"; - private const byte MinSessionTTL = GlobalConfig.DefaultHttpTimeout / 4; // Assume session is valid for at least that amount of seconds private static string SteamCommunityURL = "https://" + SteamCommunityHost; private static string SteamStoreURL = "https://" + SteamStoreHost; - private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000; // This must be int type + private static int Timeout = GlobalConfig.DefaultHttpTimeout*1000; // This must be int type private readonly Bot Bot; private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1); @@ -52,108 +52,8 @@ namespace ArchiSteamFarm { internal bool Ready { get; private set; } - private ulong SteamID; private DateTime LastSessionRefreshCheck = DateTime.MinValue; - - internal static void Init() { - Timeout = Program.GlobalConfig.HttpTimeout * 1000; - SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunityHost; - SteamStoreURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamStoreHost; - } - - private static uint GetAppIDFromMarketHashName(string hashName) { - if (string.IsNullOrEmpty(hashName)) { - ASF.ArchiLogger.LogNullError(nameof(hashName)); - return 0; - } - - int index = hashName.IndexOf('-'); - if (index <= 0) { - return 0; - } - - uint appID; - return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0; - } - - private static Steam.Item.EType GetItemType(string name) { - if (string.IsNullOrEmpty(name)) { - ASF.ArchiLogger.LogNullError(nameof(name)); - return Steam.Item.EType.Unknown; - } - - switch (name) { - case "Booster Pack": - return Steam.Item.EType.BoosterPack; - case "Coupon": - return Steam.Item.EType.Coupon; - case "Gift": - return Steam.Item.EType.Gift; - case "Steam Gems": - return Steam.Item.EType.SteamGems; - default: - if (name.EndsWith("Emoticon", StringComparison.Ordinal)) { - return Steam.Item.EType.Emoticon; - } - - if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) { - return Steam.Item.EType.FoilTradingCard; - } - - if (name.EndsWith("Profile Background", StringComparison.Ordinal)) { - return Steam.Item.EType.ProfileBackground; - } - - return name.EndsWith("Trading Card", StringComparison.Ordinal) ? Steam.Item.EType.TradingCard : Steam.Item.EType.Unknown; - } - } - - private static bool ParseItems(Dictionary> descriptions, List input, HashSet output) { - if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) { - ASF.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output)); - return false; - } - - foreach (KeyValue item in input) { - uint appID = item["appid"].AsUnsignedInteger(); - if (appID == 0) { - ASF.ArchiLogger.LogNullError(nameof(appID)); - return false; - } - - ulong contextID = item["contextid"].AsUnsignedLong(); - if (contextID == 0) { - ASF.ArchiLogger.LogNullError(nameof(contextID)); - return false; - } - - ulong classID = item["classid"].AsUnsignedLong(); - if (classID == 0) { - ASF.ArchiLogger.LogNullError(nameof(classID)); - return false; - } - - uint amount = item["amount"].AsUnsignedInteger(); - if (amount == 0) { - ASF.ArchiLogger.LogNullError(nameof(amount)); - return false; - } - - uint realAppID = appID; - Steam.Item.EType type = Steam.Item.EType.Unknown; - - Tuple description; - if (descriptions.TryGetValue(classID, out description)) { - realAppID = description.Item1; - type = description.Item2; - } - - Steam.Item steamItem = new Steam.Item(appID, contextID, classID, amount, realAppID, type); - output.Add(steamItem); - } - - return true; - } + private ulong SteamID; internal ArchiWebHandler(Bot bot) { if (bot == null) { @@ -167,127 +67,28 @@ namespace ArchiSteamFarm { public void Dispose() => SessionSemaphore.Dispose(); - internal void OnDisconnected() => Ready = false; - - internal async Task Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) { - if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) { - Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin)); - return false; + internal async Task AcceptTradeOffer(ulong tradeID) { + if (tradeID == 0) { + Bot.ArchiLogger.LogNullError(nameof(tradeID)); + return; } - SteamID = steamID; - - string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString())); - - // Generate an AES session key - byte[] sessionKey = SteamKit2.CryptoHelper.GenerateRandomBlock(32); - - // RSA encrypt it with the public key for the universe we're on - byte[] cryptedSessionKey; - using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(universe))) { - cryptedSessionKey = rsa.Encrypt(sessionKey); - } - - // Copy our login key - byte[] loginKey = new byte[webAPIUserNonce.Length]; - Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length); - - // AES encrypt the loginkey with our session key - byte[] cryptedLoginKey = SteamKit2.CryptoHelper.SymmetricEncrypt(loginKey, sessionKey); - - // Do the magic - Bot.ArchiLogger.LogGenericInfo("Logging in to ISteamUserAuth..."); - - KeyValue authResult; - using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) { - iSteamUserAuth.Timeout = Timeout; - - try { - authResult = iSteamUserAuth.AuthenticateUser( - steamid: steamID, - sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)), - encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)), - method: WebRequestMethods.Http.Post, - secure: !Program.GlobalConfig.ForceHttp - ); - } catch (Exception e) { - Bot.ArchiLogger.LogGenericException(e); - return false; - } - } - - if (authResult == null) { - Bot.ArchiLogger.LogNullError(nameof(authResult)); - return false; - } - - string steamLogin = authResult["token"].Value; - if (string.IsNullOrEmpty(steamLogin)) { - Bot.ArchiLogger.LogNullError(nameof(steamLogin)); - return false; - } - - string steamLoginSecure = authResult["tokensecure"].Value; - if (string.IsNullOrEmpty(steamLoginSecure)) { - Bot.ArchiLogger.LogNullError(nameof(steamLoginSecure)); - return false; - } - - WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost)); - WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamStoreHost)); - - WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost)); - WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamStoreHost)); - - WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost)); - WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamStoreHost)); - - Bot.ArchiLogger.LogGenericInfo("Success!"); - - // Unlock Steam Parental if needed - if (!parentalPin.Equals("0")) { - if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) { - return false; - } - } - - Ready = true; - LastSessionRefreshCheck = DateTime.Now; - return true; - } - - internal async Task> GetFamilySharingSteamIDs() { if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; + return; } - string request = SteamStoreURL + "/account/managedevices"; - HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); - - HtmlNodeCollection htmlNodes = htmlDocument?.DocumentNode.SelectNodes("(//table[@class='accountTable'])[last()]//a/@data-miniprofile"); - if (htmlNodes == null) { - return null; // OK, no authorized steamIDs + string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid"); + if (string.IsNullOrEmpty(sessionID)) { + Bot.ArchiLogger.LogNullError(nameof(sessionID)); + return; } - HashSet result = new HashSet(); + string referer = SteamCommunityURL + "/tradeoffer/" + tradeID; + string request = referer + "/accept"; - foreach (string miniProfile in htmlNodes.Select(htmlNode => htmlNode.GetAttributeValue("data-miniprofile", null))) { - if (string.IsNullOrEmpty(miniProfile)) { - Bot.ArchiLogger.LogNullError(nameof(miniProfile)); - return null; - } + Dictionary data = new Dictionary(3) { { "sessionid", sessionID }, { "serverid", "1" }, { "tradeofferid", tradeID.ToString() } }; - uint steamID3; - if (!uint.TryParse(miniProfile, out steamID3) || (steamID3 == 0)) { - Bot.ArchiLogger.LogNullError(nameof(steamID3)); - return null; - } - - ulong steamID = new SteamID(steamID3, EUniverse.Public, EAccountType.Individual); - result.Add(steamID); - } - - return result; + await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false); } internal async Task AddFreeLicense(uint subID) { @@ -307,180 +108,25 @@ namespace ArchiSteamFarm { } string request = SteamStoreURL + "/checkout/addfreelicense"; - Dictionary data = new Dictionary(3) { - { "sessionid", sessionID }, - { "subid", subID.ToString() }, - { "action", "add_to_cart" } - }; + Dictionary data = new Dictionary(3) { { "sessionid", sessionID }, { "subid", subID.ToString() }, { "action", "add_to_cart" } }; HtmlDocument htmlDocument = await WebBrowser.UrlPostToHtmlDocumentRetry(request, data).ConfigureAwait(false); return htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='add_free_content_success_area']") != null; } - internal async Task JoinGroup(ulong groupID) { - if (groupID == 0) { - Bot.ArchiLogger.LogNullError(nameof(groupID)); - return false; - } - - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return false; - } - - string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid"); - if (string.IsNullOrEmpty(sessionID)) { - Bot.ArchiLogger.LogNullError(nameof(sessionID)); - return false; - } - - string request = SteamCommunityURL + "/gid/" + groupID; - Dictionary data = new Dictionary(2) { - { "sessionID", sessionID }, - { "action", "join" } - }; - - return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false); - } - - internal async Task GetConfirmations(string deviceID, string confirmationHash, uint time) { - if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0)) { - Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time)); - return null; - } - - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; - } - - string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf"; - return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); - } - - internal async Task GetConfirmationDetails(string deviceID, string confirmationHash, uint time, MobileAuthenticator.Confirmation confirmation) { - if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmation == null)) { - Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmation)); - return null; - } - - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; - } - - string request = SteamCommunityURL + "/mobileconf/details/" + confirmation.ID + "?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf"; - - Steam.ConfirmationDetails response = await WebBrowser.UrlGetToJsonResultRetry(request).ConfigureAwait(false); - if ((response == null) || !response.Success) { - return null; - } - - response.Confirmation = confirmation; - return response; - } - - internal async Task HandleConfirmation(string deviceID, string confirmationHash, uint time, uint confirmationID, ulong confirmationKey, bool accept) { - if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0) || (confirmationKey == 0)) { - Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID) + " || " + nameof(confirmationKey)); - return null; - } - - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; - } - - string request = SteamCommunityURL + "/mobileconf/ajaxop?op=" + (accept ? "allow" : "cancel") + "&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf&cid=" + confirmationID + "&ck=" + confirmationKey; - - Steam.ConfirmationResponse response = await WebBrowser.UrlGetToJsonResultRetry(request).ConfigureAwait(false); - return response?.Success; - } - - internal async Task HandleConfirmations(string deviceID, string confirmationHash, uint time, HashSet confirmations, bool accept) { - if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmations == null) || (confirmations.Count == 0)) { - Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmations)); - return null; - } - - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; - } - - string request = SteamCommunityURL + "/mobileconf/multiajaxop"; - - List> data = new List>(7 + confirmations.Count * 2) { - new KeyValuePair("op", accept ? "allow" : "cancel"), - new KeyValuePair("p", deviceID), - new KeyValuePair("a", SteamID.ToString()), - new KeyValuePair("k", confirmationHash), - new KeyValuePair("t", time.ToString()), - new KeyValuePair("m", "android"), - new KeyValuePair("tag", "conf") - }; - - foreach (MobileAuthenticator.Confirmation confirmation in confirmations) { - data.Add(new KeyValuePair("cid[]", confirmation.ID.ToString())); - data.Add(new KeyValuePair("ck[]", confirmation.Key.ToString())); - } - - Steam.ConfirmationResponse response = await WebBrowser.UrlPostToJsonResultRetry(request, data).ConfigureAwait(false); - return response?.Success; - } - - internal async Task> GetOwnedGames() { - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; - } - - string request = SteamCommunityURL + "/my/games/?xml=1"; - - XmlDocument response = await WebBrowser.UrlGetToXMLRetry(request).ConfigureAwait(false); - - XmlNodeList xmlNodeList = response?.SelectNodes("gamesList/games/game"); - if ((xmlNodeList == null) || (xmlNodeList.Count == 0)) { - return null; - } - - Dictionary result = new Dictionary(xmlNodeList.Count); - foreach (XmlNode xmlNode in xmlNodeList) { - XmlNode appNode = xmlNode.SelectSingleNode("appID"); - if (appNode == null) { - Bot.ArchiLogger.LogNullError(nameof(appNode)); - return null; - } - - uint appID; - if (!uint.TryParse(appNode.InnerText, out appID)) { - Bot.ArchiLogger.LogNullError(nameof(appID)); - return null; - } - - XmlNode nameNode = xmlNode.SelectSingleNode("name"); - if (nameNode == null) { - Bot.ArchiLogger.LogNullError(nameof(nameNode)); - return null; - } - - result[appID] = nameNode.InnerText; - } - - return result; - } - - internal Dictionary GetOwnedGames(ulong steamID) { - if ((steamID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { - Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(Bot.BotConfig.SteamApiKey)); - return null; + internal void DeclineTradeOffer(ulong tradeID) { + if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { + Bot.ArchiLogger.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey)); + return; } KeyValue response = null; for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { - using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) { - iPlayerService.Timeout = Timeout; + using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) { + iEconService.Timeout = Timeout; try { - response = iPlayerService.GetOwnedGames( - steamid: steamID, - include_appinfo: 1, - secure: !Program.GlobalConfig.ForceHttp - ); + response = iEconService.DeclineTradeOffer(tradeofferid: tradeID.ToString(), method: WebRequestMethods.Http.Post, secure: !Program.GlobalConfig.ForceHttp); } catch (Exception e) { Bot.ArchiLogger.LogGenericException(e); } @@ -489,116 +135,7 @@ namespace ArchiSteamFarm { if (response == null) { Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries"); - return null; } - - Dictionary result = new Dictionary(response["games"].Children.Count); - foreach (KeyValue game in response["games"].Children) { - uint appID = game["appid"].AsUnsignedInteger(); - if (appID == 0) { - Bot.ArchiLogger.LogNullError(nameof(appID)); - return null; - } - - result[appID] = game["name"].Value; - } - - return result; - } - - internal uint GetServerTime() { - KeyValue response = null; - for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { - using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) { - iTwoFactorService.Timeout = Timeout; - - try { - response = iTwoFactorService.QueryTime( - method: WebRequestMethods.Http.Post, - secure: !Program.GlobalConfig.ForceHttp - ); - } catch (Exception e) { - Bot.ArchiLogger.LogGenericException(e); - } - } - } - - if (response != null) { - return response["server_time"].AsUnsignedInteger(); - } - - Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries"); - return 0; - } - - internal async Task GetTradeHoldDuration(ulong tradeID) { - if (tradeID == 0) { - Bot.ArchiLogger.LogNullError(nameof(tradeID)); - return null; - } - - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; - } - - string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english"; - - HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); - - HtmlNode htmlNode = htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script"); - if (htmlNode == null) { // Trade can be no longer valid - return null; - } - - string text = htmlNode.InnerText; - if (string.IsNullOrEmpty(text)) { - Bot.ArchiLogger.LogNullError(nameof(text)); - return null; - } - - int index = text.IndexOf("g_daysTheirEscrow = ", StringComparison.Ordinal); - if (index < 0) { - Bot.ArchiLogger.LogNullError(nameof(index)); - return null; - } - - index += 20; - text = text.Substring(index); - - index = text.IndexOf(';'); - if (index < 0) { - Bot.ArchiLogger.LogNullError(nameof(index)); - return null; - } - - text = text.Substring(0, index); - - byte holdDuration; - if (byte.TryParse(text, out holdDuration)) { - return holdDuration; - } - - Bot.ArchiLogger.LogNullError(nameof(holdDuration)); - return null; - } - - internal async Task RedeemWalletKey(string key) { - if (string.IsNullOrEmpty(key)) { - Bot.ArchiLogger.LogNullError(nameof(key)); - return ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Unknown; - } - - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Timeout; - } - - string request = SteamStoreURL + "/account/validatewalletcode"; - Dictionary data = new Dictionary(1) { - { "wallet_code", key } - }; - - Steam.RedeemWalletResponse response = await WebBrowser.UrlPostToJsonResultRetry(request, data).ConfigureAwait(false); - return response?.PurchaseResult ?? ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Timeout; } internal HashSet GetActiveTradeOffers() { @@ -613,12 +150,7 @@ namespace ArchiSteamFarm { iEconService.Timeout = Timeout; try { - response = iEconService.GetTradeOffers( - get_received_offers: 1, - active_only: 1, - get_descriptions: 1, - secure: !Program.GlobalConfig.ForceHttp - ); + response = iEconService.GetTradeOffers(get_received_offers: 1, active_only: 1, get_descriptions: 1, secure: !Program.GlobalConfig.ForceHttp); } catch (Exception e) { Bot.ArchiLogger.LogGenericException(e); } @@ -711,60 +243,101 @@ namespace ArchiSteamFarm { return result; } - internal async Task AcceptTradeOffer(ulong tradeID) { - if (tradeID == 0) { - Bot.ArchiLogger.LogNullError(nameof(tradeID)); - return; + internal async Task GetBadgePage(byte page) { + if (page == 0) { + Bot.ArchiLogger.LogNullError(nameof(page)); + return null; } if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return; + return null; } - string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid"); - if (string.IsNullOrEmpty(sessionID)) { - Bot.ArchiLogger.LogNullError(nameof(sessionID)); - return; - } - - string referer = SteamCommunityURL + "/tradeoffer/" + tradeID; - string request = referer + "/accept"; - - Dictionary data = new Dictionary(3) { - { "sessionid", sessionID }, - { "serverid", "1" }, - { "tradeofferid", tradeID.ToString() } - }; - - await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false); + string request = SteamCommunityURL + "/my/badges?l=english&p=" + page; + return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); } - internal void DeclineTradeOffer(ulong tradeID) { - if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { - Bot.ArchiLogger.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey)); - return; + internal async Task GetConfirmationDetails(string deviceID, string confirmationHash, uint time, MobileAuthenticator.Confirmation confirmation) { + if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmation == null)) { + Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmation)); + return null; } - KeyValue response = null; - for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { - using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) { - iEconService.Timeout = Timeout; + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } - try { - response = iEconService.DeclineTradeOffer( - tradeofferid: tradeID.ToString(), - method: WebRequestMethods.Http.Post, - secure: !Program.GlobalConfig.ForceHttp - ); - } catch (Exception e) { - Bot.ArchiLogger.LogGenericException(e); - } + string request = SteamCommunityURL + "/mobileconf/details/" + confirmation.ID + "?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf"; + + Steam.ConfirmationDetails response = await WebBrowser.UrlGetToJsonResultRetry(request).ConfigureAwait(false); + if ((response == null) || !response.Success) { + return null; + } + + response.Confirmation = confirmation; + return response; + } + + internal async Task GetConfirmations(string deviceID, string confirmationHash, uint time) { + if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0)) { + Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time)); + return null; + } + + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + + string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf"; + return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); + } + + internal async Task> GetFamilySharingSteamIDs() { + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + + string request = SteamStoreURL + "/account/managedevices"; + HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); + + HtmlNodeCollection htmlNodes = htmlDocument?.DocumentNode.SelectNodes("(//table[@class='accountTable'])[last()]//a/@data-miniprofile"); + if (htmlNodes == null) { + return null; // OK, no authorized steamIDs + } + + HashSet result = new HashSet(); + + foreach (string miniProfile in htmlNodes.Select(htmlNode => htmlNode.GetAttributeValue("data-miniprofile", null))) { + if (string.IsNullOrEmpty(miniProfile)) { + Bot.ArchiLogger.LogNullError(nameof(miniProfile)); + return null; } + + uint steamID3; + if (!uint.TryParse(miniProfile, out steamID3) || (steamID3 == 0)) { + Bot.ArchiLogger.LogNullError(nameof(steamID3)); + return null; + } + + ulong steamID = new SteamID(steamID3, EUniverse.Public, EAccountType.Individual); + result.Add(steamID); } - if (response == null) { - Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries"); + return result; + } + + internal async Task GetGameCardsPage(ulong appID) { + if (appID == 0) { + Bot.ArchiLogger.LogNullError(nameof(appID)); + return null; } + + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + + string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english"; + return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); } internal async Task> GetMySteamInventory(bool tradable) { @@ -883,6 +456,333 @@ namespace ArchiSteamFarm { return result; } + internal async Task> GetOwnedGames() { + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + + string request = SteamCommunityURL + "/my/games/?xml=1"; + + XmlDocument response = await WebBrowser.UrlGetToXMLRetry(request).ConfigureAwait(false); + + XmlNodeList xmlNodeList = response?.SelectNodes("gamesList/games/game"); + if ((xmlNodeList == null) || (xmlNodeList.Count == 0)) { + return null; + } + + Dictionary result = new Dictionary(xmlNodeList.Count); + foreach (XmlNode xmlNode in xmlNodeList) { + XmlNode appNode = xmlNode.SelectSingleNode("appID"); + if (appNode == null) { + Bot.ArchiLogger.LogNullError(nameof(appNode)); + return null; + } + + uint appID; + if (!uint.TryParse(appNode.InnerText, out appID)) { + Bot.ArchiLogger.LogNullError(nameof(appID)); + return null; + } + + XmlNode nameNode = xmlNode.SelectSingleNode("name"); + if (nameNode == null) { + Bot.ArchiLogger.LogNullError(nameof(nameNode)); + return null; + } + + result[appID] = nameNode.InnerText; + } + + return result; + } + + internal Dictionary GetOwnedGames(ulong steamID) { + if ((steamID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(Bot.BotConfig.SteamApiKey)); + return null; + } + + KeyValue response = null; + for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { + using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) { + iPlayerService.Timeout = Timeout; + + try { + response = iPlayerService.GetOwnedGames(steamid: steamID, include_appinfo: 1, secure: !Program.GlobalConfig.ForceHttp); + } catch (Exception e) { + Bot.ArchiLogger.LogGenericException(e); + } + } + } + + if (response == null) { + Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries"); + return null; + } + + Dictionary result = new Dictionary(response["games"].Children.Count); + foreach (KeyValue game in response["games"].Children) { + uint appID = game["appid"].AsUnsignedInteger(); + if (appID == 0) { + Bot.ArchiLogger.LogNullError(nameof(appID)); + return null; + } + + result[appID] = game["name"].Value; + } + + return result; + } + + internal uint GetServerTime() { + KeyValue response = null; + for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { + using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) { + iTwoFactorService.Timeout = Timeout; + + try { + response = iTwoFactorService.QueryTime(method: WebRequestMethods.Http.Post, secure: !Program.GlobalConfig.ForceHttp); + } catch (Exception e) { + Bot.ArchiLogger.LogGenericException(e); + } + } + } + + if (response != null) { + return response["server_time"].AsUnsignedInteger(); + } + + Bot.ArchiLogger.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries"); + return 0; + } + + internal async Task GetTradeHoldDuration(ulong tradeID) { + if (tradeID == 0) { + Bot.ArchiLogger.LogNullError(nameof(tradeID)); + return null; + } + + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + + string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english"; + + HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); + + HtmlNode htmlNode = htmlDocument?.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script"); + if (htmlNode == null) { // Trade can be no longer valid + return null; + } + + string text = htmlNode.InnerText; + if (string.IsNullOrEmpty(text)) { + Bot.ArchiLogger.LogNullError(nameof(text)); + return null; + } + + int index = text.IndexOf("g_daysTheirEscrow = ", StringComparison.Ordinal); + if (index < 0) { + Bot.ArchiLogger.LogNullError(nameof(index)); + return null; + } + + index += 20; + text = text.Substring(index); + + index = text.IndexOf(';'); + if (index < 0) { + Bot.ArchiLogger.LogNullError(nameof(index)); + return null; + } + + text = text.Substring(0, index); + + byte holdDuration; + if (byte.TryParse(text, out holdDuration)) { + return holdDuration; + } + + Bot.ArchiLogger.LogNullError(nameof(holdDuration)); + return null; + } + + internal async Task HandleConfirmation(string deviceID, string confirmationHash, uint time, uint confirmationID, ulong confirmationKey, bool accept) { + if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0) || (confirmationKey == 0)) { + Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID) + " || " + nameof(confirmationKey)); + return null; + } + + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + + string request = SteamCommunityURL + "/mobileconf/ajaxop?op=" + (accept ? "allow" : "cancel") + "&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf&cid=" + confirmationID + "&ck=" + confirmationKey; + + Steam.ConfirmationResponse response = await WebBrowser.UrlGetToJsonResultRetry(request).ConfigureAwait(false); + return response?.Success; + } + + internal async Task HandleConfirmations(string deviceID, string confirmationHash, uint time, HashSet confirmations, bool accept) { + if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmations == null) || (confirmations.Count == 0)) { + Bot.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmations)); + return null; + } + + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return null; + } + + string request = SteamCommunityURL + "/mobileconf/multiajaxop"; + + List> data = new List>(7 + confirmations.Count*2) { new KeyValuePair("op", accept ? "allow" : "cancel"), new KeyValuePair("p", deviceID), new KeyValuePair("a", SteamID.ToString()), new KeyValuePair("k", confirmationHash), new KeyValuePair("t", time.ToString()), new KeyValuePair("m", "android"), new KeyValuePair("tag", "conf") }; + + foreach (MobileAuthenticator.Confirmation confirmation in confirmations) { + data.Add(new KeyValuePair("cid[]", confirmation.ID.ToString())); + data.Add(new KeyValuePair("ck[]", confirmation.Key.ToString())); + } + + Steam.ConfirmationResponse response = await WebBrowser.UrlPostToJsonResultRetry(request, data).ConfigureAwait(false); + return response?.Success; + } + + internal static void Init() { + Timeout = Program.GlobalConfig.HttpTimeout*1000; + SteamCommunityURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamCommunityHost; + SteamStoreURL = (Program.GlobalConfig.ForceHttp ? "http://" : "https://") + SteamStoreHost; + } + + internal async Task Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) { + if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) { + Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin)); + return false; + } + + SteamID = steamID; + + string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString())); + + // Generate an AES session key + byte[] sessionKey = SteamKit2.CryptoHelper.GenerateRandomBlock(32); + + // RSA encrypt it with the public key for the universe we're on + byte[] cryptedSessionKey; + using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(universe))) { + cryptedSessionKey = rsa.Encrypt(sessionKey); + } + + // Copy our login key + byte[] loginKey = new byte[webAPIUserNonce.Length]; + Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length); + + // AES encrypt the loginkey with our session key + byte[] cryptedLoginKey = SteamKit2.CryptoHelper.SymmetricEncrypt(loginKey, sessionKey); + + // Do the magic + Bot.ArchiLogger.LogGenericInfo("Logging in to ISteamUserAuth..."); + + KeyValue authResult; + using (dynamic iSteamUserAuth = WebAPI.GetInterface("ISteamUserAuth")) { + iSteamUserAuth.Timeout = Timeout; + + try { + authResult = iSteamUserAuth.AuthenticateUser(steamid: steamID, sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)), encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)), method: WebRequestMethods.Http.Post, secure: !Program.GlobalConfig.ForceHttp); + } catch (Exception e) { + Bot.ArchiLogger.LogGenericException(e); + return false; + } + } + + if (authResult == null) { + Bot.ArchiLogger.LogNullError(nameof(authResult)); + return false; + } + + string steamLogin = authResult["token"].Value; + if (string.IsNullOrEmpty(steamLogin)) { + Bot.ArchiLogger.LogNullError(nameof(steamLogin)); + return false; + } + + string steamLoginSecure = authResult["tokensecure"].Value; + if (string.IsNullOrEmpty(steamLoginSecure)) { + Bot.ArchiLogger.LogNullError(nameof(steamLoginSecure)); + return false; + } + + WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost)); + WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamStoreHost)); + + WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost)); + WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamStoreHost)); + + WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost)); + WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamStoreHost)); + + Bot.ArchiLogger.LogGenericInfo("Success!"); + + // Unlock Steam Parental if needed + if (!parentalPin.Equals("0")) { + if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) { + return false; + } + } + + Ready = true; + LastSessionRefreshCheck = DateTime.Now; + return true; + } + + internal async Task JoinGroup(ulong groupID) { + if (groupID == 0) { + Bot.ArchiLogger.LogNullError(nameof(groupID)); + return false; + } + + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return false; + } + + string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid"); + if (string.IsNullOrEmpty(sessionID)) { + Bot.ArchiLogger.LogNullError(nameof(sessionID)); + return false; + } + + string request = SteamCommunityURL + "/gid/" + groupID; + Dictionary data = new Dictionary(2) { { "sessionID", sessionID }, { "action", "join" } }; + + return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false); + } + + internal async Task MarkInventory() { + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return false; + } + + string request = SteamCommunityURL + "/my/inventory"; + return await WebBrowser.UrlHeadRetry(request).ConfigureAwait(false); + } + + internal void OnDisconnected() => Ready = false; + + internal async Task RedeemWalletKey(string key) { + if (string.IsNullOrEmpty(key)) { + Bot.ArchiLogger.LogNullError(nameof(key)); + return ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Unknown; + } + + if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { + return ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Timeout; + } + + string request = SteamStoreURL + "/account/validatewalletcode"; + Dictionary data = new Dictionary(1) { { "wallet_code", key } }; + + Steam.RedeemWalletResponse response = await WebBrowser.UrlPostToJsonResultRetry(request, data).ConfigureAwait(false); + return response?.PurchaseResult ?? ArchiHandler.PurchaseResponseCallback.EPurchaseResult.Timeout; + } + internal async Task SendTradeOffer(HashSet inventory, ulong partnerID, string token = null) { if ((inventory == null) || (inventory.Count == 0) || (partnerID == 0)) { Bot.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(inventory.Count) + " || " + nameof(partnerID)); @@ -920,14 +820,7 @@ namespace ArchiSteamFarm { string referer = SteamCommunityURL + "/tradeoffer/new"; string request = referer + "/send"; - foreach (Dictionary data in trades.Select(trade => new Dictionary(6) { - { "sessionid", sessionID }, - { "serverid", "1" }, - { "partner", partnerID.ToString() }, - { "tradeoffermessage", "Sent by ASF" }, - { "json_tradeoffer", JsonConvert.SerializeObject(trade) }, - { "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}" } - })) { + foreach (Dictionary data in trades.Select(trade => new Dictionary(6) { { "sessionid", sessionID }, { "serverid", "1" }, { "partner", partnerID.ToString() }, { "tradeoffermessage", "Sent by ASF" }, { "json_tradeoffer", JsonConvert.SerializeObject(trade) }, { "trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}" } })) { if (!await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false)) { return false; } @@ -936,41 +829,51 @@ namespace ArchiSteamFarm { return true; } - internal async Task GetBadgePage(byte page) { - if (page == 0) { - Bot.ArchiLogger.LogNullError(nameof(page)); - return null; + private static uint GetAppIDFromMarketHashName(string hashName) { + if (string.IsNullOrEmpty(hashName)) { + ASF.ArchiLogger.LogNullError(nameof(hashName)); + return 0; } - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; + int index = hashName.IndexOf('-'); + if (index <= 0) { + return 0; } - string request = SteamCommunityURL + "/my/badges?l=english&p=" + page; - return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); + uint appID; + return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0; } - internal async Task GetGameCardsPage(ulong appID) { - if (appID == 0) { - Bot.ArchiLogger.LogNullError(nameof(appID)); - return null; + private static Steam.Item.EType GetItemType(string name) { + if (string.IsNullOrEmpty(name)) { + ASF.ArchiLogger.LogNullError(nameof(name)); + return Steam.Item.EType.Unknown; } - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return null; + switch (name) { + case "Booster Pack": + return Steam.Item.EType.BoosterPack; + case "Coupon": + return Steam.Item.EType.Coupon; + case "Gift": + return Steam.Item.EType.Gift; + case "Steam Gems": + return Steam.Item.EType.SteamGems; + default: + if (name.EndsWith("Emoticon", StringComparison.Ordinal)) { + return Steam.Item.EType.Emoticon; + } + + if (name.EndsWith("Foil Trading Card", StringComparison.Ordinal)) { + return Steam.Item.EType.FoilTradingCard; + } + + if (name.EndsWith("Profile Background", StringComparison.Ordinal)) { + return Steam.Item.EType.ProfileBackground; + } + + return name.EndsWith("Trading Card", StringComparison.Ordinal) ? Steam.Item.EType.TradingCard : Steam.Item.EType.Unknown; } - - string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english"; - return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false); - } - - internal async Task MarkInventory() { - if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) { - return false; - } - - string request = SteamCommunityURL + "/my/inventory"; - return await WebBrowser.UrlHeadRetry(request).ConfigureAwait(false); } private async Task IsLoggedIn() { @@ -982,6 +885,53 @@ namespace ArchiSteamFarm { return !uri?.AbsolutePath.StartsWith("/login", StringComparison.Ordinal); } + private static bool ParseItems(Dictionary> descriptions, List input, HashSet output) { + if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) { + ASF.ArchiLogger.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output)); + return false; + } + + foreach (KeyValue item in input) { + uint appID = item["appid"].AsUnsignedInteger(); + if (appID == 0) { + ASF.ArchiLogger.LogNullError(nameof(appID)); + return false; + } + + ulong contextID = item["contextid"].AsUnsignedLong(); + if (contextID == 0) { + ASF.ArchiLogger.LogNullError(nameof(contextID)); + return false; + } + + ulong classID = item["classid"].AsUnsignedLong(); + if (classID == 0) { + ASF.ArchiLogger.LogNullError(nameof(classID)); + return false; + } + + uint amount = item["amount"].AsUnsignedInteger(); + if (amount == 0) { + ASF.ArchiLogger.LogNullError(nameof(amount)); + return false; + } + + uint realAppID = appID; + Steam.Item.EType type = Steam.Item.EType.Unknown; + + Tuple description; + if (descriptions.TryGetValue(classID, out description)) { + realAppID = description.Item1; + type = description.Item2; + } + + Steam.Item steamItem = new Steam.Item(appID, contextID, classID, amount, realAppID, type); + output.Add(steamItem); + } + + return true; + } + private async Task RefreshSessionIfNeeded() { if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) { return true; @@ -1016,9 +966,7 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericInfo("Unlocking parental account..."); string request = SteamCommunityURL + "/parental/ajaxunlock"; - Dictionary data = new Dictionary(1) { - { "pin", parentalPin } - }; + Dictionary data = new Dictionary(1) { { "pin", parentalPin } }; bool result = await WebBrowser.UrlPostRetry(request, data, SteamCommunityURL).ConfigureAwait(false); if (!result) { @@ -1030,4 +978,4 @@ namespace ArchiSteamFarm { return true; } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index 389af288b..876f1b36c 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -22,9 +22,6 @@ */ -using Newtonsoft.Json; -using SteamKit2; -using SteamKit2.Internal; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -32,26 +29,18 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; using System.Text; using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; using ArchiSteamFarm.JSON; +using Newtonsoft.Json; +using SteamKit2; using SteamKit2.Discovery; +using SteamKit2.Internal; namespace ArchiSteamFarm { internal sealed class Bot : IDisposable { - [Flags] - private enum ERedeemFlags : byte { - None = 0, - Validate = 1, - ForceForwarding = 2, - SkipForwarding = 4, - ForceDistribution = 8, - SkipDistribution = 16, - SkipInitial = 32 - } - private const ushort CallbackSleep = 500; // In miliseconds private const byte FamilySharingInactivityMinutes = 5; private const uint LoginID = 0; // This must be the same for all ASF bots and all ASF processes @@ -62,104 +51,52 @@ namespace ArchiSteamFarm { private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1); private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1); - internal readonly string BotName; internal readonly ArchiHandler ArchiHandler; internal readonly ArchiLogger ArchiLogger; internal readonly ArchiWebHandler ArchiWebHandler; - - private readonly string SentryFile; - private readonly BotDatabase BotDatabase; - private readonly CallbackManager CallbackManager; - - [JsonProperty] - private readonly CardsFarmer CardsFarmer; - - private readonly ConcurrentHashSet HandledGifts = new ConcurrentHashSet(); - private readonly ConcurrentHashSet SteamFamilySharingIDs = new ConcurrentHashSet(); - private readonly ConcurrentHashSet OwnedPackageIDs = new ConcurrentHashSet(); - private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1); - private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1); - private readonly SteamApps SteamApps; - private readonly SteamClient SteamClient; - private readonly SteamFriends SteamFriends; - private readonly SteamUser SteamUser; - private readonly Timer HeartBeatTimer; - private readonly Trading Trading; - - [JsonProperty] - internal bool KeepRunning { get; private set; } - - internal bool IsLimitedUser { get; private set; } - internal BotConfig BotConfig { get; private set; } + internal readonly string BotName; internal bool HasMobileAuthenticator => BotDatabase.MobileAuthenticator != null; internal bool IsConnectedAndLoggedOn => SteamClient.IsConnected && (SteamClient.SteamID != null); internal bool IsPlayingPossible => !PlayingBlocked && (LibraryLockedBySteamID == 0); internal ulong SteamID => SteamClient.SteamID; - private bool FirstTradeSent, PlayingBlocked, SkipFirstShutdown; - private byte HeartBeatFailures; + private readonly BotDatabase BotDatabase; + private readonly CallbackManager CallbackManager; + private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(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 ConcurrentHashSet OwnedPackageIDs = new ConcurrentHashSet(); + + private readonly string SentryFile; + private readonly SteamApps SteamApps; + private readonly SteamClient SteamClient; + private readonly ConcurrentHashSet SteamFamilySharingIDs = new ConcurrentHashSet(); + private readonly SteamFriends SteamFriends; + private readonly SteamUser SteamUser; + private readonly Trading Trading; + + internal BotConfig BotConfig { get; private set; } + internal bool IsLimitedUser { get; private set; } + + [JsonProperty] + internal bool KeepRunning { get; private set; } + + private Timer AcceptConfirmationsTimer; private string AuthCode, TwoFactorCode; - private ulong LibraryLockedBySteamID; + private Timer FamilySharingInactivityTimer; + private bool FirstTradeSent; + private byte HeartBeatFailures; private EResult LastLogOnResult; - private Timer AcceptConfirmationsTimer, FamilySharingInactivityTimer, SendItemsTimer; - - internal static string GetAPIStatus() { - var response = new { - Bots - }; - - try { - return JsonConvert.SerializeObject(response); - } catch (JsonException e) { - ASF.ArchiLogger.LogGenericException(e); - return null; - } - } - - internal static void InitializeCMs(uint cellID, IServerListProvider serverListProvider) { - if (serverListProvider == null) { - ASF.ArchiLogger.LogNullError(nameof(serverListProvider)); - return; - } - - CMClient.Servers.CellID = cellID; - CMClient.Servers.ServerListProvider = serverListProvider; - } - - private static bool IsOwner(ulong steamID) { - if (steamID != 0) { - return (steamID == Program.GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID)); - } - - ASF.ArchiLogger.LogNullError(nameof(steamID)); - return false; - } - - private static bool IsValidCdKey(string key) { - if (!string.IsNullOrEmpty(key)) { - return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.IgnoreCase); - } - - ASF.ArchiLogger.LogNullError(nameof(key)); - return false; - } - - private static async Task LimitGiftsRequestsAsync() { - await GiftsSemaphore.WaitAsync().ConfigureAwait(false); - Task.Run(async () => { - await Task.Delay(Program.GlobalConfig.GiftsLimiterDelay * 1000).ConfigureAwait(false); - GiftsSemaphore.Release(); - }).Forget(); - } - - private static async Task LimitLoginRequestsAsync() { - await LoginSemaphore.WaitAsync().ConfigureAwait(false); - Task.Run(async () => { - await Task.Delay(Program.GlobalConfig.LoginLimiterDelay * 1000).ConfigureAwait(false); - LoginSemaphore.Release(); - }).Forget(); - } + private ulong LibraryLockedBySteamID; + private bool PlayingBlocked; + private Timer SendItemsTimer; + private bool SkipFirstShutdown; internal Bot(string botName) { if (string.IsNullOrEmpty(botName)) { @@ -262,97 +199,13 @@ namespace ArchiSteamFarm { Trading = new Trading(this); - HeartBeatTimer = new Timer( - async e => await HeartBeat().ConfigureAwait(false), - null, - TimeSpan.FromMinutes(1) + TimeSpan.FromMinutes(0.2 * Bots.Count), // Delay + 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(); } - internal async Task OnNewConfigLoaded(ASF.BotConfigEventArgs args) { - if (args == null) { - ArchiLogger.LogNullError(nameof(args)); - return; - } - - if (args.BotConfig == null) { - Destroy(); - return; - } - - if (args.BotConfig == BotConfig) { - return; - } - - await InitializationSemaphore.WaitAsync().ConfigureAwait(false); - - try { - if (args.BotConfig == BotConfig) { - return; - } - - Stop(false); - BotConfig = args.BotConfig; - - CardsFarmer.SetInitialState(BotConfig.Paused); - - if (BotConfig.AcceptConfirmationsPeriod > 0) { - TimeSpan delay = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) + TimeSpan.FromMinutes(0.2 * Bots.Count); - TimeSpan period = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod); - - if (AcceptConfirmationsTimer == null) { - AcceptConfirmationsTimer = new Timer( - async e => await AcceptConfirmations(true).ConfigureAwait(false), - null, - delay, // Delay - period // Period - ); - } else { - AcceptConfirmationsTimer.Change(delay, period); - } - } else { - AcceptConfirmationsTimer?.Change(Timeout.Infinite, Timeout.Infinite); - AcceptConfirmationsTimer?.Dispose(); - } - - if ((BotConfig.SendTradePeriod > 0) && (BotConfig.SteamMasterID != 0)) { - TimeSpan delay = TimeSpan.FromHours(BotConfig.SendTradePeriod) + TimeSpan.FromMinutes(Bots.Count); - TimeSpan period = TimeSpan.FromHours(BotConfig.SendTradePeriod); - - if (SendItemsTimer == null) { - SendItemsTimer = new Timer( - async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false), - null, - delay, // Delay - period // Period - ); - } else { - SendItemsTimer.Change(delay, period); - } - } else { - SendItemsTimer?.Change(Timeout.Infinite, Timeout.Infinite); - SendItemsTimer?.Dispose(); - } - - await Initialize().ConfigureAwait(false); - } finally { - InitializationSemaphore.Release(); - } - } - - private async Task Initialize() { - if (!BotConfig.Enabled) { - ArchiLogger.LogGenericInfo("Not starting this instance because it's disabled in config file"); - return; - } - - // Start - await Start().ConfigureAwait(false); - } - public void Dispose() { // Those are objects that are always being created if constructor doesn't throw exception ArchiWebHandler.Dispose(); @@ -400,10 +253,7 @@ namespace ArchiSteamFarm { Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false); - HashSet ignoredConfirmations = new HashSet(detailsResults.Where(details => (details != null) && ( - ((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) || - ((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID)) - )).Select(details => details.Confirmation)); + HashSet ignoredConfirmations = new HashSet(detailsResults.Where(details => (details != null) && (((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) || ((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID)))).Select(details => details.Confirmation)); if (ignoredConfirmations.Count > 0) { confirmations.ExceptWith(ignoredConfirmations); @@ -418,6 +268,119 @@ namespace ArchiSteamFarm { } } + internal static string GetAPIStatus() { + var response = new { Bots }; + + try { + return JsonConvert.SerializeObject(response); + } catch (JsonException e) { + ASF.ArchiLogger.LogGenericException(e); + return null; + } + } + + internal static void InitializeCMs(uint cellID, IServerListProvider serverListProvider) { + if (serverListProvider == null) { + ASF.ArchiLogger.LogNullError(nameof(serverListProvider)); + return; + } + + CMClient.Servers.CellID = cellID; + CMClient.Servers.ServerListProvider = serverListProvider; + } + + internal async Task LootIfNeeded() { + if (!BotConfig.SendOnFarmingFinished || (BotConfig.SteamMasterID == 0) || !IsConnectedAndLoggedOn || (BotConfig.SteamMasterID == SteamClient.SteamID)) { + return; + } + + await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false); + } + + internal async Task OnFarmingFinished(bool farmedSomething) { + OnFarmingStopped(); + + if (farmedSomething || !FirstTradeSent) { + FirstTradeSent = true; + await LootIfNeeded().ConfigureAwait(false); + } + + if (BotConfig.ShutdownOnFarmingFinished) { + if (SkipFirstShutdown) { + SkipFirstShutdown = false; + } else { + Stop(); + } + } + } + + internal void OnFarmingStopped() => ResetGamesPlayed(); + + internal async Task OnNewConfigLoaded(ASF.BotConfigEventArgs args) { + if (args == null) { + ArchiLogger.LogNullError(nameof(args)); + return; + } + + if (args.BotConfig == null) { + Destroy(); + return; + } + + if (args.BotConfig == BotConfig) { + return; + } + + await InitializationSemaphore.WaitAsync().ConfigureAwait(false); + + try { + if (args.BotConfig == BotConfig) { + return; + } + + Stop(false); + BotConfig = args.BotConfig; + + CardsFarmer.SetInitialState(BotConfig.Paused); + + if (BotConfig.AcceptConfirmationsPeriod > 0) { + TimeSpan delay = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod) + TimeSpan.FromMinutes(0.2*Bots.Count); + TimeSpan period = TimeSpan.FromMinutes(BotConfig.AcceptConfirmationsPeriod); + + if (AcceptConfirmationsTimer == null) { + AcceptConfirmationsTimer = new Timer(async e => await AcceptConfirmations(true).ConfigureAwait(false), null, delay, // Delay + period // Period + ); + } else { + AcceptConfirmationsTimer.Change(delay, period); + } + } else { + AcceptConfirmationsTimer?.Change(Timeout.Infinite, Timeout.Infinite); + AcceptConfirmationsTimer?.Dispose(); + } + + if ((BotConfig.SendTradePeriod > 0) && (BotConfig.SteamMasterID != 0)) { + TimeSpan delay = TimeSpan.FromHours(BotConfig.SendTradePeriod) + TimeSpan.FromMinutes(Bots.Count); + TimeSpan period = TimeSpan.FromHours(BotConfig.SendTradePeriod); + + if (SendItemsTimer == null) { + SendItemsTimer = new Timer(async e => await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false), null, delay, // Delay + period // Period + ); + } else { + SendItemsTimer.Change(delay, period); + } + } else { + SendItemsTimer?.Change(Timeout.Infinite, Timeout.Infinite); + SendItemsTimer?.Dispose(); + } + + await Initialize().ConfigureAwait(false); + } finally { + InitializationSemaphore.Release(); + } + } + internal async Task RefreshSession() { if (!IsConnectedAndLoggedOn) { return false; @@ -446,50 +409,6 @@ namespace ArchiSteamFarm { return false; } - internal void Stop(bool withShutdownEvent = true) { - if (!KeepRunning) { - return; - } - - ArchiLogger.LogGenericInfo("Stopping..."); - KeepRunning = false; - - if (SteamClient.IsConnected) { - Disconnect(); - } - - if (withShutdownEvent) { - Events.OnBotShutdown(); - } - } - - internal async Task LootIfNeeded() { - if (!BotConfig.SendOnFarmingFinished || (BotConfig.SteamMasterID == 0) || !IsConnectedAndLoggedOn || (BotConfig.SteamMasterID == SteamClient.SteamID)) { - return; - } - - await ResponseLoot(BotConfig.SteamMasterID).ConfigureAwait(false); - } - - internal void OnFarmingStopped() => ResetGamesPlayed(); - - internal async Task OnFarmingFinished(bool farmedSomething) { - OnFarmingStopped(); - - if (farmedSomething || !FirstTradeSent) { - FirstTradeSent = true; - await LootIfNeeded().ConfigureAwait(false); - } - - if (BotConfig.ShutdownOnFarmingFinished) { - if (SkipFirstShutdown) { - SkipFirstShutdown = false; - } else { - Stop(); - } - } - } - internal async Task Response(ulong steamID, string message) { if ((steamID == 0) || string.IsNullOrEmpty(message)) { ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(message)); @@ -625,41 +544,41 @@ namespace ArchiSteamFarm { } } - private void Destroy(bool force = false) { - if (!force) { - Stop(); - } else { - // Stop() will most likely block due to fuckup, don't wait for it - Task.Run(() => Stop()).Forget(); - } - - Bot ignored; - Bots.TryRemove(BotName, out ignored); - } - - private async Task HeartBeat() { - if (!IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) { + internal void Stop(bool withShutdownEvent = true) { + if (!KeepRunning) { return; } - try { - await SteamApps.PICSGetProductInfo(0, null); - } catch { - if (!IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) { - return; - } + ArchiLogger.LogGenericInfo("Stopping..."); + KeepRunning = false; - if (++HeartBeatFailures >= 15) { - HeartBeatFailures = byte.MaxValue; - ArchiLogger.LogGenericError("HeartBeat failed to disconnect the client, abandoning this bot instance!"); - Destroy(true); - new Bot(BotName).Forget(); - return; - } - - ArchiLogger.LogGenericWarning("Connection to Steam Network lost, reconnecting..."); - Connect(true).Forget(); + if (SteamClient.IsConnected) { + Disconnect(); } + + if (withShutdownEvent) { + Events.OnBotShutdown(); + } + } + + private void CheckFamilySharingInactivity() { + if (!IsPlayingPossible) { + return; + } + + ArchiLogger.LogGenericInfo("Shared library has not been launched in given time period, farming process resumed!"); + CardsFarmer.Resume(false); + } + + private void CheckOccupationStatus() { + if (!IsPlayingPossible) { + ArchiLogger.LogGenericInfo("Account is currently being used, ASF will resume farming when it's free..."); + StopFamilySharingInactivityTimer(); + return; + } + + ArchiLogger.LogGenericInfo("Account is no longer occupied, farming process resumed!"); + CardsFarmer.Resume(false); } private async Task Connect(bool force = false) { @@ -686,81 +605,78 @@ namespace ArchiSteamFarm { } } + private void Destroy(bool force = false) { + if (!force) { + Stop(); + } else { + // Stop() will most likely block due to fuckup, don't wait for it + Task.Run(() => Stop()).Forget(); + } + + Bot ignored; + Bots.TryRemove(BotName, out ignored); + } + private void Disconnect() { lock (SteamClient) { SteamClient.Disconnect(); } } - private async Task Start() { - if (!KeepRunning) { - KeepRunning = true; - Task.Run(() => HandleCallbacks()).Forget(); - ArchiLogger.LogGenericInfo("Starting..."); - } + private void HandleCallbacks() { + TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep); + while (KeepRunning || SteamClient.IsConnected) { + if (!CallbackSemaphore.Wait(0)) { + return; + } - await Connect().ConfigureAwait(false); + try { + CallbackManager.RunWaitCallbacks(timeSpan); + } catch (Exception e) { + ArchiLogger.LogGenericException(e); + } finally { + CallbackSemaphore.Release(); + } + } } - private bool IsMaster(ulong steamID) { - if (steamID != 0) { - return (steamID == BotConfig.SteamMasterID) || IsOwner(steamID); - } - - ArchiLogger.LogNullError(nameof(steamID)); - return false; - } - - private void CheckOccupationStatus() { - if (!IsPlayingPossible) { - ArchiLogger.LogGenericInfo("Account is currently being used, ASF will resume farming when it's free..."); - StopFamilySharingInactivityTimer(); + private async Task HandleMessage(ulong chatID, ulong steamID, string message) { + if ((chatID == 0) || (steamID == 0) || string.IsNullOrEmpty(message)) { + ArchiLogger.LogNullError(nameof(chatID) + " || " + nameof(steamID) + " || " + nameof(message)); return; } - ArchiLogger.LogGenericInfo("Account is no longer occupied, farming process resumed!"); - CardsFarmer.Resume(false); - } - - private void CheckFamilySharingInactivity() { - if (!IsPlayingPossible) { + string response = await Response(steamID, message).ConfigureAwait(false); + if (string.IsNullOrEmpty(response)) { // We respond with null when user is not authorized (and similar) return; } - ArchiLogger.LogGenericInfo("Shared library has not been launched in given time period, farming process resumed!"); - CardsFarmer.Resume(false); + SendMessage(chatID, response); } - private void StartFamilySharingInactivityTimer() { - if (FamilySharingInactivityTimer != null) { + private async Task HeartBeat() { + if (!IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) { return; } - FamilySharingInactivityTimer = new Timer( - e => CheckFamilySharingInactivity(), - null, - TimeSpan.FromMinutes(FamilySharingInactivityMinutes), // Delay - Timeout.InfiniteTimeSpan // Period - ); - } + try { + await SteamApps.PICSGetProductInfo(0, null); + } catch { + if (!IsConnectedAndLoggedOn || (HeartBeatFailures == byte.MaxValue)) { + return; + } - private void StopFamilySharingInactivityTimer() { - if (FamilySharingInactivityTimer == null) { - return; + if (++HeartBeatFailures >= 15) { + HeartBeatFailures = byte.MaxValue; + ArchiLogger.LogGenericError("HeartBeat failed to disconnect the client, abandoning this bot instance!"); + Destroy(true); + new Bot(BotName).Forget(); + return; + } + + ArchiLogger.LogGenericWarning("Connection to Steam Network lost, reconnecting..."); + Connect(true).Forget(); } - - FamilySharingInactivityTimer.Change(Timeout.Infinite, Timeout.Infinite); - FamilySharingInactivityTimer.Dispose(); - FamilySharingInactivityTimer = null; - } - - private async Task InitializeFamilySharing() { - HashSet steamIDs = await ArchiWebHandler.GetFamilySharingSteamIDs().ConfigureAwait(false); - if ((steamIDs == null) || (steamIDs.Count == 0)) { - return; - } - - SteamFamilySharingIDs.ReplaceIfNeededWith(steamIDs); } private void ImportAuthenticator(string maFilePath) { @@ -800,284 +716,668 @@ namespace ArchiSteamFarm { ArchiLogger.LogGenericInfo("Successfully finished importing mobile authenticator!"); } - private string ResponsePassword(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; + private async Task Initialize() { + if (!BotConfig.Enabled) { + ArchiLogger.LogGenericInfo("Not starting this instance because it's disabled in config file"); + return; } - if (!IsMaster(steamID)) { - return null; - } - - if (string.IsNullOrEmpty(BotConfig.SteamPassword)) { - return "Can't encrypt null password!"; - } - - return Environment.NewLine + - "[" + CryptoHelper.ECryptoMethod.AES + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.AES, BotConfig.SteamPassword) + Environment.NewLine + - "[" + CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, BotConfig.SteamPassword); + // Start + await Start().ConfigureAwait(false); } - private static string ResponsePassword(ulong steamID, string botName) { - if ((steamID == 0) || string.IsNullOrEmpty(botName)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); - return null; + private async Task InitializeFamilySharing() { + HashSet steamIDs = await ArchiWebHandler.GetFamilySharingSteamIDs().ConfigureAwait(false); + if ((steamIDs == null) || (steamIDs.Count == 0)) { + return; } - Bot bot; - if (Bots.TryGetValue(botName, out bot)) { - return bot.ResponsePassword(steamID); - } - - if (IsOwner(steamID)) { - return "Couldn't find any bot named " + botName + "!"; - } - - return null; + SteamFamilySharingIDs.ReplaceIfNeededWith(steamIDs); } - private async Task ResponsePause(ulong steamID, bool sticky) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; + private bool InitializeLoginAndPassword(bool requiresPassword) { + if (string.IsNullOrEmpty(BotConfig.SteamLogin)) { + BotConfig.SteamLogin = Program.GetUserInput(ASF.EUserInputType.Login, BotName); + if (string.IsNullOrEmpty(BotConfig.SteamLogin)) { + return false; + } } - if (!IsMaster(steamID) && !SteamFamilySharingIDs.Contains(steamID)) { - return null; + if (!string.IsNullOrEmpty(BotConfig.SteamPassword) || (!requiresPassword && !string.IsNullOrEmpty(BotDatabase.LoginKey))) { + return true; } - if (!IsConnectedAndLoggedOn) { - return "This bot instance is not connected!"; - } - - if (CardsFarmer.Paused) { - return "Automatic farming is paused already!"; - } - - await CardsFarmer.Pause(sticky).ConfigureAwait(false); - - if (!SteamFamilySharingIDs.Contains(steamID)) { - return "Automatic farming is now paused!"; - } - - StartFamilySharingInactivityTimer(); - return "Automatic farming is now paused! You have " + FamilySharingInactivityMinutes + " minutes to start a game."; + BotConfig.SteamPassword = Program.GetUserInput(ASF.EUserInputType.Password, BotName); + return !string.IsNullOrEmpty(BotConfig.SteamPassword); } - private static async Task ResponsePause(ulong steamID, string botName, bool sticky) { - if ((steamID == 0) || string.IsNullOrEmpty(botName)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); - return null; + private bool IsMaster(ulong steamID) { + if (steamID != 0) { + return (steamID == BotConfig.SteamMasterID) || IsOwner(steamID); } - Bot bot; - if (Bots.TryGetValue(botName, out bot)) { - return await bot.ResponsePause(steamID, sticky).ConfigureAwait(false); - } - - if (IsOwner(steamID)) { - return "Couldn't find any bot named " + botName + "!"; - } - - return null; + ArchiLogger.LogNullError(nameof(steamID)); + return false; } - private string ResponseResume(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; + private static bool IsOwner(ulong steamID) { + if (steamID != 0) { + return (steamID == Program.GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID)); } - if (!IsMaster(steamID) && !SteamFamilySharingIDs.Contains(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return "This bot instance is not connected!"; - } - - if (!CardsFarmer.Paused) { - return "Automatic farming is resumed already!"; - } - - StopFamilySharingInactivityTimer(); - CardsFarmer.Resume(true); - return "Automatic farming is now resumed!"; + ASF.ArchiLogger.LogNullError(nameof(steamID)); + return false; } - private static string ResponseResume(ulong steamID, string botName) { - if ((steamID == 0) || string.IsNullOrEmpty(botName)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); - return null; + private static bool IsValidCdKey(string key) { + if (!string.IsNullOrEmpty(key)) { + return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.IgnoreCase); } - Bot bot; - if (Bots.TryGetValue(botName, out bot)) { - return bot.ResponseResume(steamID); - } - - if (IsOwner(steamID)) { - return "Couldn't find any bot named " + botName + "!"; - } - - return null; + ASF.ArchiLogger.LogNullError(nameof(key)); + return false; } - private string ResponseStatus(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; + private void JoinMasterChat() { + if (!IsConnectedAndLoggedOn || (BotConfig.SteamMasterClanID == 0)) { + return; } - if (!IsMaster(steamID)) { - return null; + SteamFriends.JoinChat(BotConfig.SteamMasterClanID); + } + + private static async Task LimitGiftsRequestsAsync() { + await GiftsSemaphore.WaitAsync().ConfigureAwait(false); + Task.Run(async () => { + await Task.Delay(Program.GlobalConfig.GiftsLimiterDelay*1000).ConfigureAwait(false); + GiftsSemaphore.Release(); + }).Forget(); + } + + private static async Task LimitLoginRequestsAsync() { + await LoginSemaphore.WaitAsync().ConfigureAwait(false); + Task.Run(async () => { + await Task.Delay(Program.GlobalConfig.LoginLimiterDelay*1000).ConfigureAwait(false); + LoginSemaphore.Release(); + }).Forget(); + } + + private void OnAccountInfo(SteamUser.AccountInfoCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; } - if (!IsConnectedAndLoggedOn) { - if (KeepRunning) { - return "Bot " + BotName + " is not connected."; + if (!BotConfig.FarmOffline) { + SteamFriends.SetPersonaState(EPersonaState.Online); + } + } + + private void OnChatInvite(SteamFriends.ChatInviteCallback callback) { + if ((callback?.ChatRoomID == null) || (callback.PatronID == null)) { + ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.ChatRoomID) + " || " + nameof(callback.PatronID)); + return; + } + + if (!IsMaster(callback.PatronID)) { + return; + } + + SteamFriends.JoinChat(callback.ChatRoomID); + } + + private async void OnChatMsg(SteamFriends.ChatMsgCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + if (callback.ChatMsgType != EChatEntryType.ChatMsg) { + return; + } + + if ((callback.ChatRoomID == null) || (callback.ChatterID == null) || string.IsNullOrEmpty(callback.Message)) { + ArchiLogger.LogNullError(nameof(callback.ChatRoomID) + " || " + nameof(callback.ChatterID) + " || " + nameof(callback.Message)); + return; + } + + ArchiLogger.LogGenericTrace(callback.ChatRoomID.ConvertToUInt64() + "/" + callback.ChatterID.ConvertToUInt64() + ": " + callback.Message); + + switch (callback.Message) { + case "!leave": + if (!IsMaster(callback.ChatterID)) { + break; + } + + SteamFriends.LeaveChat(callback.ChatRoomID); + break; + default: + await HandleMessage(callback.ChatRoomID, callback.ChatterID, callback.Message).ConfigureAwait(false); + break; + } + } + + private void OnConnected(SteamClient.ConnectedCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + if (callback.Result != EResult.OK) { + ArchiLogger.LogGenericError("Unable to connect to Steam: " + callback.Result); + return; + } + + ArchiLogger.LogGenericInfo("Connected to Steam!"); + + if (!KeepRunning) { + ArchiLogger.LogGenericInfo("Disconnecting..."); + Disconnect(); + return; + } + + byte[] sentryFileHash = null; + if (File.Exists(SentryFile)) { + try { + byte[] sentryFileContent = File.ReadAllBytes(SentryFile); + sentryFileHash = SteamKit2.CryptoHelper.SHAHash(sentryFileContent); + } catch (Exception e) { + ArchiLogger.LogGenericException(e); + } + } + + if (!InitializeLoginAndPassword(false)) { + Stop(); + return; + } + + ArchiLogger.LogGenericInfo("Logging in..."); + + string password = BotConfig.SteamPassword; + if (!string.IsNullOrEmpty(password)) { + // Steam silently ignores non-ASCII characters in password, we're going to do the same + // Don't ask me why, I know it's stupid + password = Regex.Replace(password, @"[^\u0000-\u007F]+", ""); + } + + // Decrypt login key if needed + string loginKey = BotDatabase.LoginKey; + if (!string.IsNullOrEmpty(loginKey) && (loginKey.Length > 19)) { + loginKey = CryptoHelper.Decrypt(BotConfig.PasswordFormat, loginKey); + } + + SteamUser.LogOnDetails logOnDetails = new SteamUser.LogOnDetails { Username = BotConfig.SteamLogin, Password = password, AuthCode = AuthCode, CellID = Program.GlobalDatabase.CellID, LoginID = LoginID, LoginKey = loginKey, TwoFactorCode = TwoFactorCode, SentryFileHash = sentryFileHash, ShouldRememberPassword = true }; + + try { + SteamUser.LogOn(logOnDetails); + } catch { + // TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed + ArchiHandler.LogOnWithoutMachineID(logOnDetails); + } + } + + private async void OnDisconnected(SteamClient.DisconnectedCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + HeartBeatFailures = 0; + + EResult lastLogOnResult = LastLogOnResult; + LastLogOnResult = EResult.Invalid; + + ArchiLogger.LogGenericInfo("Disconnected from Steam!"); + + ArchiWebHandler.OnDisconnected(); + CardsFarmer.OnDisconnected(); + Trading.OnDisconnected(); + + FirstTradeSent = false; + HandledGifts.ClearAndTrim(); + + // If we initiated disconnect, do not attempt to reconnect + if (callback.UserInitiated) { + return; + } + + switch (lastLogOnResult) { + case EResult.Invalid: + // Invalid means that we didn't get OnLoggedOn() in the first place, so Steam is down + // Always reset one-time-only access tokens in this case, as OnLoggedOn() didn't do that for us + AuthCode = TwoFactorCode = null; + break; + case EResult.InvalidPassword: + // If we didn't use login key, it's nearly always rate limiting + if (string.IsNullOrEmpty(BotDatabase.LoginKey)) { + goto case EResult.RateLimitExceeded; + } + + BotDatabase.LoginKey = null; + ArchiLogger.LogGenericInfo("Removed expired login key"); + break; + case EResult.NoConnection: + case EResult.ServiceUnavailable: + case EResult.Timeout: + case EResult.TryAnotherCM: + await Task.Delay(5000).ConfigureAwait(false); + break; + case EResult.RateLimitExceeded: + ArchiLogger.LogGenericInfo("Will retry after 25 minutes..."); + await Task.Delay(25*60*1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25 + break; + } + + if (!KeepRunning || SteamClient.IsConnected) { + return; + } + + ArchiLogger.LogGenericInfo("Reconnecting..."); + await Connect().ConfigureAwait(false); + } + + private void OnFreeLicense(SteamApps.FreeLicenseCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + } + } + + private async void OnFriendMsg(SteamFriends.FriendMsgCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + if (callback.EntryType != EChatEntryType.ChatMsg) { + return; + } + + if ((callback.Sender == null) || string.IsNullOrEmpty(callback.Message)) { + ArchiLogger.LogNullError(nameof(callback.Sender) + " || " + nameof(callback.Message)); + return; + } + + ArchiLogger.LogGenericTrace(callback.Sender.ConvertToUInt64() + ": " + callback.Message); + + await HandleMessage(callback.Sender, callback.Sender, callback.Message).ConfigureAwait(false); + } + + private async void OnFriendMsgHistory(SteamFriends.FriendMsgHistoryCallback callback) { + if ((callback?.Messages == null) || (callback.SteamID == null)) { + ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.Messages) + " || " + nameof(callback.SteamID)); + return; + } + + if ((callback.Messages.Count == 0) || !IsMaster(callback.SteamID)) { + return; + } + + // Get last message + SteamFriends.FriendMsgHistoryCallback.FriendMessage lastMessage = callback.Messages[callback.Messages.Count - 1]; + + // If message is read already, return + if (!lastMessage.Unread) { + return; + } + + // If message is too old, return + if (DateTime.UtcNow.Subtract(lastMessage.Timestamp).TotalHours > 1) { + return; + } + + // Handle the message + await HandleMessage(callback.SteamID, callback.SteamID, lastMessage.Message).ConfigureAwait(false); + } + + private void OnFriendsList(SteamFriends.FriendsListCallback callback) { + if (callback?.FriendList == null) { + ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.FriendList)); + return; + } + + foreach (SteamFriends.FriendsListCallback.Friend friend in callback.FriendList.Where(friend => friend.Relationship == EFriendRelationship.RequestRecipient)) { + if (IsMaster(friend.SteamID)) { + SteamFriends.AddFriend(friend.SteamID); + } else if (BotConfig.IsBotAccount) { + SteamFriends.RemoveFriend(friend.SteamID); + } + } + } + + private async void OnGuestPassList(SteamApps.GuestPassListCallback callback) { + if (callback?.GuestPasses == null) { + ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.GuestPasses)); + 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); + + ArchiLogger.LogGenericInfo("Accepting gift: " + gid + "..."); + await LimitGiftsRequestsAsync().ConfigureAwait(false); + + ArchiHandler.RedeemGuestPassResponseCallback response = await ArchiHandler.RedeemGuestPass(gid).ConfigureAwait(false); + if (response != null) { + if (response.Result == EResult.OK) { + ArchiLogger.LogGenericInfo("Success!"); + } else { + ArchiLogger.LogGenericWarning("Failed with error: " + response.Result); + } + } else { + ArchiLogger.LogGenericWarning("Failed!"); + } + } + } + + private async void OnLicenseList(SteamApps.LicenseListCallback callback) { + if (callback?.LicenseList == null) { + ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LicenseList)); + 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); } - return "Bot " + BotName + " is not running."; + if (!ArchiWebHandler.Ready) { + return; + } } - if (PlayingBlocked) { - return "Bot " + BotName + " is currently being used."; + await CardsFarmer.OnNewGameAdded().ConfigureAwait(false); + } + + private void OnLoggedOff(SteamUser.LoggedOffCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; } - if (CardsFarmer.Paused) { - return "Bot " + BotName + " is paused or running in manual mode."; + ArchiLogger.LogGenericInfo("Logged off of Steam: " + callback.Result); + + switch (callback.Result) { + case EResult.LogonSessionReplaced: + ArchiLogger.LogGenericError("This account seems to be used in another ASF instance, which is undefined behaviour, refusing to keep it running!"); + Stop(); + break; + } + } + + private async void OnLoggedOn(SteamUser.LoggedOnCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; } - if (CardsFarmer.CurrentGamesFarming.Count == 0) { - return "Bot " + BotName + " is not farming anything."; + // Always reset one-time-only access tokens + AuthCode = TwoFactorCode = null; + + // Keep LastLogOnResult for OnDisconnected() + LastLogOnResult = callback.Result; + + switch (callback.Result) { + case EResult.AccountLogonDenied: + AuthCode = Program.GetUserInput(ASF.EUserInputType.SteamGuard, BotName); + if (string.IsNullOrEmpty(AuthCode)) { + Stop(); + } + + break; + case EResult.AccountLoginDeniedNeedTwoFactor: + if (BotDatabase.MobileAuthenticator == null) { + TwoFactorCode = Program.GetUserInput(ASF.EUserInputType.TwoFactorAuthentication, BotName); + if (string.IsNullOrEmpty(TwoFactorCode)) { + Stop(); + } + } else { + ArchiLogger.LogGenericWarning("2FA code was invalid despite of using ASF 2FA. Invalid authenticator or bad timing?"); + } + + break; + case EResult.OK: + ArchiLogger.LogGenericInfo("Successfully logged on!"); + + // Old status for these doesn't matter, we'll update them if needed + LibraryLockedBySteamID = 0; + IsLimitedUser = PlayingBlocked = false; + + if (callback.AccountFlags.HasFlag(EAccountFlags.LimitedUser)) { + IsLimitedUser = true; + ArchiLogger.LogGenericWarning("This account is limited, farming process is permanently unavailable until the restriction is removed!"); + } + + if ((callback.CellID != 0) && (Program.GlobalDatabase.CellID != callback.CellID)) { + Program.GlobalDatabase.CellID = callback.CellID; + } + + if (BotDatabase.MobileAuthenticator == null) { + // Support and convert SDA files + string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile"); + if (File.Exists(maFilePath)) { + ImportAuthenticator(maFilePath); + } + } + + if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) { + BotConfig.SteamParentalPIN = Program.GetUserInput(ASF.EUserInputType.SteamParentalPIN, BotName); + if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) { + Stop(); + return; + } + } + + if (!await ArchiWebHandler.Init(callback.ClientSteamID, SteamClient.ConnectedUniverse, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) { + if (!await RefreshSession().ConfigureAwait(false)) { + return; + } + } + + InitializeFamilySharing().Forget(); + + if (BotConfig.DismissInventoryNotifications) { + ArchiWebHandler.MarkInventory().Forget(); + } + + if (BotConfig.SteamMasterClanID != 0) { + Task.Run(async () => { + await ArchiWebHandler.JoinGroup(BotConfig.SteamMasterClanID).ConfigureAwait(false); + JoinMasterChat(); + }).Forget(); + } + + if (Program.GlobalConfig.Statistics) { + ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).Forget(); + } + + Trading.CheckTrades().Forget(); + break; + case EResult.InvalidPassword: + case EResult.NoConnection: + case EResult.RateLimitExceeded: + case EResult.ServiceUnavailable: + case EResult.Timeout: + case EResult.TryAnotherCM: + case EResult.TwoFactorCodeMismatch: + ArchiLogger.LogGenericWarning("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult); + break; + default: // Unexpected result, shutdown immediately + ArchiLogger.LogGenericError("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult); + Stop(); + break; + } + } + + private void OnLoginKey(SteamUser.LoginKeyCallback callback) { + if (string.IsNullOrEmpty(callback?.LoginKey)) { + ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey)); + return; } - StringBuilder response = new StringBuilder("Bot " + BotName + " is farming "); + string loginKey = callback.LoginKey; + if (!string.IsNullOrEmpty(loginKey)) { + loginKey = CryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey); + } - if (CardsFarmer.CurrentGamesFarming.Count == 1) { - CardsFarmer.Game game = CardsFarmer.CurrentGamesFarming.First(); - response.Append("game " + game.AppID + " (" + game.GameName + ", " + game.CardsRemaining + " card drops remaining)"); + BotDatabase.LoginKey = loginKey; + SteamUser.AcceptNewLoginKey(callback); + } + + private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + int fileSize; + byte[] sentryHash; + + try { + using (FileStream fileStream = File.Open(SentryFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) { + fileStream.Seek(callback.Offset, SeekOrigin.Begin); + fileStream.Write(callback.Data, 0, callback.BytesToWrite); + fileSize = (int) fileStream.Length; + + fileStream.Seek(0, SeekOrigin.Begin); + using (SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider()) { + sentryHash = sha.ComputeHash(fileStream); + } + } + } catch (Exception e) { + ArchiLogger.LogGenericException(e); + return; + } + + // Inform the steam servers that we're accepting this sentry file + SteamUser.SendMachineAuthResponse(new SteamUser.MachineAuthDetails { JobID = callback.JobID, FileName = callback.FileName, BytesWritten = callback.BytesToWrite, FileSize = fileSize, Offset = callback.Offset, Result = EResult.OK, LastError = 0, OneTimePassword = callback.OneTimePassword, SentryFileHash = sentryHash }); + } + + private void OnNotifications(ArchiHandler.NotificationsCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + if ((callback.Notifications == null) || (callback.Notifications.Count == 0)) { + return; + } + + foreach (ArchiHandler.NotificationsCallback.ENotification notification in callback.Notifications) { + switch (notification) { + case ArchiHandler.NotificationsCallback.ENotification.Items: + CardsFarmer.OnNewItemsNotification().Forget(); + if (BotConfig.DismissInventoryNotifications) { + ArchiWebHandler.MarkInventory().Forget(); + } + break; + case ArchiHandler.NotificationsCallback.ENotification.Trading: + Trading.CheckTrades().Forget(); + break; + } + } + } + + private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + if ((callback.OfflineMessagesCount == 0) || !BotConfig.HandleOfflineMessages) { + return; + } + + SteamFriends.RequestOfflineMessages(); + } + + private void OnPersonaState(SteamFriends.PersonaStateCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + if (callback.FriendID == SteamClient.SteamID) { + Events.OnStateUpdated(this, callback); + } else if ((callback.FriendID == LibraryLockedBySteamID) && (callback.GameID == 0)) { + LibraryLockedBySteamID = 0; + CheckOccupationStatus(); + } + } + + private void OnPlayingSessionState(ArchiHandler.PlayingSessionStateCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + if (callback.PlayingBlocked == PlayingBlocked) { + return; // No status update, we're not interested + } + + PlayingBlocked = callback.PlayingBlocked; + CheckOccupationStatus(); + } + + private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + } + } + + private void OnSharedLibraryLockStatus(ArchiHandler.SharedLibraryLockStatusCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); + return; + } + + // Ignore no status updates + if (LibraryLockedBySteamID == 0) { + if ((callback.LibraryLockedBySteamID == 0) || (callback.LibraryLockedBySteamID == SteamClient.SteamID)) { + return; + } + + LibraryLockedBySteamID = callback.LibraryLockedBySteamID; } else { - response.Append("appIDs " + string.Join(", ", CardsFarmer.CurrentGamesFarming.Select(game => game.AppID))); - } - - response.Append(" and has a total of " + CardsFarmer.GamesToFarm.Count + " games (" + CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining) + " cards) left to farm."); - return response.ToString(); - } - - private static string ResponseStatus(ulong steamID, string botName) { - if ((steamID == 0) || string.IsNullOrEmpty(botName)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); - return null; - } - - Bot bot; - if (Bots.TryGetValue(botName, out bot)) { - return bot.ResponseStatus(steamID); - } - - if (IsOwner(steamID)) { - return "Couldn't find any bot named " + botName + "!"; - } - - return null; - } - - private static string ResponseStatusAll(ulong steamID) { - if (steamID == 0) { - ASF.ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsOwner(steamID)) { - return null; - } - - HashSet botsRunning = new HashSet(Bots.Where(bot => bot.Value.KeepRunning).OrderBy(bot => bot.Key).Select(bot => bot.Value)); - IEnumerable statuses = botsRunning.Select(bot => bot.ResponseStatus(steamID)); - - return Environment.NewLine + - string.Join(Environment.NewLine, statuses) + Environment.NewLine + - "There are " + botsRunning.Count + "/" + Bots.Count + " bots running, with total of " + botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Count) + " games (" + botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining)) + " cards) left to farm."; - } - - private async Task ResponseLoot(ulong steamID) { - if (steamID == 0) { - ArchiLogger.LogNullError(nameof(steamID)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return "This bot instance is not connected!"; - } - - if (BotConfig.SteamMasterID == 0) { - return "Trade couldn't be send because SteamMasterID is not defined!"; - } - - if (BotConfig.SteamMasterID == SteamClient.SteamID) { - return "You can't loot yourself!"; - } - - await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false); - - HashSet inventory = await ArchiWebHandler.GetMySteamInventory(true).ConfigureAwait(false); - if ((inventory == null) || (inventory.Count == 0)) { - return "Nothing to send, inventory seems empty!"; - } - - // Remove from our pending inventory all items that are not steam cards and boosters - if (inventory.RemoveWhere(item => (item.Type != Steam.Item.EType.TradingCard) && ((item.Type != Steam.Item.EType.FoilTradingCard) || !BotConfig.IsBotAccount) && (item.Type != Steam.Item.EType.BoosterPack)) > 0) { - if (inventory.Count == 0) { - return "Nothing to send, inventory seems empty!"; + if ((callback.LibraryLockedBySteamID != 0) && (callback.LibraryLockedBySteamID != SteamClient.SteamID)) { + return; } + + if (SteamFriends.GetFriendGamePlayed(LibraryLockedBySteamID) != 0) { + return; + } + + LibraryLockedBySteamID = 0; } - if (!await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) { - return "Trade offer failed due to error!"; - } - - await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment - await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, BotConfig.SteamMasterID).ConfigureAwait(false); - return "Trade offer sent successfully!"; + CheckOccupationStatus(); } - private static async Task ResponseLoot(ulong steamID, string botName) { - if ((steamID == 0) || string.IsNullOrEmpty(botName)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); - return null; + private void OnWebAPIUserNonce(SteamUser.WebAPIUserNonceCallback callback) { + if (callback == null) { + ArchiLogger.LogNullError(nameof(callback)); } - - Bot bot; - if (Bots.TryGetValue(botName, out bot)) { - return await bot.ResponseLoot(steamID).ConfigureAwait(false); - } - - if (IsOwner(steamID)) { - return "Couldn't find any bot named " + botName + "!"; - } - - return null; } - private static async Task ResponseLootAll(ulong steamID) { - if (steamID == 0) { - ASF.ArchiLogger.LogNullError(nameof(steamID)); - return null; + private void ResetGamesPlayed() { + if (PlayingBlocked) { + return; } - if (!IsOwner(steamID)) { - return null; - } - - await Task.WhenAll(Bots.Values.Where(bot => bot.IsConnectedAndLoggedOn).Select(bot => bot.ResponseLoot(steamID))).ConfigureAwait(false); - return "Done!"; + ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle, BotConfig.CustomGamePlayedWhileIdle); } private async Task Response2FA(ulong steamID) { @@ -1159,6 +1459,74 @@ namespace ArchiSteamFarm { return null; } + private async Task ResponseAddLicense(ulong steamID, ICollection gameIDs) { + if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) { + ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count)); + return null; + } + + if (!IsMaster(steamID)) { + return null; + } + + if (!IsConnectedAndLoggedOn) { + return "This bot instance is not connected!"; + } + + StringBuilder result = new StringBuilder(); + foreach (uint gameID in gameIDs) { + SteamApps.FreeLicenseCallback callback = await SteamApps.RequestFreeLicense(gameID); + if (callback == null) { + result.AppendLine(Environment.NewLine + "Result: Timeout!"); + break; + } + + if ((callback.GrantedApps.Count != 0) || (callback.GrantedPackages.Count != 0)) { + result.AppendLine(Environment.NewLine + "Result: " + callback.Result + " | Granted apps:" + (callback.GrantedApps.Count != 0 ? " " + string.Join(", ", callback.GrantedApps) : "") + (callback.GrantedPackages.Count != 0 ? " " + string.Join(", ", callback.GrantedPackages) : "")); + } else if (await ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) { + result.AppendLine(Environment.NewLine + "Result: " + EResult.OK + " | Granted apps: " + gameID); + } else { + result.AppendLine(Environment.NewLine + "Result: " + EResult.AccessDenied); + } + } + + return result.ToString(); + } + + private static async Task ResponseAddLicense(ulong steamID, string botName, string games) { + if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games)); + return null; + } + + Bot bot; + if (!Bots.TryGetValue(botName, out bot)) { + if (IsOwner(steamID)) { + return "Couldn't find any bot named " + botName + "!"; + } + + return null; + } + + string[] gameIDs = games.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + HashSet gamesToRedeem = new HashSet(); + foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) { + uint gameID; + if (!uint.TryParse(game, out gameID)) { + return "Couldn't parse games list!"; + } + + gamesToRedeem.Add(gameID); + } + + if (gamesToRedeem.Count == 0) { + return "List of games is empty!"; + } + + return await bot.ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false); + } + private static string ResponseAPI(ulong steamID) { if (steamID != 0) { return !IsOwner(steamID) ? null : GetAPIStatus(); @@ -1237,6 +1605,317 @@ namespace ArchiSteamFarm { return "https://github.com/" + SharedInfo.GithubRepo + "/wiki/Commands"; } + private async Task ResponseLoot(ulong steamID) { + if (steamID == 0) { + ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!IsMaster(steamID)) { + return null; + } + + if (!IsConnectedAndLoggedOn) { + return "This bot instance is not connected!"; + } + + if (BotConfig.SteamMasterID == 0) { + return "Trade couldn't be send because SteamMasterID is not defined!"; + } + + if (BotConfig.SteamMasterID == SteamClient.SteamID) { + return "You can't loot yourself!"; + } + + await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false); + + HashSet inventory = await ArchiWebHandler.GetMySteamInventory(true).ConfigureAwait(false); + if ((inventory == null) || (inventory.Count == 0)) { + return "Nothing to send, inventory seems empty!"; + } + + // Remove from our pending inventory all items that are not steam cards and boosters + if (inventory.RemoveWhere(item => (item.Type != Steam.Item.EType.TradingCard) && ((item.Type != Steam.Item.EType.FoilTradingCard) || !BotConfig.IsBotAccount) && (item.Type != Steam.Item.EType.BoosterPack)) > 0) { + if (inventory.Count == 0) { + return "Nothing to send, inventory seems empty!"; + } + } + + if (!await ArchiWebHandler.SendTradeOffer(inventory, BotConfig.SteamMasterID, BotConfig.SteamTradeToken).ConfigureAwait(false)) { + return "Trade offer failed due to error!"; + } + + await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment + await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, BotConfig.SteamMasterID).ConfigureAwait(false); + return "Trade offer sent successfully!"; + } + + private static async Task ResponseLoot(ulong steamID, string botName) { + if ((steamID == 0) || string.IsNullOrEmpty(botName)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); + return null; + } + + Bot bot; + if (Bots.TryGetValue(botName, out bot)) { + return await bot.ResponseLoot(steamID).ConfigureAwait(false); + } + + if (IsOwner(steamID)) { + return "Couldn't find any bot named " + botName + "!"; + } + + return null; + } + + private static async Task ResponseLootAll(ulong steamID) { + if (steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!IsOwner(steamID)) { + return null; + } + + await Task.WhenAll(Bots.Values.Where(bot => bot.IsConnectedAndLoggedOn).Select(bot => bot.ResponseLoot(steamID))).ConfigureAwait(false); + return "Done!"; + } + + private async Task ResponseOwns(ulong steamID, string query) { + if ((steamID == 0) || string.IsNullOrEmpty(query)) { + ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query)); + return null; + } + + if (!IsMaster(steamID)) { + return null; + } + + if (!IsConnectedAndLoggedOn) { + return "This bot instance is not connected!"; + } + + Dictionary ownedGames; + if (!string.IsNullOrEmpty(BotConfig.SteamApiKey)) { + ownedGames = ArchiWebHandler.GetOwnedGames(SteamClient.SteamID); + } else { + ownedGames = await ArchiWebHandler.GetOwnedGames().ConfigureAwait(false); + } + + if ((ownedGames == null) || (ownedGames.Count == 0)) { + return Environment.NewLine + "<" + BotName + "> List of owned games is empty!"; + } + + StringBuilder response = new StringBuilder(); + + string[] games = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string game in games.Where(game => !string.IsNullOrEmpty(game))) { + // Check if this is appID + uint appID; + if (uint.TryParse(game, out appID)) { + string ownedName; + if (ownedGames.TryGetValue(appID, out ownedName)) { + response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + appID + " | " + ownedName); + } else { + response.Append(Environment.NewLine + "<" + BotName + "> Not owned yet: " + appID); + } + + continue; + } + + // This is a string, so check our entire library + foreach (KeyValuePair ownedGame in ownedGames.Where(ownedGame => ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) { + response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + ownedGame.Key + " | " + ownedGame.Value); + } + } + + if (response.Length > 0) { + return response.ToString(); + } + + return Environment.NewLine + "<" + BotName + "> Not owned yet: " + query; + } + + private static async Task ResponseOwns(ulong steamID, string botName, string query) { + if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(query)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(query)); + return null; + } + + Bot bot; + if (Bots.TryGetValue(botName, out bot)) { + return await bot.ResponseOwns(steamID, query).ConfigureAwait(false); + } + + if (IsOwner(steamID)) { + return "Couldn't find any bot named " + botName + "!"; + } + + return null; + } + + private static async Task ResponseOwnsAll(ulong steamID, string query) { + if ((steamID == 0) || string.IsNullOrEmpty(query)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query)); + return null; + } + + if (!IsOwner(steamID)) { + return null; + } + + string[] responses = await Task.WhenAll(Bots.Where(bot => bot.Value.IsConnectedAndLoggedOn).OrderBy(bot => bot.Key).Select(bot => bot.Value.ResponseOwns(steamID, query))).ConfigureAwait(false); + + StringBuilder result = new StringBuilder(); + foreach (string response in responses.Where(response => !string.IsNullOrEmpty(response))) { + result.Append(response); + } + + return result.Length != 0 ? result.ToString() : null; + } + + private string ResponsePassword(ulong steamID) { + if (steamID == 0) { + ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!IsMaster(steamID)) { + return null; + } + + if (string.IsNullOrEmpty(BotConfig.SteamPassword)) { + return "Can't encrypt null password!"; + } + + return Environment.NewLine + "[" + CryptoHelper.ECryptoMethod.AES + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.AES, BotConfig.SteamPassword) + Environment.NewLine + "[" + CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser + "] password: " + CryptoHelper.Encrypt(CryptoHelper.ECryptoMethod.ProtectedDataForCurrentUser, BotConfig.SteamPassword); + } + + private static string ResponsePassword(ulong steamID, string botName) { + if ((steamID == 0) || string.IsNullOrEmpty(botName)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); + return null; + } + + Bot bot; + if (Bots.TryGetValue(botName, out bot)) { + return bot.ResponsePassword(steamID); + } + + if (IsOwner(steamID)) { + return "Couldn't find any bot named " + botName + "!"; + } + + return null; + } + + private async Task ResponsePause(ulong steamID, bool sticky) { + if (steamID == 0) { + ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!IsMaster(steamID) && !SteamFamilySharingIDs.Contains(steamID)) { + return null; + } + + if (!IsConnectedAndLoggedOn) { + return "This bot instance is not connected!"; + } + + if (CardsFarmer.Paused) { + return "Automatic farming is paused already!"; + } + + await CardsFarmer.Pause(sticky).ConfigureAwait(false); + + if (!SteamFamilySharingIDs.Contains(steamID)) { + return "Automatic farming is now paused!"; + } + + StartFamilySharingInactivityTimer(); + return "Automatic farming is now paused! You have " + FamilySharingInactivityMinutes + " minutes to start a game."; + } + + private static async Task ResponsePause(ulong steamID, string botName, bool sticky) { + if ((steamID == 0) || string.IsNullOrEmpty(botName)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); + return null; + } + + Bot bot; + if (Bots.TryGetValue(botName, out bot)) { + return await bot.ResponsePause(steamID, sticky).ConfigureAwait(false); + } + + if (IsOwner(steamID)) { + return "Couldn't find any bot named " + botName + "!"; + } + + return null; + } + + private async Task ResponsePlay(ulong steamID, HashSet gameIDs) { + if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) { + ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count)); + return null; + } + + if (!IsMaster(steamID)) { + return null; + } + + if (!IsConnectedAndLoggedOn) { + return "This bot instance is not connected!"; + } + + if (!CardsFarmer.Paused) { + await CardsFarmer.Pause(false).ConfigureAwait(false); + } + + ArchiHandler.PlayGames(gameIDs); + return "Done!"; + } + + private static async Task ResponsePlay(ulong steamID, string botName, string games) { + if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games)); + return null; + } + + Bot bot; + if (!Bots.TryGetValue(botName, out bot)) { + if (IsOwner(steamID)) { + return "Couldn't find any bot named " + botName + "!"; + } + + return null; + } + + string[] gameIDs = games.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + HashSet gamesToPlay = new HashSet(); + foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) { + uint gameID; + if (!uint.TryParse(game, out gameID)) { + return "Couldn't parse games list!"; + } + + gamesToPlay.Add(gameID); + + if (gamesToPlay.Count >= CardsFarmer.MaxGamesPlayedConcurrently) { + break; + } + } + + if (gamesToPlay.Count == 0) { + return "List of games is empty!"; + } + + return await bot.ResponsePlay(steamID, gamesToPlay).ConfigureAwait(false); + } + [SuppressMessage("ReSharper", "FunctionComplexityOverflow")] private async Task ResponseRedeem(ulong steamID, string message, ERedeemFlags redeemFlags = ERedeemFlags.None) { if ((steamID == 0) || string.IsNullOrEmpty(message)) { @@ -1416,191 +2095,13 @@ namespace ArchiSteamFarm { return "Done!"; } - private static string ResponseStartAll(ulong steamID) { + private string ResponseResume(ulong steamID) { if (steamID == 0) { - ASF.ArchiLogger.LogNullError(nameof(steamID)); + ArchiLogger.LogNullError(nameof(steamID)); return null; } - if (!IsOwner(steamID)) { - return null; - } - - foreach (Bot bot in Bots.Values.Where(bot => !bot.KeepRunning)) { - bot.ResponseStart(steamID); - } - - return "Done!"; - } - - private async Task ResponseAddLicense(ulong steamID, ICollection gameIDs) { - if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return "This bot instance is not connected!"; - } - - StringBuilder result = new StringBuilder(); - foreach (uint gameID in gameIDs) { - SteamApps.FreeLicenseCallback callback = await SteamApps.RequestFreeLicense(gameID); - if (callback == null) { - result.AppendLine(Environment.NewLine + "Result: Timeout!"); - break; - } - - if ((callback.GrantedApps.Count != 0) || (callback.GrantedPackages.Count != 0)) { - result.AppendLine(Environment.NewLine + "Result: " + callback.Result + " | Granted apps:" + (callback.GrantedApps.Count != 0 ? " " + string.Join(", ", callback.GrantedApps) : "") + (callback.GrantedPackages.Count != 0 ? " " + string.Join(", ", callback.GrantedPackages) : "")); - } else if (await ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false)) { - result.AppendLine(Environment.NewLine + "Result: " + EResult.OK + " | Granted apps: " + gameID); - } else { - result.AppendLine(Environment.NewLine + "Result: " + EResult.AccessDenied); - } - } - - return result.ToString(); - } - - private static async Task ResponseAddLicense(ulong steamID, string botName, string games) { - if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games)); - return null; - } - - Bot bot; - if (!Bots.TryGetValue(botName, out bot)) { - if (IsOwner(steamID)) { - return "Couldn't find any bot named " + botName + "!"; - } - - return null; - } - - string[] gameIDs = games.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - HashSet gamesToRedeem = new HashSet(); - foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) { - uint gameID; - if (!uint.TryParse(game, out gameID)) { - return "Couldn't parse games list!"; - } - - gamesToRedeem.Add(gameID); - } - - if (gamesToRedeem.Count == 0) { - return "List of games is empty!"; - } - - return await bot.ResponseAddLicense(steamID, gamesToRedeem).ConfigureAwait(false); - } - - private async Task ResponseOwns(ulong steamID, string query) { - if ((steamID == 0) || string.IsNullOrEmpty(query)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query)); - return null; - } - - if (!IsMaster(steamID)) { - return null; - } - - if (!IsConnectedAndLoggedOn) { - return "This bot instance is not connected!"; - } - - Dictionary ownedGames; - if (!string.IsNullOrEmpty(BotConfig.SteamApiKey)) { - ownedGames = ArchiWebHandler.GetOwnedGames(SteamClient.SteamID); - } else { - ownedGames = await ArchiWebHandler.GetOwnedGames().ConfigureAwait(false); - } - - if ((ownedGames == null) || (ownedGames.Count == 0)) { - return Environment.NewLine + "<" + BotName + "> List of owned games is empty!"; - } - - StringBuilder response = new StringBuilder(); - - string[] games = query.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - foreach (string game in games.Where(game => !string.IsNullOrEmpty(game))) { - // Check if this is appID - uint appID; - if (uint.TryParse(game, out appID)) { - string ownedName; - if (ownedGames.TryGetValue(appID, out ownedName)) { - response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + appID + " | " + ownedName); - } else { - response.Append(Environment.NewLine + "<" + BotName + "> Not owned yet: " + appID); - } - - continue; - } - - // This is a string, so check our entire library - foreach (KeyValuePair ownedGame in ownedGames.Where(ownedGame => ownedGame.Value.IndexOf(game, StringComparison.OrdinalIgnoreCase) >= 0)) { - response.Append(Environment.NewLine + "<" + BotName + "> Owned already: " + ownedGame.Key + " | " + ownedGame.Value); - } - } - - if (response.Length > 0) { - return response.ToString(); - } - - return Environment.NewLine + "<" + BotName + "> Not owned yet: " + query; - } - - private static async Task ResponseOwns(ulong steamID, string botName, string query) { - if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(query)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(query)); - return null; - } - - Bot bot; - if (Bots.TryGetValue(botName, out bot)) { - return await bot.ResponseOwns(steamID, query).ConfigureAwait(false); - } - - if (IsOwner(steamID)) { - return "Couldn't find any bot named " + botName + "!"; - } - - return null; - } - - private static async Task ResponseOwnsAll(ulong steamID, string query) { - if ((steamID == 0) || string.IsNullOrEmpty(query)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(query)); - return null; - } - - if (!IsOwner(steamID)) { - return null; - } - - string[] responses = await Task.WhenAll(Bots.Where(bot => bot.Value.IsConnectedAndLoggedOn).OrderBy(bot => bot.Key).Select(bot => bot.Value.ResponseOwns(steamID, query))).ConfigureAwait(false); - - StringBuilder result = new StringBuilder(); - foreach (string response in responses.Where(response => !string.IsNullOrEmpty(response))) { - result.Append(response); - } - - return result.Length != 0 ? result.ToString() : null; - } - - private async Task ResponsePlay(ulong steamID, HashSet gameIDs) { - if ((steamID == 0) || (gameIDs == null) || (gameIDs.Count == 0)) { - ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(gameIDs) + " || " + nameof(gameIDs.Count)); - return null; - } - - if (!IsMaster(steamID)) { + if (!IsMaster(steamID) && !SteamFamilySharingIDs.Contains(steamID)) { return null; } @@ -1609,49 +2110,30 @@ namespace ArchiSteamFarm { } if (!CardsFarmer.Paused) { - await CardsFarmer.Pause(false).ConfigureAwait(false); + return "Automatic farming is resumed already!"; } - ArchiHandler.PlayGames(gameIDs); - return "Done!"; + StopFamilySharingInactivityTimer(); + CardsFarmer.Resume(true); + return "Automatic farming is now resumed!"; } - private static async Task ResponsePlay(ulong steamID, string botName, string games) { - if ((steamID == 0) || string.IsNullOrEmpty(botName) || string.IsNullOrEmpty(games)) { - ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName) + " || " + nameof(games)); + private static string ResponseResume(ulong steamID, string botName) { + if ((steamID == 0) || string.IsNullOrEmpty(botName)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); return null; } Bot bot; - if (!Bots.TryGetValue(botName, out bot)) { - if (IsOwner(steamID)) { - return "Couldn't find any bot named " + botName + "!"; - } - - return null; + if (Bots.TryGetValue(botName, out bot)) { + return bot.ResponseResume(steamID); } - string[] gameIDs = games.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - - HashSet gamesToPlay = new HashSet(); - foreach (string game in gameIDs.Where(game => !string.IsNullOrEmpty(game))) { - uint gameID; - if (!uint.TryParse(game, out gameID)) { - return "Couldn't parse games list!"; - } - - gamesToPlay.Add(gameID); - - if (gamesToPlay.Count >= CardsFarmer.MaxGamesPlayedConcurrently) { - break; - } + if (IsOwner(steamID)) { + return "Couldn't find any bot named " + botName + "!"; } - if (gamesToPlay.Count == 0) { - return "List of games is empty!"; - } - - return await bot.ResponsePlay(steamID, gamesToPlay).ConfigureAwait(false); + return null; } private string ResponseStart(ulong steamID) { @@ -1691,6 +2173,100 @@ namespace ArchiSteamFarm { return null; } + private static string ResponseStartAll(ulong steamID) { + if (steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!IsOwner(steamID)) { + return null; + } + + foreach (Bot bot in Bots.Values.Where(bot => !bot.KeepRunning)) { + bot.ResponseStart(steamID); + } + + return "Done!"; + } + + private string ResponseStatus(ulong steamID) { + if (steamID == 0) { + ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!IsMaster(steamID)) { + return null; + } + + if (!IsConnectedAndLoggedOn) { + if (KeepRunning) { + return "Bot " + BotName + " is not connected."; + } + + return "Bot " + BotName + " is not running."; + } + + if (PlayingBlocked) { + return "Bot " + BotName + " is currently being used."; + } + + if (CardsFarmer.Paused) { + return "Bot " + BotName + " is paused or running in manual mode."; + } + + if (CardsFarmer.CurrentGamesFarming.Count == 0) { + return "Bot " + BotName + " is not farming anything."; + } + + StringBuilder response = new StringBuilder("Bot " + BotName + " is farming "); + + if (CardsFarmer.CurrentGamesFarming.Count == 1) { + CardsFarmer.Game game = CardsFarmer.CurrentGamesFarming.First(); + response.Append("game " + game.AppID + " (" + game.GameName + ", " + game.CardsRemaining + " card drops remaining)"); + } else { + response.Append("appIDs " + string.Join(", ", CardsFarmer.CurrentGamesFarming.Select(game => game.AppID))); + } + + response.Append(" and has a total of " + CardsFarmer.GamesToFarm.Count + " games (" + CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining) + " cards) left to farm."); + return response.ToString(); + } + + private static string ResponseStatus(ulong steamID, string botName) { + if ((steamID == 0) || string.IsNullOrEmpty(botName)) { + ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botName)); + return null; + } + + Bot bot; + if (Bots.TryGetValue(botName, out bot)) { + return bot.ResponseStatus(steamID); + } + + if (IsOwner(steamID)) { + return "Couldn't find any bot named " + botName + "!"; + } + + return null; + } + + private static string ResponseStatusAll(ulong steamID) { + if (steamID == 0) { + ASF.ArchiLogger.LogNullError(nameof(steamID)); + return null; + } + + if (!IsOwner(steamID)) { + return null; + } + + HashSet botsRunning = new HashSet(Bots.Where(bot => bot.Value.KeepRunning).OrderBy(bot => bot.Key).Select(bot => bot.Value)); + IEnumerable statuses = botsRunning.Select(bot => bot.ResponseStatus(steamID)); + + return Environment.NewLine + string.Join(Environment.NewLine, statuses) + Environment.NewLine + "There are " + botsRunning.Count + "/" + Bots.Count + " bots running, with total of " + botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Count) + " games (" + botsRunning.Sum(bot => bot.CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining)) + " cards) left to farm."; + } + private string ResponseStop(ulong steamID) { if (steamID == 0) { ArchiLogger.LogNullError(nameof(steamID)); @@ -1763,37 +2339,6 @@ namespace ArchiSteamFarm { return "ASF V" + SharedInfo.Version; } - private void HandleCallbacks() { - TimeSpan timeSpan = TimeSpan.FromMilliseconds(CallbackSleep); - while (KeepRunning || SteamClient.IsConnected) { - if (!CallbackSemaphore.Wait(0)) { - return; - } - - try { - CallbackManager.RunWaitCallbacks(timeSpan); - } catch (Exception e) { - ArchiLogger.LogGenericException(e); - } finally { - CallbackSemaphore.Release(); - } - } - } - - private async Task HandleMessage(ulong chatID, ulong steamID, string message) { - if ((chatID == 0) || (steamID == 0) || string.IsNullOrEmpty(message)) { - ArchiLogger.LogNullError(nameof(chatID) + " || " + nameof(steamID) + " || " + nameof(message)); - return; - } - - string response = await Response(steamID, message).ConfigureAwait(false); - if (string.IsNullOrEmpty(response)) { // We respond with null when user is not authorized (and similar) - return; - } - - SendMessage(chatID, response); - } - private void SendMessage(ulong steamID, string message) { if ((steamID == 0) || string.IsNullOrEmpty(message)) { ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(message)); @@ -1839,626 +2384,45 @@ namespace ArchiSteamFarm { } } - private void JoinMasterChat() { - if (!IsConnectedAndLoggedOn || (BotConfig.SteamMasterClanID == 0)) { - return; - } - - SteamFriends.JoinChat(BotConfig.SteamMasterClanID); - } - - private bool InitializeLoginAndPassword(bool requiresPassword) { - if (string.IsNullOrEmpty(BotConfig.SteamLogin)) { - BotConfig.SteamLogin = Program.GetUserInput(ASF.EUserInputType.Login, BotName); - if (string.IsNullOrEmpty(BotConfig.SteamLogin)) { - return false; - } - } - - if (!string.IsNullOrEmpty(BotConfig.SteamPassword) || (!requiresPassword && !string.IsNullOrEmpty(BotDatabase.LoginKey))) { - return true; - } - - BotConfig.SteamPassword = Program.GetUserInput(ASF.EUserInputType.Password, BotName); - return !string.IsNullOrEmpty(BotConfig.SteamPassword); - } - - private void ResetGamesPlayed() { - if (PlayingBlocked) { - return; - } - - ArchiHandler.PlayGames(BotConfig.GamesPlayedWhileIdle, BotConfig.CustomGamePlayedWhileIdle); - } - - private void OnConnected(SteamClient.ConnectedCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - if (callback.Result != EResult.OK) { - ArchiLogger.LogGenericError("Unable to connect to Steam: " + callback.Result); - return; - } - - ArchiLogger.LogGenericInfo("Connected to Steam!"); - + private async Task Start() { if (!KeepRunning) { - ArchiLogger.LogGenericInfo("Disconnecting..."); - Disconnect(); - return; + KeepRunning = true; + Task.Run(() => HandleCallbacks()).Forget(); + ArchiLogger.LogGenericInfo("Starting..."); } - byte[] sentryFileHash = null; - if (File.Exists(SentryFile)) { - try { - byte[] sentryFileContent = File.ReadAllBytes(SentryFile); - sentryFileHash = SteamKit2.CryptoHelper.SHAHash(sentryFileContent); - } catch (Exception e) { - ArchiLogger.LogGenericException(e); - } - } - - if (!InitializeLoginAndPassword(false)) { - Stop(); - return; - } - - ArchiLogger.LogGenericInfo("Logging in..."); - - string password = BotConfig.SteamPassword; - if (!string.IsNullOrEmpty(password)) { - // Steam silently ignores non-ASCII characters in password, we're going to do the same - // Don't ask me why, I know it's stupid - password = Regex.Replace(password, @"[^\u0000-\u007F]+", ""); - } - - // Decrypt login key if needed - string loginKey = BotDatabase.LoginKey; - if (!string.IsNullOrEmpty(loginKey) && (loginKey.Length > 19)) { - loginKey = CryptoHelper.Decrypt(BotConfig.PasswordFormat, loginKey); - } - - SteamUser.LogOnDetails logOnDetails = new SteamUser.LogOnDetails { - Username = BotConfig.SteamLogin, - Password = password, - AuthCode = AuthCode, - CellID = Program.GlobalDatabase.CellID, - LoginID = LoginID, - LoginKey = loginKey, - TwoFactorCode = TwoFactorCode, - SentryFileHash = sentryFileHash, - ShouldRememberPassword = true - }; - - try { - SteamUser.LogOn(logOnDetails); - } catch { - // TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed - ArchiHandler.LogOnWithoutMachineID(logOnDetails); - } - } - - private async void OnDisconnected(SteamClient.DisconnectedCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - HeartBeatFailures = 0; - - EResult lastLogOnResult = LastLogOnResult; - LastLogOnResult = EResult.Invalid; - - ArchiLogger.LogGenericInfo("Disconnected from Steam!"); - - ArchiWebHandler.OnDisconnected(); - CardsFarmer.OnDisconnected(); - Trading.OnDisconnected(); - - FirstTradeSent = false; - HandledGifts.ClearAndTrim(); - - // If we initiated disconnect, do not attempt to reconnect - if (callback.UserInitiated) { - return; - } - - switch (lastLogOnResult) { - case EResult.Invalid: - // Invalid means that we didn't get OnLoggedOn() in the first place, so Steam is down - // Always reset one-time-only access tokens in this case, as OnLoggedOn() didn't do that for us - AuthCode = TwoFactorCode = null; - break; - case EResult.InvalidPassword: - // If we didn't use login key, it's nearly always rate limiting - if (string.IsNullOrEmpty(BotDatabase.LoginKey)) { - goto case EResult.RateLimitExceeded; - } - - BotDatabase.LoginKey = null; - ArchiLogger.LogGenericInfo("Removed expired login key"); - break; - case EResult.NoConnection: - case EResult.ServiceUnavailable: - case EResult.Timeout: - case EResult.TryAnotherCM: - await Task.Delay(5000).ConfigureAwait(false); - break; - case EResult.RateLimitExceeded: - ArchiLogger.LogGenericInfo("Will retry after 25 minutes..."); - await Task.Delay(25 * 60 * 1000).ConfigureAwait(false); // Captcha disappears after around 20 minutes, so we make it 25 - break; - } - - if (!KeepRunning || SteamClient.IsConnected) { - return; - } - - ArchiLogger.LogGenericInfo("Reconnecting..."); await Connect().ConfigureAwait(false); } - private void OnFreeLicense(SteamApps.FreeLicenseCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); + private void StartFamilySharingInactivityTimer() { + if (FamilySharingInactivityTimer != null) { + return; } + + FamilySharingInactivityTimer = new Timer(e => CheckFamilySharingInactivity(), null, TimeSpan.FromMinutes(FamilySharingInactivityMinutes), // Delay + Timeout.InfiniteTimeSpan // Period + ); } - private async void OnGuestPassList(SteamApps.GuestPassListCallback callback) { - if (callback?.GuestPasses == null) { - ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.GuestPasses)); + private void StopFamilySharingInactivityTimer() { + if (FamilySharingInactivityTimer == null) { 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); - - ArchiLogger.LogGenericInfo("Accepting gift: " + gid + "..."); - await LimitGiftsRequestsAsync().ConfigureAwait(false); - - ArchiHandler.RedeemGuestPassResponseCallback response = await ArchiHandler.RedeemGuestPass(gid).ConfigureAwait(false); - if (response != null) { - if (response.Result == EResult.OK) { - ArchiLogger.LogGenericInfo("Success!"); - } else { - ArchiLogger.LogGenericWarning("Failed with error: " + response.Result); - } - } else { - ArchiLogger.LogGenericWarning("Failed!"); - } - } + FamilySharingInactivityTimer.Change(Timeout.Infinite, Timeout.Infinite); + FamilySharingInactivityTimer.Dispose(); + FamilySharingInactivityTimer = null; } - private async void OnLicenseList(SteamApps.LicenseListCallback callback) { - if (callback?.LicenseList == null) { - ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LicenseList)); - 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); - } - - private void OnChatInvite(SteamFriends.ChatInviteCallback callback) { - if ((callback?.ChatRoomID == null) || (callback.PatronID == null)) { - ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.ChatRoomID) + " || " + nameof(callback.PatronID)); - return; - } - - if (!IsMaster(callback.PatronID)) { - return; - } - - SteamFriends.JoinChat(callback.ChatRoomID); - } - - private async void OnChatMsg(SteamFriends.ChatMsgCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - if (callback.ChatMsgType != EChatEntryType.ChatMsg) { - return; - } - - if ((callback.ChatRoomID == null) || (callback.ChatterID == null) || string.IsNullOrEmpty(callback.Message)) { - ArchiLogger.LogNullError(nameof(callback.ChatRoomID) + " || " + nameof(callback.ChatterID) + " || " + nameof(callback.Message)); - return; - } - - ArchiLogger.LogGenericTrace(callback.ChatRoomID.ConvertToUInt64() + "/" + callback.ChatterID.ConvertToUInt64() + ": " + callback.Message); - - switch (callback.Message) { - case "!leave": - if (!IsMaster(callback.ChatterID)) { - break; - } - - SteamFriends.LeaveChat(callback.ChatRoomID); - break; - default: - await HandleMessage(callback.ChatRoomID, callback.ChatterID, callback.Message).ConfigureAwait(false); - break; - } - } - - private void OnFriendsList(SteamFriends.FriendsListCallback callback) { - if (callback?.FriendList == null) { - ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.FriendList)); - return; - } - - foreach (SteamFriends.FriendsListCallback.Friend friend in callback.FriendList.Where(friend => friend.Relationship == EFriendRelationship.RequestRecipient)) { - if (IsMaster(friend.SteamID)) { - SteamFriends.AddFriend(friend.SteamID); - } else if (BotConfig.IsBotAccount) { - SteamFriends.RemoveFriend(friend.SteamID); - } - } - } - - private async void OnFriendMsg(SteamFriends.FriendMsgCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - if (callback.EntryType != EChatEntryType.ChatMsg) { - return; - } - - if ((callback.Sender == null) || string.IsNullOrEmpty(callback.Message)) { - ArchiLogger.LogNullError(nameof(callback.Sender) + " || " + nameof(callback.Message)); - return; - } - - ArchiLogger.LogGenericTrace(callback.Sender.ConvertToUInt64() + ": " + callback.Message); - - await HandleMessage(callback.Sender, callback.Sender, callback.Message).ConfigureAwait(false); - } - - private async void OnFriendMsgHistory(SteamFriends.FriendMsgHistoryCallback callback) { - if ((callback?.Messages == null) || (callback.SteamID == null)) { - ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.Messages) + " || " + nameof(callback.SteamID)); - return; - } - - if ((callback.Messages.Count == 0) || !IsMaster(callback.SteamID)) { - return; - } - - // Get last message - SteamFriends.FriendMsgHistoryCallback.FriendMessage lastMessage = callback.Messages[callback.Messages.Count - 1]; - - // If message is read already, return - if (!lastMessage.Unread) { - return; - } - - // If message is too old, return - if (DateTime.UtcNow.Subtract(lastMessage.Timestamp).TotalHours > 1) { - return; - } - - // Handle the message - await HandleMessage(callback.SteamID, callback.SteamID, lastMessage.Message).ConfigureAwait(false); - } - - private void OnPersonaState(SteamFriends.PersonaStateCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - if (callback.FriendID == SteamClient.SteamID) { - Events.OnStateUpdated(this, callback); - } else if ((callback.FriendID == LibraryLockedBySteamID) && (callback.GameID == 0)) { - LibraryLockedBySteamID = 0; - CheckOccupationStatus(); - } - } - - private void OnAccountInfo(SteamUser.AccountInfoCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - if (!BotConfig.FarmOffline) { - SteamFriends.SetPersonaState(EPersonaState.Online); - } - } - - private void OnLoggedOff(SteamUser.LoggedOffCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - ArchiLogger.LogGenericInfo("Logged off of Steam: " + callback.Result); - - switch (callback.Result) { - case EResult.LogonSessionReplaced: - ArchiLogger.LogGenericError("This account seems to be used in another ASF instance, which is undefined behaviour, refusing to keep it running!"); - Stop(); - break; - } - } - - private async void OnLoggedOn(SteamUser.LoggedOnCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - // Always reset one-time-only access tokens - AuthCode = TwoFactorCode = null; - - // Keep LastLogOnResult for OnDisconnected() - LastLogOnResult = callback.Result; - - switch (callback.Result) { - case EResult.AccountLogonDenied: - AuthCode = Program.GetUserInput(ASF.EUserInputType.SteamGuard, BotName); - if (string.IsNullOrEmpty(AuthCode)) { - Stop(); - } - - break; - case EResult.AccountLoginDeniedNeedTwoFactor: - if (BotDatabase.MobileAuthenticator == null) { - TwoFactorCode = Program.GetUserInput(ASF.EUserInputType.TwoFactorAuthentication, BotName); - if (string.IsNullOrEmpty(TwoFactorCode)) { - Stop(); - } - } else { - ArchiLogger.LogGenericWarning("2FA code was invalid despite of using ASF 2FA. Invalid authenticator or bad timing?"); - } - - break; - case EResult.OK: - ArchiLogger.LogGenericInfo("Successfully logged on!"); - - // Old status for these doesn't matter, we'll update them if needed - LibraryLockedBySteamID = 0; - IsLimitedUser = PlayingBlocked = false; - - if (callback.AccountFlags.HasFlag(EAccountFlags.LimitedUser)) { - IsLimitedUser = true; - ArchiLogger.LogGenericWarning("This account is limited, farming process is permanently unavailable until the restriction is removed!"); - } - - if ((callback.CellID != 0) && (Program.GlobalDatabase.CellID != callback.CellID)) { - Program.GlobalDatabase.CellID = callback.CellID; - } - - if (BotDatabase.MobileAuthenticator == null) { - // Support and convert SDA files - string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile"); - if (File.Exists(maFilePath)) { - ImportAuthenticator(maFilePath); - } - } - - if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) { - BotConfig.SteamParentalPIN = Program.GetUserInput(ASF.EUserInputType.SteamParentalPIN, BotName); - if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) { - Stop(); - return; - } - } - - if (!await ArchiWebHandler.Init(callback.ClientSteamID, SteamClient.ConnectedUniverse, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) { - if (!await RefreshSession().ConfigureAwait(false)) { - return; - } - } - - InitializeFamilySharing().Forget(); - - if (BotConfig.DismissInventoryNotifications) { - ArchiWebHandler.MarkInventory().Forget(); - } - - if (BotConfig.SteamMasterClanID != 0) { - Task.Run(async () => { - await ArchiWebHandler.JoinGroup(BotConfig.SteamMasterClanID).ConfigureAwait(false); - JoinMasterChat(); - }).Forget(); - } - - if (Program.GlobalConfig.Statistics) { - ArchiWebHandler.JoinGroup(SharedInfo.ASFGroupSteamID).Forget(); - } - - Trading.CheckTrades().Forget(); - break; - case EResult.InvalidPassword: - case EResult.NoConnection: - case EResult.RateLimitExceeded: - case EResult.ServiceUnavailable: - case EResult.Timeout: - case EResult.TryAnotherCM: - case EResult.TwoFactorCodeMismatch: - ArchiLogger.LogGenericWarning("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult); - break; - default: // Unexpected result, shutdown immediately - ArchiLogger.LogGenericError("Unable to login to Steam: " + callback.Result + " / " + callback.ExtendedResult); - Stop(); - break; - } - } - - private void OnLoginKey(SteamUser.LoginKeyCallback callback) { - if (string.IsNullOrEmpty(callback?.LoginKey)) { - ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey)); - return; - } - - string loginKey = callback.LoginKey; - if (!string.IsNullOrEmpty(loginKey)) { - loginKey = CryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey); - } - - BotDatabase.LoginKey = loginKey; - SteamUser.AcceptNewLoginKey(callback); - } - - private void OnMachineAuth(SteamUser.UpdateMachineAuthCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - int fileSize; - byte[] sentryHash; - - try { - using (FileStream fileStream = File.Open(SentryFile, FileMode.OpenOrCreate, FileAccess.ReadWrite)) { - fileStream.Seek(callback.Offset, SeekOrigin.Begin); - fileStream.Write(callback.Data, 0, callback.BytesToWrite); - fileSize = (int) fileStream.Length; - - fileStream.Seek(0, SeekOrigin.Begin); - using (SHA1CryptoServiceProvider sha = new SHA1CryptoServiceProvider()) { - sentryHash = sha.ComputeHash(fileStream); - } - } - } catch (Exception e) { - ArchiLogger.LogGenericException(e); - return; - } - - // Inform the steam servers that we're accepting this sentry file - SteamUser.SendMachineAuthResponse(new SteamUser.MachineAuthDetails { - JobID = callback.JobID, - FileName = callback.FileName, - BytesWritten = callback.BytesToWrite, - FileSize = fileSize, - Offset = callback.Offset, - Result = EResult.OK, - LastError = 0, - OneTimePassword = callback.OneTimePassword, - SentryFileHash = sentryHash - }); - } - - private void OnWebAPIUserNonce(SteamUser.WebAPIUserNonceCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - } - } - - private void OnNotifications(ArchiHandler.NotificationsCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - if ((callback.Notifications == null) || (callback.Notifications.Count == 0)) { - return; - } - - foreach (ArchiHandler.NotificationsCallback.ENotification notification in callback.Notifications) { - switch (notification) { - case ArchiHandler.NotificationsCallback.ENotification.Items: - CardsFarmer.OnNewItemsNotification().Forget(); - if (BotConfig.DismissInventoryNotifications) { - ArchiWebHandler.MarkInventory().Forget(); - } - break; - case ArchiHandler.NotificationsCallback.ENotification.Trading: - Trading.CheckTrades().Forget(); - break; - } - } - } - - private void OnOfflineMessage(ArchiHandler.OfflineMessageCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - if ((callback.OfflineMessagesCount == 0) || !BotConfig.HandleOfflineMessages) { - return; - } - - SteamFriends.RequestOfflineMessages(); - } - - private void OnPlayingSessionState(ArchiHandler.PlayingSessionStateCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - if (callback.PlayingBlocked == PlayingBlocked) { - return; // No status update, we're not interested - } - - PlayingBlocked = callback.PlayingBlocked; - CheckOccupationStatus(); - } - - private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - } - } - - private void OnSharedLibraryLockStatus(ArchiHandler.SharedLibraryLockStatusCallback callback) { - if (callback == null) { - ArchiLogger.LogNullError(nameof(callback)); - return; - } - - // Ignore no status updates - if (LibraryLockedBySteamID == 0) { - if ((callback.LibraryLockedBySteamID == 0) || (callback.LibraryLockedBySteamID == SteamClient.SteamID)) { - return; - } - - LibraryLockedBySteamID = callback.LibraryLockedBySteamID; - } else { - if ((callback.LibraryLockedBySteamID != 0) && (callback.LibraryLockedBySteamID != SteamClient.SteamID)) { - return; - } - - if (SteamFriends.GetFriendGamePlayed(LibraryLockedBySteamID) != 0) { - return; - } - - LibraryLockedBySteamID = 0; - } - - CheckOccupationStatus(); + [Flags] + private enum ERedeemFlags : byte { + None = 0, + Validate = 1, + ForceForwarding = 2, + SkipForwarding = 4, + ForceDistribution = 8, + SkipDistribution = 16, + SkipInitial = 32 } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/BotConfig.cs b/ArchiSteamFarm/BotConfig.cs index fcc774e8d..ee8fdee11 100644 --- a/ArchiSteamFarm/BotConfig.cs +++ b/ArchiSteamFarm/BotConfig.cs @@ -22,12 +22,12 @@ */ -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using Newtonsoft.Json; namespace ArchiSteamFarm { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] @@ -35,98 +35,14 @@ namespace ArchiSteamFarm { [SuppressMessage("ReSharper", "ConvertToConstant.Local")] [SuppressMessage("ReSharper", "ConvertToConstant.Global")] internal sealed class BotConfig { - internal enum EFarmingOrder : byte { - Unordered, - AppIDsAscending, - AppIDsDescending, - CardDropsAscending, - CardDropsDescending, - HoursAscending, - HoursDescending, - NamesAscending, - NamesDescending - } - - [Flags] - internal enum ETradingPreferences : byte { - [SuppressMessage("ReSharper", "UnusedMember.Global")] - None = 0, - AcceptDonations = 1, - SteamTradeMatcher = 2, - MatchEverything = 4 - } - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool Enabled = false; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool Paused = false; - - [JsonProperty] - internal string SteamLogin { get; set; } - - [JsonProperty] - internal string SteamPassword { get; set; } - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText; - - [JsonProperty] - internal string SteamParentalPIN { get; set; } = "0"; - - [JsonProperty] - internal readonly string SteamApiKey = null; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly ulong SteamMasterID = 0; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly ulong SteamMasterClanID = 0; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool CardDropsRestricted = true; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool DismissInventoryNotifications = true; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool FarmOffline = false; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool HandleOfflineMessages = false; + internal readonly byte AcceptConfirmationsPeriod = 0; [JsonProperty(Required = Required.DisallowNull)] internal readonly bool AcceptGifts = false; [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool IsBotAccount = false; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool ForwardKeysToOtherBots = false; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool DistributeKeys = false; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool ShutdownOnFarmingFinished = false; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool SendOnFarmingFinished = false; - - [JsonProperty] - internal readonly string SteamTradeToken = null; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly byte SendTradePeriod = 0; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly ETradingPreferences TradingPreferences = ETradingPreferences.AcceptDonations; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly byte AcceptConfirmationsPeriod = 0; + internal readonly bool CardDropsRestricted = true; [JsonProperty] internal readonly string CustomGamePlayedWhileFarming = null; @@ -134,9 +50,75 @@ namespace ArchiSteamFarm { [JsonProperty] internal readonly string CustomGamePlayedWhileIdle = null; + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool DismissInventoryNotifications = true; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool DistributeKeys = false; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool Enabled = false; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool FarmOffline = false; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool ForwardKeysToOtherBots = false; + [JsonProperty(Required = Required.DisallowNull)] internal readonly HashSet GamesPlayedWhileIdle = new HashSet(); + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool HandleOfflineMessages = false; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool IsBotAccount = false; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool Paused = false; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool SendOnFarmingFinished = false; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly byte SendTradePeriod = 0; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly bool ShutdownOnFarmingFinished = false; + + [JsonProperty] + internal readonly string SteamApiKey = null; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly ulong SteamMasterClanID = 0; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly ulong SteamMasterID = 0; + + [JsonProperty] + internal readonly string SteamTradeToken = null; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly ETradingPreferences TradingPreferences = ETradingPreferences.AcceptDonations; + + [JsonProperty] + internal string SteamLogin { get; set; } + + [JsonProperty] + internal string SteamParentalPIN { get; set; } = "0"; + + [JsonProperty] + internal string SteamPassword { get; set; } + + // This constructor is used only by deserializer + private BotConfig() { } + internal static BotConfig Load(string filePath) { if (string.IsNullOrEmpty(filePath)) { ASF.ArchiLogger.LogNullError(nameof(filePath)); @@ -182,7 +164,25 @@ namespace ArchiSteamFarm { return botConfig; } - // This constructor is used only by deserializer - private BotConfig() { } + internal enum EFarmingOrder : byte { + Unordered, + AppIDsAscending, + AppIDsDescending, + CardDropsAscending, + CardDropsDescending, + HoursAscending, + HoursDescending, + NamesAscending, + NamesDescending + } + + [Flags] + internal enum ETradingPreferences : byte { + [SuppressMessage("ReSharper", "UnusedMember.Global")] + None = 0, + AcceptDonations = 1, + SteamTradeMatcher = 2, + MatchEverything = 4 + } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/BotDatabase.cs b/ArchiSteamFarm/BotDatabase.cs index a9c642a9a..1eec758f4 100644 --- a/ArchiSteamFarm/BotDatabase.cs +++ b/ArchiSteamFarm/BotDatabase.cs @@ -22,21 +22,18 @@ */ -using Newtonsoft.Json; using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; +using Newtonsoft.Json; namespace ArchiSteamFarm { internal sealed class BotDatabase { - [JsonProperty] - private string _LoginKey; + private readonly object FileLock = new object(); internal string LoginKey { - get { - return _LoginKey; - } + get { return _LoginKey; } set { if (_LoginKey == value) { @@ -48,13 +45,8 @@ namespace ArchiSteamFarm { } } - [JsonProperty] - private MobileAuthenticator _MobileAuthenticator; - internal MobileAuthenticator MobileAuthenticator { - get { - return _MobileAuthenticator; - } + get { return _MobileAuthenticator; } set { if (_MobileAuthenticator == value) { @@ -66,10 +58,28 @@ namespace ArchiSteamFarm { } } - private readonly object FileLock = new object(); + [JsonProperty] + private string _LoginKey; + + [JsonProperty] + private MobileAuthenticator _MobileAuthenticator; private string FilePath; + // This constructor is used when creating new database + private BotDatabase(string filePath) { + if (string.IsNullOrEmpty(filePath)) { + throw new ArgumentNullException(nameof(filePath)); + } + + FilePath = filePath; + Save(); + } + + // This constructor is used only by deserializer + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private BotDatabase() { } + internal static BotDatabase Load(string filePath) { if (string.IsNullOrEmpty(filePath)) { ASF.ArchiLogger.LogNullError(nameof(filePath)); @@ -98,20 +108,6 @@ namespace ArchiSteamFarm { return botDatabase; } - // This constructor is used when creating new database - private BotDatabase(string filePath) { - if (string.IsNullOrEmpty(filePath)) { - throw new ArgumentNullException(nameof(filePath)); - } - - FilePath = filePath; - Save(); - } - - // This constructor is used only by deserializer - [SuppressMessage("ReSharper", "UnusedMember.Local")] - private BotDatabase() { } - internal void Save() { string json = JsonConvert.SerializeObject(this); if (string.IsNullOrEmpty(json)) { @@ -133,4 +129,4 @@ namespace ArchiSteamFarm { } } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/CardsFarmer.cs b/ArchiSteamFarm/CardsFarmer.cs index 189b4e1ab..0b86fdb70 100755 --- a/ArchiSteamFarm/CardsFarmer.cs +++ b/ArchiSteamFarm/CardsFarmer.cs @@ -22,7 +22,6 @@ */ -using HtmlAgilityPack; using System; using System.Collections.Generic; using System.Globalization; @@ -30,71 +29,30 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using HtmlAgilityPack; using Newtonsoft.Json; namespace ArchiSteamFarm { internal sealed class CardsFarmer : IDisposable { - internal sealed class Game { - [JsonProperty] - internal readonly uint AppID; - - [JsonProperty] - internal readonly string GameName; - - [JsonProperty] - internal float HoursPlayed { get; set; } - - [JsonProperty] - internal ushort CardsRemaining { get; set; } - - //internal string HeaderURL => "https://steamcdn-a.akamaihd.net/steam/apps/" + AppID + "/header.jpg"; - - internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining) { - if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) { - throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining)); - } - - AppID = appID; - GameName = gameName; - HoursPlayed = hoursPlayed; - CardsRemaining = cardsRemaining; - } - - public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) { - return false; - } - - if (ReferenceEquals(this, obj)) { - return true; - } - - Game game = obj as Game; - return (game != null) && Equals(game); - } - - public override int GetHashCode() => (int) AppID; - - private bool Equals(Game other) => AppID == other.AppID; - } - internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network - [JsonProperty] - internal readonly ConcurrentHashSet GamesToFarm = new ConcurrentHashSet(); - [JsonProperty] internal readonly ConcurrentHashSet CurrentGamesFarming = new ConcurrentHashSet(); - private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false); - private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1); + [JsonProperty] + internal readonly ConcurrentHashSet GamesToFarm = new ConcurrentHashSet(); + private readonly Bot Bot; + private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1); + private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false); private readonly Timer IdleFarmingTimer; [JsonProperty] internal bool Paused { get; private set; } - private bool KeepFarming, NowFarming, StickyPause; + private bool KeepFarming; + private bool NowFarming; + private bool StickyPause; internal CardsFarmer(Bot bot) { if (bot == null) { @@ -104,16 +62,50 @@ namespace ArchiSteamFarm { Bot = bot; if (Program.GlobalConfig.IdleFarmingPeriod > 0) { - IdleFarmingTimer = new Timer( - e => CheckGamesForFarming(), - null, - TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5 * Bot.Bots.Count), // Delay + IdleFarmingTimer = new Timer(e => CheckGamesForFarming(), null, TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5*Bot.Bots.Count), // Delay TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period ); } } - internal void SetInitialState(bool paused) => StickyPause = Paused = paused; + public void Dispose() { + // Those are objects that are always being created if constructor doesn't throw exception + CurrentGamesFarming.Dispose(); + GamesToFarm.Dispose(); + FarmingSemaphore.Dispose(); + FarmResetEvent.Dispose(); + + // Those are objects that might be null and the check should be in-place + IdleFarmingTimer?.Dispose(); + } + + internal void OnDisconnected() => StopFarming().Forget(); + + internal async Task OnNewGameAdded() { + if (!NowFarming) { + // If we're not farming yet, obviously it's worth it to make a check + StartFarming().Forget(); + return; + } + + if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < 2)) { + // If we have Complex algorithm and some games to boost, it's also worth to make a check + // That's because we would check for new games after our current round anyway + await StopFarming().ConfigureAwait(false); + StartFarming().Forget(); + } + } + + internal async Task OnNewItemsNotification() { + if (NowFarming) { + FarmResetEvent.Set(); + return; + } + + // If we're not farming, and we got new items, it's likely to be a booster pack or likewise + // In this case, perform a loot if user wants to do so + await Bot.LootIfNeeded().ConfigureAwait(false); + } internal async Task Pause(bool sticky) { if (sticky) { @@ -142,6 +134,8 @@ namespace ArchiSteamFarm { } } + internal void SetInitialState(bool paused) => StickyPause = Paused = paused; + internal async Task StartFarming() { if (NowFarming || Paused || !Bot.IsPlayingPossible) { return; @@ -257,117 +251,12 @@ namespace ArchiSteamFarm { } } - internal void OnDisconnected() => StopFarming().Forget(); - - internal async Task OnNewItemsNotification() { - if (NowFarming) { - FarmResetEvent.Set(); + private void CheckGamesForFarming() { + if (NowFarming || Paused || !Bot.IsConnectedAndLoggedOn) { return; } - // If we're not farming, and we got new items, it's likely to be a booster pack or likewise - // In this case, perform a loot if user wants to do so - await Bot.LootIfNeeded().ConfigureAwait(false); - } - - internal async Task OnNewGameAdded() { - if (!NowFarming) { - // If we're not farming yet, obviously it's worth it to make a check - StartFarming().Forget(); - return; - } - - if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < 2)) { - // If we have Complex algorithm and some games to boost, it's also worth to make a check - // That's because we would check for new games after our current round anyway - await StopFarming().ConfigureAwait(false); - StartFarming().Forget(); - } - } - - private async Task IsAnythingToFarm() { - Bot.ArchiLogger.LogGenericInfo("Checking badges..."); - - // Find the number of badge pages - Bot.ArchiLogger.LogGenericInfo("Checking first page..."); - HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false); - if (htmlDocument == null) { - Bot.ArchiLogger.LogGenericWarning("Could not get badges information, will try again later!"); - return false; - } - - byte maxPages = 1; - - HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("(//a[@class='pagelink'])[last()]"); - if (htmlNode != null) { - string lastPage = htmlNode.InnerText; - if (string.IsNullOrEmpty(lastPage)) { - Bot.ArchiLogger.LogNullError(nameof(lastPage)); - return false; - } - - if (!byte.TryParse(lastPage, out maxPages) || (maxPages == 0)) { - Bot.ArchiLogger.LogNullError(nameof(maxPages)); - return false; - } - } - - GamesToFarm.ClearAndTrim(); - CheckPage(htmlDocument); - - if (maxPages == 1) { - SortGamesToFarm(); - return GamesToFarm.Count > 0; - } - - Bot.ArchiLogger.LogGenericInfo("Checking other pages..."); - - List tasks = new List(maxPages - 1); - for (byte page = 2; page <= maxPages; page++) { - byte currentPage = page; // We need a copy of variable being passed when in for loops, as loop will proceed before task is launched - tasks.Add(CheckPage(currentPage)); - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - SortGamesToFarm(); - return GamesToFarm.Count > 0; - } - - private void SortGamesToFarm() { - IOrderedEnumerable gamesToFarm; - switch (Bot.BotConfig.FarmingOrder) { - case BotConfig.EFarmingOrder.Unordered: - return; - case BotConfig.EFarmingOrder.AppIDsAscending: - gamesToFarm = GamesToFarm.OrderBy(game => game.AppID); - break; - case BotConfig.EFarmingOrder.AppIDsDescending: - gamesToFarm = GamesToFarm.OrderByDescending(game => game.AppID); - break; - case BotConfig.EFarmingOrder.CardDropsAscending: - gamesToFarm = GamesToFarm.OrderBy(game => game.CardsRemaining); - break; - case BotConfig.EFarmingOrder.CardDropsDescending: - gamesToFarm = GamesToFarm.OrderByDescending(game => game.CardsRemaining); - break; - case BotConfig.EFarmingOrder.HoursAscending: - gamesToFarm = GamesToFarm.OrderBy(game => game.HoursPlayed); - break; - case BotConfig.EFarmingOrder.HoursDescending: - gamesToFarm = GamesToFarm.OrderByDescending(game => game.HoursPlayed); - break; - case BotConfig.EFarmingOrder.NamesAscending: - gamesToFarm = GamesToFarm.OrderBy(game => game.GameName); - break; - case BotConfig.EFarmingOrder.NamesDescending: - gamesToFarm = GamesToFarm.OrderByDescending(game => game.GameName); - break; - default: - Bot.ArchiLogger.LogGenericError("Unhandled case: " + Bot.BotConfig.FarmingOrder); - return; - } - - GamesToFarm.ReplaceWith(gamesToFarm.ToList()); // We must call ToList() here as we can't enumerate during replacing + StartFarming().Forget(); } private void CheckPage(HtmlDocument htmlDocument) { @@ -513,92 +402,6 @@ namespace ArchiSteamFarm { CheckPage(htmlDocument); } - private void CheckGamesForFarming() { - if (NowFarming || Paused || !Bot.IsConnectedAndLoggedOn) { - return; - } - - StartFarming().Forget(); - } - - private async Task ShouldFarm(Game game) { - if (game == null) { - Bot.ArchiLogger.LogNullError(nameof(game)); - return false; - } - - HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(game.AppID).ConfigureAwait(false); - if (htmlDocument == null) { - return null; - } - - HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']"); - if (htmlNode == null) { - Bot.ArchiLogger.LogNullError(nameof(htmlNode)); - return null; - } - - string progress = htmlNode.InnerText; - if (string.IsNullOrEmpty(progress)) { - Bot.ArchiLogger.LogNullError(nameof(progress)); - return null; - } - - ushort cardsRemaining = 0; - - Match match = Regex.Match(progress, @"\d+"); - if (match.Success) { - if (!ushort.TryParse(match.Value, out cardsRemaining)) { - Bot.ArchiLogger.LogNullError(nameof(cardsRemaining)); - return null; - } - } - - game.CardsRemaining = cardsRemaining; - - Bot.ArchiLogger.LogGenericInfo("Status for " + game.AppID + " (" + game.GameName + "): " + cardsRemaining + " cards remaining"); - return cardsRemaining > 0; - } - - private bool FarmMultiple(IEnumerable games) { - if (games == null) { - Bot.ArchiLogger.LogNullError(nameof(games)); - return false; - } - - CurrentGamesFarming.ReplaceWith(games); - - Bot.ArchiLogger.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming.Select(game => game.AppID))); - - bool result = FarmHours(CurrentGamesFarming); - CurrentGamesFarming.ClearAndTrim(); - return result; - } - - private async Task FarmSolo(Game game) { - if (game == null) { - Bot.ArchiLogger.LogNullError(nameof(game)); - return true; - } - - CurrentGamesFarming.Add(game); - - Bot.ArchiLogger.LogGenericInfo("Now farming: " + game.AppID + " (" + game.GameName + ")"); - - bool result = await Farm(game).ConfigureAwait(false); - CurrentGamesFarming.ClearAndTrim(); - - if (!result) { - return false; - } - - GamesToFarm.Remove(game); - - TimeSpan timeSpan = TimeSpan.FromHours(game.HoursPlayed); - Bot.ArchiLogger.LogGenericInfo("Done farming: " + game.AppID + " (" + game.GameName + ") after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!"); - return true; - } - private async Task Farm(Game game) { if (game == null) { Bot.ArchiLogger.LogNullError(nameof(game)); @@ -615,7 +418,7 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericInfo("Still farming: " + game.AppID + " (" + game.GameName + ")"); DateTime startFarmingPeriod = DateTime.Now; - if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { + if (FarmResetEvent.Wait(60*1000*Program.GlobalConfig.FarmingDelay)) { FarmResetEvent.Reset(); success = KeepFarming; } @@ -658,7 +461,7 @@ namespace ArchiSteamFarm { Bot.ArchiLogger.LogGenericInfo("Still farming: " + string.Join(", ", games.Select(game => game.AppID))); DateTime startFarmingPeriod = DateTime.Now; - if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { + if (FarmResetEvent.Wait(60*1000*Program.GlobalConfig.FarmingDelay)) { FarmResetEvent.Reset(); success = KeepFarming; } @@ -680,15 +483,211 @@ namespace ArchiSteamFarm { return success; } - public void Dispose() { - // Those are objects that are always being created if constructor doesn't throw exception - CurrentGamesFarming.Dispose(); - GamesToFarm.Dispose(); - FarmingSemaphore.Dispose(); - FarmResetEvent.Dispose(); + private bool FarmMultiple(IEnumerable games) { + if (games == null) { + Bot.ArchiLogger.LogNullError(nameof(games)); + return false; + } - // Those are objects that might be null and the check should be in-place - IdleFarmingTimer?.Dispose(); + CurrentGamesFarming.ReplaceWith(games); + + Bot.ArchiLogger.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming.Select(game => game.AppID))); + + bool result = FarmHours(CurrentGamesFarming); + CurrentGamesFarming.ClearAndTrim(); + return result; + } + + private async Task FarmSolo(Game game) { + if (game == null) { + Bot.ArchiLogger.LogNullError(nameof(game)); + return true; + } + + CurrentGamesFarming.Add(game); + + Bot.ArchiLogger.LogGenericInfo("Now farming: " + game.AppID + " (" + game.GameName + ")"); + + bool result = await Farm(game).ConfigureAwait(false); + CurrentGamesFarming.ClearAndTrim(); + + if (!result) { + return false; + } + + GamesToFarm.Remove(game); + + TimeSpan timeSpan = TimeSpan.FromHours(game.HoursPlayed); + Bot.ArchiLogger.LogGenericInfo("Done farming: " + game.AppID + " (" + game.GameName + ") after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!"); + return true; + } + + private async Task IsAnythingToFarm() { + Bot.ArchiLogger.LogGenericInfo("Checking badges..."); + + // Find the number of badge pages + Bot.ArchiLogger.LogGenericInfo("Checking first page..."); + HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false); + if (htmlDocument == null) { + Bot.ArchiLogger.LogGenericWarning("Could not get badges information, will try again later!"); + return false; + } + + byte maxPages = 1; + + HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("(//a[@class='pagelink'])[last()]"); + if (htmlNode != null) { + string lastPage = htmlNode.InnerText; + if (string.IsNullOrEmpty(lastPage)) { + Bot.ArchiLogger.LogNullError(nameof(lastPage)); + return false; + } + + if (!byte.TryParse(lastPage, out maxPages) || (maxPages == 0)) { + Bot.ArchiLogger.LogNullError(nameof(maxPages)); + return false; + } + } + + GamesToFarm.ClearAndTrim(); + CheckPage(htmlDocument); + + if (maxPages == 1) { + SortGamesToFarm(); + return GamesToFarm.Count > 0; + } + + Bot.ArchiLogger.LogGenericInfo("Checking other pages..."); + + List tasks = new List(maxPages - 1); + for (byte page = 2; page <= maxPages; page++) { + byte currentPage = page; // We need a copy of variable being passed when in for loops, as loop will proceed before task is launched + tasks.Add(CheckPage(currentPage)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + SortGamesToFarm(); + return GamesToFarm.Count > 0; + } + + private async Task ShouldFarm(Game game) { + if (game == null) { + Bot.ArchiLogger.LogNullError(nameof(game)); + return false; + } + + HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(game.AppID).ConfigureAwait(false); + if (htmlDocument == null) { + return null; + } + + HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']"); + if (htmlNode == null) { + Bot.ArchiLogger.LogNullError(nameof(htmlNode)); + return null; + } + + string progress = htmlNode.InnerText; + if (string.IsNullOrEmpty(progress)) { + Bot.ArchiLogger.LogNullError(nameof(progress)); + return null; + } + + ushort cardsRemaining = 0; + + Match match = Regex.Match(progress, @"\d+"); + if (match.Success) { + if (!ushort.TryParse(match.Value, out cardsRemaining)) { + Bot.ArchiLogger.LogNullError(nameof(cardsRemaining)); + return null; + } + } + + game.CardsRemaining = cardsRemaining; + + Bot.ArchiLogger.LogGenericInfo("Status for " + game.AppID + " (" + game.GameName + "): " + cardsRemaining + " cards remaining"); + return cardsRemaining > 0; + } + + private void SortGamesToFarm() { + IOrderedEnumerable gamesToFarm; + switch (Bot.BotConfig.FarmingOrder) { + case BotConfig.EFarmingOrder.Unordered: + return; + case BotConfig.EFarmingOrder.AppIDsAscending: + gamesToFarm = GamesToFarm.OrderBy(game => game.AppID); + break; + case BotConfig.EFarmingOrder.AppIDsDescending: + gamesToFarm = GamesToFarm.OrderByDescending(game => game.AppID); + break; + case BotConfig.EFarmingOrder.CardDropsAscending: + gamesToFarm = GamesToFarm.OrderBy(game => game.CardsRemaining); + break; + case BotConfig.EFarmingOrder.CardDropsDescending: + gamesToFarm = GamesToFarm.OrderByDescending(game => game.CardsRemaining); + break; + case BotConfig.EFarmingOrder.HoursAscending: + gamesToFarm = GamesToFarm.OrderBy(game => game.HoursPlayed); + break; + case BotConfig.EFarmingOrder.HoursDescending: + gamesToFarm = GamesToFarm.OrderByDescending(game => game.HoursPlayed); + break; + case BotConfig.EFarmingOrder.NamesAscending: + gamesToFarm = GamesToFarm.OrderBy(game => game.GameName); + break; + case BotConfig.EFarmingOrder.NamesDescending: + gamesToFarm = GamesToFarm.OrderByDescending(game => game.GameName); + break; + default: + Bot.ArchiLogger.LogGenericError("Unhandled case: " + Bot.BotConfig.FarmingOrder); + return; + } + + GamesToFarm.ReplaceWith(gamesToFarm.ToList()); // We must call ToList() here as we can't enumerate during replacing + } + + internal sealed class Game { + [JsonProperty] + internal readonly uint AppID; + + [JsonProperty] + internal readonly string GameName; + + [JsonProperty] + internal ushort CardsRemaining { get; set; } + + [JsonProperty] + internal float HoursPlayed { get; set; } + + //internal string HeaderURL => "https://steamcdn-a.akamaihd.net/steam/apps/" + AppID + "/header.jpg"; + + internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining) { + if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) { + throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining)); + } + + AppID = appID; + GameName = gameName; + HoursPlayed = hoursPlayed; + CardsRemaining = cardsRemaining; + } + + public override bool Equals(object obj) { + if (ReferenceEquals(null, obj)) { + return false; + } + + if (ReferenceEquals(this, obj)) { + return true; + } + + Game game = obj as Game; + return (game != null) && Equals(game); + } + + public override int GetHashCode() => (int) AppID; + + private bool Equals(Game other) => AppID == other.AppID; } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/ConcurrentEnumerator.cs b/ArchiSteamFarm/ConcurrentEnumerator.cs index a9e0268bc..cd27b4ef0 100644 --- a/ArchiSteamFarm/ConcurrentEnumerator.cs +++ b/ArchiSteamFarm/ConcurrentEnumerator.cs @@ -31,11 +31,11 @@ namespace ArchiSteamFarm { internal sealed class ConcurrentEnumerator : IEnumerator { public T Current => Enumerator.Current; - object IEnumerator.Current => Current; - private readonly IEnumerator Enumerator; private readonly ReaderWriterLockSlim Lock; + object IEnumerator.Current => Current; + internal ConcurrentEnumerator(ICollection collection, ReaderWriterLockSlim rwLock) { if ((collection == null) || (rwLock == null)) { throw new ArgumentNullException(nameof(collection) + " || " + nameof(rwLock)); @@ -47,9 +47,9 @@ namespace ArchiSteamFarm { Enumerator = collection.GetEnumerator(); } + public void Dispose() => Lock?.ExitReadLock(); + public bool MoveNext() => Enumerator.MoveNext(); public void Reset() => Enumerator.Reset(); - - public void Dispose() => Lock?.ExitReadLock(); } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/ConcurrentHashSet.cs b/ArchiSteamFarm/ConcurrentHashSet.cs index 1b327e76b..bb45334f6 100644 --- a/ArchiSteamFarm/ConcurrentHashSet.cs +++ b/ArchiSteamFarm/ConcurrentHashSet.cs @@ -29,15 +29,6 @@ using System.Threading; namespace ArchiSteamFarm { internal sealed class ConcurrentHashSet : ICollection, IDisposable { - private readonly HashSet HashSet = new HashSet(); - private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); - - public bool IsReadOnly => false; - public IEnumerator GetEnumerator() => new ConcurrentEnumerator(HashSet, Lock); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - void ICollection.Add(T item) => Add(item); - public int Count { get { Lock.EnterReadLock(); @@ -50,6 +41,11 @@ namespace ArchiSteamFarm { } } + public bool IsReadOnly => false; + + private readonly HashSet HashSet = new HashSet(); + private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(); + public void Clear() { Lock.EnterWriteLock(); @@ -70,18 +66,6 @@ namespace ArchiSteamFarm { } } - public bool Remove(T item) { - Lock.EnterWriteLock(); - - try { - return HashSet.Remove(item); - } finally { - Lock.ExitWriteLock(); - } - } - - public void Dispose() => Lock.Dispose(); - public void CopyTo(T[] array, int arrayIndex) { Lock.EnterReadLock(); @@ -92,6 +76,23 @@ namespace ArchiSteamFarm { } } + public void Dispose() => Lock.Dispose(); + public IEnumerator GetEnumerator() => new ConcurrentEnumerator(HashSet, Lock); + + public bool Remove(T item) { + Lock.EnterWriteLock(); + + try { + return HashSet.Remove(item); + } finally { + Lock.ExitWriteLock(); + } + } + + void ICollection.Add(T item) => Add(item); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + internal void Add(T item) { Lock.EnterWriteLock(); @@ -102,6 +103,17 @@ namespace ArchiSteamFarm { } } + internal void ClearAndTrim() { + Lock.EnterWriteLock(); + + try { + HashSet.Clear(); + HashSet.TrimExcess(); + } finally { + Lock.ExitWriteLock(); + } + } + internal bool ReplaceIfNeededWith(HashSet items) { Lock.EnterUpgradeableReadLock(); @@ -133,17 +145,6 @@ namespace ArchiSteamFarm { } } - internal void ClearAndTrim() { - Lock.EnterWriteLock(); - - try { - HashSet.Clear(); - HashSet.TrimExcess(); - } finally { - Lock.ExitWriteLock(); - } - } - internal void TrimExcess() { Lock.EnterWriteLock(); @@ -154,4 +155,4 @@ namespace ArchiSteamFarm { } } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/CryptoHelper.cs b/ArchiSteamFarm/CryptoHelper.cs index ec32fa60b..5a2ca1fb9 100644 --- a/ArchiSteamFarm/CryptoHelper.cs +++ b/ArchiSteamFarm/CryptoHelper.cs @@ -28,21 +28,24 @@ using System.Text; namespace ArchiSteamFarm { internal static class CryptoHelper { - internal enum ECryptoMethod : byte { - PlainText, - AES, - ProtectedDataForCurrentUser - } - private static byte[] EncryptionKey = Encoding.UTF8.GetBytes("ArchiSteamFarm"); - internal static void SetEncryptionKey(string key) { - if (string.IsNullOrEmpty(key)) { - ASF.ArchiLogger.LogNullError(nameof(key)); - return; + internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) { + if (string.IsNullOrEmpty(encrypted)) { + ASF.ArchiLogger.LogNullError(nameof(encrypted)); + return null; } - EncryptionKey = Encoding.UTF8.GetBytes(key); + switch (cryptoMethod) { + case ECryptoMethod.PlainText: + return encrypted; + case ECryptoMethod.AES: + return DecryptAES(encrypted); + case ECryptoMethod.ProtectedDataForCurrentUser: + return DecryptProtectedDataForCurrentUser(encrypted); + default: + return null; + } } internal static string Encrypt(ECryptoMethod cryptoMethod, string decrypted) { @@ -63,21 +66,50 @@ namespace ArchiSteamFarm { } } - internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) { + internal static void SetEncryptionKey(string key) { + if (string.IsNullOrEmpty(key)) { + ASF.ArchiLogger.LogNullError(nameof(key)); + return; + } + + EncryptionKey = Encoding.UTF8.GetBytes(key); + } + + private static string DecryptAES(string encrypted) { if (string.IsNullOrEmpty(encrypted)) { ASF.ArchiLogger.LogNullError(nameof(encrypted)); return null; } - switch (cryptoMethod) { - case ECryptoMethod.PlainText: - return encrypted; - case ECryptoMethod.AES: - return DecryptAES(encrypted); - case ECryptoMethod.ProtectedDataForCurrentUser: - return DecryptProtectedDataForCurrentUser(encrypted); - default: - return null; + try { + byte[] key; + using (SHA256Cng sha256 = new SHA256Cng()) { + key = sha256.ComputeHash(EncryptionKey); + } + + byte[] decryptedData = Convert.FromBase64String(encrypted); + decryptedData = SteamKit2.CryptoHelper.SymmetricDecrypt(decryptedData, key); + return Encoding.UTF8.GetString(decryptedData); + } catch (Exception e) { + ASF.ArchiLogger.LogGenericException(e); + return null; + } + } + + private static string DecryptProtectedDataForCurrentUser(string encrypted) { + if (string.IsNullOrEmpty(encrypted)) { + ASF.ArchiLogger.LogNullError(nameof(encrypted)); + return null; + } + + try { + byte[] decryptedData = ProtectedData.Unprotect(Convert.FromBase64String(encrypted), EncryptionKey, // This is used as salt only and it's fine that it's known + DataProtectionScope.CurrentUser); + + return Encoding.UTF8.GetString(decryptedData); + } catch (Exception e) { + ASF.ArchiLogger.LogGenericException(e); + return null; } } @@ -102,27 +134,6 @@ namespace ArchiSteamFarm { } } - private static string DecryptAES(string encrypted) { - if (string.IsNullOrEmpty(encrypted)) { - ASF.ArchiLogger.LogNullError(nameof(encrypted)); - return null; - } - - try { - byte[] key; - using (SHA256Cng sha256 = new SHA256Cng()) { - key = sha256.ComputeHash(EncryptionKey); - } - - byte[] decryptedData = Convert.FromBase64String(encrypted); - decryptedData = SteamKit2.CryptoHelper.SymmetricDecrypt(decryptedData, key); - return Encoding.UTF8.GetString(decryptedData); - } catch (Exception e) { - ASF.ArchiLogger.LogGenericException(e); - return null; - } - } - private static string EncryptProtectedDataForCurrentUser(string decrypted) { if (string.IsNullOrEmpty(decrypted)) { ASF.ArchiLogger.LogNullError(nameof(decrypted)); @@ -130,11 +141,8 @@ namespace ArchiSteamFarm { } try { - byte[] encryptedData = ProtectedData.Protect( - Encoding.UTF8.GetBytes(decrypted), - EncryptionKey, // This is used as salt only and it's fine that it's known - DataProtectionScope.CurrentUser - ); + byte[] encryptedData = ProtectedData.Protect(Encoding.UTF8.GetBytes(decrypted), EncryptionKey, // This is used as salt only and it's fine that it's known + DataProtectionScope.CurrentUser); return Convert.ToBase64String(encryptedData); } catch (Exception e) { @@ -143,24 +151,10 @@ namespace ArchiSteamFarm { } } - private static string DecryptProtectedDataForCurrentUser(string encrypted) { - if (string.IsNullOrEmpty(encrypted)) { - ASF.ArchiLogger.LogNullError(nameof(encrypted)); - return null; - } - - try { - byte[] decryptedData = ProtectedData.Unprotect( - Convert.FromBase64String(encrypted), - EncryptionKey, // This is used as salt only and it's fine that it's known - DataProtectionScope.CurrentUser - ); - - return Encoding.UTF8.GetString(decryptedData); - } catch (Exception e) { - ASF.ArchiLogger.LogGenericException(e); - return null; - } + internal enum ECryptoMethod : byte { + PlainText, + AES, + ProtectedDataForCurrentUser } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/Debugging.cs b/ArchiSteamFarm/Debugging.cs index af79f1d86..47b06f91d 100644 --- a/ArchiSteamFarm/Debugging.cs +++ b/ArchiSteamFarm/Debugging.cs @@ -22,8 +22,8 @@ */ -using SteamKit2; using System.Diagnostics.CodeAnalysis; +using SteamKit2; namespace ArchiSteamFarm { internal static class Debugging { @@ -46,4 +46,4 @@ namespace ArchiSteamFarm { } } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/Events.cs b/ArchiSteamFarm/Events.cs index 0c4ce5359..085220e26 100644 --- a/ArchiSteamFarm/Events.cs +++ b/ArchiSteamFarm/Events.cs @@ -28,9 +28,6 @@ using SteamKit2; namespace ArchiSteamFarm { internal static class Events { - internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) { - } - internal static async void OnBotShutdown() { if (Program.IsWCFRunning || Bot.Bots.Values.Any(bot => bot.KeepRunning)) { return; @@ -40,5 +37,7 @@ namespace ArchiSteamFarm { await Task.Delay(5000).ConfigureAwait(false); Program.Shutdown(); } + + internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) { } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/GlobalConfig.cs b/ArchiSteamFarm/GlobalConfig.cs index 3c2ae80e1..b19d4c6bb 100644 --- a/ArchiSteamFarm/GlobalConfig.cs +++ b/ArchiSteamFarm/GlobalConfig.cs @@ -22,94 +22,90 @@ */ -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net.Sockets; +using Newtonsoft.Json; namespace ArchiSteamFarm { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] [SuppressMessage("ReSharper", "ConvertToConstant.Global")] internal sealed class GlobalConfig { - [SuppressMessage("ReSharper", "UnusedMember.Global")] - internal enum EUpdateChannel : byte { - None, - Stable, - Experimental - } - internal const byte DefaultHttpTimeout = 60; - private const byte DefaultMaxFarmingTime = 10; private const byte DefaultFarmingDelay = 15; - private const ushort DefaultWCFPort = 1242; + private const byte DefaultMaxFarmingTime = 10; private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp; + private const ushort DefaultWCFPort = 1242; // This is hardcoded blacklist which should not be possible to change internal static readonly HashSet GlobalBlacklist = new HashSet { 267420, 303700, 335590, 368020, 425280, 480730 }; [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool Debug = false; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool Headless = false; + internal readonly bool AutoRestart = true; [JsonProperty(Required = Required.DisallowNull)] internal readonly bool AutoUpdates = true; [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool AutoRestart = true; + internal readonly HashSet Blacklist = new HashSet(GlobalBlacklist); [JsonProperty(Required = Required.DisallowNull)] - internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly ProtocolType SteamProtocol = DefaultSteamProtocol; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly ulong SteamOwnerID = 0; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly byte MaxFarmingTime = DefaultMaxFarmingTime; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly byte IdleFarmingPeriod = 3; + internal readonly bool Debug = false; [JsonProperty(Required = Required.DisallowNull)] internal readonly byte FarmingDelay = DefaultFarmingDelay; [JsonProperty(Required = Required.DisallowNull)] - internal readonly byte LoginLimiterDelay = 10; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly byte InventoryLimiterDelay = 3; + internal readonly bool ForceHttp = false; [JsonProperty(Required = Required.DisallowNull)] internal readonly byte GiftsLimiterDelay = 1; [JsonProperty(Required = Required.DisallowNull)] - internal readonly byte MaxTradeHoldDuration = 15; - - [JsonProperty(Required = Required.DisallowNull)] - internal readonly bool ForceHttp = false; + internal readonly bool Headless = false; [JsonProperty(Required = Required.DisallowNull)] internal readonly byte HttpTimeout = DefaultHttpTimeout; - [JsonProperty] - internal string WCFHostname { get; set; } = "localhost"; + [JsonProperty(Required = Required.DisallowNull)] + internal readonly byte IdleFarmingPeriod = 3; [JsonProperty(Required = Required.DisallowNull)] - internal readonly ushort WCFPort = DefaultWCFPort; + internal readonly byte InventoryLimiterDelay = 3; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly byte LoginLimiterDelay = 10; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly byte MaxFarmingTime = DefaultMaxFarmingTime; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly byte MaxTradeHoldDuration = 15; [JsonProperty(Required = Required.DisallowNull)] internal readonly bool Statistics = true; [JsonProperty(Required = Required.DisallowNull)] - internal readonly HashSet Blacklist = new HashSet(GlobalBlacklist); + internal readonly ulong SteamOwnerID = 0; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly ProtocolType SteamProtocol = DefaultSteamProtocol; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable; + + [JsonProperty(Required = Required.DisallowNull)] + internal readonly ushort WCFPort = DefaultWCFPort; + + [JsonProperty] + internal string WCFHostname { get; set; } = "localhost"; + + // This constructor is used only by deserializer + private GlobalConfig() { } internal static GlobalConfig Load(string filePath) { if (string.IsNullOrEmpty(filePath)) { @@ -171,7 +167,11 @@ namespace ArchiSteamFarm { return null; } - // This constructor is used only by deserializer - private GlobalConfig() { } + [SuppressMessage("ReSharper", "UnusedMember.Global")] + internal enum EUpdateChannel : byte { + None, + Stable, + Experimental + } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/GlobalDatabase.cs b/ArchiSteamFarm/GlobalDatabase.cs index 77d2b4790..80fe654fd 100644 --- a/ArchiSteamFarm/GlobalDatabase.cs +++ b/ArchiSteamFarm/GlobalDatabase.cs @@ -22,28 +22,23 @@ */ -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Threading; +using Newtonsoft.Json; namespace ArchiSteamFarm { internal sealed class GlobalDatabase : IDisposable { - private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings { - Converters = new List(2) { - new IPAddressConverter(), - new IPEndPointConverter() - } - }; + private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings { Converters = new List(2) { new IPAddressConverter(), new IPEndPointConverter() } }; [JsonProperty(Required = Required.DisallowNull)] - private uint _CellID; + internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider(); + + private readonly object FileLock = new object(); internal uint CellID { - get { - return _CellID; - } + get { return _CellID; } set { if ((value == 0) || (_CellID == value)) { return; @@ -55,12 +50,30 @@ namespace ArchiSteamFarm { } [JsonProperty(Required = Required.DisallowNull)] - internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider(); - - private readonly object FileLock = new object(); + private uint _CellID; private string FilePath; + // This constructor is used when creating new database + private GlobalDatabase(string filePath) : this() { + if (string.IsNullOrEmpty(filePath)) { + throw new ArgumentNullException(nameof(filePath)); + } + + FilePath = filePath; + Save(); + } + + // This constructor is used only by deserializer + private GlobalDatabase() { + ServerListProvider.ServerListUpdated += OnServerListUpdated; + } + + public void Dispose() { + ServerListProvider.ServerListUpdated -= OnServerListUpdated; + ServerListProvider.Dispose(); + } + internal static GlobalDatabase Load(string filePath) { if (string.IsNullOrEmpty(filePath)) { ASF.ArchiLogger.LogNullError(nameof(filePath)); @@ -89,26 +102,6 @@ namespace ArchiSteamFarm { return globalDatabase; } - public void Dispose() { - ServerListProvider.ServerListUpdated -= OnServerListUpdated; - ServerListProvider.Dispose(); - } - - // This constructor is used when creating new database - private GlobalDatabase(string filePath) : this() { - if (string.IsNullOrEmpty(filePath)) { - throw new ArgumentNullException(nameof(filePath)); - } - - FilePath = filePath; - Save(); - } - - // This constructor is used only by deserializer - private GlobalDatabase() { - ServerListProvider.ServerListUpdated += OnServerListUpdated; - } - private void OnServerListUpdated(object sender, EventArgs e) => Save(); private void Save() { @@ -132,4 +125,4 @@ namespace ArchiSteamFarm { } } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/IPAddressConverter.cs b/ArchiSteamFarm/IPAddressConverter.cs index 553ee0080..a35253c73 100644 --- a/ArchiSteamFarm/IPAddressConverter.cs +++ b/ArchiSteamFarm/IPAddressConverter.cs @@ -41,4 +41,4 @@ namespace ArchiSteamFarm { writer.WriteValue(ip.ToString()); } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/IPEndPointConverter.cs b/ArchiSteamFarm/IPEndPointConverter.cs index 041e57681..105a37947 100644 --- a/ArchiSteamFarm/IPEndPointConverter.cs +++ b/ArchiSteamFarm/IPEndPointConverter.cs @@ -48,4 +48,4 @@ namespace ArchiSteamFarm { writer.WriteEndObject(); } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/InMemoryServerListProvider.cs b/ArchiSteamFarm/InMemoryServerListProvider.cs index 6f9a101bd..ed911d624 100644 --- a/ArchiSteamFarm/InMemoryServerListProvider.cs +++ b/ArchiSteamFarm/InMemoryServerListProvider.cs @@ -34,8 +34,7 @@ namespace ArchiSteamFarm { [JsonProperty(Required = Required.DisallowNull)] private readonly ConcurrentHashSet Servers = new ConcurrentHashSet(); - internal event EventHandler ServerListUpdated; - + public void Dispose() => Servers.Dispose(); public Task> FetchServerListAsync() => Task.FromResult>(Servers); public Task UpdateServerListAsync(IEnumerable endPoints) { @@ -54,6 +53,6 @@ namespace ArchiSteamFarm { return Task.Delay(0); } - public void Dispose() => Servers.Dispose(); + internal event EventHandler ServerListUpdated; } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/JSON/GitHub.cs b/ArchiSteamFarm/JSON/GitHub.cs index f18c63657..7838c110e 100644 --- a/ArchiSteamFarm/JSON/GitHub.cs +++ b/ArchiSteamFarm/JSON/GitHub.cs @@ -32,11 +32,11 @@ namespace ArchiSteamFarm.JSON { internal sealed class ReleaseResponse { #pragma warning disable 649 internal sealed class Asset { - [JsonProperty(PropertyName = "name", Required = Required.Always)] - internal readonly string Name; - [JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)] internal readonly string DownloadURL; + + [JsonProperty(PropertyName = "name", Required = Required.Always)] + internal readonly string Name; } [JsonProperty(PropertyName = "tag_name", Required = Required.Always)] @@ -47,4 +47,4 @@ namespace ArchiSteamFarm.JSON { #pragma warning restore 649 } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/JSON/Steam.cs b/ArchiSteamFarm/JSON/Steam.cs index 45eb63ccb..b17a8c66e 100644 --- a/ArchiSteamFarm/JSON/Steam.cs +++ b/ArchiSteamFarm/JSON/Steam.cs @@ -33,137 +33,207 @@ using SteamKit2; namespace ArchiSteamFarm.JSON { internal static class Steam { + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] + internal sealed class ConfirmationDetails { +#pragma warning disable 649 + [JsonProperty(PropertyName = "success", Required = Required.Always)] + internal readonly bool Success; +#pragma warning restore 649 + internal ulong OtherSteamID64 { + get { + if (_OtherSteamID64 != 0) { + return _OtherSteamID64; + } + + if ((Type != EType.Trade) || (OtherSteamID3 == 0)) { + return 0; + } + + _OtherSteamID64 = new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual); + return _OtherSteamID64; + } + } + + internal ulong TradeOfferID { + get { + if (_TradeOfferID != 0) { + return _TradeOfferID; + } + + if ((Type != EType.Trade) || (HtmlDocument == null)) { + return 0; + } + + HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='tradeoffer']"); + if (htmlNode == null) { + ASF.ArchiLogger.LogNullError(nameof(htmlNode)); + return 0; + } + + string id = htmlNode.GetAttributeValue("id", null); + if (string.IsNullOrEmpty(id)) { + ASF.ArchiLogger.LogNullError(nameof(id)); + return 0; + } + + int index = id.IndexOf('_'); + if (index < 0) { + ASF.ArchiLogger.LogNullError(nameof(index)); + return 0; + } + + index++; + if (id.Length <= index) { + ASF.ArchiLogger.LogNullError(nameof(id.Length)); + return 0; + } + + id = id.Substring(index); + if (ulong.TryParse(id, out _TradeOfferID) && (_TradeOfferID != 0)) { + return _TradeOfferID; + } + + ASF.ArchiLogger.LogNullError(nameof(_TradeOfferID)); + return 0; + } + } + +#pragma warning disable 649 + [JsonProperty(PropertyName = "html", Required = Required.DisallowNull)] + private readonly string HTML; +#pragma warning restore 649 + private HtmlDocument HtmlDocument { + get { + if (_HtmlDocument != null) { + return _HtmlDocument; + } + + if (string.IsNullOrEmpty(HTML)) { + return null; + } + + _HtmlDocument = new HtmlDocument(); + _HtmlDocument.LoadHtml(WebUtility.HtmlDecode(HTML)); + return _HtmlDocument; + } + } + + private uint OtherSteamID3 { + get { + if (_OtherSteamID3 != 0) { + return _OtherSteamID3; + } + + if ((Type != EType.Trade) || (HtmlDocument == null)) { + return 0; + } + + HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//a/@data-miniprofile"); + if (htmlNode == null) { + ASF.ArchiLogger.LogNullError(nameof(htmlNode)); + return 0; + } + + string miniProfile = htmlNode.GetAttributeValue("data-miniprofile", null); + if (string.IsNullOrEmpty(miniProfile)) { + ASF.ArchiLogger.LogNullError(nameof(miniProfile)); + return 0; + } + + if (uint.TryParse(miniProfile, out _OtherSteamID3) && (_OtherSteamID3 != 0)) { + return _OtherSteamID3; + } + + ASF.ArchiLogger.LogNullError(nameof(_OtherSteamID3)); + return 0; + } + } + + private EType Type { + get { + if (_Type != EType.Unknown) { + return _Type; + } + + if (HtmlDocument == null) { + return EType.Unknown; + } + + HtmlNode testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_listing_prices']"); + if (testNode != null) { + _Type = EType.Market; + return _Type; + } + + testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_trade_area']"); + if (testNode != null) { + _Type = EType.Trade; + return _Type; + } + + _Type = EType.Other; + return _Type; + } + } + + internal MobileAuthenticator.Confirmation Confirmation { + get { return _Confirmation; } + + set { + if (value == null) { + ASF.ArchiLogger.LogNullError(nameof(value)); + return; + } + + _Confirmation = value; + } + } + + private MobileAuthenticator.Confirmation _Confirmation; + private HtmlDocument _HtmlDocument; + private uint _OtherSteamID3; + private ulong _OtherSteamID64; + private ulong _TradeOfferID; + private EType _Type; + private ConfirmationDetails() { } // Deserialized from JSON + + internal enum EType : byte { + Unknown, + Trade, + Market, + Other + } + } + + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] + internal sealed class ConfirmationResponse { // Deserialized from JSON +#pragma warning disable 649 + [JsonProperty(PropertyName = "success", Required = Required.Always)] + internal readonly bool Success; +#pragma warning restore 649 + + private ConfirmationResponse() { } + } + internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset | Deserialized from JSON (SteamCommunity) and constructed from code internal const ushort SteamAppID = 753; internal const byte SteamCommunityContextID = 6; - internal enum EType : byte { - Unknown, - - BoosterPack, - Coupon, - Gift, - SteamGems, - - Emoticon, - FoilTradingCard, - ProfileBackground, - TradingCard - } - + internal uint Amount { get; private set; } internal uint AppID { get; set; } - - [JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - private string AppIDString { - get { - return AppID.ToString(); - } - - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - return; - } - - uint appID; - if (!uint.TryParse(value, out appID) || (appID == 0)) { - ASF.ArchiLogger.LogNullError(nameof(appID)); - return; - } - - AppID = appID; - } - } - + internal ulong ClassID { get; private set; } internal ulong ContextID { get; set; } - - [JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - private string ContextIDString { - get { - return ContextID.ToString(); - } - - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - return; - } - - ulong contextID; - if (!ulong.TryParse(value, out contextID) || (contextID == 0)) { - ASF.ArchiLogger.LogNullError(nameof(contextID)); - return; - } - - ContextID = contextID; - } - } + internal uint RealAppID { get; set; } + internal EType Type { get; set; } private ulong AssetID; - [JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)] - private string AssetIDString { - get { - return AssetID.ToString(); - } - - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - return; - } - - ulong assetID; - if (!ulong.TryParse(value, out assetID) || (assetID == 0)) { - ASF.ArchiLogger.LogNullError(nameof(assetID)); - return; - } - - AssetID = assetID; - } - } - - [JsonProperty(PropertyName = "id", Required = Required.DisallowNull)] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - private string ID { - get { return AssetIDString; } - set { AssetIDString = value; } - } - - internal ulong ClassID { get; private set; } - - [JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)] - [SuppressMessage("ReSharper", "UnusedMember.Local")] - private string ClassIDString { - get { - return ClassID.ToString(); - } - - set { - if (string.IsNullOrEmpty(value)) { - ASF.ArchiLogger.LogNullError(nameof(value)); - return; - } - - ulong classID; - if (!ulong.TryParse(value, out classID) || (classID == 0)) { - return; - } - - ClassID = classID; - } - } - - internal uint Amount { get; private set; } - [JsonProperty(PropertyName = "amount", Required = Required.Always)] [SuppressMessage("ReSharper", "UnusedMember.Local")] private string AmountString { - get { - return Amount.ToString(); - } + get { return Amount.ToString(); } set { if (string.IsNullOrEmpty(value)) { @@ -181,8 +251,94 @@ namespace ArchiSteamFarm.JSON { } } - internal uint RealAppID { get; set; } - internal EType Type { get; set; } + [JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private string AppIDString { + get { return AppID.ToString(); } + + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + return; + } + + uint appID; + if (!uint.TryParse(value, out appID) || (appID == 0)) { + ASF.ArchiLogger.LogNullError(nameof(appID)); + return; + } + + AppID = appID; + } + } + + [JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)] + private string AssetIDString { + get { return AssetID.ToString(); } + + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + return; + } + + ulong assetID; + if (!ulong.TryParse(value, out assetID) || (assetID == 0)) { + ASF.ArchiLogger.LogNullError(nameof(assetID)); + return; + } + + AssetID = assetID; + } + } + + [JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private string ClassIDString { + get { return ClassID.ToString(); } + + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + return; + } + + ulong classID; + if (!ulong.TryParse(value, out classID) || (classID == 0)) { + return; + } + + ClassID = classID; + } + } + + [JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private string ContextIDString { + get { return ContextID.ToString(); } + + set { + if (string.IsNullOrEmpty(value)) { + ASF.ArchiLogger.LogNullError(nameof(value)); + return; + } + + ulong contextID; + if (!ulong.TryParse(value, out contextID) || (contextID == 0)) { + ASF.ArchiLogger.LogNullError(nameof(contextID)); + return; + } + + ContextID = contextID; + } + } + + [JsonProperty(PropertyName = "id", Required = Required.DisallowNull)] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private string ID { + get { return AssetIDString; } + set { AssetIDString = value; } + } // This constructor is used for constructing items in trades being received internal Item(uint appID, ulong contextID, ulong classID, uint amount, uint realAppID, EType type) { @@ -200,33 +356,39 @@ namespace ArchiSteamFarm.JSON { [SuppressMessage("ReSharper", "UnusedMember.Local")] private Item() { } + + internal enum EType : byte { + Unknown, + + BoosterPack, + Coupon, + Gift, + SteamGems, + + Emoticon, + FoilTradingCard, + ProfileBackground, + TradingCard + } } - internal sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer | Constructed from code - [SuppressMessage("ReSharper", "UnusedMember.Global")] - internal enum ETradeOfferState : byte { - Unknown, - Invalid, - Active, - Accepted, - Countered, - Expired, - Canceled, - Declined, - InvalidItems, - EmailPending, - EmailCanceled, - OnHold - } + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] + internal sealed class RedeemWalletResponse { // Deserialized from JSON +#pragma warning disable 649 + [JsonProperty(PropertyName = "detail", Required = Required.Always)] + internal readonly ArchiHandler.PurchaseResponseCallback.EPurchaseResult PurchaseResult; +#pragma warning restore 649 - internal readonly ulong TradeOfferID; - internal readonly ETradeOfferState State; + private RedeemWalletResponse() { } + } + + internal sealed class TradeOffer { internal readonly HashSet ItemsToGive = new HashSet(); internal readonly HashSet ItemsToReceive = new HashSet(); + internal readonly ETradeOfferState State; + internal readonly ulong TradeOfferID; - private readonly uint OtherSteamID3; - - private ulong _OtherSteamID64; internal ulong OtherSteamID64 { get { if (_OtherSteamID64 != 0) { @@ -243,6 +405,10 @@ namespace ArchiSteamFarm.JSON { } } + private readonly uint OtherSteamID3; + + private ulong _OtherSteamID64; + internal TradeOffer(ulong tradeOfferID, uint otherSteamID3, ETradeOfferState state) { if ((tradeOfferID == 0) || (otherSteamID3 == 0) || (state == ETradeOfferState.Unknown)) { throw new ArgumentNullException(nameof(tradeOfferID) + " || " + nameof(otherSteamID3) + " || " + nameof(state)); @@ -253,8 +419,6 @@ namespace ArchiSteamFarm.JSON { State = state; } - internal bool IsSteamCardsRequest() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamCommunityContextID) && (item.Type == Item.EType.TradingCard)); - internal bool IsFairTypesExchange() { Dictionary> itemsToGivePerGame = new Dictionary>(); foreach (Item item in ItemsToGive) { @@ -309,217 +473,38 @@ namespace ArchiSteamFarm.JSON { return true; } - } - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] - internal sealed class RedeemWalletResponse { // Deserialized from JSON -#pragma warning disable 649 - [JsonProperty(PropertyName = "detail", Required = Required.Always)] - internal readonly ArchiHandler.PurchaseResponseCallback.EPurchaseResult PurchaseResult; -#pragma warning restore 649 + internal bool IsSteamCardsRequest() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamCommunityContextID) && (item.Type == Item.EType.TradingCard)); // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer | Constructed from code - private RedeemWalletResponse() { } + [SuppressMessage("ReSharper", "UnusedMember.Global")] + internal enum ETradeOfferState : byte { + Unknown, + Invalid, + Active, + Accepted, + Countered, + Expired, + Canceled, + Declined, + InvalidItems, + EmailPending, + EmailCanceled, + OnHold + } } [SuppressMessage("ReSharper", "UnusedMember.Global")] - internal sealed class TradeOfferRequest { // Constructed from code - internal sealed class ItemList { - [JsonProperty(PropertyName = "assets", Required = Required.Always)] - internal readonly HashSet Assets = new HashSet(); - } - + internal sealed class TradeOfferRequest { [JsonProperty(PropertyName = "me", Required = Required.Always)] internal readonly ItemList ItemsToGive = new ItemList(); [JsonProperty(PropertyName = "them", Required = Required.Always)] - internal readonly ItemList ItemsToReceive = new ItemList(); - } + internal readonly ItemList ItemsToReceive = new ItemList(); // Constructed from code - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] - internal sealed class ConfirmationResponse { // Deserialized from JSON -#pragma warning disable 649 - [JsonProperty(PropertyName = "success", Required = Required.Always)] - internal readonly bool Success; -#pragma warning restore 649 - - private ConfirmationResponse() { } - } - - [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] - internal sealed class ConfirmationDetails { // Deserialized from JSON - internal enum EType : byte { - Unknown, - Trade, - Market, - Other + internal sealed class ItemList { + [JsonProperty(PropertyName = "assets", Required = Required.Always)] + internal readonly HashSet Assets = new HashSet(); } - - private MobileAuthenticator.Confirmation _Confirmation; - internal MobileAuthenticator.Confirmation Confirmation { - get { return _Confirmation; } - - set { - if (value == null) { - ASF.ArchiLogger.LogNullError(nameof(value)); - return; - } - - _Confirmation = value; - } - } - -#pragma warning disable 649 - [JsonProperty(PropertyName = "success", Required = Required.Always)] - internal readonly bool Success; -#pragma warning restore 649 - - private EType _Type; - private EType Type { - get { - if (_Type != EType.Unknown) { - return _Type; - } - - if (HtmlDocument == null) { - return EType.Unknown; - } - - HtmlNode testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_listing_prices']"); - if (testNode != null) { - _Type = EType.Market; - return _Type; - } - - testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_trade_area']"); - if (testNode != null) { - _Type = EType.Trade; - return _Type; - } - - _Type = EType.Other; - return _Type; - } - } - - private ulong _TradeOfferID; - internal ulong TradeOfferID { - get { - if (_TradeOfferID != 0) { - return _TradeOfferID; - } - - if ((Type != EType.Trade) || (HtmlDocument == null)) { - return 0; - } - - HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='tradeoffer']"); - if (htmlNode == null) { - ASF.ArchiLogger.LogNullError(nameof(htmlNode)); - return 0; - } - - string id = htmlNode.GetAttributeValue("id", null); - if (string.IsNullOrEmpty(id)) { - ASF.ArchiLogger.LogNullError(nameof(id)); - return 0; - } - - int index = id.IndexOf('_'); - if (index < 0) { - ASF.ArchiLogger.LogNullError(nameof(index)); - return 0; - } - - index++; - if (id.Length <= index) { - ASF.ArchiLogger.LogNullError(nameof(id.Length)); - return 0; - } - - id = id.Substring(index); - if (ulong.TryParse(id, out _TradeOfferID) && (_TradeOfferID != 0)) { - return _TradeOfferID; - } - - ASF.ArchiLogger.LogNullError(nameof(_TradeOfferID)); - return 0; - } - } - - private ulong _OtherSteamID64; - internal ulong OtherSteamID64 { - get { - if (_OtherSteamID64 != 0) { - return _OtherSteamID64; - } - - if ((Type != EType.Trade) || (OtherSteamID3 == 0)) { - return 0; - } - - _OtherSteamID64 = new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual); - return _OtherSteamID64; - } - } - -#pragma warning disable 649 - [JsonProperty(PropertyName = "html", Required = Required.DisallowNull)] - private readonly string HTML; -#pragma warning restore 649 - - private uint _OtherSteamID3; - private uint OtherSteamID3 { - get { - if (_OtherSteamID3 != 0) { - return _OtherSteamID3; - } - - if ((Type != EType.Trade) || (HtmlDocument == null)) { - return 0; - } - - HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//a/@data-miniprofile"); - if (htmlNode == null) { - ASF.ArchiLogger.LogNullError(nameof(htmlNode)); - return 0; - } - - string miniProfile = htmlNode.GetAttributeValue("data-miniprofile", null); - if (string.IsNullOrEmpty(miniProfile)) { - ASF.ArchiLogger.LogNullError(nameof(miniProfile)); - return 0; - } - - if (uint.TryParse(miniProfile, out _OtherSteamID3) && (_OtherSteamID3 != 0)) { - return _OtherSteamID3; - } - - ASF.ArchiLogger.LogNullError(nameof(_OtherSteamID3)); - return 0; - } - } - - private HtmlDocument _HtmlDocument; - private HtmlDocument HtmlDocument { - get { - if (_HtmlDocument != null) { - return _HtmlDocument; - } - - if (string.IsNullOrEmpty(HTML)) { - return null; - } - - _HtmlDocument = new HtmlDocument(); - _HtmlDocument.LoadHtml(WebUtility.HtmlDecode(HTML)); - return _HtmlDocument; - } - } - - private ConfirmationDetails() { } } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/Logging.cs b/ArchiSteamFarm/Logging.cs index f76f3390c..4edf0742a 100644 --- a/ArchiSteamFarm/Logging.cs +++ b/ArchiSteamFarm/Logging.cs @@ -29,9 +29,9 @@ using NLog.Targets; namespace ArchiSteamFarm { internal static class Logging { - private const string LayoutMessage = @"${logger}|${message}${onexception:inner= ${exception:format=toString,Data}}"; - private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${processname}-${processid}|${level:uppercase=true}|" + LayoutMessage; private const string EventLogLayout = LayoutMessage; + private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${processname}-${processid}|${level:uppercase=true}|" + LayoutMessage; + private const string LayoutMessage = @"${logger}|${message}${onexception:inner= ${exception:format=toString,Data}}"; private static readonly ConcurrentHashSet ConsoleLoggingRules = new ConcurrentHashSet(); @@ -47,29 +47,18 @@ namespace ArchiSteamFarm { LoggingConfiguration config = new LoggingConfiguration(); - ColoredConsoleTarget coloredConsoleTarget = new ColoredConsoleTarget("ColoredConsole") { - DetectConsoleAvailable = false, - Layout = GeneralLayout - }; + ColoredConsoleTarget coloredConsoleTarget = new ColoredConsoleTarget("ColoredConsole") { DetectConsoleAvailable = false, Layout = GeneralLayout }; config.AddTarget(coloredConsoleTarget); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, coloredConsoleTarget)); if (Program.IsRunningAsService) { - EventLogTarget eventLogTarget = new EventLogTarget("EventLog") { - Layout = EventLogLayout, - Log = SharedInfo.EventLog, - Source = SharedInfo.EventLogSource - }; + EventLogTarget eventLogTarget = new EventLogTarget("EventLog") { Layout = EventLogLayout, Log = SharedInfo.EventLog, Source = SharedInfo.EventLogSource }; config.AddTarget(eventLogTarget); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, eventLogTarget)); } else if (!Program.Mode.HasFlag(Program.EMode.Client) || Program.Mode.HasFlag(Program.EMode.Server)) { - FileTarget fileTarget = new FileTarget("File") { - DeleteOldFileOnStartup = true, - FileName = SharedInfo.LogFile, - Layout = GeneralLayout - }; + FileTarget fileTarget = new FileTarget("File") { DeleteOldFileOnStartup = true, FileName = SharedInfo.LogFile, Layout = GeneralLayout }; config.AddTarget(fileTarget); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); @@ -79,20 +68,6 @@ namespace ArchiSteamFarm { InitConsoleLoggers(); } - internal static void OnUserInputStart() { - IsWaitingForUserInput = true; - - if (ConsoleLoggingRules.Count == 0) { - return; - } - - foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) { - LogManager.Configuration.LoggingRules.Remove(consoleLoggingRule); - } - - LogManager.ReconfigExistingLoggers(); - } - internal static void OnUserInputEnd() { IsWaitingForUserInput = false; @@ -107,6 +82,20 @@ namespace ArchiSteamFarm { LogManager.ReconfigExistingLoggers(); } + internal static void OnUserInputStart() { + IsWaitingForUserInput = true; + + if (ConsoleLoggingRules.Count == 0) { + return; + } + + foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) { + LogManager.Configuration.LoggingRules.Remove(consoleLoggingRule); + } + + LogManager.ReconfigExistingLoggers(); + } + private static void InitConsoleLoggers() { ConsoleLoggingRules.Clear(); foreach (LoggingRule loggingRule in LogManager.Configuration.LoggingRules.Where(loggingRule => loggingRule.Targets.Any(target => target is ColoredConsoleTarget || target is ConsoleTarget))) { @@ -127,4 +116,4 @@ namespace ArchiSteamFarm { } } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/MobileAuthenticator.cs b/ArchiSteamFarm/MobileAuthenticator.cs index 5646d7007..6d41b609e 100644 --- a/ArchiSteamFarm/MobileAuthenticator.cs +++ b/ArchiSteamFarm/MobileAuthenticator.cs @@ -37,61 +37,26 @@ namespace ArchiSteamFarm { [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] internal sealed class MobileAuthenticator : IDisposable { - internal sealed class Confirmation { - internal readonly uint ID; - internal readonly ulong Key; - internal readonly Steam.ConfirmationDetails.EType Type; - - internal Confirmation(uint id, ulong key, Steam.ConfirmationDetails.EType type) { - if ((id == 0) || (key == 0) || (type == Steam.ConfirmationDetails.EType.Unknown)) { - throw new ArgumentNullException(nameof(id) + " || " + nameof(key) + " || " + nameof(type)); - } - - ID = id; - Key = key; - Type = type; - } - } - private const byte CodeDigits = 5; 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 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 short? SteamTimeDifference; + internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator + private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1); -#pragma warning disable 649 - [JsonProperty(PropertyName = "shared_secret", Required = Required.Always)] - private readonly string SharedSecret; - - [JsonProperty(PropertyName = "identity_secret", Required = Required.Always)] - private readonly string IdentitySecret; -#pragma warning restore 649 + private Bot Bot; [JsonProperty(PropertyName = "device_id")] private string DeviceID; - private Bot Bot; - - internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator - private MobileAuthenticator() { } - internal void Init(Bot bot) { - if (bot == null) { - throw new ArgumentNullException(nameof(bot)); - } - - Bot = bot; - } + public void Dispose() => ConfirmationsSemaphore.Dispose(); internal void CorrectDeviceID(string deviceID) { if (string.IsNullOrEmpty(deviceID)) { @@ -102,56 +67,14 @@ namespace ArchiSteamFarm { DeviceID = deviceID; } - internal async Task HandleConfirmations(HashSet confirmations, bool accept) { - if ((confirmations == null) || (confirmations.Count == 0)) { - Bot.ArchiLogger.LogNullError(nameof(confirmations)); - return false; + internal async Task GenerateToken() { + uint time = await GetSteamTime().ConfigureAwait(false); + if (time != 0) { + return GenerateTokenForTime(time); } - if (!HasCorrectDeviceID) { - Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!"); - return false; - } - - await ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false); - - try { - uint time = await GetSteamTime().ConfigureAwait(false); - if (time == 0) { - Bot.ArchiLogger.LogNullError(nameof(time)); - return false; - } - - string confirmationHash = GenerateConfirmationKey(time, "conf"); - if (string.IsNullOrEmpty(confirmationHash)) { - Bot.ArchiLogger.LogNullError(nameof(confirmationHash)); - return false; - } - - bool? result = await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false); - if (!result.HasValue) { // Request timed out - return false; - } - - if (result.Value) { // Request succeeded - return true; - } - - // Our multi request failed, this is almost always Steam fuckup that happens randomly - // In this case, we'll accept all pending confirmations one-by-one, synchronously (as Steam can't handle them in parallel) - // We totally ignore actual result returned by those calls, abort only if request timed out - - foreach (Confirmation confirmation in confirmations) { - bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false); - if (!confirmationResult.HasValue) { - return false; - } - } - - return true; - } finally { - ConfirmationsSemaphore.Release(); - } + Bot.ArchiLogger.LogNullError(nameof(time)); + return null; } internal async Task GetConfirmationDetails(Confirmation confirmation) { @@ -185,16 +108,6 @@ namespace ArchiSteamFarm { return response; } - internal async Task GenerateToken() { - uint time = await GetSteamTime().ConfigureAwait(false); - if (time != 0) { - return GenerateTokenForTime(time); - } - - Bot.ArchiLogger.LogNullError(nameof(time)); - return null; - } - internal async Task> GetConfirmations() { if (!HasCorrectDeviceID) { Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!"); @@ -271,68 +184,64 @@ namespace ArchiSteamFarm { return result; } - private async Task GetSteamTime() { - if (SteamTimeDifference.HasValue) { - return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault()); + internal async Task HandleConfirmations(HashSet confirmations, bool accept) { + if ((confirmations == null) || (confirmations.Count == 0)) { + Bot.ArchiLogger.LogNullError(nameof(confirmations)); + return false; } - await TimeSemaphore.WaitAsync().ConfigureAwait(false); + if (!HasCorrectDeviceID) { + Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!"); + return false; + } + + await ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false); try { - if (!SteamTimeDifference.HasValue) { - uint serverTime = Bot.ArchiWebHandler.GetServerTime(); - if (serverTime != 0) { - SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime()); + uint time = await GetSteamTime().ConfigureAwait(false); + if (time == 0) { + Bot.ArchiLogger.LogNullError(nameof(time)); + return false; + } + + string confirmationHash = GenerateConfirmationKey(time, "conf"); + if (string.IsNullOrEmpty(confirmationHash)) { + Bot.ArchiLogger.LogNullError(nameof(confirmationHash)); + return false; + } + + bool? result = await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false); + if (!result.HasValue) { // Request timed out + return false; + } + + if (result.Value) { // Request succeeded + return true; + } + + // Our multi request failed, this is almost always Steam fuckup that happens randomly + // In this case, we'll accept all pending confirmations one-by-one, synchronously (as Steam can't handle them in parallel) + // We totally ignore actual result returned by those calls, abort only if request timed out + + foreach (Confirmation confirmation in confirmations) { + bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false); + if (!confirmationResult.HasValue) { + return false; } } - } finally { - TimeSemaphore.Release(); - } - return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault()); + return true; + } finally { + ConfirmationsSemaphore.Release(); + } } - private string GenerateTokenForTime(uint time) { - if (time == 0) { - Bot.ArchiLogger.LogNullError(nameof(time)); - return null; + internal void Init(Bot bot) { + if (bot == null) { + throw new ArgumentNullException(nameof(bot)); } - byte[] sharedSecret = Convert.FromBase64String(SharedSecret); - - byte[] timeArray = BitConverter.GetBytes((long) time / CodeInterval); - if (BitConverter.IsLittleEndian) { - Array.Reverse(timeArray); - } - - byte[] hash; - using (HMACSHA1 hmac = new HMACSHA1(sharedSecret)) { - hash = hmac.ComputeHash(timeArray); - } - - // The last 4 bits of the mac say where the code starts - int start = hash[hash.Length - 1] & 0x0f; - - // Extract those 4 bytes - byte[] bytes = new byte[4]; - - Array.Copy(hash, start, bytes, 0, 4); - - if (BitConverter.IsLittleEndian) { - Array.Reverse(bytes); - } - - uint fullCode = BitConverter.ToUInt32(bytes, 0) & 0x7fffffff; - - // Build the alphanumeric code - StringBuilder code = new StringBuilder(); - - for (byte i = 0; i < CodeDigits; i++) { - code.Append(CodeCharacters[fullCode % CodeCharacters.Length]); - fullCode /= (uint) CodeCharacters.Length; - } - - return code.ToString(); + Bot = bot; } private string GenerateConfirmationKey(uint time, string tag = null) { @@ -368,6 +277,92 @@ namespace ArchiSteamFarm { return Convert.ToBase64String(hash); } - public void Dispose() => ConfirmationsSemaphore.Dispose(); + private string GenerateTokenForTime(uint time) { + if (time == 0) { + Bot.ArchiLogger.LogNullError(nameof(time)); + return null; + } + + byte[] sharedSecret = Convert.FromBase64String(SharedSecret); + + byte[] timeArray = BitConverter.GetBytes((long) time/CodeInterval); + if (BitConverter.IsLittleEndian) { + Array.Reverse(timeArray); + } + + byte[] hash; + using (HMACSHA1 hmac = new HMACSHA1(sharedSecret)) { + hash = hmac.ComputeHash(timeArray); + } + + // The last 4 bits of the mac say where the code starts + int start = hash[hash.Length - 1] & 0x0f; + + // Extract those 4 bytes + byte[] bytes = new byte[4]; + + Array.Copy(hash, start, bytes, 0, 4); + + if (BitConverter.IsLittleEndian) { + Array.Reverse(bytes); + } + + uint fullCode = BitConverter.ToUInt32(bytes, 0) & 0x7fffffff; + + // Build the alphanumeric code + StringBuilder code = new StringBuilder(); + + for (byte i = 0; i < CodeDigits; i++) { + code.Append(CodeCharacters[fullCode%CodeCharacters.Length]); + fullCode /= (uint) CodeCharacters.Length; + } + + return code.ToString(); + } + + private async Task GetSteamTime() { + if (SteamTimeDifference.HasValue) { + return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault()); + } + + await TimeSemaphore.WaitAsync().ConfigureAwait(false); + + try { + if (!SteamTimeDifference.HasValue) { + uint serverTime = Bot.ArchiWebHandler.GetServerTime(); + if (serverTime != 0) { + SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime()); + } + } + } finally { + TimeSemaphore.Release(); + } + + return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault()); + } + + internal sealed class Confirmation { + internal readonly uint ID; + internal readonly ulong Key; + internal readonly Steam.ConfirmationDetails.EType Type; + + internal Confirmation(uint id, ulong key, Steam.ConfirmationDetails.EType type) { + if ((id == 0) || (key == 0) || (type == Steam.ConfirmationDetails.EType.Unknown)) { + throw new ArgumentNullException(nameof(id) + " || " + nameof(key) + " || " + nameof(type)); + } + + ID = id; + Key = key; + Type = type; + } + } + +#pragma warning disable 649 + [JsonProperty(PropertyName = "shared_secret", Required = Required.Always)] + private readonly string SharedSecret; + + [JsonProperty(PropertyName = "identity_secret", Required = Required.Always)] + private readonly string IdentitySecret; +#pragma warning restore 649 } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/Program.cs b/ArchiSteamFarm/Program.cs index 7d8124eca..a35d6e773 100644 --- a/ArchiSteamFarm/Program.cs +++ b/ArchiSteamFarm/Program.cs @@ -31,50 +31,28 @@ using System.Reflection; using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; +using SteamKit2; namespace ArchiSteamFarm { internal static class Program { - [Flags] - internal enum EMode : byte { - Normal = 0, // Standard most common usage - Client = 1, // WCF client - Server = 2 // WCF server - } + internal static bool IsWCFRunning => WCF.IsServerRunning; + internal static GlobalConfig GlobalConfig { get; private set; } + internal static GlobalDatabase GlobalDatabase { get; private set; } + internal static bool IsRunningAsService { get; private set; } + internal static EMode Mode { get; private set; } = EMode.Normal; + internal static WebBrowser WebBrowser { get; private set; } private static readonly object ConsoleLock = new object(); private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false); private static readonly WCF WCF = new WCF(); - internal static bool IsRunningAsService { get; private set; } - internal static EMode Mode { get; private set; } = EMode.Normal; - internal static GlobalConfig GlobalConfig { get; private set; } - internal static GlobalDatabase GlobalDatabase { get; private set; } - internal static WebBrowser WebBrowser { get; private set; } - private static bool ShutdownSequenceInitialized; - internal static bool IsWCFRunning => WCF.IsServerRunning; - internal static void Exit(byte exitCode = 0) { Shutdown(); Environment.Exit(exitCode); } - internal static void Restart() { - if (!InitShutdownSequence()) { - return; - } - - try { - Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1))); - } catch (Exception e) { - ASF.ArchiLogger.LogGenericException(e); - } - - ShutdownResetEvent.Set(); - Environment.Exit(0); - } - internal static string GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) { if (userInputType == ASF.EUserInputType.Unknown) { return null; @@ -137,6 +115,21 @@ namespace ArchiSteamFarm { return !string.IsNullOrEmpty(result) ? result.Trim() : null; } + internal static void Restart() { + if (!InitShutdownSequence()) { + return; + } + + try { + Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1))); + } catch (Exception e) { + ASF.ArchiLogger.LogGenericException(e); + } + + ShutdownResetEvent.Set(); + Environment.Exit(0); + } + internal static void Shutdown() { if (!InitShutdownSequence()) { return; @@ -145,19 +138,72 @@ namespace ArchiSteamFarm { ShutdownResetEvent.Set(); } - private static bool InitShutdownSequence() { - if (ShutdownSequenceInitialized) { - return false; + private static void Init(string[] args) { + AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; + TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; + + string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + if (!string.IsNullOrEmpty(homeDirectory)) { + Directory.SetCurrentDirectory(homeDirectory); + + // Allow loading configs from source tree if it's a debug build + if (Debugging.IsDebugBuild) { + // Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up + for (byte i = 0; i < 4; i++) { + Directory.SetCurrentDirectory(".."); + if (Directory.Exists(SharedInfo.ConfigDirectory)) { + break; + } + } + + // If config directory doesn't exist after our adjustment, abort all of that + if (!Directory.Exists(SharedInfo.ConfigDirectory)) { + Directory.SetCurrentDirectory(homeDirectory); + } + } } - ShutdownSequenceInitialized = true; - - WCF.StopServer(); - foreach (Bot bot in Bot.Bots.Values) { - bot.Stop(); + // Parse pre-init args + if (args != null) { + ParsePreInitArgs(args); } - return true; + Logging.InitLoggers(); + ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version); + + if (!Runtime.IsRuntimeSupported) { + ASF.ArchiLogger.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!"); + Thread.Sleep(10000); + } + + InitServices(); + + // If debugging is on, we prepare debug directory prior to running + if (GlobalConfig.Debug) { + if (Directory.Exists(SharedInfo.DebugDirectory)) { + Directory.Delete(SharedInfo.DebugDirectory, true); + Thread.Sleep(1000); // Dirty workaround giving Windows some time to sync + } + + Directory.CreateDirectory(SharedInfo.DebugDirectory); + + DebugLog.AddListener(new Debugging.DebugListener()); + DebugLog.Enabled = true; + } + + // Parse post-init args + if (args != null) { + ParsePostInitArgs(args); + } + + // If we ran ASF as a client, we're done by now + if (Mode.HasFlag(EMode.Client) && !Mode.HasFlag(EMode.Server)) { + Exit(); + } + + ASF.CheckForUpdate().Wait(); + ASF.InitBots(); + ASF.InitFileWatcher(); } private static void InitServices() { @@ -186,30 +232,36 @@ namespace ArchiSteamFarm { WebBrowser = new WebBrowser(ASF.ArchiLogger); } - private static void ParsePreInitArgs(IEnumerable args) { - if (args == null) { - ASF.ArchiLogger.LogNullError(nameof(args)); - return; + private static bool InitShutdownSequence() { + if (ShutdownSequenceInitialized) { + return false; } - foreach (string arg in args) { - switch (arg) { - case "": - break; - case "--client": - Mode |= EMode.Client; - break; - case "--server": - Mode |= EMode.Server; - break; - default: - if (arg.StartsWith("--", StringComparison.Ordinal)) { - if (arg.StartsWith("--path=", StringComparison.Ordinal) && (arg.Length > 7)) { - Directory.SetCurrentDirectory(arg.Substring(7)); - } - } + ShutdownSequenceInitialized = true; - break; + WCF.StopServer(); + foreach (Bot bot in Bot.Bots.Values) { + bot.Stop(); + } + + return true; + } + + private static void Main(string[] args) { + if (Runtime.IsUserInteractive) { + // App + Init(args); + + // Wait for signal to shutdown + ShutdownResetEvent.Wait(); + + // We got a signal to shutdown + Exit(); + } else { + // Service + IsRunningAsService = true; + using (Service service = new Service()) { + ServiceBase.Run(service); } } } @@ -253,6 +305,34 @@ namespace ArchiSteamFarm { } } + private static void ParsePreInitArgs(IEnumerable args) { + if (args == null) { + ASF.ArchiLogger.LogNullError(nameof(args)); + return; + } + + foreach (string arg in args) { + switch (arg) { + case "": + break; + case "--client": + Mode |= EMode.Client; + break; + case "--server": + Mode |= EMode.Server; + break; + default: + if (arg.StartsWith("--", StringComparison.Ordinal)) { + if (arg.StartsWith("--path=", StringComparison.Ordinal) && (arg.Length > 7)) { + Directory.SetCurrentDirectory(arg.Substring(7)); + } + } + + break; + } + } + } + private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) { if (args?.ExceptionObject == null) { ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject)); @@ -271,92 +351,11 @@ namespace ArchiSteamFarm { ASF.ArchiLogger.LogFatalException(args.Exception); } - private static void Init(string[] args) { - AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; - TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; - - string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - if (!string.IsNullOrEmpty(homeDirectory)) { - Directory.SetCurrentDirectory(homeDirectory); - - // Allow loading configs from source tree if it's a debug build - if (Debugging.IsDebugBuild) { - - // Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up - for (byte i = 0; i < 4; i++) { - Directory.SetCurrentDirectory(".."); - if (Directory.Exists(SharedInfo.ConfigDirectory)) { - break; - } - } - - // If config directory doesn't exist after our adjustment, abort all of that - if (!Directory.Exists(SharedInfo.ConfigDirectory)) { - Directory.SetCurrentDirectory(homeDirectory); - } - } - } - - // Parse pre-init args - if (args != null) { - ParsePreInitArgs(args); - } - - Logging.InitLoggers(); - ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version); - - if (!Runtime.IsRuntimeSupported) { - ASF.ArchiLogger.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!"); - Thread.Sleep(10000); - } - - InitServices(); - - // If debugging is on, we prepare debug directory prior to running - if (GlobalConfig.Debug) { - if (Directory.Exists(SharedInfo.DebugDirectory)) { - Directory.Delete(SharedInfo.DebugDirectory, true); - Thread.Sleep(1000); // Dirty workaround giving Windows some time to sync - } - - Directory.CreateDirectory(SharedInfo.DebugDirectory); - - SteamKit2.DebugLog.AddListener(new Debugging.DebugListener()); - SteamKit2.DebugLog.Enabled = true; - } - - // Parse post-init args - if (args != null) { - ParsePostInitArgs(args); - } - - // If we ran ASF as a client, we're done by now - if (Mode.HasFlag(EMode.Client) && !Mode.HasFlag(EMode.Server)) { - Exit(); - } - - ASF.CheckForUpdate().Wait(); - ASF.InitBots(); - ASF.InitFileWatcher(); - } - - private static void Main(string[] args) { - if (Runtime.IsUserInteractive) { - // App - Init(args); - - // Wait for signal to shutdown - ShutdownResetEvent.Wait(); - - // We got a signal to shutdown - Exit(); - } else { - // Service - IsRunningAsService = true; - using (Service service = new Service()) { - ServiceBase.Run(service); - } - } + [Flags] + internal enum EMode : byte { + Normal = 0, // Standard most common usage + Client = 1, // WCF client + Server = 2 // WCF server } private sealed class Service : ServiceBase { @@ -373,5 +372,4 @@ namespace ArchiSteamFarm { protected override void OnStop() => Shutdown(); } } - -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/Properties/AssemblyInfo.cs b/ArchiSteamFarm/Properties/AssemblyInfo.cs index 5e5adcb67..33445158b 100644 --- a/ArchiSteamFarm/Properties/AssemblyInfo.cs +++ b/ArchiSteamFarm/Properties/AssemblyInfo.cs @@ -5,6 +5,7 @@ using ArchiSteamFarm; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. + [assembly: AssemblyTitle(SharedInfo.ServiceName)] [assembly: AssemblyDescription(SharedInfo.ServiceDescription)] [assembly: AssemblyConfiguration("")] @@ -17,9 +18,11 @@ using ArchiSteamFarm; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. + [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM + [assembly: Guid("35af7887-08b9-40e8-a5ea-797d8b60b30c")] // Version information for an assembly consists of the following four values: @@ -32,5 +35,6 @@ using ArchiSteamFarm; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] + [assembly: AssemblyVersion(SharedInfo.VersionNumber)] -[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)] +[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)] \ No newline at end of file diff --git a/ArchiSteamFarm/Runtime.cs b/ArchiSteamFarm/Runtime.cs index 283e66197..babf1579c 100644 --- a/ArchiSteamFarm/Runtime.cs +++ b/ArchiSteamFarm/Runtime.cs @@ -28,34 +28,8 @@ using Microsoft.Win32; namespace ArchiSteamFarm { internal static class Runtime { - private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime"); - internal static bool IsRunningOnMono => MonoRuntime != null; - private static bool? _IsUserInteractive; - internal static bool IsUserInteractive { - get { - if (_IsUserInteractive.HasValue) { - return _IsUserInteractive.Value; - } - - if (Environment.UserInteractive) { - _IsUserInteractive = true; - } else if (!IsRunningOnMono) { - // If it's non-Mono, we can trust the result - _IsUserInteractive = false; - } else { - // In Mono, Environment.UserInteractive is always false - // There is really no reliable way for now, so assume always being interactive - // Maybe in future I find out some awful hack or workaround that could be at least semi-reliable - _IsUserInteractive = true; - } - - return _IsUserInteractive.Value; - } - } - - private static bool? _IsRuntimeSupported; internal static bool IsRuntimeSupported { get { if (_IsRuntimeSupported.HasValue) { @@ -102,6 +76,69 @@ namespace ArchiSteamFarm { } } + internal static bool IsUserInteractive { + get { + if (_IsUserInteractive.HasValue) { + return _IsUserInteractive.Value; + } + + if (Environment.UserInteractive) { + _IsUserInteractive = true; + } else if (!IsRunningOnMono) { + // If it's non-Mono, we can trust the result + _IsUserInteractive = false; + } else { + // In Mono, Environment.UserInteractive is always false + // There is really no reliable way for now, so assume always being interactive + // Maybe in future I find out some awful hack or workaround that could be at least semi-reliable + _IsUserInteractive = true; + } + + return _IsUserInteractive.Value; + } + } + + private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime"); + + private static bool? _IsRuntimeSupported; + + private static bool? _IsUserInteractive; + + private static Version GetMonoVersion() { + if (MonoRuntime == null) { + ASF.ArchiLogger.LogNullError(nameof(MonoRuntime)); + return null; + } + + MethodInfo displayName = MonoRuntime.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); + if (displayName == null) { + ASF.ArchiLogger.LogNullError(nameof(displayName)); + return null; + } + + string versionString = (string) displayName.Invoke(null, null); + if (string.IsNullOrEmpty(versionString)) { + ASF.ArchiLogger.LogNullError(nameof(versionString)); + return null; + } + + int index = versionString.IndexOf(' '); + if (index <= 0) { + ASF.ArchiLogger.LogNullError(nameof(index)); + return null; + } + + versionString = versionString.Substring(0, index); + + Version version; + if (Version.TryParse(versionString, out version)) { + return version; + } + + ASF.ArchiLogger.LogNullError(nameof(version)); + return null; + } + private static Version GetNetVersion() { uint release; using (RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) { @@ -144,40 +181,5 @@ namespace ArchiSteamFarm { return release >= 378389 ? new Version(4, 5) : null; } - - private static Version GetMonoVersion() { - if (MonoRuntime == null) { - ASF.ArchiLogger.LogNullError(nameof(MonoRuntime)); - return null; - } - - MethodInfo displayName = MonoRuntime.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); - if (displayName == null) { - ASF.ArchiLogger.LogNullError(nameof(displayName)); - return null; - } - - string versionString = (string) displayName.Invoke(null, null); - if (string.IsNullOrEmpty(versionString)) { - ASF.ArchiLogger.LogNullError(nameof(versionString)); - return null; - } - - int index = versionString.IndexOf(' '); - if (index <= 0) { - ASF.ArchiLogger.LogNullError(nameof(index)); - return null; - } - - versionString = versionString.Substring(0, index); - - Version version; - if (Version.TryParse(versionString, out version)) { - return version; - } - - ASF.ArchiLogger.LogNullError(nameof(version)); - return null; - } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/SharedInfo.cs b/ArchiSteamFarm/SharedInfo.cs index c512b1813..82425dc5e 100644 --- a/ArchiSteamFarm/SharedInfo.cs +++ b/ArchiSteamFarm/SharedInfo.cs @@ -27,30 +27,24 @@ using System.Reflection; namespace ArchiSteamFarm { internal static class SharedInfo { - internal const string VersionNumber = "2.1.6.9"; - internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016"; - - internal const string GithubRepo = "JustArchi/ArchiSteamFarm"; - - internal const string ServiceName = "ArchiSteamFarm"; - internal const string ServiceDescription = "ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously."; - - internal const string EventLog = ServiceName; - internal const string EventLogSource = EventLog + "Logger"; - + internal const ulong ArchiSteamID = 76561198006963719; internal const string ASF = "ASF"; internal const string ASFDirectory = "ArchiSteamFarm"; - internal const string ConfigDirectory = "config"; - internal const string DebugDirectory = "debug"; - internal const string LogFile = "log.txt"; - - internal const ulong ArchiSteamID = 76561198006963719; internal const ulong ASFGroupSteamID = 103582791440160998; - + internal const string ConfigDirectory = "config"; + internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016"; + internal const string DebugDirectory = "debug"; + internal const string EventLog = ServiceName; + internal const string EventLogSource = EventLog + "Logger"; internal const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only + internal const string GithubRepo = "JustArchi/ArchiSteamFarm"; internal const string GlobalConfigFileName = ASF + ".json"; internal const string GlobalDatabaseFileName = ASF + ".db"; + internal const string LogFile = "log.txt"; + internal const string ServiceDescription = "ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously."; + internal const string ServiceName = "ArchiSteamFarm"; + internal const string VersionNumber = "2.1.6.9"; internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version; } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/Trading.cs b/ArchiSteamFarm/Trading.cs index 79a84ec7f..46a8573b8 100644 --- a/ArchiSteamFarm/Trading.cs +++ b/ArchiSteamFarm/Trading.cs @@ -31,28 +31,6 @@ using ArchiSteamFarm.JSON; namespace ArchiSteamFarm { internal sealed class Trading : IDisposable { - private sealed class ParseTradeResult { - internal enum EResult : byte { - Unknown, - AcceptedWithItemLose, - AcceptedWithoutItemLose, - RejectedTemporarily, - RejectedPermanently - } - - internal readonly ulong TradeID; - internal readonly EResult Result; - - internal ParseTradeResult(ulong tradeID, EResult result) { - if ((tradeID == 0) || (result == EResult.Unknown)) { - throw new ArgumentNullException(nameof(tradeID) + " || " + nameof(result)); - } - - TradeID = tradeID; - Result = result; - } - } - internal const byte MaxItemsPerTrade = 150; // This is due to limit on POST size in WebBrowser internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve @@ -64,14 +42,6 @@ namespace ArchiSteamFarm { private bool ParsingScheduled; - internal static async Task LimitInventoryRequestsAsync() { - await InventorySemaphore.WaitAsync().ConfigureAwait(false); - Task.Run(async () => { - await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false); - InventorySemaphore.Release(); - }).Forget(); - } - internal Trading(Bot bot) { if (bot == null) { throw new ArgumentNullException(nameof(bot)); @@ -85,8 +55,6 @@ namespace ArchiSteamFarm { TradesSemaphore.Dispose(); } - internal void OnDisconnected() => IgnoredTrades.ClearAndTrim(); - internal async Task CheckTrades() { // We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue // This way we can call this function as many times as needed e.g. because of Steam events @@ -111,6 +79,16 @@ namespace ArchiSteamFarm { } } + internal static async Task LimitInventoryRequestsAsync() { + await InventorySemaphore.WaitAsync().ConfigureAwait(false); + Task.Run(async () => { + await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay*1000).ConfigureAwait(false); + InventorySemaphore.Release(); + }).Forget(); + } + + internal void OnDisconnected() => IgnoredTrades.ClearAndTrim(); + private async Task ParseActiveTrades() { if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { return; @@ -321,5 +299,28 @@ namespace ArchiSteamFarm { // If not, we assume that the trade might be good for us in the future, unless we're bot account where we assume that inventory doesn't change return new ParseTradeResult(tradeOffer.TradeOfferID, difference > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : (Bot.BotConfig.IsBotAccount ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily)); } + + private sealed class ParseTradeResult { + internal readonly EResult Result; + + internal readonly ulong TradeID; + + internal ParseTradeResult(ulong tradeID, EResult result) { + if ((tradeID == 0) || (result == EResult.Unknown)) { + throw new ArgumentNullException(nameof(tradeID) + " || " + nameof(result)); + } + + TradeID = tradeID; + Result = result; + } + + internal enum EResult : byte { + Unknown, + AcceptedWithItemLose, + AcceptedWithoutItemLose, + RejectedTemporarily, + RejectedPermanently + } + } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/Utilities.cs b/ArchiSteamFarm/Utilities.cs index 31d09fa78..8abb4ce1f 100644 --- a/ArchiSteamFarm/Utilities.cs +++ b/ArchiSteamFarm/Utilities.cs @@ -55,4 +55,4 @@ namespace ArchiSteamFarm { internal static uint GetUnixTime() => (uint) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds; } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/WCF.cs b/ArchiSteamFarm/WCF.cs index 6124c8d44..00417686b 100644 --- a/ArchiSteamFarm/WCF.cs +++ b/ArchiSteamFarm/WCF.cs @@ -41,22 +41,18 @@ namespace ArchiSteamFarm { internal sealed class WCF : IWCF, IDisposable { private static string URL = "http://localhost:1242/ASF"; - private ServiceHost ServiceHost; - private Client Client; - internal bool IsServerRunning => ServiceHost != null; - internal static void Init() { - if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) { - Program.GlobalConfig.WCFHostname = Program.GetUserInput(ASF.EUserInputType.WCFHostname); - if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) { - return; - } - } + private Client Client; + private ServiceHost ServiceHost; - URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF"; + public void Dispose() { + StopClient(); + StopServer(); } + public string GetStatus() => Program.GlobalConfig.SteamOwnerID == 0 ? "{}" : Bot.GetAPIStatus(); + public string HandleCommand(string input) { if (string.IsNullOrEmpty(input)) { ASF.ArchiLogger.LogNullError(nameof(input)); @@ -79,11 +75,28 @@ namespace ArchiSteamFarm { return output; } - public string GetStatus() => Program.GlobalConfig.SteamOwnerID == 0 ? "{}" : Bot.GetAPIStatus(); + internal static void Init() { + if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) { + Program.GlobalConfig.WCFHostname = Program.GetUserInput(ASF.EUserInputType.WCFHostname); + if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) { + return; + } + } - public void Dispose() { - StopClient(); - StopServer(); + URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF"; + } + + internal string SendCommand(string input) { + if (string.IsNullOrEmpty(input)) { + ASF.ArchiLogger.LogNullError(nameof(input)); + return null; + } + + if (Client == null) { + Client = new Client(new BasicHttpBinding(), new EndpointAddress(URL)); + } + + return Client.HandleCommand(input); } internal void StartServer() { @@ -96,9 +109,7 @@ namespace ArchiSteamFarm { try { ServiceHost = new ServiceHost(typeof(WCF), new Uri(URL)); - ServiceHost.Description.Behaviors.Add(new ServiceMetadataBehavior { - HttpGetEnabled = true - }); + ServiceHost.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true }); ServiceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); ServiceHost.AddServiceEndpoint(typeof(IWCF), new BasicHttpBinding(), string.Empty); @@ -129,19 +140,6 @@ namespace ArchiSteamFarm { ServiceHost = null; } - internal string SendCommand(string input) { - if (string.IsNullOrEmpty(input)) { - ASF.ArchiLogger.LogNullError(nameof(input)); - return null; - } - - if (Client == null) { - Client = new Client(new BasicHttpBinding(), new EndpointAddress(URL)); - } - - return Client.HandleCommand(input); - } - private void StopClient() { if (Client == null) { return; @@ -172,4 +170,4 @@ namespace ArchiSteamFarm { } } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/WebBrowser.cs b/ArchiSteamFarm/WebBrowser.cs index ea7d658a0..eb627d216 100644 --- a/ArchiSteamFarm/WebBrowser.cs +++ b/ArchiSteamFarm/WebBrowser.cs @@ -22,15 +22,15 @@ */ -using HtmlAgilityPack; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; using System.Xml; +using HtmlAgilityPack; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace ArchiSteamFarm { internal sealed class WebBrowser { @@ -44,12 +44,27 @@ namespace ArchiSteamFarm { private readonly ArchiLogger ArchiLogger; private readonly HttpClient HttpClient; + internal WebBrowser(ArchiLogger archiLogger) { + if (archiLogger == null) { + throw new ArgumentNullException(nameof(archiLogger)); + } + + ArchiLogger = archiLogger; + + HttpClientHandler httpClientHandler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, CookieContainer = CookieContainer }; + + HttpClient = new HttpClient(httpClientHandler) { Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout) }; + + // Most web services expect that UserAgent is set, so we declare it globally + HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ArchiSteamFarm/" + SharedInfo.Version); + } + internal static void Init() { // Set max connection limit from default of 2 to desired value ServicePointManager.DefaultConnectionLimit = MaxConnections; // Set max idle time from default of 100 seconds (100 * 1000) to desired value - ServicePointManager.MaxServicePointIdleTime = MaxIdleTime * 1000; + ServicePointManager.MaxServicePointIdleTime = MaxIdleTime*1000; // Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets ServicePointManager.Expect100Continue = false; @@ -66,68 +81,6 @@ namespace ArchiSteamFarm { #endif } -#if !__MonoCS__ - private static void InitNonMonoBehaviour() => ServicePointManager.ReusePort = true; -#endif - - internal WebBrowser(ArchiLogger archiLogger) { - if (archiLogger == null) { - throw new ArgumentNullException(nameof(archiLogger)); - } - - ArchiLogger = archiLogger; - - HttpClientHandler httpClientHandler = new HttpClientHandler { - AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, - CookieContainer = CookieContainer - }; - - HttpClient = new HttpClient(httpClientHandler) { - Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout) - }; - - // Most web services expect that UserAgent is set, so we declare it globally - HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ArchiSteamFarm/" + SharedInfo.Version); - } - - internal async Task UrlHeadRetry(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); - return false; - } - - bool result = false; - for (byte i = 0; (i < MaxRetries) && !result; i++) { - result = await UrlHead(request, referer).ConfigureAwait(false); - } - - if (result) { - return true; - } - - ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries"); - return false; - } - - internal async Task UrlHeadToUriRetry(string request, string referer = null) { - if (string.IsNullOrEmpty(request)) { - ArchiLogger.LogNullError(nameof(request)); - return null; - } - - Uri result = null; - for (byte i = 0; (i < MaxRetries) && (result == null); i++) { - result = await UrlHeadToUri(request, referer).ConfigureAwait(false); - } - - if (result != null) { - return result; - } - - ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries"); - return null; - } - internal async Task UrlGetToBytesRetry(string request, string referer = null) { if (string.IsNullOrEmpty(request)) { ArchiLogger.LogNullError(nameof(request)); @@ -223,6 +176,44 @@ namespace ArchiSteamFarm { return null; } + internal async Task UrlHeadRetry(string request, string referer = null) { + if (string.IsNullOrEmpty(request)) { + ArchiLogger.LogNullError(nameof(request)); + return false; + } + + bool result = false; + for (byte i = 0; (i < MaxRetries) && !result; i++) { + result = await UrlHead(request, referer).ConfigureAwait(false); + } + + if (result) { + return true; + } + + ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries"); + return false; + } + + internal async Task UrlHeadToUriRetry(string request, string referer = null) { + if (string.IsNullOrEmpty(request)) { + ArchiLogger.LogNullError(nameof(request)); + return null; + } + + Uri result = null; + for (byte i = 0; (i < MaxRetries) && (result == null); i++) { + result = await UrlHeadToUri(request, referer).ConfigureAwait(false); + } + + if (result != null) { + return result; + } + + ArchiLogger.LogGenericWarning("Request failed even after " + MaxRetries + " tries"); + return null; + } + internal async Task UrlPostRetry(string request, ICollection> data = null, string referer = null) { if (string.IsNullOrEmpty(request)) { ArchiLogger.LogNullError(nameof(request)); @@ -277,6 +268,10 @@ namespace ArchiSteamFarm { } } +#if !__MonoCS__ + private static void InitNonMonoBehaviour() => ServicePointManager.ReusePort = true; +#endif + private async Task UrlGetToBytes(string request, string referer = null) { if (string.IsNullOrEmpty(request)) { ArchiLogger.LogNullError(nameof(request)); @@ -537,4 +532,4 @@ namespace ArchiSteamFarm { return null; } } -} +} \ No newline at end of file diff --git a/ArchiSteamFarm/config/ASF.json b/ArchiSteamFarm/config/ASF.json index 3354c3d6d..46ea6d8d1 100644 --- a/ArchiSteamFarm/config/ASF.json +++ b/ArchiSteamFarm/config/ASF.json @@ -1,29 +1,29 @@ { - "Debug": false, - "Headless": false, - "AutoUpdates": true, - "AutoRestart": true, - "UpdateChannel": 1, - "SteamProtocol": 6, - "SteamOwnerID": 0, - "MaxFarmingTime": 10, - "IdleFarmingPeriod": 3, - "FarmingDelay": 15, - "LoginLimiterDelay": 10, - "InventoryLimiterDelay": 3, - "GiftsLimiterDelay": 1, - "MaxTradeHoldDuration": 15, - "ForceHttp": false, - "HttpTimeout": 60, - "WCFHostname": "localhost", - "WCFPort": 1242, - "Statistics": true, - "Blacklist": [ - 267420, - 303700, - 335590, - 368020, - 425280, - 480730 - ] + "Debug": false, + "Headless": false, + "AutoUpdates": true, + "AutoRestart": true, + "UpdateChannel": 1, + "SteamProtocol": 6, + "SteamOwnerID": 0, + "MaxFarmingTime": 10, + "IdleFarmingPeriod": 3, + "FarmingDelay": 15, + "LoginLimiterDelay": 10, + "InventoryLimiterDelay": 3, + "GiftsLimiterDelay": 1, + "MaxTradeHoldDuration": 15, + "ForceHttp": false, + "HttpTimeout": 60, + "WCFHostname": "localhost", + "WCFPort": 1242, + "Statistics": true, + "Blacklist": [ + 267420, + 303700, + 335590, + 368020, + 425280, + 480730 + ] } \ No newline at end of file diff --git a/ArchiSteamFarm/config/example.json b/ArchiSteamFarm/config/example.json index 0f9ce2679..63617b797 100644 --- a/ArchiSteamFarm/config/example.json +++ b/ArchiSteamFarm/config/example.json @@ -1,29 +1,29 @@ { - "Enabled": false, - "Paused": false, - "SteamLogin": null, - "SteamPassword": null, - "PasswordFormat": 0, - "SteamParentalPIN": "0", - "SteamApiKey": null, - "SteamMasterID": 0, - "SteamMasterClanID": 0, - "CardDropsRestricted": true, - "DismissInventoryNotifications": true, - "FarmingOrder": 0, - "FarmOffline": false, - "HandleOfflineMessages": false, - "AcceptGifts": false, - "IsBotAccount": false, - "ForwardKeysToOtherBots": false, - "DistributeKeys": false, - "ShutdownOnFarmingFinished": false, - "SendOnFarmingFinished": false, - "SteamTradeToken": null, - "SendTradePeriod": 0, - "TradingPreferences": 1, - "AcceptConfirmationsPeriod": 0, - "CustomGamePlayedWhileFarming": null, - "CustomGamePlayedWhileIdle": null, - "GamesPlayedWhileIdle": [ ] + "Enabled": false, + "Paused": false, + "SteamLogin": null, + "SteamPassword": null, + "PasswordFormat": 0, + "SteamParentalPIN": "0", + "SteamApiKey": null, + "SteamMasterID": 0, + "SteamMasterClanID": 0, + "CardDropsRestricted": true, + "DismissInventoryNotifications": true, + "FarmingOrder": 0, + "FarmOffline": false, + "HandleOfflineMessages": false, + "AcceptGifts": false, + "IsBotAccount": false, + "ForwardKeysToOtherBots": false, + "DistributeKeys": false, + "ShutdownOnFarmingFinished": false, + "SendOnFarmingFinished": false, + "SteamTradeToken": null, + "SendTradePeriod": 0, + "TradingPreferences": 1, + "AcceptConfirmationsPeriod": 0, + "CustomGamePlayedWhileFarming": null, + "CustomGamePlayedWhileIdle": null, + "GamesPlayedWhileIdle": [] } \ No newline at end of file diff --git a/ArchiSteamFarm/config/minimal.json b/ArchiSteamFarm/config/minimal.json index a667c1b78..a75051327 100644 --- a/ArchiSteamFarm/config/minimal.json +++ b/ArchiSteamFarm/config/minimal.json @@ -1,5 +1,5 @@ { - "Enabled": false, - "SteamLogin": null, - "SteamPassword": null + "Enabled": false, + "SteamLogin": null, + "SteamPassword": null } \ No newline at end of file diff --git a/ArchiSteamFarm/packages.config b/ArchiSteamFarm/packages.config index c0225b2ce..d9ec4890a 100644 --- a/ArchiSteamFarm/packages.config +++ b/ArchiSteamFarm/packages.config @@ -1,4 +1,5 @@  + diff --git a/ConfigGenerator/ASFConfig.cs b/ConfigGenerator/ASFConfig.cs index 76198336e..7a27f6413 100644 --- a/ConfigGenerator/ASFConfig.cs +++ b/ConfigGenerator/ASFConfig.cs @@ -22,20 +22,20 @@ */ -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using ArchiSteamFarm; +using Newtonsoft.Json; namespace ConfigGenerator { internal abstract class ASFConfig { internal static readonly HashSet ASFConfigs = new HashSet(); - internal string FilePath { get; set; } - private readonly object FileLock = new object(); + internal string FilePath { get; set; } + protected ASFConfig() { ASFConfigs.Add(this); } @@ -48,16 +48,6 @@ namespace ConfigGenerator { FilePath = filePath; } - internal void Save() { - lock (FileLock) { - try { - File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented)); - } catch (Exception e) { - Logging.LogGenericException(e); - } - } - } - internal void Remove() { string queryPath = Path.GetFileNameWithoutExtension(FilePath); lock (FileLock) { @@ -92,5 +82,15 @@ namespace ConfigGenerator { FilePath = Path.Combine(SharedInfo.ConfigDirectory, botName + ".json"); } } + + internal void Save() { + lock (FileLock) { + try { + File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented)); + } catch (Exception e) { + Logging.LogGenericException(e); + } + } + } } -} +} \ No newline at end of file diff --git a/ConfigGenerator/App.config b/ConfigGenerator/App.config index bae5d6d81..efb4d02fd 100644 --- a/ConfigGenerator/App.config +++ b/ConfigGenerator/App.config @@ -1,6 +1,7 @@ - + + - - - - + + + + \ No newline at end of file diff --git a/ConfigGenerator/BotConfig.cs b/ConfigGenerator/BotConfig.cs index d98b9aeab..f71014f40 100644 --- a/ConfigGenerator/BotConfig.cs +++ b/ConfigGenerator/BotConfig.cs @@ -22,12 +22,13 @@ */ -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.Drawing.Design; using System.IO; +using Newtonsoft.Json; namespace ConfigGenerator { [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] @@ -35,120 +36,16 @@ namespace ConfigGenerator { [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")] internal sealed class BotConfig : ASFConfig { - internal enum ECryptoMethod : byte { - PlainText, - AES, - ProtectedDataForCurrentUser - } - - internal enum EFarmingOrder : byte { - Unordered, - AppIDsAscending, - AppIDsDescending, - CardDropsAscending, - CardDropsDescending, - HoursAscending, - HoursDescending, - NamesAscending, - NamesDescending - } - - [Flags] - internal enum ETradingPreferences : byte { - None = 0, - AcceptDonations = 1, - SteamTradeMatcher = 2, - MatchEverything = 4 - } - - [Category("\t\tCore")] - [JsonProperty(Required = Required.DisallowNull)] - public bool Enabled { get; set; } = false; - [Category("\tAdvanced")] [JsonProperty(Required = Required.DisallowNull)] - public bool Paused { get; set; } = false; - - [Category("\t\tCore")] - [JsonProperty] - public string SteamLogin { get; set; } = null; - - [Category("\t\tCore")] - [JsonProperty] - [PasswordPropertyText(true)] - public string SteamPassword { get; set; } = null; - - [Category("\tAccess")] - [JsonProperty(Required = Required.DisallowNull)] - public ECryptoMethod PasswordFormat { get; set; } = ECryptoMethod.PlainText; - - [Category("\tAccess")] - [JsonProperty] - public string SteamParentalPIN { get; set; } = "0"; - - [Category("\tAccess")] - [JsonProperty] - public string SteamApiKey { get; set; } = null; - - [Category("\tAccess")] - [JsonProperty(Required = Required.DisallowNull)] - public ulong SteamMasterID { get; set; } = 0; - - [Category("\tAccess")] - [JsonProperty(Required = Required.DisallowNull)] - public ulong SteamMasterClanID { get; set; } = 0; - - [Category("\tPerformance")] - [JsonProperty(Required = Required.DisallowNull)] - public bool CardDropsRestricted { get; set; } = true; - - [JsonProperty(Required = Required.DisallowNull)] - public bool DismissInventoryNotifications { get; set; } = true; - - [JsonProperty(Required = Required.DisallowNull)] - public EFarmingOrder FarmingOrder { get; set; } = EFarmingOrder.Unordered; - - [JsonProperty(Required = Required.DisallowNull)] - public bool FarmOffline { get; set; } = false; - - [Category("\tAdvanced")] - [JsonProperty(Required = Required.DisallowNull)] - public bool HandleOfflineMessages { get; set; } = false; + public byte AcceptConfirmationsPeriod { get; set; } = 0; [JsonProperty(Required = Required.DisallowNull)] public bool AcceptGifts { get; set; } = false; - [Category("\tAdvanced")] + [Category("\tPerformance")] [JsonProperty(Required = Required.DisallowNull)] - public bool IsBotAccount { get; set; } = false; - - [JsonProperty(Required = Required.DisallowNull)] - public bool ForwardKeysToOtherBots { get; set; } = false; - - [JsonProperty(Required = Required.DisallowNull)] - public bool DistributeKeys { get; set; } = false; - - [JsonProperty(Required = Required.DisallowNull)] - public bool ShutdownOnFarmingFinished { get; set; } = false; - - [JsonProperty(Required = Required.DisallowNull)] - public bool SendOnFarmingFinished { get; set; } = false; - - [Category("\tAccess")] - [JsonProperty] - public string SteamTradeToken { get; set; } = null; - - [JsonProperty(Required = Required.DisallowNull)] - public byte SendTradePeriod { get; set; } = 0; - - [Category("\tAdvanced")] - [Editor(typeof(FlagEnumUIEditor), typeof(System.Drawing.Design.UITypeEditor))] - [JsonProperty(Required = Required.DisallowNull)] - public ETradingPreferences TradingPreferences { get; set; } = ETradingPreferences.AcceptDonations; - - [Category("\tAdvanced")] - [JsonProperty(Required = Required.DisallowNull)] - public byte AcceptConfirmationsPeriod { get; set; } = 0; + public bool CardDropsRestricted { get; set; } = true; [JsonProperty] public string CustomGamePlayedWhileFarming { get; set; } = null; @@ -156,9 +53,98 @@ namespace ConfigGenerator { [JsonProperty] public string CustomGamePlayedWhileIdle { get; set; } = null; + [JsonProperty(Required = Required.DisallowNull)] + public bool DismissInventoryNotifications { get; set; } = true; + + [JsonProperty(Required = Required.DisallowNull)] + public bool DistributeKeys { get; set; } = false; + + [Category("\t\tCore")] + [JsonProperty(Required = Required.DisallowNull)] + public bool Enabled { get; set; } = false; + + [JsonProperty(Required = Required.DisallowNull)] + public EFarmingOrder FarmingOrder { get; set; } = EFarmingOrder.Unordered; + + [JsonProperty(Required = Required.DisallowNull)] + public bool FarmOffline { get; set; } = false; + + [JsonProperty(Required = Required.DisallowNull)] + public bool ForwardKeysToOtherBots { get; set; } = false; + [JsonProperty(Required = Required.DisallowNull)] public List GamesPlayedWhileIdle { get; set; } = new List(); + [Category("\tAdvanced")] + [JsonProperty(Required = Required.DisallowNull)] + public bool HandleOfflineMessages { get; set; } = false; + + [Category("\tAdvanced")] + [JsonProperty(Required = Required.DisallowNull)] + public bool IsBotAccount { get; set; } = false; + + [Category("\tAccess")] + [JsonProperty(Required = Required.DisallowNull)] + public ECryptoMethod PasswordFormat { get; set; } = ECryptoMethod.PlainText; + + [Category("\tAdvanced")] + [JsonProperty(Required = Required.DisallowNull)] + public bool Paused { get; set; } = false; + + [JsonProperty(Required = Required.DisallowNull)] + public bool SendOnFarmingFinished { get; set; } = false; + + [JsonProperty(Required = Required.DisallowNull)] + public byte SendTradePeriod { get; set; } = 0; + + [JsonProperty(Required = Required.DisallowNull)] + public bool ShutdownOnFarmingFinished { get; set; } = false; + + [Category("\tAccess")] + [JsonProperty] + public string SteamApiKey { get; set; } = null; + + [Category("\t\tCore")] + [JsonProperty] + public string SteamLogin { get; set; } = null; + + [Category("\tAccess")] + [JsonProperty(Required = Required.DisallowNull)] + public ulong SteamMasterClanID { get; set; } = 0; + + [Category("\tAccess")] + [JsonProperty(Required = Required.DisallowNull)] + public ulong SteamMasterID { get; set; } = 0; + + [Category("\tAccess")] + [JsonProperty] + public string SteamParentalPIN { get; set; } = "0"; + + [Category("\t\tCore")] + [JsonProperty] + [PasswordPropertyText(true)] + public string SteamPassword { get; set; } = null; + + [Category("\tAccess")] + [JsonProperty] + public string SteamTradeToken { get; set; } = null; + + [Category("\tAdvanced")] + [Editor(typeof(FlagEnumUiEditor), typeof(UITypeEditor))] + [JsonProperty(Required = Required.DisallowNull)] + public ETradingPreferences TradingPreferences { get; set; } = ETradingPreferences.AcceptDonations; + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private BotConfig() { } + + private BotConfig(string filePath) : base(filePath) { + if (string.IsNullOrEmpty(filePath)) { + throw new ArgumentNullException(nameof(filePath)); + } + + Save(); + } + internal static BotConfig Load(string filePath) { if (string.IsNullOrEmpty(filePath)) { Logging.LogNullError(nameof(filePath)); @@ -187,15 +173,30 @@ namespace ConfigGenerator { return botConfig; } - [SuppressMessage("ReSharper", "UnusedMember.Local")] - private BotConfig() { } + internal enum ECryptoMethod : byte { + PlainText, + AES, + ProtectedDataForCurrentUser + } - private BotConfig(string filePath) : base(filePath) { - if (string.IsNullOrEmpty(filePath)) { - throw new ArgumentNullException(nameof(filePath)); - } + internal enum EFarmingOrder : byte { + Unordered, + AppIDsAscending, + AppIDsDescending, + CardDropsAscending, + CardDropsDescending, + HoursAscending, + HoursDescending, + NamesAscending, + NamesDescending + } - Save(); + [Flags] + internal enum ETradingPreferences : byte { + None = 0, + AcceptDonations = 1, + SteamTradeMatcher = 2, + MatchEverything = 4 } } -} +} \ No newline at end of file diff --git a/ConfigGenerator/ConfigPage.cs b/ConfigGenerator/ConfigPage.cs index 43c4ee902..3b9eebf81 100644 --- a/ConfigGenerator/ConfigPage.cs +++ b/ConfigGenerator/ConfigPage.cs @@ -45,4 +45,4 @@ namespace ConfigGenerator { internal void RefreshText() => Text = Path.GetFileNameWithoutExtension(ASFConfig.FilePath); } -} +} \ No newline at end of file diff --git a/ConfigGenerator/Debugging.cs b/ConfigGenerator/Debugging.cs index bb56ec613..b32c9c97c 100644 --- a/ConfigGenerator/Debugging.cs +++ b/ConfigGenerator/Debugging.cs @@ -34,4 +34,4 @@ namespace ConfigGenerator { internal static readonly bool IsDebugBuild = false; #endif } -} +} \ No newline at end of file diff --git a/ConfigGenerator/DialogBox.cs b/ConfigGenerator/DialogBox.cs index 82ba258fc..5e249d761 100644 --- a/ConfigGenerator/DialogBox.cs +++ b/ConfigGenerator/DialogBox.cs @@ -36,43 +36,11 @@ namespace ConfigGenerator { return DialogResult.Abort; } - TextBox textBox = new TextBox { - Anchor = AnchorStyles.Right, - Bounds = new Rectangle(12, 36, 372, 20), - Width = 1000 - }; - - Button buttonOk = new Button { - Anchor = AnchorStyles.Bottom | AnchorStyles.Right, - Bounds = new Rectangle(228, 72, 75, 23), - DialogResult = DialogResult.OK, - Text = Resources.OK - }; - - Button buttonCancel = new Button { - Anchor = AnchorStyles.Bottom | AnchorStyles.Right, - Bounds = new Rectangle(309, 72, 75, 23), - DialogResult = DialogResult.Cancel, - Text = Resources.Cancel - }; - - Label label = new Label { - AutoSize = true, - Bounds = new Rectangle(9, 20, 372, 13), - Text = promptText - }; - - Form form = new Form { - AcceptButton = buttonOk, - CancelButton = buttonCancel, - ClientSize = new Size(Math.Max(300, label.Right + 10), 107), - Controls = { label, textBox, buttonOk, buttonCancel }, - FormBorderStyle = FormBorderStyle.FixedDialog, - MinimizeBox = false, - MaximizeBox = false, - StartPosition = FormStartPosition.CenterScreen, - Text = title - }; + TextBox textBox = new TextBox { Anchor = AnchorStyles.Right, Bounds = new Rectangle(12, 36, 372, 20), Width = 1000 }; + Button buttonOk = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(228, 72, 75, 23), DialogResult = DialogResult.OK, Text = Resources.OK }; + Button buttonCancel = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(309, 72, 75, 23), DialogResult = DialogResult.Cancel, Text = Resources.Cancel }; + Label label = new Label { AutoSize = true, Bounds = new Rectangle(9, 20, 372, 13), Text = promptText }; + Form form = new Form { AcceptButton = buttonOk, CancelButton = buttonCancel, ClientSize = new Size(Math.Max(300, label.Right + 10), 107), Controls = { label, textBox, buttonOk, buttonCancel }, FormBorderStyle = FormBorderStyle.FixedDialog, MinimizeBox = false, MaximizeBox = false, StartPosition = FormStartPosition.CenterScreen, Text = title }; DialogResult dialogResult = form.ShowDialog(); value = textBox.Text; @@ -85,40 +53,13 @@ namespace ConfigGenerator { return DialogResult.Abort; } - Button buttonYes = new Button { - Anchor = AnchorStyles.Bottom | AnchorStyles.Right, - Bounds = new Rectangle(228, 72, 75, 23), - DialogResult = DialogResult.Yes, - Text = Resources.Yes - }; - - Button buttonNo = new Button { - Anchor = AnchorStyles.Bottom | AnchorStyles.Right, - Bounds = new Rectangle(309, 72, 75, 23), - DialogResult = DialogResult.No, - Text = Resources.No - }; - - Label label = new Label { - AutoSize = true, - Bounds = new Rectangle(9, 20, 372, 13), - Text = promptText - }; - - Form form = new Form { - AcceptButton = buttonYes, - CancelButton = buttonNo, - ClientSize = new Size(Math.Max(300, label.Right + 10), 107), - Controls = { label, buttonYes, buttonNo }, - FormBorderStyle = FormBorderStyle.FixedDialog, - MinimizeBox = false, - MaximizeBox = false, - StartPosition = FormStartPosition.CenterScreen, - Text = title - }; + Button buttonYes = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(228, 72, 75, 23), DialogResult = DialogResult.Yes, Text = Resources.Yes }; + Button buttonNo = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(309, 72, 75, 23), DialogResult = DialogResult.No, Text = Resources.No }; + Label label = new Label { AutoSize = true, Bounds = new Rectangle(9, 20, 372, 13), Text = promptText }; + Form form = new Form { AcceptButton = buttonYes, CancelButton = buttonNo, ClientSize = new Size(Math.Max(300, label.Right + 10), 107), Controls = { label, buttonYes, buttonNo }, FormBorderStyle = FormBorderStyle.FixedDialog, MinimizeBox = false, MaximizeBox = false, StartPosition = FormStartPosition.CenterScreen, Text = title }; DialogResult dialogResult = form.ShowDialog(); return dialogResult; } } -} +} \ No newline at end of file diff --git a/ConfigGenerator/EnhancedPropertyGrid.cs b/ConfigGenerator/EnhancedPropertyGrid.cs index d345194a1..b7d5fe8d5 100644 --- a/ConfigGenerator/EnhancedPropertyGrid.cs +++ b/ConfigGenerator/EnhancedPropertyGrid.cs @@ -43,6 +43,16 @@ namespace ConfigGenerator { ToolbarVisible = false; } + protected override void OnGotFocus(EventArgs args) { + if (args == null) { + Logging.LogNullError(nameof(args)); + return; + } + + base.OnGotFocus(args); + ASFConfig.Save(); + } + protected override void OnPropertyValueChanged(PropertyValueChangedEventArgs args) { if (args == null) { Logging.LogNullError(nameof(args)); @@ -74,15 +84,5 @@ namespace ConfigGenerator { Tutorial.OnAction(Tutorial.EPhase.GlobalConfigReady); } } - - protected override void OnGotFocus(EventArgs args) { - if (args == null) { - Logging.LogNullError(nameof(args)); - return; - } - - base.OnGotFocus(args); - ASFConfig.Save(); - } } -} +} \ No newline at end of file diff --git a/ConfigGenerator/FlagEnumEditor.cs b/ConfigGenerator/FlagEnumEditor.cs index e2096f2b5..93a8ddd75 100644 --- a/ConfigGenerator/FlagEnumEditor.cs +++ b/ConfigGenerator/FlagEnumEditor.cs @@ -7,28 +7,31 @@ using System.Windows.Forms.Design; namespace ConfigGenerator { internal sealed class FlagCheckedListBox : CheckedListBox { + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + internal Enum EnumValue { + get { + object e = Enum.ToObject(EnumType, GetCurrentValue()); + return (Enum) e; + } + + set { + Items.Clear(); + _EnumValue = value; // Store the current enum value + EnumType = value.GetType(); // Store enum type + FillEnumMembers(); // Add items for enum members + ApplyEnumValue(); // Check/uncheck items depending on enum value + } + } + + private Enum _EnumValue; + private Type EnumType; + private bool IsUpdatingCheckStates; internal FlagCheckedListBox() { // This call is required by the Windows.Forms Form Designer. InitializeComponent(); } - #region Component Designer generated code - private void InitializeComponent() { - // - // FlaggedCheckedListBox - // - CheckOnClick = true; - - } - #endregion - - // Adds an integer value and its associated description - private void Add(int v, string c) { - FlagCheckedListBoxItem item = new FlagCheckedListBoxItem(v, c); - Items.Add(item); - } - protected override void OnItemCheck(ItemCheckEventArgs e) { base.OnItemCheck(e); @@ -42,9 +45,44 @@ namespace ConfigGenerator { UpdateCheckedItems(item, e.NewValue); } + // Adds an integer value and its associated description + private void Add(int v, string c) { + FlagCheckedListBoxItem item = new FlagCheckedListBoxItem(v, c); + Items.Add(item); + } + + // Checks/unchecks items based on the current value of the enum variable + private void ApplyEnumValue() { + int intVal = (int) Convert.ChangeType(_EnumValue, typeof(int)); + UpdateCheckedItems(intVal); + } + + // Adds items to the checklistbox based on the members of the enum + private void FillEnumMembers() { + foreach (string name in Enum.GetNames(EnumType)) { + object val = Enum.Parse(EnumType, name); + int intVal = (int) Convert.ChangeType(val, typeof(int)); + + Add(intVal, name); + } + } + + // Gets the current bit value corresponding to all checked items + private int GetCurrentValue() => (from object t in Items select t as FlagCheckedListBoxItem).Where((item, i) => (item != null) && GetItemChecked(i)).Aggregate(0, (current, item) => current | item.Value); + + #region Component Designer generated code + + private void InitializeComponent() { + // + // FlaggedCheckedListBox + // + CheckOnClick = true; + } + + #endregion + // Checks/Unchecks items depending on the give bitvalue private void UpdateCheckedItems(int value) { - IsUpdatingCheckStates = true; // Iterate over all items @@ -57,7 +95,6 @@ namespace ConfigGenerator { if (item.Value == 0) { SetItemChecked(i, value == 0); } else { - // If the bit for the current item is on in the bitvalue, check it if (((item.Value & value) == item.Value) && (item.Value != 0)) { SetItemChecked(i, true); @@ -70,20 +107,17 @@ namespace ConfigGenerator { } IsUpdatingCheckStates = false; - } // Updates items in the checklistbox // composite = The item that was checked/unchecked // cs = The check state of that item private void UpdateCheckedItems(FlagCheckedListBoxItem composite, CheckState cs) { - // If the value of the item is 0, call directly. if (composite.Value == 0) { UpdateCheckedItems(0); } - // Get the total value of all checked items int sum = (from object t in Items select t as FlagCheckedListBoxItem).Where((item, i) => (item != null) && GetItemChecked(i)).Aggregate(0, (current, item) => current | item.Value); @@ -98,53 +132,7 @@ namespace ConfigGenerator { // Update all items in the checklistbox based on the final bit value UpdateCheckedItems(sum); - } - - private bool IsUpdatingCheckStates; - - // Gets the current bit value corresponding to all checked items - private int GetCurrentValue() => (from object t in Items select t as FlagCheckedListBoxItem).Where((item, i) => (item != null) && GetItemChecked(i)).Aggregate(0, (current, item) => current | item.Value); - - private Type EnumType; - private Enum _EnumValue; - - // Adds items to the checklistbox based on the members of the enum - private void FillEnumMembers() { - foreach (string name in Enum.GetNames(EnumType)) { - object val = Enum.Parse(EnumType, name); - int intVal = (int) Convert.ChangeType(val, typeof(int)); - - Add(intVal, name); - } - } - - // Checks/unchecks items based on the current value of the enum variable - private void ApplyEnumValue() { - int intVal = (int) Convert.ChangeType(_EnumValue, typeof(int)); - UpdateCheckedItems(intVal); - - } - - [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - internal Enum EnumValue { - get { - object e = Enum.ToObject(EnumType, GetCurrentValue()); - return (Enum) e; - } - - set { - - Items.Clear(); - _EnumValue = value; // Store the current enum value - EnumType = value.GetType(); // Store enum type - FillEnumMembers(); // Add items for enum members - ApplyEnumValue(); // Check/uncheck items depending on enum value - - } - } - - } // Represents an item in the checklistbox @@ -161,13 +149,12 @@ namespace ConfigGenerator { public override string ToString() => Caption; } - // UITypeEditor for flag enums - internal sealed class FlagEnumUIEditor : UITypeEditor { + internal sealed class FlagEnumUiEditor : UITypeEditor { // The checklistbox private readonly FlagCheckedListBox FlagEnumCb; - internal FlagEnumUIEditor() { + internal FlagEnumUiEditor() { FlagEnumCb = new FlagCheckedListBox { BorderStyle = BorderStyle.None }; } @@ -190,4 +177,4 @@ namespace ConfigGenerator { public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.DropDown; } -} +} \ No newline at end of file diff --git a/ConfigGenerator/FodyWeavers.xml b/ConfigGenerator/FodyWeavers.xml index 5df39cbf9..c4626742c 100644 --- a/ConfigGenerator/FodyWeavers.xml +++ b/ConfigGenerator/FodyWeavers.xml @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/ConfigGenerator/GlobalConfig.cs b/ConfigGenerator/GlobalConfig.cs index 998c5da10..f009d9a54 100644 --- a/ConfigGenerator/GlobalConfig.cs +++ b/ConfigGenerator/GlobalConfig.cs @@ -22,13 +22,13 @@ */ -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net.Sockets; +using Newtonsoft.Json; namespace ConfigGenerator { [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] @@ -36,52 +36,49 @@ namespace ConfigGenerator { [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")] internal sealed class GlobalConfig : ASFConfig { - internal enum EUpdateChannel : byte { - None, - Stable, - Experimental - } - - private const byte DefaultMaxFarmingTime = 10; private const byte DefaultFarmingDelay = 15; private const byte DefaultHttpTimeout = 60; - private const ushort DefaultWCFPort = 1242; + private const byte DefaultMaxFarmingTime = 10; private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp; + private const ushort DefaultWCFPort = 1242; // This is hardcoded blacklist which should not be possible to change private static readonly HashSet GlobalBlacklist = new HashSet { 267420, 303700, 335590, 368020, 425280, 480730 }; - [Category("\tDebugging")] - [JsonProperty(Required = Required.DisallowNull)] - public bool Debug { get; set; } = false; - - [Category("\tAdvanced")] - [JsonProperty(Required = Required.DisallowNull)] - public bool Headless { get; set; } = false; - - [Category("\tUpdates")] - [JsonProperty(Required = Required.DisallowNull)] - public bool AutoUpdates { get; set; } = true; - [Category("\tUpdates")] [JsonProperty(Required = Required.DisallowNull)] public bool AutoRestart { get; set; } = true; [Category("\tUpdates")] [JsonProperty(Required = Required.DisallowNull)] - public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable; + public bool AutoUpdates { get; set; } = true; - [Category("\tAdvanced")] [JsonProperty(Required = Required.DisallowNull)] - public ProtocolType SteamProtocol { get; set; } = DefaultSteamProtocol; + public List Blacklist { get; set; } = new List(); - [Category("\tAccess")] + [Category("\tDebugging")] [JsonProperty(Required = Required.DisallowNull)] - public ulong SteamOwnerID { get; set; } = 0; + public bool Debug { get; set; } = false; [Category("\tPerformance")] [JsonProperty(Required = Required.DisallowNull)] - public byte MaxFarmingTime { get; set; } = DefaultMaxFarmingTime; + public byte FarmingDelay { get; set; } = DefaultFarmingDelay; + + [Category("\tDebugging")] + [JsonProperty(Required = Required.DisallowNull)] + public bool ForceHttp { get; set; } = false; + + [Category("\tPerformance")] + [JsonProperty(Required = Required.DisallowNull)] + public byte GiftsLimiterDelay { get; set; } = 1; + + [Category("\tAdvanced")] + [JsonProperty(Required = Required.DisallowNull)] + public bool Headless { get; set; } = false; + + [Category("\tDebugging")] + [JsonProperty(Required = Required.DisallowNull)] + public byte HttpTimeout { get; set; } = DefaultHttpTimeout; [Category("\tPerformance")] [JsonProperty(Required = Required.DisallowNull)] @@ -89,7 +86,7 @@ namespace ConfigGenerator { [Category("\tPerformance")] [JsonProperty(Required = Required.DisallowNull)] - public byte FarmingDelay { get; set; } = DefaultFarmingDelay; + public byte InventoryLimiterDelay { get; set; } = 3; [Category("\tPerformance")] [JsonProperty(Required = Required.DisallowNull)] @@ -97,22 +94,25 @@ namespace ConfigGenerator { [Category("\tPerformance")] [JsonProperty(Required = Required.DisallowNull)] - public byte InventoryLimiterDelay { get; set; } = 3; - - [Category("\tPerformance")] - [JsonProperty(Required = Required.DisallowNull)] - public byte GiftsLimiterDelay { get; set; } = 1; + public byte MaxFarmingTime { get; set; } = DefaultMaxFarmingTime; [JsonProperty(Required = Required.DisallowNull)] public byte MaxTradeHoldDuration { get; set; } = 15; - [Category("\tDebugging")] [JsonProperty(Required = Required.DisallowNull)] - public bool ForceHttp { get; set; } = false; + public bool Statistics { get; set; } = true; - [Category("\tDebugging")] + [Category("\tAccess")] [JsonProperty(Required = Required.DisallowNull)] - public byte HttpTimeout { get; set; } = DefaultHttpTimeout; + public ulong SteamOwnerID { get; set; } = 0; + + [Category("\tAdvanced")] + [JsonProperty(Required = Required.DisallowNull)] + public ProtocolType SteamProtocol { get; set; } = DefaultSteamProtocol; + + [Category("\tUpdates")] + [JsonProperty(Required = Required.DisallowNull)] + public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable; [Category("\tAccess")] [JsonProperty] @@ -122,11 +122,17 @@ namespace ConfigGenerator { [JsonProperty(Required = Required.DisallowNull)] public ushort WCFPort { get; set; } = DefaultWCFPort; - [JsonProperty(Required = Required.DisallowNull)] - public bool Statistics { get; set; } = true; + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private GlobalConfig() { } - [JsonProperty(Required = Required.DisallowNull)] - public List Blacklist { get; set; } = new List(); + private GlobalConfig(string filePath) : base(filePath) { + if (string.IsNullOrEmpty(filePath)) { + throw new ArgumentNullException(nameof(filePath)); + } + + Blacklist.AddRange(GlobalBlacklist); + Save(); + } internal static GlobalConfig Load(string filePath) { if (string.IsNullOrEmpty(filePath)) { @@ -192,16 +198,10 @@ namespace ConfigGenerator { return globalConfig; } - [SuppressMessage("ReSharper", "UnusedMember.Local")] - private GlobalConfig() { } - - private GlobalConfig(string filePath) : base(filePath) { - if (string.IsNullOrEmpty(filePath)) { - throw new ArgumentNullException(nameof(filePath)); - } - - Blacklist.AddRange(GlobalBlacklist); - Save(); + internal enum EUpdateChannel : byte { + None, + Stable, + Experimental } } -} +} \ No newline at end of file diff --git a/ConfigGenerator/Logging.cs b/ConfigGenerator/Logging.cs index b1894ff71..89a479ec7 100644 --- a/ConfigGenerator/Logging.cs +++ b/ConfigGenerator/Logging.cs @@ -30,15 +30,6 @@ using ConfigGenerator.Properties; namespace ConfigGenerator { internal static class Logging { - internal static void LogGenericInfoWithoutStacktrace(string message) { - if (string.IsNullOrEmpty(message)) { - LogNullError(nameof(message)); - return; - } - - MessageBox.Show(message, Resources.Information, MessageBoxButtons.OK, MessageBoxIcon.Information); - } - internal static void LogGenericErrorWithoutStacktrace(string message) { if (string.IsNullOrEmpty(message)) { LogNullError(nameof(message)); @@ -66,6 +57,15 @@ namespace ConfigGenerator { } } + internal static void LogGenericInfoWithoutStacktrace(string message) { + if (string.IsNullOrEmpty(message)) { + LogNullError(nameof(message)); + return; + } + + MessageBox.Show(message, Resources.Information, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + internal static void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) { if (string.IsNullOrEmpty(message)) { LogNullError(nameof(message)); @@ -97,4 +97,4 @@ namespace ConfigGenerator { LogGenericErrorWithoutStacktrace(previousMethodName + @"() " + message); } } -} +} \ No newline at end of file diff --git a/ConfigGenerator/MainForm.cs b/ConfigGenerator/MainForm.cs index 980f1d64b..10ca57257 100644 --- a/ConfigGenerator/MainForm.cs +++ b/ConfigGenerator/MainForm.cs @@ -46,6 +46,18 @@ namespace ConfigGenerator { InitializeComponent(); } + private void MainForm_HelpButtonClicked(object sender, CancelEventArgs args) { + if ((sender == null) || (args == null)) { + Logging.LogNullError(nameof(sender) + " || " + nameof(args)); + return; + } + + args.Cancel = true; + Tutorial.OnAction(Tutorial.EPhase.Help); + Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/wiki/Configuration"); + Tutorial.OnAction(Tutorial.EPhase.HelpFinished); + } + private void MainForm_Load(object sender, EventArgs args) { if ((sender == null) || (args == null)) { Logging.LogNullError(nameof(sender) + " || " + nameof(args)); @@ -73,6 +85,24 @@ namespace ConfigGenerator { Tutorial.OnAction(Tutorial.EPhase.Start); } + private void MainForm_Shown(object sender, EventArgs args) { + if ((sender == null) || (args == null)) { + Logging.LogNullError(nameof(sender) + " || " + nameof(args)); + return; + } + + Tutorial.OnAction(Tutorial.EPhase.Shown); + } + + private void MainTab_Deselecting(object sender, TabControlCancelEventArgs args) { + if ((sender == null) || (args == null)) { + Logging.LogNullError(nameof(sender) + " || " + nameof(args)); + return; + } + + OldTab = args.TabPage; + } + private void MainTab_Selected(object sender, TabControlEventArgs args) { if ((sender == null) || (args == null)) { Logging.LogNullError(nameof(sender) + " || " + nameof(args)); @@ -183,35 +213,5 @@ namespace ConfigGenerator { Tutorial.OnAction(Tutorial.EPhase.GlobalConfigOpened); } } - - private void MainTab_Deselecting(object sender, TabControlCancelEventArgs args) { - if ((sender == null) || (args == null)) { - Logging.LogNullError(nameof(sender) + " || " + nameof(args)); - return; - } - - OldTab = args.TabPage; - } - - private void MainForm_Shown(object sender, EventArgs args) { - if ((sender == null) || (args == null)) { - Logging.LogNullError(nameof(sender) + " || " + nameof(args)); - return; - } - - Tutorial.OnAction(Tutorial.EPhase.Shown); - } - - private void MainForm_HelpButtonClicked(object sender, CancelEventArgs args) { - if ((sender == null) || (args == null)) { - Logging.LogNullError(nameof(sender) + " || " + nameof(args)); - return; - } - - args.Cancel = true; - Tutorial.OnAction(Tutorial.EPhase.Help); - Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/wiki/Configuration"); - Tutorial.OnAction(Tutorial.EPhase.HelpFinished); - } } -} +} \ No newline at end of file diff --git a/ConfigGenerator/Program.cs b/ConfigGenerator/Program.cs index 819d780d9..9ffc7fd54 100644 --- a/ConfigGenerator/Program.cs +++ b/ConfigGenerator/Program.cs @@ -34,17 +34,6 @@ namespace ConfigGenerator { internal static class Program { private const string ASFExecutableFile = SharedInfo.ASF + ".exe"; - /// - /// The main entry point for the application. - /// - [STAThread] - private static void Main() { - Init(); - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new MainForm()); - } - private static void Init() { AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; @@ -55,7 +44,6 @@ namespace ConfigGenerator { // Allow loading configs from source tree if it's a debug build if (Debugging.IsDebugBuild) { - // Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up for (byte i = 0; i < 4; i++) { Directory.SetCurrentDirectory(".."); @@ -92,17 +80,23 @@ namespace ConfigGenerator { return; } - Logging.LogGenericErrorWithoutStacktrace( - "Version of ASF and ConfigGenerator doesn't match!" + Environment.NewLine + - "ASF version: " + asfVersion + " | ConfigGenerator version: " + cgVersion + Environment.NewLine + - Environment.NewLine + - "Please use ConfigGenerator from the same ASF release, I'll redirect you to appropriate ASF release..." - ); + Logging.LogGenericErrorWithoutStacktrace("Version of ASF and ConfigGenerator doesn't match!" + Environment.NewLine + "ASF version: " + asfVersion + " | ConfigGenerator version: " + cgVersion + Environment.NewLine + Environment.NewLine + "Please use ConfigGenerator from the same ASF release, I'll redirect you to appropriate ASF release..."); Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/releases/tag/" + asfVersion); Environment.Exit(1); } + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main() { + Init(); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) { if (args?.ExceptionObject == null) { Logging.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject)); @@ -121,4 +115,4 @@ namespace ConfigGenerator { Logging.LogGenericException(args.Exception); } } -} +} \ No newline at end of file diff --git a/ConfigGenerator/Properties/AssemblyInfo.cs b/ConfigGenerator/Properties/AssemblyInfo.cs index f83116994..235a684f7 100644 --- a/ConfigGenerator/Properties/AssemblyInfo.cs +++ b/ConfigGenerator/Properties/AssemblyInfo.cs @@ -5,6 +5,7 @@ using ArchiSteamFarm; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. + [assembly: AssemblyTitle(SharedInfo.ServiceName + "-ConfigGenerator")] [assembly: AssemblyDescription(SharedInfo.ServiceDescription)] [assembly: AssemblyConfiguration("")] @@ -17,9 +18,11 @@ using ArchiSteamFarm; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. + [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM + [assembly: Guid("c3f6fe68-5e75-415e-bea1-1e7c16d6a433")] // Version information for an assembly consists of the following four values: @@ -32,5 +35,6 @@ using ArchiSteamFarm; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] + [assembly: AssemblyVersion(SharedInfo.VersionNumber)] -[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)] +[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)] \ No newline at end of file diff --git a/ConfigGenerator/Properties/Settings.settings b/ConfigGenerator/Properties/Settings.settings index 39645652a..e04fc6310 100644 --- a/ConfigGenerator/Properties/Settings.settings +++ b/ConfigGenerator/Properties/Settings.settings @@ -1,7 +1,8 @@  + - + \ No newline at end of file diff --git a/ConfigGenerator/RunTime.cs b/ConfigGenerator/RunTime.cs index e464f48a0..06553095b 100644 --- a/ConfigGenerator/RunTime.cs +++ b/ConfigGenerator/RunTime.cs @@ -26,8 +26,7 @@ using System; namespace ConfigGenerator { internal static class Runtime { - private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime"); - internal static bool IsRunningOnMono => MonoRuntime != null; + private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime"); } -} +} \ No newline at end of file diff --git a/ConfigGenerator/Tutorial.cs b/ConfigGenerator/Tutorial.cs index 97dc5fd7d..5c1008c66 100644 --- a/ConfigGenerator/Tutorial.cs +++ b/ConfigGenerator/Tutorial.cs @@ -24,20 +24,6 @@ namespace ConfigGenerator { internal static class Tutorial { - internal enum EPhase : byte { - Unknown, - Start, - Shown, - Help, - HelpFinished, - BotNickname, - BotNicknameFinished, - BotEnabled, - BotReady, - GlobalConfigOpened, - GlobalConfigReady - } - internal static bool Enabled { private get; set; } = true; private static EPhase NextPhase = EPhase.Start; @@ -99,5 +85,19 @@ namespace ConfigGenerator { NextPhase++; } + + internal enum EPhase : byte { + Unknown, + Start, + Shown, + Help, + HelpFinished, + BotNickname, + BotNicknameFinished, + BotEnabled, + BotReady, + GlobalConfigOpened, + GlobalConfigReady + } } -} +} \ No newline at end of file diff --git a/ConfigGenerator/packages.config b/ConfigGenerator/packages.config index a82f17503..699908060 100644 --- a/ConfigGenerator/packages.config +++ b/ConfigGenerator/packages.config @@ -1,4 +1,5 @@  + diff --git a/GUI/App.config b/GUI/App.config index 731f6de6c..efb4d02fd 100644 --- a/GUI/App.config +++ b/GUI/App.config @@ -1,6 +1,7 @@ - + + - - - + + + \ No newline at end of file diff --git a/GUI/BotStatusForm.cs b/GUI/BotStatusForm.cs index a15e8c6b2..93529d4b5 100644 --- a/GUI/BotStatusForm.cs +++ b/GUI/BotStatusForm.cs @@ -43,4 +43,4 @@ namespace GUI { MainForm.UpdateBotAvatar(Bot.BotName, AvatarPictureBox.Image); } } -} +} \ No newline at end of file diff --git a/GUI/Events.cs b/GUI/Events.cs index f2be04e33..f0b691e12 100644 --- a/GUI/Events.cs +++ b/GUI/Events.cs @@ -26,6 +26,7 @@ using GUI; using SteamKit2; // ReSharper disable once CheckNamespace + namespace ArchiSteamFarm { internal static class Events { internal static void OnBotShutdown() { } @@ -49,4 +50,4 @@ namespace ArchiSteamFarm { form.OnStateUpdated(callback); } } -} +} \ No newline at end of file diff --git a/GUI/FodyWeavers.xml b/GUI/FodyWeavers.xml index 5df39cbf9..c4626742c 100644 --- a/GUI/FodyWeavers.xml +++ b/GUI/FodyWeavers.xml @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/GUI/Logging.cs b/GUI/Logging.cs index ab5958926..7a3af88eb 100644 --- a/GUI/Logging.cs +++ b/GUI/Logging.cs @@ -29,6 +29,7 @@ using NLog.Targets; using NLog.Windows.Forms; // ReSharper disable once CheckNamespace + namespace ArchiSteamFarm { internal static class Logging { private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} | ${level:uppercase=true} | ${logger} | ${message}${onexception:inner= | ${exception:format=toString,Data}}"; @@ -46,10 +47,7 @@ namespace ArchiSteamFarm { } } - MessageBoxTarget messageBoxTarget = new MessageBoxTarget { - Name = "MessageBox", - Layout = GeneralLayout - }; + MessageBoxTarget messageBoxTarget = new MessageBoxTarget { Name = "MessageBox", Layout = GeneralLayout }; LogManager.Configuration.AddTarget(messageBoxTarget); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Fatal, messageBoxTarget)); @@ -61,11 +59,7 @@ namespace ArchiSteamFarm { return; } - FileTarget fileTarget = new FileTarget("File") { - DeleteOldFileOnStartup = true, - FileName = SharedInfo.LogFile, - Layout = GeneralLayout - }; + FileTarget fileTarget = new FileTarget("File") { DeleteOldFileOnStartup = true, FileName = SharedInfo.LogFile, Layout = GeneralLayout }; LogManager.Configuration.AddTarget(fileTarget); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); @@ -74,23 +68,15 @@ namespace ArchiSteamFarm { } internal static void InitFormLogger() { - RichTextBoxTarget formControlTarget = new RichTextBoxTarget { - AutoScroll = true, - ControlName = "LogTextBox", - FormName = "MainForm", - Layout = GeneralLayout, - MaxLines = byte.MaxValue, - Name = "RichTextBox" - }; + RichTextBoxTarget formControlTarget = new RichTextBoxTarget { AutoScroll = true, ControlName = "LogTextBox", FormName = "MainForm", Layout = GeneralLayout, MaxLines = byte.MaxValue, Name = "RichTextBox" }; formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Error", "Red", "Black")); formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Warn", "Yellow", "Black")); - LogManager.Configuration.AddTarget(formControlTarget); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, formControlTarget)); LogManager.ReconfigExistingLoggers(); } } -} +} \ No newline at end of file diff --git a/GUI/MainForm.cs b/GUI/MainForm.cs index 56619f6eb..319d5a20f 100644 --- a/GUI/MainForm.cs +++ b/GUI/MainForm.cs @@ -47,44 +47,20 @@ namespace GUI { })); } - private static Bitmap ResizeImage(Image image, int width, int height) { - if ((image == null) || (width <= 0) || (height <= 0)) { - ASF.ArchiLogger.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height)); - return null; + private void BotListView_SelectedIndexChanged(object sender, EventArgs e) { + if (!string.IsNullOrEmpty(PreviouslySelectedBotName)) { + BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = false; } - Rectangle destRect = new Rectangle(0, 0, width, height); - Bitmap destImage = new Bitmap(width, height); - - destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); - - using (Graphics graphics = Graphics.FromImage(destImage)) { - graphics.CompositingMode = CompositingMode.SourceCopy; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - - using (ImageAttributes wrapMode = new ImageAttributes()) { - wrapMode.SetWrapMode(WrapMode.TileFlipXY); - graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode); - } + if (BotListView.SelectedItems.Count == 0) { + return; } - return destImage; + PreviouslySelectedBotName = BotListView.SelectedItems[0].Text; + BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = true; } - private void MainForm_Resize(object sender, EventArgs e) { - switch (WindowState) { - case FormWindowState.Minimized: - MinimizeIcon.Visible = true; - MinimizeIcon.ShowBalloonTip(5000); - break; - case FormWindowState.Normal: - MinimizeIcon.Visible = false; - break; - } - } + private void MainForm_FormClosed(object sender, FormClosedEventArgs e) => Program.InitShutdownSequence(); private async void MainForm_Load(object sender, EventArgs e) { Logging.InitFormLogger(); @@ -124,10 +100,7 @@ namespace GUI { botStatusForm.TopLevel = false; BotStatusPanel.Controls.Add(botStatusForm); - ListViewItem botListViewItem = new ListViewItem { - ImageIndex = BotIndexes[botName], - Text = botName - }; + ListViewItem botListViewItem = new ListViewItem { ImageIndex = BotIndexes[botName], Text = botName }; BotListView.Items.Add(botListViewItem); } @@ -138,24 +111,48 @@ namespace GUI { } } + private void MainForm_Resize(object sender, EventArgs e) { + switch (WindowState) { + case FormWindowState.Minimized: + MinimizeIcon.Visible = true; + MinimizeIcon.ShowBalloonTip(5000); + break; + case FormWindowState.Normal: + MinimizeIcon.Visible = false; + break; + } + } + private void MinimizeIcon_DoubleClick(object sender, EventArgs e) { Show(); WindowState = FormWindowState.Normal; } - private void MainForm_FormClosed(object sender, FormClosedEventArgs e) => Program.InitShutdownSequence(); - - private void BotListView_SelectedIndexChanged(object sender, EventArgs e) { - if (!string.IsNullOrEmpty(PreviouslySelectedBotName)) { - BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = false; + private static Bitmap ResizeImage(Image image, int width, int height) { + if ((image == null) || (width <= 0) || (height <= 0)) { + ASF.ArchiLogger.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height)); + return null; } - if (BotListView.SelectedItems.Count == 0) { - return; + Rectangle destRect = new Rectangle(0, 0, width, height); + Bitmap destImage = new Bitmap(width, height); + + destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + + using (Graphics graphics = Graphics.FromImage(destImage)) { + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + + using (ImageAttributes wrapMode = new ImageAttributes()) { + wrapMode.SetWrapMode(WrapMode.TileFlipXY); + graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode); + } } - PreviouslySelectedBotName = BotListView.SelectedItems[0].Text; - BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = true; + return destImage; } } -} +} \ No newline at end of file diff --git a/GUI/Program.cs b/GUI/Program.cs index 4e4c9ca17..061385432 100644 --- a/GUI/Program.cs +++ b/GUI/Program.cs @@ -7,21 +7,29 @@ using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using GUI; +using SteamKit2; // ReSharper disable once CheckNamespace + namespace ArchiSteamFarm { internal static class Program { internal static GlobalConfig GlobalConfig { get; private set; } internal static GlobalDatabase GlobalDatabase { get; private set; } internal static WebBrowser WebBrowser { get; private set; } + internal static void Exit(int exitCode = 0) { + InitShutdownSequence(); + Environment.Exit(exitCode); + } + internal static string GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) { return null; // TODO } - internal static void Exit(int exitCode = 0) { - InitShutdownSequence(); - Environment.Exit(exitCode); + internal static void InitShutdownSequence() { + foreach (Bot bot in Bot.Bots.Values.Where(bot => bot.KeepRunning)) { + bot.Stop(); + } } internal static void Restart() { @@ -36,53 +44,6 @@ namespace ArchiSteamFarm { Environment.Exit(0); } - internal static void InitShutdownSequence() { - foreach (Bot bot in Bot.Bots.Values.Where(bot => bot.KeepRunning)) { - bot.Stop(); - } - } - - private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) { - if (args?.ExceptionObject == null) { - ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject)); - return; - } - - ASF.ArchiLogger.LogFatalException((Exception) args.ExceptionObject); - } - - private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) { - if (args?.Exception == null) { - ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.Exception)); - return; - } - - ASF.ArchiLogger.LogFatalException(args.Exception); - } - - private static void InitServices() { - string globalConfigFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName); - - GlobalConfig = GlobalConfig.Load(globalConfigFile); - if (GlobalConfig == null) { - ASF.ArchiLogger.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!"); - Exit(1); - } - - string globalDatabaseFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName); - - GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile); - if (GlobalDatabase == null) { - ASF.ArchiLogger.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!"); - Exit(1); - } - - ArchiWebHandler.Init(); - WebBrowser.Init(); - - WebBrowser = new WebBrowser(ASF.ArchiLogger); - } - private static void Init() { AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; @@ -99,7 +60,6 @@ namespace ArchiSteamFarm { // Allow loading configs from source tree if it's a debug build if (Debugging.IsDebugBuild) { - // Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up for (byte i = 0; i < 4; i++) { Directory.SetCurrentDirectory(".."); @@ -129,15 +89,38 @@ namespace ArchiSteamFarm { Directory.CreateDirectory(SharedInfo.DebugDirectory); - SteamKit2.DebugLog.AddListener(new Debugging.DebugListener()); - SteamKit2.DebugLog.Enabled = true; + DebugLog.AddListener(new Debugging.DebugListener()); + DebugLog.Enabled = true; } Logging.InitEnhancedLoggers(); } + private static void InitServices() { + string globalConfigFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName); + + GlobalConfig = GlobalConfig.Load(globalConfigFile); + if (GlobalConfig == null) { + ASF.ArchiLogger.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!"); + Exit(1); + } + + string globalDatabaseFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName); + + GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile); + if (GlobalDatabase == null) { + ASF.ArchiLogger.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!"); + Exit(1); + } + + ArchiWebHandler.Init(); + WebBrowser.Init(); + + WebBrowser = new WebBrowser(ASF.ArchiLogger); + } + /// - /// The main entry point for the application. + /// The main entry point for the application. /// [STAThread] private static void Main() { @@ -146,5 +129,23 @@ namespace ArchiSteamFarm { Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); } + + private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) { + if (args?.ExceptionObject == null) { + ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject)); + return; + } + + ASF.ArchiLogger.LogFatalException((Exception) args.ExceptionObject); + } + + private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) { + if (args?.Exception == null) { + ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.Exception)); + return; + } + + ASF.ArchiLogger.LogFatalException(args.Exception); + } } -} +} \ No newline at end of file diff --git a/GUI/Properties/AssemblyInfo.cs b/GUI/Properties/AssemblyInfo.cs index 783210ed9..60cd4e468 100644 --- a/GUI/Properties/AssemblyInfo.cs +++ b/GUI/Properties/AssemblyInfo.cs @@ -5,6 +5,7 @@ using ArchiSteamFarm; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. + [assembly: AssemblyTitle(SharedInfo.ServiceName + "-GUI")] [assembly: AssemblyDescription(SharedInfo.ServiceDescription)] [assembly: AssemblyConfiguration("")] @@ -17,9 +18,11 @@ using ArchiSteamFarm; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. + [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM + [assembly: Guid("13949b41-787c-4558-90ae-a9f9e7f86b1f")] // Version information for an assembly consists of the following four values: @@ -32,5 +35,6 @@ using ArchiSteamFarm; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] + [assembly: AssemblyVersion(SharedInfo.VersionNumber)] -[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)] +[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)] \ No newline at end of file diff --git a/GUI/Properties/Settings.settings b/GUI/Properties/Settings.settings index 39645652a..e04fc6310 100644 --- a/GUI/Properties/Settings.settings +++ b/GUI/Properties/Settings.settings @@ -1,7 +1,8 @@  + - + \ No newline at end of file diff --git a/GUI/packages.config b/GUI/packages.config index 7412e17dd..574119999 100644 --- a/GUI/packages.config +++ b/GUI/packages.config @@ -1,4 +1,5 @@  +