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
This commit is contained in:
JustArchi 2016-11-24 07:32:16 +01:00
parent 1a49c80bc4
commit df218074ad
65 changed files with 4299 additions and 4356 deletions

View file

@ -90,10 +90,14 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_OWNER_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_OWNER_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_CONTROL_TRANSFER_STATEMENTS/@EntryValue">1</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">END_OF_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER_SAME_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER_SAME_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">END_OF_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INVOCABLE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INVOCABLE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_USER_LINEBREAKS/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">END_OF_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CATCH_ON_NEW_LINE/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CATCH_ON_NEW_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ELSE_ON_NEW_LINE/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ELSE_ON_NEW_LINE/@EntryValue">False</s:Boolean>
@ -102,9 +106,216 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">255</s:Int64> <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">1000</s:Int64>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CSharpFileLayoutPatterns/Pattern/@EntryValue">&lt;?xml version="1.0" encoding="utf-16"?&gt;&#xD;
&lt;Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"&gt;&#xD;
&lt;TypePattern DisplayName="ArchiPattern" Priority="150"&gt;&#xD;
&lt;Entry DisplayName="Public (Events and Delegates)"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Access Is="Public" /&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Kind Is="Delegate" /&gt;&#xD;
&lt;Kind Is="Event" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access /&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Constants"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Constant" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access /&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Static (Fields, Properties and Indexers)"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Field" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Autoproperty" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Property" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Indexer" /&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access /&gt;&#xD;
&lt;Readonly /&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Non-static (Fields, Properties and Indexers)"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Not&gt;&#xD;
&lt;Static /&gt;&#xD;
&lt;/Not&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Kind Is="Field" /&gt;&#xD;
&lt;Kind Is="Autoproperty" /&gt;&#xD;
&lt;Kind Is="Property" /&gt;&#xD;
&lt;Kind Is="Indexer" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Readonly /&gt;&#xD;
&lt;Access /&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Constructors"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Constructor" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access /&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Interfaces"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;ImplementsInterface /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access /&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Everything else"&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access /&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="Nested"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Kind Is="Type" /&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Access /&gt;&#xD;
&lt;Kind Is="Member" /&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="COM interfaces or structs"&gt;&#xD;
&lt;TypePattern.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Interface" /&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /&gt;&#xD;
&lt;HasAttribute Name="System.Runtime.InteropServices.ComImport" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;Kind Is="Struct" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/TypePattern.Match&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="xUnit.net Test Classes" RemoveRegions="All"&gt;&#xD;
&lt;TypePattern.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Class" /&gt;&#xD;
&lt;HasMember&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="Xunit.FactAttribute" Inherited="True" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/HasMember&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/TypePattern.Match&gt;&#xD;
&lt;Entry DisplayName="Setup/Teardown Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Kind Is="Constructor" /&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;ImplementsInterface Name="System.IDisposable" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Kind Order="Constructor" /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="Xunit.FactAttribute" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"&gt;&#xD;
&lt;TypePattern.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Class" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/TypePattern.Match&gt;&#xD;
&lt;Entry DisplayName="Setup/Teardown Methods"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;Entry DisplayName="All other members" /&gt;&#xD;
&lt;Entry DisplayName="Test Methods" Priority="100"&gt;&#xD;
&lt;Entry.Match&gt;&#xD;
&lt;And&gt;&#xD;
&lt;Kind Is="Method" /&gt;&#xD;
&lt;HasAttribute Name="NUnit.Framework.TestAttribute" /&gt;&#xD;
&lt;/And&gt;&#xD;
&lt;/Entry.Match&gt;&#xD;
&lt;Entry.SortBy&gt;&#xD;
&lt;Name /&gt;&#xD;
&lt;/Entry.SortBy&gt;&#xD;
&lt;/Entry&gt;&#xD;
&lt;/TypePattern&gt;&#xD;
&lt;/Patterns&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String> <s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String> <s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String> <s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
@ -129,5 +340,6 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="AaBb" /&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="AaBb" /&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="AaBb" /&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="AaBb" /&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -34,28 +34,6 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class ASF { 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); internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF);
private static readonly ConcurrentDictionary<Bot, DateTime> LastWriteTimes = new ConcurrentDictionary<Bot, DateTime>(); private static readonly ConcurrentDictionary<Bot, DateTime> LastWriteTimes = new ConcurrentDictionary<Bot, DateTime>();
@ -90,10 +68,7 @@ namespace ArchiSteamFarm {
} }
if ((AutoUpdatesTimer == null) && Program.GlobalConfig.AutoUpdates) { if ((AutoUpdatesTimer == null) && Program.GlobalConfig.AutoUpdates) {
AutoUpdatesTimer = new Timer( AutoUpdatesTimer = new Timer(async e => await CheckForUpdate().ConfigureAwait(false), null, TimeSpan.FromDays(1), // Delay
async e => await CheckForUpdate().ConfigureAwait(false),
null,
TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period TimeSpan.FromDays(1) // Period
); );
@ -258,9 +233,7 @@ namespace ArchiSteamFarm {
return; return;
} }
FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*.json") { FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*.json") { NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite };
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite
};
FileSystemWatcher.Changed += OnChanged; FileSystemWatcher.Changed += OnChanged;
FileSystemWatcher.Created += OnCreated; FileSystemWatcher.Created += OnCreated;
@ -408,5 +381,27 @@ namespace ArchiSteamFarm {
CreateBot(newBotName).Forget(); 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
}
} }
} }

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup> </startup>
</configuration> </configuration>

View file

@ -22,8 +22,6 @@
*/ */
using SteamKit2;
using SteamKit2.Internal;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@ -31,6 +29,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using SteamKit2;
using SteamKit2.Internal;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class ArchiHandler : ClientMsgHandler { internal sealed class ArchiHandler : ClientMsgHandler {
@ -44,187 +44,37 @@ namespace ArchiSteamFarm {
ArchiLogger = archiLogger; ArchiLogger = archiLogger;
} }
/* public override void HandleMsg(IPacketMsg packetMsg) {
____ _ _ _ _ if (packetMsg == null) {
/ ___| __ _ | || || |__ __ _ ___ | | __ ___ ArchiLogger.LogNullError(nameof(packetMsg));
| | / _` || || || '_ \ / _` | / __|| |/ // __| return;
| |___| (_| || || || |_) || (_| || (__ | < \__ \
\____|\__,_||_||_||_.__/ \__,_| \___||_|\_\|___/
*/
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
} }
internal readonly HashSet<ENotification> Notifications; switch (packetMsg.MsgType) {
case EMsg.ClientFSOfflineMessageNotification:
internal NotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) { HandleFSOfflineMessageNotification(packetMsg);
if ((jobID == null) || (msg == null)) { break;
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); case EMsg.ClientItemAnnouncements:
} HandleItemAnnouncements(packetMsg);
break;
JobID = jobID; case EMsg.ClientPlayingSessionState:
HandlePlayingSessionState(packetMsg);
if (msg.notifications.Count == 0) { break;
return; case EMsg.ClientPurchaseResponse:
} HandlePurchaseResponse(packetMsg);
break;
Notifications = new HashSet<ENotification>(msg.notifications.Select(notification => (ENotification) notification.user_notification_type)); case EMsg.ClientRedeemGuestPassResponse:
} HandleRedeemGuestPassResponse(packetMsg);
break;
internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) { case EMsg.ClientSharedLibraryLockStatus:
if ((jobID == null) || (msg == null)) { HandleSharedLibraryLockStatus(packetMsg);
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg)); break;
} case EMsg.ClientUserNotifications:
HandleUserNotifications(packetMsg);
JobID = jobID; break;
if (msg.count_new_items > 0) {
Notifications = new HashSet<ENotification> { ENotification.Items };
}
} }
} }
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<uint, string> 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<KeyValue> lineItems = receiptInfo["lineitems"].Children;
if (lineItems.Count == 0) {
return;
}
Items = new Dictionary<uint, string>(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 // TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed
internal void LogOnWithoutMachineID(SteamUser.LogOnDetails details) { internal void LogOnWithoutMachineID(SteamUser.LogOnDetails details) {
if (details == null) { if (details == null) {
@ -276,7 +126,6 @@ namespace ArchiSteamFarm {
logon.Body.sha_sentryfile = details.SentryFileHash; logon.Body.sha_sentryfile = details.SentryFileHash;
logon.Body.eresult_sentryfile = (int) (details.SentryFileHash != null ? EResult.OK : EResult.FileNotFound); logon.Body.eresult_sentryfile = (int) (details.SentryFileHash != null ? EResult.OK : EResult.FileNotFound);
Client.Send(logon); Client.Send(logon);
} }
@ -301,19 +150,11 @@ namespace ArchiSteamFarm {
ClientMsgProtobuf<CMsgClientGamesPlayed> request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed); ClientMsgProtobuf<CMsgClientGamesPlayed> request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
if (!string.IsNullOrEmpty(gameName)) { if (!string.IsNullOrEmpty(gameName)) {
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_extra_info = gameName, game_id = new GameID { AppType = GameID.GameType.Shortcut, ModID = uint.MaxValue } });
game_extra_info = gameName,
game_id = new GameID {
AppType = GameID.GameType.Shortcut,
ModID = uint.MaxValue
}
});
} }
foreach (uint gameID in gameIDs.Where(gameID => gameID != 0)) { foreach (uint gameID in gameIDs.Where(gameID => gameID != 0)) {
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_id = new GameID(gameID) });
game_id = new GameID(gameID)
});
} }
Client.Send(request); Client.Send(request);
@ -329,9 +170,7 @@ namespace ArchiSteamFarm {
return null; return null;
} }
ClientMsgProtobuf<CMsgClientRedeemGuestPass> request = new ClientMsgProtobuf<CMsgClientRedeemGuestPass>(EMsg.ClientRedeemGuestPass) { ClientMsgProtobuf<CMsgClientRedeemGuestPass> request = new ClientMsgProtobuf<CMsgClientRedeemGuestPass>(EMsg.ClientRedeemGuestPass) { SourceJobID = Client.GetNextJobID() };
SourceJobID = Client.GetNextJobID()
};
request.Body.guest_pass_id = guestPassID; request.Body.guest_pass_id = guestPassID;
@ -355,9 +194,7 @@ namespace ArchiSteamFarm {
return null; return null;
} }
ClientMsgProtobuf<CMsgClientRegisterKey> request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey) { ClientMsgProtobuf<CMsgClientRegisterKey> request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey) { SourceJobID = Client.GetNextJobID() };
SourceJobID = Client.GetNextJobID()
};
request.Body.key = key; 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) { private void HandleFSOfflineMessageNotification(IPacketMsg packetMsg) {
if (packetMsg == null) { if (packetMsg == null) {
ArchiLogger.LogNullError(nameof(packetMsg)); ArchiLogger.LogNullError(nameof(packetMsg));
@ -480,5 +277,168 @@ namespace ArchiSteamFarm {
ClientMsgProtobuf<CMsgClientUserNotifications> response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg); ClientMsgProtobuf<CMsgClientUserNotifications> response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg);
Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body)); Client.PostCallback(new NotificationsCallback(packetMsg.TargetJobID, response.Body));
} }
internal sealed class NotificationsCallback : CallbackMsg {
internal readonly HashSet<ENotification> 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<ENotification>(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> { 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<uint, string> 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<KeyValue> lineItems = receiptInfo["lineitems"].Children;
if (lineItems.Count == 0) {
return;
}
Items = new Dictionary<uint, string>(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);
}
}
} }
} }

View file

@ -40,24 +40,6 @@ namespace ArchiSteamFarm {
Logger = LogManager.GetLogger(name); 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")] [SuppressMessage("ReSharper", "LocalizableElement")]
internal void LogFatalException(Exception exception, [CallerMemberName] string previousMethodName = null) { internal void LogFatalException(Exception exception, [CallerMemberName] string previousMethodName = null) {
if (exception == 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)) { if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message)); LogNullError(nameof(message));
return; 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) { internal void LogGenericInfo(string message, [CallerMemberName] string previousMethodName = null) {
@ -104,24 +104,6 @@ namespace ArchiSteamFarm {
Logger.Info($"{previousMethodName}() {message}"); 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) { internal void LogGenericTrace(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message)); LogNullError(nameof(message));
@ -130,5 +112,23 @@ namespace ArchiSteamFarm {
Logger.Trace($"{previousMethodName}() {message}"); 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);
}
} }
} }

View file

@ -48,10 +48,7 @@ namespace ArchiSteamFarm {
serviceInstaller.Installers.Clear(); serviceInstaller.Installers.Clear();
EventLogInstaller logInstaller = new EventLogInstaller { EventLogInstaller logInstaller = new EventLogInstaller { Log = SharedInfo.EventLog, Source = SharedInfo.EventLogSource };
Log = SharedInfo.EventLog,
Source = SharedInfo.EventLogSource
};
Installers.Add(serviceInstaller); Installers.Add(serviceInstaller);
Installers.Add(serviceProcessInstaller); Installers.Add(serviceProcessInstaller);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -22,12 +22,12 @@
*/ */
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
@ -35,98 +35,14 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ConvertToConstant.Local")] [SuppressMessage("ReSharper", "ConvertToConstant.Local")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")] [SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class BotConfig { 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)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Enabled = false; internal readonly byte AcceptConfirmationsPeriod = 0;
[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;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AcceptGifts = false; internal readonly bool AcceptGifts = false;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool IsBotAccount = false; internal readonly bool CardDropsRestricted = true;
[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;
[JsonProperty] [JsonProperty]
internal readonly string CustomGamePlayedWhileFarming = null; internal readonly string CustomGamePlayedWhileFarming = null;
@ -134,9 +50,75 @@ namespace ArchiSteamFarm {
[JsonProperty] [JsonProperty]
internal readonly string CustomGamePlayedWhileIdle = null; 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)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet<uint> GamesPlayedWhileIdle = new HashSet<uint>(); internal readonly HashSet<uint> GamesPlayedWhileIdle = new HashSet<uint>();
[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) { internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath)); ASF.ArchiLogger.LogNullError(nameof(filePath));
@ -182,7 +164,25 @@ namespace ArchiSteamFarm {
return botConfig; return botConfig;
} }
// This constructor is used only by deserializer internal enum EFarmingOrder : byte {
private BotConfig() { } 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
}
} }
} }

View file

@ -22,21 +22,18 @@
*/ */
using Newtonsoft.Json;
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using Newtonsoft.Json;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class BotDatabase { internal sealed class BotDatabase {
[JsonProperty] private readonly object FileLock = new object();
private string _LoginKey;
internal string LoginKey { internal string LoginKey {
get { get { return _LoginKey; }
return _LoginKey;
}
set { set {
if (_LoginKey == value) { if (_LoginKey == value) {
@ -48,13 +45,8 @@ namespace ArchiSteamFarm {
} }
} }
[JsonProperty]
private MobileAuthenticator _MobileAuthenticator;
internal MobileAuthenticator MobileAuthenticator { internal MobileAuthenticator MobileAuthenticator {
get { get { return _MobileAuthenticator; }
return _MobileAuthenticator;
}
set { set {
if (_MobileAuthenticator == value) { 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; 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) { internal static BotDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath)); ASF.ArchiLogger.LogNullError(nameof(filePath));
@ -98,20 +108,6 @@ namespace ArchiSteamFarm {
return botDatabase; 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() { internal void Save() {
string json = JsonConvert.SerializeObject(this); string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) { if (string.IsNullOrEmpty(json)) {

View file

@ -22,7 +22,6 @@
*/ */
using HtmlAgilityPack;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@ -30,71 +29,30 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using HtmlAgilityPack;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class CardsFarmer : IDisposable { 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 internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
[JsonProperty]
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>();
[JsonProperty] [JsonProperty]
internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>(); internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>();
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false); [JsonProperty]
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1); internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>();
private readonly Bot Bot; private readonly Bot Bot;
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
private readonly Timer IdleFarmingTimer; private readonly Timer IdleFarmingTimer;
[JsonProperty] [JsonProperty]
internal bool Paused { get; private set; } internal bool Paused { get; private set; }
private bool KeepFarming, NowFarming, StickyPause; private bool KeepFarming;
private bool NowFarming;
private bool StickyPause;
internal CardsFarmer(Bot bot) { internal CardsFarmer(Bot bot) {
if (bot == null) { if (bot == null) {
@ -104,16 +62,50 @@ namespace ArchiSteamFarm {
Bot = bot; Bot = bot;
if (Program.GlobalConfig.IdleFarmingPeriod > 0) { if (Program.GlobalConfig.IdleFarmingPeriod > 0) {
IdleFarmingTimer = new Timer( IdleFarmingTimer = new Timer(e => CheckGamesForFarming(), null, TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5*Bot.Bots.Count), // Delay
e => CheckGamesForFarming(),
null,
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5 * Bot.Bots.Count), // Delay
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period 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) { internal async Task Pause(bool sticky) {
if (sticky) { if (sticky) {
@ -142,6 +134,8 @@ namespace ArchiSteamFarm {
} }
} }
internal void SetInitialState(bool paused) => StickyPause = Paused = paused;
internal async Task StartFarming() { internal async Task StartFarming() {
if (NowFarming || Paused || !Bot.IsPlayingPossible) { if (NowFarming || Paused || !Bot.IsPlayingPossible) {
return; return;
@ -257,117 +251,12 @@ namespace ArchiSteamFarm {
} }
} }
internal void OnDisconnected() => StopFarming().Forget(); private void CheckGamesForFarming() {
if (NowFarming || Paused || !Bot.IsConnectedAndLoggedOn) {
internal async Task OnNewItemsNotification() {
if (NowFarming) {
FarmResetEvent.Set();
return; return;
} }
// If we're not farming, and we got new items, it's likely to be a booster pack or likewise StartFarming().Forget();
// 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<bool> 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<Task> tasks = new List<Task>(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<Game> 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
} }
private void CheckPage(HtmlDocument htmlDocument) { private void CheckPage(HtmlDocument htmlDocument) {
@ -513,92 +402,6 @@ namespace ArchiSteamFarm {
CheckPage(htmlDocument); CheckPage(htmlDocument);
} }
private void CheckGamesForFarming() {
if (NowFarming || Paused || !Bot.IsConnectedAndLoggedOn) {
return;
}
StartFarming().Forget();
}
private async Task<bool?> 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<Game> 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<bool> 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<bool> Farm(Game game) { private async Task<bool> Farm(Game game) {
if (game == null) { if (game == null) {
Bot.ArchiLogger.LogNullError(nameof(game)); Bot.ArchiLogger.LogNullError(nameof(game));
@ -615,7 +418,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo("Still farming: " + game.AppID + " (" + game.GameName + ")"); Bot.ArchiLogger.LogGenericInfo("Still farming: " + game.AppID + " (" + game.GameName + ")");
DateTime startFarmingPeriod = DateTime.Now; DateTime startFarmingPeriod = DateTime.Now;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { if (FarmResetEvent.Wait(60*1000*Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset(); FarmResetEvent.Reset();
success = KeepFarming; success = KeepFarming;
} }
@ -658,7 +461,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo("Still farming: " + string.Join(", ", games.Select(game => game.AppID))); Bot.ArchiLogger.LogGenericInfo("Still farming: " + string.Join(", ", games.Select(game => game.AppID)));
DateTime startFarmingPeriod = DateTime.Now; DateTime startFarmingPeriod = DateTime.Now;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { if (FarmResetEvent.Wait(60*1000*Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset(); FarmResetEvent.Reset();
success = KeepFarming; success = KeepFarming;
} }
@ -680,15 +483,211 @@ namespace ArchiSteamFarm {
return success; return success;
} }
public void Dispose() { private bool FarmMultiple(IEnumerable<Game> games) {
// Those are objects that are always being created if constructor doesn't throw exception if (games == null) {
CurrentGamesFarming.Dispose(); Bot.ArchiLogger.LogNullError(nameof(games));
GamesToFarm.Dispose(); return false;
FarmingSemaphore.Dispose(); }
FarmResetEvent.Dispose();
// Those are objects that might be null and the check should be in-place CurrentGamesFarming.ReplaceWith(games);
IdleFarmingTimer?.Dispose();
Bot.ArchiLogger.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming.Select(game => game.AppID)));
bool result = FarmHours(CurrentGamesFarming);
CurrentGamesFarming.ClearAndTrim();
return result;
}
private async Task<bool> 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<bool> 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<Task> tasks = new List<Task>(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<bool?> 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<Game> 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;
} }
} }
} }

View file

@ -31,11 +31,11 @@ namespace ArchiSteamFarm {
internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> { internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> {
public T Current => Enumerator.Current; public T Current => Enumerator.Current;
object IEnumerator.Current => Current;
private readonly IEnumerator<T> Enumerator; private readonly IEnumerator<T> Enumerator;
private readonly ReaderWriterLockSlim Lock; private readonly ReaderWriterLockSlim Lock;
object IEnumerator.Current => Current;
internal ConcurrentEnumerator(ICollection<T> collection, ReaderWriterLockSlim rwLock) { internal ConcurrentEnumerator(ICollection<T> collection, ReaderWriterLockSlim rwLock) {
if ((collection == null) || (rwLock == null)) { if ((collection == null) || (rwLock == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(rwLock)); throw new ArgumentNullException(nameof(collection) + " || " + nameof(rwLock));
@ -47,9 +47,9 @@ namespace ArchiSteamFarm {
Enumerator = collection.GetEnumerator(); Enumerator = collection.GetEnumerator();
} }
public void Dispose() => Lock?.ExitReadLock();
public bool MoveNext() => Enumerator.MoveNext(); public bool MoveNext() => Enumerator.MoveNext();
public void Reset() => Enumerator.Reset(); public void Reset() => Enumerator.Reset();
public void Dispose() => Lock?.ExitReadLock();
} }
} }

View file

@ -29,15 +29,6 @@ using System.Threading;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class ConcurrentHashSet<T> : ICollection<T>, IDisposable { internal sealed class ConcurrentHashSet<T> : ICollection<T>, IDisposable {
private readonly HashSet<T> HashSet = new HashSet<T>();
private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
public bool IsReadOnly => false;
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
void ICollection<T>.Add(T item) => Add(item);
public int Count { public int Count {
get { get {
Lock.EnterReadLock(); Lock.EnterReadLock();
@ -50,6 +41,11 @@ namespace ArchiSteamFarm {
} }
} }
public bool IsReadOnly => false;
private readonly HashSet<T> HashSet = new HashSet<T>();
private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
public void Clear() { public void Clear() {
Lock.EnterWriteLock(); 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) { public void CopyTo(T[] array, int arrayIndex) {
Lock.EnterReadLock(); Lock.EnterReadLock();
@ -92,6 +76,23 @@ namespace ArchiSteamFarm {
} }
} }
public void Dispose() => Lock.Dispose();
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock);
public bool Remove(T item) {
Lock.EnterWriteLock();
try {
return HashSet.Remove(item);
} finally {
Lock.ExitWriteLock();
}
}
void ICollection<T>.Add(T item) => Add(item);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal void Add(T item) { internal void Add(T item) {
Lock.EnterWriteLock(); 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<T> items) { internal bool ReplaceIfNeededWith(HashSet<T> items) {
Lock.EnterUpgradeableReadLock(); Lock.EnterUpgradeableReadLock();
@ -133,17 +145,6 @@ namespace ArchiSteamFarm {
} }
} }
internal void ClearAndTrim() {
Lock.EnterWriteLock();
try {
HashSet.Clear();
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
internal void TrimExcess() { internal void TrimExcess() {
Lock.EnterWriteLock(); Lock.EnterWriteLock();

View file

@ -28,21 +28,24 @@ using System.Text;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class CryptoHelper { internal static class CryptoHelper {
internal enum ECryptoMethod : byte {
PlainText,
AES,
ProtectedDataForCurrentUser
}
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes("ArchiSteamFarm"); private static byte[] EncryptionKey = Encoding.UTF8.GetBytes("ArchiSteamFarm");
internal static void SetEncryptionKey(string key) { internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) {
if (string.IsNullOrEmpty(key)) { if (string.IsNullOrEmpty(encrypted)) {
ASF.ArchiLogger.LogNullError(nameof(key)); ASF.ArchiLogger.LogNullError(nameof(encrypted));
return; 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) { 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)) { if (string.IsNullOrEmpty(encrypted)) {
ASF.ArchiLogger.LogNullError(nameof(encrypted)); ASF.ArchiLogger.LogNullError(nameof(encrypted));
return null; return null;
} }
switch (cryptoMethod) { try {
case ECryptoMethod.PlainText: byte[] key;
return encrypted; using (SHA256Cng sha256 = new SHA256Cng()) {
case ECryptoMethod.AES: key = sha256.ComputeHash(EncryptionKey);
return DecryptAES(encrypted); }
case ECryptoMethod.ProtectedDataForCurrentUser:
return DecryptProtectedDataForCurrentUser(encrypted); byte[] decryptedData = Convert.FromBase64String(encrypted);
default: decryptedData = SteamKit2.CryptoHelper.SymmetricDecrypt(decryptedData, key);
return null; 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) { private static string EncryptProtectedDataForCurrentUser(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) { if (string.IsNullOrEmpty(decrypted)) {
ASF.ArchiLogger.LogNullError(nameof(decrypted)); ASF.ArchiLogger.LogNullError(nameof(decrypted));
@ -130,11 +141,8 @@ namespace ArchiSteamFarm {
} }
try { try {
byte[] encryptedData = ProtectedData.Protect( byte[] encryptedData = ProtectedData.Protect(Encoding.UTF8.GetBytes(decrypted), EncryptionKey, // This is used as salt only and it's fine that it's known
Encoding.UTF8.GetBytes(decrypted), DataProtectionScope.CurrentUser);
EncryptionKey, // This is used as salt only and it's fine that it's known
DataProtectionScope.CurrentUser
);
return Convert.ToBase64String(encryptedData); return Convert.ToBase64String(encryptedData);
} catch (Exception e) { } catch (Exception e) {
@ -143,24 +151,10 @@ namespace ArchiSteamFarm {
} }
} }
private static string DecryptProtectedDataForCurrentUser(string encrypted) { internal enum ECryptoMethod : byte {
if (string.IsNullOrEmpty(encrypted)) { PlainText,
ASF.ArchiLogger.LogNullError(nameof(encrypted)); AES,
return null; ProtectedDataForCurrentUser
}
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;
}
} }
} }
} }

View file

@ -22,8 +22,8 @@
*/ */
using SteamKit2;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using SteamKit2;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Debugging { internal static class Debugging {

View file

@ -28,9 +28,6 @@ using SteamKit2;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Events { internal static class Events {
internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) {
}
internal static async void OnBotShutdown() { internal static async void OnBotShutdown() {
if (Program.IsWCFRunning || Bot.Bots.Values.Any(bot => bot.KeepRunning)) { if (Program.IsWCFRunning || Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
return; return;
@ -40,5 +37,7 @@ namespace ArchiSteamFarm {
await Task.Delay(5000).ConfigureAwait(false); await Task.Delay(5000).ConfigureAwait(false);
Program.Shutdown(); Program.Shutdown();
} }
internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) { }
} }
} }

View file

@ -22,94 +22,90 @@
*/ */
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
using Newtonsoft.Json;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")] [SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class GlobalConfig { internal sealed class GlobalConfig {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum EUpdateChannel : byte {
None,
Stable,
Experimental
}
internal const byte DefaultHttpTimeout = 60; internal const byte DefaultHttpTimeout = 60;
private const byte DefaultMaxFarmingTime = 10;
private const byte DefaultFarmingDelay = 15; private const byte DefaultFarmingDelay = 15;
private const ushort DefaultWCFPort = 1242; private const byte DefaultMaxFarmingTime = 10;
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp; private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
private const ushort DefaultWCFPort = 1242;
// This is hardcoded blacklist which should not be possible to change // This is hardcoded blacklist which should not be possible to change
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 }; internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Debug = false; internal readonly bool AutoRestart = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Headless = false;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AutoUpdates = true; internal readonly bool AutoUpdates = true;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AutoRestart = true; internal readonly HashSet<uint> Blacklist = new HashSet<uint>(GlobalBlacklist);
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable; internal readonly bool Debug = false;
[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;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly byte FarmingDelay = DefaultFarmingDelay; internal readonly byte FarmingDelay = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly byte LoginLimiterDelay = 10; internal readonly bool ForceHttp = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte InventoryLimiterDelay = 3;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly byte GiftsLimiterDelay = 1; internal readonly byte GiftsLimiterDelay = 1;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly byte MaxTradeHoldDuration = 15; internal readonly bool Headless = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool ForceHttp = false;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly byte HttpTimeout = DefaultHttpTimeout; internal readonly byte HttpTimeout = DefaultHttpTimeout;
[JsonProperty] [JsonProperty(Required = Required.DisallowNull)]
internal string WCFHostname { get; set; } = "localhost"; internal readonly byte IdleFarmingPeriod = 3;
[JsonProperty(Required = Required.DisallowNull)] [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)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Statistics = true; internal readonly bool Statistics = true;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet<uint> Blacklist = new HashSet<uint>(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) { internal static GlobalConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
@ -171,7 +167,11 @@ namespace ArchiSteamFarm {
return null; return null;
} }
// This constructor is used only by deserializer [SuppressMessage("ReSharper", "UnusedMember.Global")]
private GlobalConfig() { } internal enum EUpdateChannel : byte {
None,
Stable,
Experimental
}
} }
} }

View file

@ -22,28 +22,23 @@
*/ */
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using Newtonsoft.Json;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class GlobalDatabase : IDisposable { internal sealed class GlobalDatabase : IDisposable {
private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings { private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings { Converters = new List<JsonConverter>(2) { new IPAddressConverter(), new IPEndPointConverter() } };
Converters = new List<JsonConverter>(2) {
new IPAddressConverter(),
new IPEndPointConverter()
}
};
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
private uint _CellID; internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
private readonly object FileLock = new object();
internal uint CellID { internal uint CellID {
get { get { return _CellID; }
return _CellID;
}
set { set {
if ((value == 0) || (_CellID == value)) { if ((value == 0) || (_CellID == value)) {
return; return;
@ -55,12 +50,30 @@ namespace ArchiSteamFarm {
} }
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider(); private uint _CellID;
private readonly object FileLock = new object();
private string FilePath; 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) { internal static GlobalDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath)); ASF.ArchiLogger.LogNullError(nameof(filePath));
@ -89,26 +102,6 @@ namespace ArchiSteamFarm {
return globalDatabase; 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 OnServerListUpdated(object sender, EventArgs e) => Save();
private void Save() { private void Save() {

View file

@ -34,8 +34,7 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<IPEndPoint> Servers = new ConcurrentHashSet<IPEndPoint>(); private readonly ConcurrentHashSet<IPEndPoint> Servers = new ConcurrentHashSet<IPEndPoint>();
internal event EventHandler ServerListUpdated; public void Dispose() => Servers.Dispose();
public Task<IEnumerable<IPEndPoint>> FetchServerListAsync() => Task.FromResult<IEnumerable<IPEndPoint>>(Servers); public Task<IEnumerable<IPEndPoint>> FetchServerListAsync() => Task.FromResult<IEnumerable<IPEndPoint>>(Servers);
public Task UpdateServerListAsync(IEnumerable<IPEndPoint> endPoints) { public Task UpdateServerListAsync(IEnumerable<IPEndPoint> endPoints) {
@ -54,6 +53,6 @@ namespace ArchiSteamFarm {
return Task.Delay(0); return Task.Delay(0);
} }
public void Dispose() => Servers.Dispose(); internal event EventHandler ServerListUpdated;
} }
} }

View file

@ -32,11 +32,11 @@ namespace ArchiSteamFarm.JSON {
internal sealed class ReleaseResponse { internal sealed class ReleaseResponse {
#pragma warning disable 649 #pragma warning disable 649
internal sealed class Asset { internal sealed class Asset {
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal readonly string Name;
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)] [JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal readonly string DownloadURL; internal readonly string DownloadURL;
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal readonly string Name;
} }
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)] [JsonProperty(PropertyName = "tag_name", Required = Required.Always)]

View file

@ -33,137 +33,207 @@ using SteamKit2;
namespace ArchiSteamFarm.JSON { namespace ArchiSteamFarm.JSON {
internal static class Steam { 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 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 ushort SteamAppID = 753;
internal const byte SteamCommunityContextID = 6; internal const byte SteamCommunityContextID = 6;
internal enum EType : byte { internal uint Amount { get; private set; }
Unknown,
BoosterPack,
Coupon,
Gift,
SteamGems,
Emoticon,
FoilTradingCard,
ProfileBackground,
TradingCard
}
internal uint AppID { get; set; } internal uint AppID { get; set; }
internal ulong ClassID { get; private 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 ContextID { get; set; } internal ulong ContextID { get; set; }
internal uint RealAppID { get; set; }
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)] internal EType Type { get; set; }
[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;
}
}
private ulong AssetID; 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)] [JsonProperty(PropertyName = "amount", Required = Required.Always)]
[SuppressMessage("ReSharper", "UnusedMember.Local")] [SuppressMessage("ReSharper", "UnusedMember.Local")]
private string AmountString { private string AmountString {
get { get { return Amount.ToString(); }
return Amount.ToString();
}
set { set {
if (string.IsNullOrEmpty(value)) { if (string.IsNullOrEmpty(value)) {
@ -181,8 +251,94 @@ namespace ArchiSteamFarm.JSON {
} }
} }
internal uint RealAppID { get; set; } [JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)]
internal EType Type { get; set; } [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 // 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) { 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")] [SuppressMessage("ReSharper", "UnusedMember.Local")]
private Item() { } 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", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal enum ETradeOfferState : byte { internal sealed class RedeemWalletResponse { // Deserialized from JSON
Unknown, #pragma warning disable 649
Invalid, [JsonProperty(PropertyName = "detail", Required = Required.Always)]
Active, internal readonly ArchiHandler.PurchaseResponseCallback.EPurchaseResult PurchaseResult;
Accepted, #pragma warning restore 649
Countered,
Expired,
Canceled,
Declined,
InvalidItems,
EmailPending,
EmailCanceled,
OnHold
}
internal readonly ulong TradeOfferID; private RedeemWalletResponse() { }
internal readonly ETradeOfferState State; }
internal sealed class TradeOffer {
internal readonly HashSet<Item> ItemsToGive = new HashSet<Item>(); internal readonly HashSet<Item> ItemsToGive = new HashSet<Item>();
internal readonly HashSet<Item> ItemsToReceive = new HashSet<Item>(); internal readonly HashSet<Item> ItemsToReceive = new HashSet<Item>();
internal readonly ETradeOfferState State;
internal readonly ulong TradeOfferID;
private readonly uint OtherSteamID3;
private ulong _OtherSteamID64;
internal ulong OtherSteamID64 { internal ulong OtherSteamID64 {
get { get {
if (_OtherSteamID64 != 0) { 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) { internal TradeOffer(ulong tradeOfferID, uint otherSteamID3, ETradeOfferState state) {
if ((tradeOfferID == 0) || (otherSteamID3 == 0) || (state == ETradeOfferState.Unknown)) { if ((tradeOfferID == 0) || (otherSteamID3 == 0) || (state == ETradeOfferState.Unknown)) {
throw new ArgumentNullException(nameof(tradeOfferID) + " || " + nameof(otherSteamID3) + " || " + nameof(state)); throw new ArgumentNullException(nameof(tradeOfferID) + " || " + nameof(otherSteamID3) + " || " + nameof(state));
@ -253,8 +419,6 @@ namespace ArchiSteamFarm.JSON {
State = state; State = state;
} }
internal bool IsSteamCardsRequest() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamCommunityContextID) && (item.Type == Item.EType.TradingCard));
internal bool IsFairTypesExchange() { internal bool IsFairTypesExchange() {
Dictionary<uint, Dictionary<Item.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>(); Dictionary<uint, Dictionary<Item.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>();
foreach (Item item in ItemsToGive) { foreach (Item item in ItemsToGive) {
@ -309,217 +473,38 @@ namespace ArchiSteamFarm.JSON {
return true; return true;
} }
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] 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
[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
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")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class TradeOfferRequest { // Constructed from code internal sealed class TradeOfferRequest {
internal sealed class ItemList {
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal readonly HashSet<Item> Assets = new HashSet<Item>();
}
[JsonProperty(PropertyName = "me", Required = Required.Always)] [JsonProperty(PropertyName = "me", Required = Required.Always)]
internal readonly ItemList ItemsToGive = new ItemList(); internal readonly ItemList ItemsToGive = new ItemList();
[JsonProperty(PropertyName = "them", Required = Required.Always)] [JsonProperty(PropertyName = "them", Required = Required.Always)]
internal readonly ItemList ItemsToReceive = new ItemList(); internal readonly ItemList ItemsToReceive = new ItemList(); // Constructed from code
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] internal sealed class ItemList {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] [JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal sealed class ConfirmationResponse { // Deserialized from JSON internal readonly HashSet<Item> Assets = new HashSet<Item>();
#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
} }
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() { }
} }
} }
} }

View file

@ -29,9 +29,9 @@ using NLog.Targets;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Logging { 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 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<LoggingRule> ConsoleLoggingRules = new ConcurrentHashSet<LoggingRule>(); private static readonly ConcurrentHashSet<LoggingRule> ConsoleLoggingRules = new ConcurrentHashSet<LoggingRule>();
@ -47,29 +47,18 @@ namespace ArchiSteamFarm {
LoggingConfiguration config = new LoggingConfiguration(); LoggingConfiguration config = new LoggingConfiguration();
ColoredConsoleTarget coloredConsoleTarget = new ColoredConsoleTarget("ColoredConsole") { ColoredConsoleTarget coloredConsoleTarget = new ColoredConsoleTarget("ColoredConsole") { DetectConsoleAvailable = false, Layout = GeneralLayout };
DetectConsoleAvailable = false,
Layout = GeneralLayout
};
config.AddTarget(coloredConsoleTarget); config.AddTarget(coloredConsoleTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, coloredConsoleTarget)); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, coloredConsoleTarget));
if (Program.IsRunningAsService) { if (Program.IsRunningAsService) {
EventLogTarget eventLogTarget = new EventLogTarget("EventLog") { EventLogTarget eventLogTarget = new EventLogTarget("EventLog") { Layout = EventLogLayout, Log = SharedInfo.EventLog, Source = SharedInfo.EventLogSource };
Layout = EventLogLayout,
Log = SharedInfo.EventLog,
Source = SharedInfo.EventLogSource
};
config.AddTarget(eventLogTarget); config.AddTarget(eventLogTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, eventLogTarget)); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, eventLogTarget));
} else if (!Program.Mode.HasFlag(Program.EMode.Client) || Program.Mode.HasFlag(Program.EMode.Server)) { } else if (!Program.Mode.HasFlag(Program.EMode.Client) || Program.Mode.HasFlag(Program.EMode.Server)) {
FileTarget fileTarget = new FileTarget("File") { FileTarget fileTarget = new FileTarget("File") { DeleteOldFileOnStartup = true, FileName = SharedInfo.LogFile, Layout = GeneralLayout };
DeleteOldFileOnStartup = true,
FileName = SharedInfo.LogFile,
Layout = GeneralLayout
};
config.AddTarget(fileTarget); config.AddTarget(fileTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
@ -79,20 +68,6 @@ namespace ArchiSteamFarm {
InitConsoleLoggers(); 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() { internal static void OnUserInputEnd() {
IsWaitingForUserInput = false; IsWaitingForUserInput = false;
@ -107,6 +82,20 @@ namespace ArchiSteamFarm {
LogManager.ReconfigExistingLoggers(); 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() { private static void InitConsoleLoggers() {
ConsoleLoggingRules.Clear(); ConsoleLoggingRules.Clear();
foreach (LoggingRule loggingRule in LogManager.Configuration.LoggingRules.Where(loggingRule => loggingRule.Targets.Any(target => target is ColoredConsoleTarget || target is ConsoleTarget))) { foreach (LoggingRule loggingRule in LogManager.Configuration.LoggingRules.Where(loggingRule => loggingRule.Targets.Any(target => target is ColoredConsoleTarget || target is ConsoleTarget))) {

View file

@ -37,61 +37,26 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")] [SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class MobileAuthenticator : IDisposable { 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 CodeDigits = 5;
private const byte CodeInterval = 30; private const byte CodeInterval = 30;
private static readonly char[] CodeCharacters = { 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' };
'2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C',
'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q',
'R', 'T', 'V', 'W', 'X', 'Y'
};
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1); private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
private static short? SteamTimeDifference; 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); private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1);
#pragma warning disable 649 private Bot Bot;
[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
[JsonProperty(PropertyName = "device_id")] [JsonProperty(PropertyName = "device_id")]
private string DeviceID; private string DeviceID;
private Bot Bot;
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
private MobileAuthenticator() { } private MobileAuthenticator() { }
internal void Init(Bot bot) { public void Dispose() => ConfirmationsSemaphore.Dispose();
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
}
Bot = bot;
}
internal void CorrectDeviceID(string deviceID) { internal void CorrectDeviceID(string deviceID) {
if (string.IsNullOrEmpty(deviceID)) { if (string.IsNullOrEmpty(deviceID)) {
@ -102,56 +67,14 @@ namespace ArchiSteamFarm {
DeviceID = deviceID; DeviceID = deviceID;
} }
internal async Task<bool> HandleConfirmations(HashSet<Confirmation> confirmations, bool accept) { internal async Task<string> GenerateToken() {
if ((confirmations == null) || (confirmations.Count == 0)) { uint time = await GetSteamTime().ConfigureAwait(false);
Bot.ArchiLogger.LogNullError(nameof(confirmations)); if (time != 0) {
return false; return GenerateTokenForTime(time);
} }
if (!HasCorrectDeviceID) { Bot.ArchiLogger.LogNullError(nameof(time));
Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!"); return null;
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();
}
} }
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(Confirmation confirmation) { internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(Confirmation confirmation) {
@ -185,16 +108,6 @@ namespace ArchiSteamFarm {
return response; return response;
} }
internal async Task<string> GenerateToken() {
uint time = await GetSteamTime().ConfigureAwait(false);
if (time != 0) {
return GenerateTokenForTime(time);
}
Bot.ArchiLogger.LogNullError(nameof(time));
return null;
}
internal async Task<HashSet<Confirmation>> GetConfirmations() { internal async Task<HashSet<Confirmation>> GetConfirmations() {
if (!HasCorrectDeviceID) { if (!HasCorrectDeviceID) {
Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!"); Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!");
@ -271,68 +184,64 @@ namespace ArchiSteamFarm {
return result; return result;
} }
private async Task<uint> GetSteamTime() { internal async Task<bool> HandleConfirmations(HashSet<Confirmation> confirmations, bool accept) {
if (SteamTimeDifference.HasValue) { if ((confirmations == null) || (confirmations.Count == 0)) {
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault()); 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 { try {
if (!SteamTimeDifference.HasValue) { uint time = await GetSteamTime().ConfigureAwait(false);
uint serverTime = Bot.ArchiWebHandler.GetServerTime(); if (time == 0) {
if (serverTime != 0) { Bot.ArchiLogger.LogNullError(nameof(time));
SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime()); 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) { internal void Init(Bot bot) {
if (time == 0) { if (bot == null) {
Bot.ArchiLogger.LogNullError(nameof(time)); throw new ArgumentNullException(nameof(bot));
return null;
} }
byte[] sharedSecret = Convert.FromBase64String(SharedSecret); Bot = bot;
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 string GenerateConfirmationKey(uint time, string tag = null) { private string GenerateConfirmationKey(uint time, string tag = null) {
@ -368,6 +277,92 @@ namespace ArchiSteamFarm {
return Convert.ToBase64String(hash); 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<uint> 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
} }
} }

View file

@ -31,50 +31,28 @@ using System.Reflection;
using System.ServiceProcess; using System.ServiceProcess;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SteamKit2;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Program { internal static class Program {
[Flags] internal static bool IsWCFRunning => WCF.IsServerRunning;
internal enum EMode : byte { internal static GlobalConfig GlobalConfig { get; private set; }
Normal = 0, // Standard most common usage internal static GlobalDatabase GlobalDatabase { get; private set; }
Client = 1, // WCF client internal static bool IsRunningAsService { get; private set; }
Server = 2 // WCF server 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 object ConsoleLock = new object();
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false); private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
private static readonly WCF WCF = new WCF(); 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; private static bool ShutdownSequenceInitialized;
internal static bool IsWCFRunning => WCF.IsServerRunning;
internal static void Exit(byte exitCode = 0) { internal static void Exit(byte exitCode = 0) {
Shutdown(); Shutdown();
Environment.Exit(exitCode); 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) { internal static string GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) {
if (userInputType == ASF.EUserInputType.Unknown) { if (userInputType == ASF.EUserInputType.Unknown) {
return null; return null;
@ -137,6 +115,21 @@ namespace ArchiSteamFarm {
return !string.IsNullOrEmpty(result) ? result.Trim() : null; 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() { internal static void Shutdown() {
if (!InitShutdownSequence()) { if (!InitShutdownSequence()) {
return; return;
@ -145,19 +138,72 @@ namespace ArchiSteamFarm {
ShutdownResetEvent.Set(); ShutdownResetEvent.Set();
} }
private static bool InitShutdownSequence() { private static void Init(string[] args) {
if (ShutdownSequenceInitialized) { AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
return false; 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; // Parse pre-init args
if (args != null) {
WCF.StopServer(); ParsePreInitArgs(args);
foreach (Bot bot in Bot.Bots.Values) {
bot.Stop();
} }
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() { private static void InitServices() {
@ -186,30 +232,36 @@ namespace ArchiSteamFarm {
WebBrowser = new WebBrowser(ASF.ArchiLogger); WebBrowser = new WebBrowser(ASF.ArchiLogger);
} }
private static void ParsePreInitArgs(IEnumerable<string> args) { private static bool InitShutdownSequence() {
if (args == null) { if (ShutdownSequenceInitialized) {
ASF.ArchiLogger.LogNullError(nameof(args)); return false;
return;
} }
foreach (string arg in args) { ShutdownSequenceInitialized = true;
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; 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<string> 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) { private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if (args?.ExceptionObject == null) { if (args?.ExceptionObject == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject)); ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
@ -271,92 +351,11 @@ namespace ArchiSteamFarm {
ASF.ArchiLogger.LogFatalException(args.Exception); ASF.ArchiLogger.LogFatalException(args.Exception);
} }
private static void Init(string[] args) { [Flags]
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; internal enum EMode : byte {
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; Normal = 0, // Standard most common usage
Client = 1, // WCF client
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); Server = 2 // WCF server
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);
}
}
} }
private sealed class Service : ServiceBase { private sealed class Service : ServiceBase {
@ -373,5 +372,4 @@ namespace ArchiSteamFarm {
protected override void OnStop() => Shutdown(); protected override void OnStop() => Shutdown();
} }
} }
} }

View file

@ -5,6 +5,7 @@ using ArchiSteamFarm;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information // set of attributes. Change these attribute values to modify the information
// associated with an assembly. // associated with an assembly.
[assembly: AssemblyTitle(SharedInfo.ServiceName)] [assembly: AssemblyTitle(SharedInfo.ServiceName)]
[assembly: AssemblyDescription(SharedInfo.ServiceDescription)] [assembly: AssemblyDescription(SharedInfo.ServiceDescription)]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
@ -17,9 +18,11 @@ using ArchiSteamFarm;
// Setting ComVisible to false makes the types in this assembly not visible // 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 // to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type. // COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM // The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("35af7887-08b9-40e8-a5ea-797d8b60b30c")] [assembly: Guid("35af7887-08b9-40e8-a5ea-797d8b60b30c")]
// Version information for an assembly consists of the following four values: // 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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion(SharedInfo.VersionNumber)] [assembly: AssemblyVersion(SharedInfo.VersionNumber)]
[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)] [assembly: AssemblyFileVersion(SharedInfo.VersionNumber)]

View file

@ -28,34 +28,8 @@ using Microsoft.Win32;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Runtime { internal static class Runtime {
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
internal static bool IsRunningOnMono => MonoRuntime != null; 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 { internal static bool IsRuntimeSupported {
get { get {
if (_IsRuntimeSupported.HasValue) { 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() { private static Version GetNetVersion() {
uint release; uint release;
using (RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) { 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; 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;
}
} }
} }

View file

@ -27,29 +27,23 @@ using System.Reflection;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class SharedInfo { internal static class SharedInfo {
internal const string VersionNumber = "2.1.6.9"; internal const ulong ArchiSteamID = 76561198006963719;
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 string ASF = "ASF"; internal const string ASF = "ASF";
internal const string ASFDirectory = "ArchiSteamFarm"; 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 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 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 GlobalConfigFileName = ASF + ".json";
internal const string GlobalDatabaseFileName = ASF + ".db"; 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; internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
} }

View file

@ -31,28 +31,6 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class Trading : IDisposable { 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 MaxItemsPerTrade = 150; // This is due to limit on POST size in WebBrowser
internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve
@ -64,14 +42,6 @@ namespace ArchiSteamFarm {
private bool ParsingScheduled; 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) { internal Trading(Bot bot) {
if (bot == null) { if (bot == null) {
throw new ArgumentNullException(nameof(bot)); throw new ArgumentNullException(nameof(bot));
@ -85,8 +55,6 @@ namespace ArchiSteamFarm {
TradesSemaphore.Dispose(); TradesSemaphore.Dispose();
} }
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim();
internal async Task CheckTrades() { internal async Task CheckTrades() {
// We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue // 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 // 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() { private async Task ParseActiveTrades() {
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) { if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
return; 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 // 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)); 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
}
}
} }
} }

View file

@ -41,22 +41,18 @@ namespace ArchiSteamFarm {
internal sealed class WCF : IWCF, IDisposable { internal sealed class WCF : IWCF, IDisposable {
private static string URL = "http://localhost:1242/ASF"; private static string URL = "http://localhost:1242/ASF";
private ServiceHost ServiceHost;
private Client Client;
internal bool IsServerRunning => ServiceHost != null; internal bool IsServerRunning => ServiceHost != null;
internal static void Init() { private Client Client;
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) { private ServiceHost ServiceHost;
Program.GlobalConfig.WCFHostname = Program.GetUserInput(ASF.EUserInputType.WCFHostname);
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
return;
}
}
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) { public string HandleCommand(string input) {
if (string.IsNullOrEmpty(input)) { if (string.IsNullOrEmpty(input)) {
ASF.ArchiLogger.LogNullError(nameof(input)); ASF.ArchiLogger.LogNullError(nameof(input));
@ -79,11 +75,28 @@ namespace ArchiSteamFarm {
return output; 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() { URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF";
StopClient(); }
StopServer();
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() { internal void StartServer() {
@ -96,9 +109,7 @@ namespace ArchiSteamFarm {
try { try {
ServiceHost = new ServiceHost(typeof(WCF), new Uri(URL)); ServiceHost = new ServiceHost(typeof(WCF), new Uri(URL));
ServiceHost.Description.Behaviors.Add(new ServiceMetadataBehavior { ServiceHost.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });
HttpGetEnabled = true
});
ServiceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); ServiceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
ServiceHost.AddServiceEndpoint(typeof(IWCF), new BasicHttpBinding(), string.Empty); ServiceHost.AddServiceEndpoint(typeof(IWCF), new BasicHttpBinding(), string.Empty);
@ -129,19 +140,6 @@ namespace ArchiSteamFarm {
ServiceHost = null; 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() { private void StopClient() {
if (Client == null) { if (Client == null) {
return; return;

View file

@ -22,15 +22,15 @@
*/ */
using HtmlAgilityPack;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml; using System.Xml;
using HtmlAgilityPack;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class WebBrowser { internal sealed class WebBrowser {
@ -44,12 +44,27 @@ namespace ArchiSteamFarm {
private readonly ArchiLogger ArchiLogger; private readonly ArchiLogger ArchiLogger;
private readonly HttpClient HttpClient; 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() { internal static void Init() {
// Set max connection limit from default of 2 to desired value // Set max connection limit from default of 2 to desired value
ServicePointManager.DefaultConnectionLimit = MaxConnections; ServicePointManager.DefaultConnectionLimit = MaxConnections;
// Set max idle time from default of 100 seconds (100 * 1000) to desired value // 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 // Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets
ServicePointManager.Expect100Continue = false; ServicePointManager.Expect100Continue = false;
@ -66,68 +81,6 @@ namespace ArchiSteamFarm {
#endif #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<bool> 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<Uri> 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<byte[]> UrlGetToBytesRetry(string request, string referer = null) { internal async Task<byte[]> UrlGetToBytesRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request)); ArchiLogger.LogNullError(nameof(request));
@ -223,6 +176,44 @@ namespace ArchiSteamFarm {
return null; return null;
} }
internal async Task<bool> 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<Uri> 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<bool> UrlPostRetry(string request, ICollection<KeyValuePair<string, string>> data = null, string referer = null) { internal async Task<bool> UrlPostRetry(string request, ICollection<KeyValuePair<string, string>> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request)); ArchiLogger.LogNullError(nameof(request));
@ -277,6 +268,10 @@ namespace ArchiSteamFarm {
} }
} }
#if !__MonoCS__
private static void InitNonMonoBehaviour() => ServicePointManager.ReusePort = true;
#endif
private async Task<byte[]> UrlGetToBytes(string request, string referer = null) { private async Task<byte[]> UrlGetToBytes(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request)); ArchiLogger.LogNullError(nameof(request));

View file

@ -1,29 +1,29 @@
{ {
"Debug": false, "Debug": false,
"Headless": false, "Headless": false,
"AutoUpdates": true, "AutoUpdates": true,
"AutoRestart": true, "AutoRestart": true,
"UpdateChannel": 1, "UpdateChannel": 1,
"SteamProtocol": 6, "SteamProtocol": 6,
"SteamOwnerID": 0, "SteamOwnerID": 0,
"MaxFarmingTime": 10, "MaxFarmingTime": 10,
"IdleFarmingPeriod": 3, "IdleFarmingPeriod": 3,
"FarmingDelay": 15, "FarmingDelay": 15,
"LoginLimiterDelay": 10, "LoginLimiterDelay": 10,
"InventoryLimiterDelay": 3, "InventoryLimiterDelay": 3,
"GiftsLimiterDelay": 1, "GiftsLimiterDelay": 1,
"MaxTradeHoldDuration": 15, "MaxTradeHoldDuration": 15,
"ForceHttp": false, "ForceHttp": false,
"HttpTimeout": 60, "HttpTimeout": 60,
"WCFHostname": "localhost", "WCFHostname": "localhost",
"WCFPort": 1242, "WCFPort": 1242,
"Statistics": true, "Statistics": true,
"Blacklist": [ "Blacklist": [
267420, 267420,
303700, 303700,
335590, 335590,
368020, 368020,
425280, 425280,
480730 480730
] ]
} }

View file

@ -1,29 +1,29 @@
{ {
"Enabled": false, "Enabled": false,
"Paused": false, "Paused": false,
"SteamLogin": null, "SteamLogin": null,
"SteamPassword": null, "SteamPassword": null,
"PasswordFormat": 0, "PasswordFormat": 0,
"SteamParentalPIN": "0", "SteamParentalPIN": "0",
"SteamApiKey": null, "SteamApiKey": null,
"SteamMasterID": 0, "SteamMasterID": 0,
"SteamMasterClanID": 0, "SteamMasterClanID": 0,
"CardDropsRestricted": true, "CardDropsRestricted": true,
"DismissInventoryNotifications": true, "DismissInventoryNotifications": true,
"FarmingOrder": 0, "FarmingOrder": 0,
"FarmOffline": false, "FarmOffline": false,
"HandleOfflineMessages": false, "HandleOfflineMessages": false,
"AcceptGifts": false, "AcceptGifts": false,
"IsBotAccount": false, "IsBotAccount": false,
"ForwardKeysToOtherBots": false, "ForwardKeysToOtherBots": false,
"DistributeKeys": false, "DistributeKeys": false,
"ShutdownOnFarmingFinished": false, "ShutdownOnFarmingFinished": false,
"SendOnFarmingFinished": false, "SendOnFarmingFinished": false,
"SteamTradeToken": null, "SteamTradeToken": null,
"SendTradePeriod": 0, "SendTradePeriod": 0,
"TradingPreferences": 1, "TradingPreferences": 1,
"AcceptConfirmationsPeriod": 0, "AcceptConfirmationsPeriod": 0,
"CustomGamePlayedWhileFarming": null, "CustomGamePlayedWhileFarming": null,
"CustomGamePlayedWhileIdle": null, "CustomGamePlayedWhileIdle": null,
"GamesPlayedWhileIdle": [ ] "GamesPlayedWhileIdle": []
} }

View file

@ -1,5 +1,5 @@
{ {
"Enabled": false, "Enabled": false,
"SteamLogin": null, "SteamLogin": null,
"SteamPassword": null "SteamPassword": null
} }

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Costura.Fody" version="2.0.0-beta0018" targetFramework="net461" developmentDependency="true" /> <package id="Costura.Fody" version="2.0.0-beta0018" targetFramework="net461" developmentDependency="true" />
<package id="Fody" version="1.30.0-beta01" targetFramework="net461" developmentDependency="true" /> <package id="Fody" version="1.30.0-beta01" targetFramework="net461" developmentDependency="true" />

View file

@ -22,20 +22,20 @@
*/ */
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using ArchiSteamFarm; using ArchiSteamFarm;
using Newtonsoft.Json;
namespace ConfigGenerator { namespace ConfigGenerator {
internal abstract class ASFConfig { internal abstract class ASFConfig {
internal static readonly HashSet<ASFConfig> ASFConfigs = new HashSet<ASFConfig>(); internal static readonly HashSet<ASFConfig> ASFConfigs = new HashSet<ASFConfig>();
internal string FilePath { get; set; }
private readonly object FileLock = new object(); private readonly object FileLock = new object();
internal string FilePath { get; set; }
protected ASFConfig() { protected ASFConfig() {
ASFConfigs.Add(this); ASFConfigs.Add(this);
} }
@ -48,16 +48,6 @@ namespace ConfigGenerator {
FilePath = filePath; 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() { internal void Remove() {
string queryPath = Path.GetFileNameWithoutExtension(FilePath); string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FileLock) { lock (FileLock) {
@ -92,5 +82,15 @@ namespace ConfigGenerator {
FilePath = Path.Combine(SharedInfo.ConfigDirectory, botName + ".json"); 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);
}
}
}
} }
} }

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup> </startup>
</configuration> </configuration>

View file

@ -22,12 +22,13 @@
*/ */
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Drawing.Design;
using System.IO; using System.IO;
using Newtonsoft.Json;
namespace ConfigGenerator { namespace ConfigGenerator {
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
@ -35,120 +36,16 @@ namespace ConfigGenerator {
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class BotConfig : ASFConfig { 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")] [Category("\tAdvanced")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool Paused { get; set; } = false; public byte AcceptConfirmationsPeriod { get; set; } = 0;
[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;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool AcceptGifts { get; set; } = false; public bool AcceptGifts { get; set; } = false;
[Category("\tAdvanced")] [Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool IsBotAccount { get; set; } = false; public bool CardDropsRestricted { get; set; } = true;
[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;
[JsonProperty] [JsonProperty]
public string CustomGamePlayedWhileFarming { get; set; } = null; public string CustomGamePlayedWhileFarming { get; set; } = null;
@ -156,9 +53,98 @@ namespace ConfigGenerator {
[JsonProperty] [JsonProperty]
public string CustomGamePlayedWhileIdle { get; set; } = null; 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)] [JsonProperty(Required = Required.DisallowNull)]
public List<uint> GamesPlayedWhileIdle { get; set; } = new List<uint>(); public List<uint> GamesPlayedWhileIdle { get; set; } = new List<uint>();
[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) { internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
Logging.LogNullError(nameof(filePath)); Logging.LogNullError(nameof(filePath));
@ -187,15 +173,30 @@ namespace ConfigGenerator {
return botConfig; return botConfig;
} }
[SuppressMessage("ReSharper", "UnusedMember.Local")] internal enum ECryptoMethod : byte {
private BotConfig() { } PlainText,
AES,
ProtectedDataForCurrentUser
}
private BotConfig(string filePath) : base(filePath) { internal enum EFarmingOrder : byte {
if (string.IsNullOrEmpty(filePath)) { Unordered,
throw new ArgumentNullException(nameof(filePath)); AppIDsAscending,
} AppIDsDescending,
CardDropsAscending,
CardDropsDescending,
HoursAscending,
HoursDescending,
NamesAscending,
NamesDescending
}
Save(); [Flags]
internal enum ETradingPreferences : byte {
None = 0,
AcceptDonations = 1,
SteamTradeMatcher = 2,
MatchEverything = 4
} }
} }
} }

View file

@ -36,43 +36,11 @@ namespace ConfigGenerator {
return DialogResult.Abort; return DialogResult.Abort;
} }
TextBox textBox = new TextBox { TextBox textBox = new TextBox { Anchor = AnchorStyles.Right, Bounds = new Rectangle(12, 36, 372, 20), Width = 1000 };
Anchor = AnchorStyles.Right, Button buttonOk = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(228, 72, 75, 23), DialogResult = DialogResult.OK, Text = Resources.OK };
Bounds = new Rectangle(12, 36, 372, 20), Button buttonCancel = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(309, 72, 75, 23), DialogResult = DialogResult.Cancel, Text = Resources.Cancel };
Width = 1000 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 };
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(); DialogResult dialogResult = form.ShowDialog();
value = textBox.Text; value = textBox.Text;
@ -85,37 +53,10 @@ namespace ConfigGenerator {
return DialogResult.Abort; return DialogResult.Abort;
} }
Button buttonYes = new Button { Button buttonYes = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(228, 72, 75, 23), DialogResult = DialogResult.Yes, Text = Resources.Yes };
Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Button buttonNo = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(309, 72, 75, 23), DialogResult = DialogResult.No, Text = Resources.No };
Bounds = new Rectangle(228, 72, 75, 23), Label label = new Label { AutoSize = true, Bounds = new Rectangle(9, 20, 372, 13), Text = promptText };
DialogResult = DialogResult.Yes, 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 };
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(); DialogResult dialogResult = form.ShowDialog();
return dialogResult; return dialogResult;

View file

@ -43,6 +43,16 @@ namespace ConfigGenerator {
ToolbarVisible = false; 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) { protected override void OnPropertyValueChanged(PropertyValueChangedEventArgs args) {
if (args == null) { if (args == null) {
Logging.LogNullError(nameof(args)); Logging.LogNullError(nameof(args));
@ -74,15 +84,5 @@ namespace ConfigGenerator {
Tutorial.OnAction(Tutorial.EPhase.GlobalConfigReady); Tutorial.OnAction(Tutorial.EPhase.GlobalConfigReady);
} }
} }
protected override void OnGotFocus(EventArgs args) {
if (args == null) {
Logging.LogNullError(nameof(args));
return;
}
base.OnGotFocus(args);
ASFConfig.Save();
}
} }
} }

View file

@ -7,28 +7,31 @@ using System.Windows.Forms.Design;
namespace ConfigGenerator { namespace ConfigGenerator {
internal sealed class FlagCheckedListBox : CheckedListBox { 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() { internal FlagCheckedListBox() {
// This call is required by the Windows.Forms Form Designer. // This call is required by the Windows.Forms Form Designer.
InitializeComponent(); 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) { protected override void OnItemCheck(ItemCheckEventArgs e) {
base.OnItemCheck(e); base.OnItemCheck(e);
@ -42,9 +45,44 @@ namespace ConfigGenerator {
UpdateCheckedItems(item, e.NewValue); 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 // Checks/Unchecks items depending on the give bitvalue
private void UpdateCheckedItems(int value) { private void UpdateCheckedItems(int value) {
IsUpdatingCheckStates = true; IsUpdatingCheckStates = true;
// Iterate over all items // Iterate over all items
@ -57,7 +95,6 @@ namespace ConfigGenerator {
if (item.Value == 0) { if (item.Value == 0) {
SetItemChecked(i, value == 0); SetItemChecked(i, value == 0);
} else { } else {
// If the bit for the current item is on in the bitvalue, check it // If the bit for the current item is on in the bitvalue, check it
if (((item.Value & value) == item.Value) && (item.Value != 0)) { if (((item.Value & value) == item.Value) && (item.Value != 0)) {
SetItemChecked(i, true); SetItemChecked(i, true);
@ -70,20 +107,17 @@ namespace ConfigGenerator {
} }
IsUpdatingCheckStates = false; IsUpdatingCheckStates = false;
} }
// Updates items in the checklistbox // Updates items in the checklistbox
// composite = The item that was checked/unchecked // composite = The item that was checked/unchecked
// cs = The check state of that item // cs = The check state of that item
private void UpdateCheckedItems(FlagCheckedListBoxItem composite, CheckState cs) { private void UpdateCheckedItems(FlagCheckedListBoxItem composite, CheckState cs) {
// If the value of the item is 0, call directly. // If the value of the item is 0, call directly.
if (composite.Value == 0) { if (composite.Value == 0) {
UpdateCheckedItems(0); UpdateCheckedItems(0);
} }
// Get the total value of all checked items // 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); 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 // Update all items in the checklistbox based on the final bit value
UpdateCheckedItems(sum); 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 // Represents an item in the checklistbox
@ -161,13 +149,12 @@ namespace ConfigGenerator {
public override string ToString() => Caption; public override string ToString() => Caption;
} }
// UITypeEditor for flag enums // UITypeEditor for flag enums
internal sealed class FlagEnumUIEditor : UITypeEditor { internal sealed class FlagEnumUiEditor : UITypeEditor {
// The checklistbox // The checklistbox
private readonly FlagCheckedListBox FlagEnumCb; private readonly FlagCheckedListBox FlagEnumCb;
internal FlagEnumUIEditor() { internal FlagEnumUiEditor() {
FlagEnumCb = new FlagCheckedListBox { BorderStyle = BorderStyle.None }; FlagEnumCb = new FlagCheckedListBox { BorderStyle = BorderStyle.None };
} }

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Weavers> <Weavers>
<Costura IncludeDebugSymbols='false' /> <Costura IncludeDebugSymbols='false' />
</Weavers> </Weavers>

View file

@ -22,13 +22,13 @@
*/ */
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
using Newtonsoft.Json;
namespace ConfigGenerator { namespace ConfigGenerator {
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")] [SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
@ -36,52 +36,49 @@ namespace ConfigGenerator {
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class GlobalConfig : ASFConfig { 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 DefaultFarmingDelay = 15;
private const byte DefaultHttpTimeout = 60; private const byte DefaultHttpTimeout = 60;
private const ushort DefaultWCFPort = 1242; private const byte DefaultMaxFarmingTime = 10;
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp; private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
private const ushort DefaultWCFPort = 1242;
// This is hardcoded blacklist which should not be possible to change // This is hardcoded blacklist which should not be possible to change
private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 }; private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 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")] [Category("\tUpdates")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool AutoRestart { get; set; } = true; public bool AutoRestart { get; set; } = true;
[Category("\tUpdates")] [Category("\tUpdates")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable; public bool AutoUpdates { get; set; } = true;
[Category("\tAdvanced")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public ProtocolType SteamProtocol { get; set; } = DefaultSteamProtocol; public List<uint> Blacklist { get; set; } = new List<uint>();
[Category("\tAccess")] [Category("\tDebugging")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public ulong SteamOwnerID { get; set; } = 0; public bool Debug { get; set; } = false;
[Category("\tPerformance")] [Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)] [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")] [Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
@ -89,7 +86,7 @@ namespace ConfigGenerator {
[Category("\tPerformance")] [Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public byte FarmingDelay { get; set; } = DefaultFarmingDelay; public byte InventoryLimiterDelay { get; set; } = 3;
[Category("\tPerformance")] [Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
@ -97,22 +94,25 @@ namespace ConfigGenerator {
[Category("\tPerformance")] [Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public byte InventoryLimiterDelay { get; set; } = 3; public byte MaxFarmingTime { get; set; } = DefaultMaxFarmingTime;
[Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)]
public byte GiftsLimiterDelay { get; set; } = 1;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public byte MaxTradeHoldDuration { get; set; } = 15; public byte MaxTradeHoldDuration { get; set; } = 15;
[Category("\tDebugging")]
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public bool ForceHttp { get; set; } = false; public bool Statistics { get; set; } = true;
[Category("\tDebugging")] [Category("\tAccess")]
[JsonProperty(Required = Required.DisallowNull)] [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")] [Category("\tAccess")]
[JsonProperty] [JsonProperty]
@ -122,11 +122,17 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
public ushort WCFPort { get; set; } = DefaultWCFPort; public ushort WCFPort { get; set; } = DefaultWCFPort;
[JsonProperty(Required = Required.DisallowNull)] [SuppressMessage("ReSharper", "UnusedMember.Local")]
public bool Statistics { get; set; } = true; private GlobalConfig() { }
[JsonProperty(Required = Required.DisallowNull)] private GlobalConfig(string filePath) : base(filePath) {
public List<uint> Blacklist { get; set; } = new List<uint>(); if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(filePath));
}
Blacklist.AddRange(GlobalBlacklist);
Save();
}
internal static GlobalConfig Load(string filePath) { internal static GlobalConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
@ -192,16 +198,10 @@ namespace ConfigGenerator {
return globalConfig; return globalConfig;
} }
[SuppressMessage("ReSharper", "UnusedMember.Local")] internal enum EUpdateChannel : byte {
private GlobalConfig() { } None,
Stable,
private GlobalConfig(string filePath) : base(filePath) { Experimental
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(filePath));
}
Blacklist.AddRange(GlobalBlacklist);
Save();
} }
} }
} }

View file

@ -30,15 +30,6 @@ using ConfigGenerator.Properties;
namespace ConfigGenerator { namespace ConfigGenerator {
internal static class Logging { 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) { internal static void LogGenericErrorWithoutStacktrace(string message) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(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) { internal static void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) { if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message)); LogNullError(nameof(message));

View file

@ -46,6 +46,18 @@ namespace ConfigGenerator {
InitializeComponent(); 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) { private void MainForm_Load(object sender, EventArgs args) {
if ((sender == null) || (args == null)) { if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args)); Logging.LogNullError(nameof(sender) + " || " + nameof(args));
@ -73,6 +85,24 @@ namespace ConfigGenerator {
Tutorial.OnAction(Tutorial.EPhase.Start); 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) { private void MainTab_Selected(object sender, TabControlEventArgs args) {
if ((sender == null) || (args == null)) { if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args)); Logging.LogNullError(nameof(sender) + " || " + nameof(args));
@ -183,35 +213,5 @@ namespace ConfigGenerator {
Tutorial.OnAction(Tutorial.EPhase.GlobalConfigOpened); 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);
}
} }
} }

View file

@ -34,17 +34,6 @@ namespace ConfigGenerator {
internal static class Program { internal static class Program {
private const string ASFExecutableFile = SharedInfo.ASF + ".exe"; private const string ASFExecutableFile = SharedInfo.ASF + ".exe";
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main() {
Init();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
private static void Init() { private static void Init() {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
@ -55,7 +44,6 @@ namespace ConfigGenerator {
// Allow loading configs from source tree if it's a debug build // Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) { if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up // Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) { for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory(".."); Directory.SetCurrentDirectory("..");
@ -92,17 +80,23 @@ namespace ConfigGenerator {
return; return;
} }
Logging.LogGenericErrorWithoutStacktrace( 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...");
"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); Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/releases/tag/" + asfVersion);
Environment.Exit(1); Environment.Exit(1);
} }
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main() {
Init();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) { private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if (args?.ExceptionObject == null) { if (args?.ExceptionObject == null) {
Logging.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject)); Logging.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));

View file

@ -5,6 +5,7 @@ using ArchiSteamFarm;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information // set of attributes. Change these attribute values to modify the information
// associated with an assembly. // associated with an assembly.
[assembly: AssemblyTitle(SharedInfo.ServiceName + "-ConfigGenerator")] [assembly: AssemblyTitle(SharedInfo.ServiceName + "-ConfigGenerator")]
[assembly: AssemblyDescription(SharedInfo.ServiceDescription)] [assembly: AssemblyDescription(SharedInfo.ServiceDescription)]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
@ -17,9 +18,11 @@ using ArchiSteamFarm;
// Setting ComVisible to false makes the types in this assembly not visible // 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 // to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type. // COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM // The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c3f6fe68-5e75-415e-bea1-1e7c16d6a433")] [assembly: Guid("c3f6fe68-5e75-415e-bea1-1e7c16d6a433")]
// Version information for an assembly consists of the following four values: // 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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion(SharedInfo.VersionNumber)] [assembly: AssemblyVersion(SharedInfo.VersionNumber)]
[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)] [assembly: AssemblyFileVersion(SharedInfo.VersionNumber)]

View file

@ -1,4 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)"> <SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles> <Profiles>
<Profile Name="(Default)" /> <Profile Name="(Default)" />

View file

@ -26,8 +26,7 @@ using System;
namespace ConfigGenerator { namespace ConfigGenerator {
internal static class Runtime { internal static class Runtime {
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
internal static bool IsRunningOnMono => MonoRuntime != null; internal static bool IsRunningOnMono => MonoRuntime != null;
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
} }
} }

View file

@ -24,20 +24,6 @@
namespace ConfigGenerator { namespace ConfigGenerator {
internal static class Tutorial { 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; internal static bool Enabled { private get; set; } = true;
private static EPhase NextPhase = EPhase.Start; private static EPhase NextPhase = EPhase.Start;
@ -99,5 +85,19 @@ namespace ConfigGenerator {
NextPhase++; NextPhase++;
} }
internal enum EPhase : byte {
Unknown,
Start,
Shown,
Help,
HelpFinished,
BotNickname,
BotNicknameFinished,
BotEnabled,
BotReady,
GlobalConfigOpened,
GlobalConfigReady
}
} }
} }

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Costura.Fody" version="2.0.0-beta0018" targetFramework="net461" developmentDependency="true" /> <package id="Costura.Fody" version="2.0.0-beta0018" targetFramework="net461" developmentDependency="true" />
<package id="Fody" version="1.30.0-beta01" targetFramework="net461" developmentDependency="true" /> <package id="Fody" version="1.30.0-beta01" targetFramework="net461" developmentDependency="true" />

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup> </startup>
</configuration> </configuration>

View file

@ -26,6 +26,7 @@ using GUI;
using SteamKit2; using SteamKit2;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Events { internal static class Events {
internal static void OnBotShutdown() { } internal static void OnBotShutdown() { }

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Weavers> <Weavers>
<Costura IncludeDebugSymbols='false' /> <Costura IncludeDebugSymbols='false' />
</Weavers> </Weavers>

View file

@ -29,6 +29,7 @@ using NLog.Targets;
using NLog.Windows.Forms; using NLog.Windows.Forms;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Logging { 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}}"; 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 { MessageBoxTarget messageBoxTarget = new MessageBoxTarget { Name = "MessageBox", Layout = GeneralLayout };
Name = "MessageBox",
Layout = GeneralLayout
};
LogManager.Configuration.AddTarget(messageBoxTarget); LogManager.Configuration.AddTarget(messageBoxTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Fatal, messageBoxTarget)); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Fatal, messageBoxTarget));
@ -61,11 +59,7 @@ namespace ArchiSteamFarm {
return; return;
} }
FileTarget fileTarget = new FileTarget("File") { FileTarget fileTarget = new FileTarget("File") { DeleteOldFileOnStartup = true, FileName = SharedInfo.LogFile, Layout = GeneralLayout };
DeleteOldFileOnStartup = true,
FileName = SharedInfo.LogFile,
Layout = GeneralLayout
};
LogManager.Configuration.AddTarget(fileTarget); LogManager.Configuration.AddTarget(fileTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget)); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
@ -74,19 +68,11 @@ namespace ArchiSteamFarm {
} }
internal static void InitFormLogger() { internal static void InitFormLogger() {
RichTextBoxTarget formControlTarget = new RichTextBoxTarget { RichTextBoxTarget formControlTarget = new RichTextBoxTarget { AutoScroll = true, ControlName = "LogTextBox", FormName = "MainForm", Layout = GeneralLayout, MaxLines = byte.MaxValue, Name = "RichTextBox" };
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.Error", "Red", "Black"));
formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Warn", "Yellow", "Black")); formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Warn", "Yellow", "Black"));
LogManager.Configuration.AddTarget(formControlTarget); LogManager.Configuration.AddTarget(formControlTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, formControlTarget)); LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, formControlTarget));

View file

@ -47,44 +47,20 @@ namespace GUI {
})); }));
} }
private static Bitmap ResizeImage(Image image, int width, int height) { private void BotListView_SelectedIndexChanged(object sender, EventArgs e) {
if ((image == null) || (width <= 0) || (height <= 0)) { if (!string.IsNullOrEmpty(PreviouslySelectedBotName)) {
ASF.ArchiLogger.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height)); BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = false;
return null;
} }
Rectangle destRect = new Rectangle(0, 0, width, height); if (BotListView.SelectedItems.Count == 0) {
Bitmap destImage = new Bitmap(width, height); return;
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);
}
} }
return destImage; PreviouslySelectedBotName = BotListView.SelectedItems[0].Text;
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = true;
} }
private void MainForm_Resize(object sender, EventArgs e) { private void MainForm_FormClosed(object sender, FormClosedEventArgs e) => Program.InitShutdownSequence();
switch (WindowState) {
case FormWindowState.Minimized:
MinimizeIcon.Visible = true;
MinimizeIcon.ShowBalloonTip(5000);
break;
case FormWindowState.Normal:
MinimizeIcon.Visible = false;
break;
}
}
private async void MainForm_Load(object sender, EventArgs e) { private async void MainForm_Load(object sender, EventArgs e) {
Logging.InitFormLogger(); Logging.InitFormLogger();
@ -124,10 +100,7 @@ namespace GUI {
botStatusForm.TopLevel = false; botStatusForm.TopLevel = false;
BotStatusPanel.Controls.Add(botStatusForm); BotStatusPanel.Controls.Add(botStatusForm);
ListViewItem botListViewItem = new ListViewItem { ListViewItem botListViewItem = new ListViewItem { ImageIndex = BotIndexes[botName], Text = botName };
ImageIndex = BotIndexes[botName],
Text = botName
};
BotListView.Items.Add(botListViewItem); 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) { private void MinimizeIcon_DoubleClick(object sender, EventArgs e) {
Show(); Show();
WindowState = FormWindowState.Normal; WindowState = FormWindowState.Normal;
} }
private void MainForm_FormClosed(object sender, FormClosedEventArgs e) => Program.InitShutdownSequence(); private static Bitmap ResizeImage(Image image, int width, int height) {
if ((image == null) || (width <= 0) || (height <= 0)) {
private void BotListView_SelectedIndexChanged(object sender, EventArgs e) { ASF.ArchiLogger.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height));
if (!string.IsNullOrEmpty(PreviouslySelectedBotName)) { return null;
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = false;
} }
if (BotListView.SelectedItems.Count == 0) { Rectangle destRect = new Rectangle(0, 0, width, height);
return; 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; return destImage;
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = true;
} }
} }
} }

View file

@ -7,21 +7,29 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using GUI; using GUI;
using SteamKit2;
// ReSharper disable once CheckNamespace // ReSharper disable once CheckNamespace
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class Program { internal static class Program {
internal static GlobalConfig GlobalConfig { get; private set; } internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; } internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static WebBrowser WebBrowser { 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) { internal static string GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) {
return null; // TODO return null; // TODO
} }
internal static void Exit(int exitCode = 0) { internal static void InitShutdownSequence() {
InitShutdownSequence(); foreach (Bot bot in Bot.Bots.Values.Where(bot => bot.KeepRunning)) {
Environment.Exit(exitCode); bot.Stop();
}
} }
internal static void Restart() { internal static void Restart() {
@ -36,53 +44,6 @@ namespace ArchiSteamFarm {
Environment.Exit(0); 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() { private static void Init() {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
@ -99,7 +60,6 @@ namespace ArchiSteamFarm {
// Allow loading configs from source tree if it's a debug build // Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) { if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up // Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) { for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory(".."); Directory.SetCurrentDirectory("..");
@ -129,15 +89,38 @@ namespace ArchiSteamFarm {
Directory.CreateDirectory(SharedInfo.DebugDirectory); Directory.CreateDirectory(SharedInfo.DebugDirectory);
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener()); DebugLog.AddListener(new Debugging.DebugListener());
SteamKit2.DebugLog.Enabled = true; DebugLog.Enabled = true;
} }
Logging.InitEnhancedLoggers(); 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);
}
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
private static void Main() { private static void Main() {
@ -146,5 +129,23 @@ namespace ArchiSteamFarm {
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm()); 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);
}
} }
} }

View file

@ -5,6 +5,7 @@ using ArchiSteamFarm;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information // set of attributes. Change these attribute values to modify the information
// associated with an assembly. // associated with an assembly.
[assembly: AssemblyTitle(SharedInfo.ServiceName + "-GUI")] [assembly: AssemblyTitle(SharedInfo.ServiceName + "-GUI")]
[assembly: AssemblyDescription(SharedInfo.ServiceDescription)] [assembly: AssemblyDescription(SharedInfo.ServiceDescription)]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
@ -17,9 +18,11 @@ using ArchiSteamFarm;
// Setting ComVisible to false makes the types in this assembly not visible // 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 // to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type. // COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)] [assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM // The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("13949b41-787c-4558-90ae-a9f9e7f86b1f")] [assembly: Guid("13949b41-787c-4558-90ae-a9f9e7f86b1f")]
// Version information for an assembly consists of the following four values: // 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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion(SharedInfo.VersionNumber)] [assembly: AssemblyVersion(SharedInfo.VersionNumber)]
[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)] [assembly: AssemblyFileVersion(SharedInfo.VersionNumber)]

View file

@ -1,4 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)"> <SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles> <Profiles>
<Profile Name="(Default)" /> <Profile Name="(Default)" />

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Costura.Fody" version="2.0.0-beta0018" targetFramework="net461" developmentDependency="true" /> <package id="Costura.Fody" version="2.0.0-beta0018" targetFramework="net461" developmentDependency="true" />
<package id="Fody" version="1.30.0-beta01" targetFramework="net461" developmentDependency="true" /> <package id="Fody" version="1.30.0-beta01" targetFramework="net461" developmentDependency="true" />