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: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: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/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/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: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>
@ -102,9 +106,216 @@
<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: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: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: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/ForOtherTypes/@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/=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_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_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -34,28 +34,6 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal static class ASF {
internal sealed class BotConfigEventArgs : EventArgs {
internal readonly BotConfig BotConfig;
internal BotConfigEventArgs(BotConfig botConfig = null) {
BotConfig = botConfig;
}
}
internal enum EUserInputType : byte {
Unknown,
DeviceID,
Login,
Password,
PhoneNumber,
SMS,
SteamGuard,
SteamParentalPIN,
RevocationCode,
TwoFactorAuthentication,
WCFHostname
}
internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF);
private static readonly ConcurrentDictionary<Bot, DateTime> LastWriteTimes = new ConcurrentDictionary<Bot, DateTime>();
@ -90,10 +68,7 @@ namespace ArchiSteamFarm {
}
if ((AutoUpdatesTimer == null) && Program.GlobalConfig.AutoUpdates) {
AutoUpdatesTimer = new Timer(
async e => await CheckForUpdate().ConfigureAwait(false),
null,
TimeSpan.FromDays(1), // Delay
AutoUpdatesTimer = new Timer(async e => await CheckForUpdate().ConfigureAwait(false), null, TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period
);
@ -258,9 +233,7 @@ namespace ArchiSteamFarm {
return;
}
FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*.json") {
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite
};
FileSystemWatcher = new FileSystemWatcher(SharedInfo.ConfigDirectory, "*.json") { NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite };
FileSystemWatcher.Changed += OnChanged;
FileSystemWatcher.Created += OnCreated;
@ -408,5 +381,27 @@ namespace ArchiSteamFarm {
CreateBot(newBotName).Forget();
}
internal sealed class BotConfigEventArgs : EventArgs {
internal readonly BotConfig BotConfig;
internal BotConfigEventArgs(BotConfig botConfig = null) {
BotConfig = botConfig;
}
}
internal enum EUserInputType : byte {
Unknown,
DeviceID,
Login,
Password,
PhoneNumber,
SMS,
SteamGuard,
SteamParentalPIN,
RevocationCode,
TwoFactorAuthentication,
WCFHostname
}
}
}

View file

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

View file

@ -22,8 +22,6 @@
*/
using SteamKit2;
using SteamKit2.Internal;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@ -31,6 +29,8 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using SteamKit2;
using SteamKit2.Internal;
namespace ArchiSteamFarm {
internal sealed class ArchiHandler : ClientMsgHandler {
@ -44,187 +44,37 @@ namespace ArchiSteamFarm {
ArchiLogger = archiLogger;
}
/*
____ _ _ _ _
/ ___| __ _ | || || |__ __ _ ___ | | __ ___
| | / _` || || || '_ \ / _` | / __|| |/ // __|
| |___| (_| || || || |_) || (_| || (__ | < \__ \
\____|\__,_||_||_||_.__/ \__,_| \___||_|\_\|___/
*/
internal sealed class NotificationsCallback : CallbackMsg {
internal enum ENotification : byte {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
Unknown = 0,
Trading = 1,
// Only custom below, different than ones available as user_notification_type
Items = 254
public override void HandleMsg(IPacketMsg packetMsg) {
if (packetMsg == null) {
ArchiLogger.LogNullError(nameof(packetMsg));
return;
}
internal readonly HashSet<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 };
}
switch (packetMsg.MsgType) {
case EMsg.ClientFSOfflineMessageNotification:
HandleFSOfflineMessageNotification(packetMsg);
break;
case EMsg.ClientItemAnnouncements:
HandleItemAnnouncements(packetMsg);
break;
case EMsg.ClientPlayingSessionState:
HandlePlayingSessionState(packetMsg);
break;
case EMsg.ClientPurchaseResponse:
HandlePurchaseResponse(packetMsg);
break;
case EMsg.ClientRedeemGuestPassResponse:
HandleRedeemGuestPassResponse(packetMsg);
break;
case EMsg.ClientSharedLibraryLockStatus:
HandleSharedLibraryLockStatus(packetMsg);
break;
case EMsg.ClientUserNotifications:
HandleUserNotifications(packetMsg);
break;
}
}
internal sealed class OfflineMessageCallback : CallbackMsg {
internal readonly uint OfflineMessagesCount;
internal OfflineMessageCallback(JobID jobID, CMsgClientOfflineMessageNotification msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}
JobID = jobID;
OfflineMessagesCount = msg.offline_messages;
}
}
internal sealed class PlayingSessionStateCallback : CallbackMsg {
internal readonly bool PlayingBlocked;
internal PlayingSessionStateCallback(JobID jobID, CMsgClientPlayingSessionState msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}
JobID = jobID;
PlayingBlocked = msg.playing_blocked;
}
}
internal sealed class PurchaseResponseCallback : CallbackMsg {
internal enum EPurchaseResult : sbyte {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
Unknown = -2,
Timeout = -1,
OK = 0,
AlreadyOwned = 9,
RegionLocked = 13,
InvalidKey = 14,
DuplicatedKey = 15,
BaseGameRequired = 24,
SteamWalletCode = 50,
OnCooldown = 53
}
internal readonly Dictionary<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
internal void LogOnWithoutMachineID(SteamUser.LogOnDetails details) {
if (details == null) {
@ -276,7 +126,6 @@ namespace ArchiSteamFarm {
logon.Body.sha_sentryfile = details.SentryFileHash;
logon.Body.eresult_sentryfile = (int) (details.SentryFileHash != null ? EResult.OK : EResult.FileNotFound);
Client.Send(logon);
}
@ -301,19 +150,11 @@ namespace ArchiSteamFarm {
ClientMsgProtobuf<CMsgClientGamesPlayed> request = new ClientMsgProtobuf<CMsgClientGamesPlayed>(EMsg.ClientGamesPlayed);
if (!string.IsNullOrEmpty(gameName)) {
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
game_extra_info = gameName,
game_id = new GameID {
AppType = GameID.GameType.Shortcut,
ModID = uint.MaxValue
}
});
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_extra_info = gameName, game_id = new GameID { AppType = GameID.GameType.Shortcut, ModID = uint.MaxValue } });
}
foreach (uint gameID in gameIDs.Where(gameID => gameID != 0)) {
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed {
game_id = new GameID(gameID)
});
request.Body.games_played.Add(new CMsgClientGamesPlayed.GamePlayed { game_id = new GameID(gameID) });
}
Client.Send(request);
@ -329,9 +170,7 @@ namespace ArchiSteamFarm {
return null;
}
ClientMsgProtobuf<CMsgClientRedeemGuestPass> request = new ClientMsgProtobuf<CMsgClientRedeemGuestPass>(EMsg.ClientRedeemGuestPass) {
SourceJobID = Client.GetNextJobID()
};
ClientMsgProtobuf<CMsgClientRedeemGuestPass> request = new ClientMsgProtobuf<CMsgClientRedeemGuestPass>(EMsg.ClientRedeemGuestPass) { SourceJobID = Client.GetNextJobID() };
request.Body.guest_pass_id = guestPassID;
@ -355,9 +194,7 @@ namespace ArchiSteamFarm {
return null;
}
ClientMsgProtobuf<CMsgClientRegisterKey> request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey) {
SourceJobID = Client.GetNextJobID()
};
ClientMsgProtobuf<CMsgClientRegisterKey> request = new ClientMsgProtobuf<CMsgClientRegisterKey>(EMsg.ClientRegisterKey) { SourceJobID = Client.GetNextJobID() };
request.Body.key = key;
@ -371,46 +208,6 @@ namespace ArchiSteamFarm {
}
}
/*
_ _ _ _
| | | | __ _ _ __ __| || | ___ _ __ ___
| |_| | / _` || '_ \ / _` || | / _ \| '__|/ __|
| _ || (_| || | | || (_| || || __/| | \__ \
|_| |_| \__,_||_| |_| \__,_||_| \___||_| |___/
*/
public override void HandleMsg(IPacketMsg packetMsg) {
if (packetMsg == null) {
ArchiLogger.LogNullError(nameof(packetMsg));
return;
}
switch (packetMsg.MsgType) {
case EMsg.ClientFSOfflineMessageNotification:
HandleFSOfflineMessageNotification(packetMsg);
break;
case EMsg.ClientItemAnnouncements:
HandleItemAnnouncements(packetMsg);
break;
case EMsg.ClientPlayingSessionState:
HandlePlayingSessionState(packetMsg);
break;
case EMsg.ClientPurchaseResponse:
HandlePurchaseResponse(packetMsg);
break;
case EMsg.ClientRedeemGuestPassResponse:
HandleRedeemGuestPassResponse(packetMsg);
break;
case EMsg.ClientSharedLibraryLockStatus:
HandleSharedLibraryLockStatus(packetMsg);
break;
case EMsg.ClientUserNotifications:
HandleUserNotifications(packetMsg);
break;
}
}
private void HandleFSOfflineMessageNotification(IPacketMsg packetMsg) {
if (packetMsg == null) {
ArchiLogger.LogNullError(nameof(packetMsg));
@ -480,5 +277,168 @@ namespace ArchiSteamFarm {
ClientMsgProtobuf<CMsgClientUserNotifications> response = new ClientMsgProtobuf<CMsgClientUserNotifications>(packetMsg);
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);
}
internal void LogGenericError(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
}
Logger.Error($"{previousMethodName}() {message}");
}
internal void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception));
return;
}
Logger.Error(exception, $"{previousMethodName}()");
}
[SuppressMessage("ReSharper", "LocalizableElement")]
internal void LogFatalException(Exception exception, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
@ -86,13 +68,31 @@ namespace ArchiSteamFarm {
}
}
internal void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) {
internal void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
}
Logger.Warn($"{previousMethodName}() {message}");
Logger.Debug($"{previousMethodName}() {message}");
}
internal void LogGenericError(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
}
Logger.Error($"{previousMethodName}() {message}");
}
internal void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception));
return;
}
Logger.Error(exception, $"{previousMethodName}()");
}
internal void LogGenericInfo(string message, [CallerMemberName] string previousMethodName = null) {
@ -104,24 +104,6 @@ namespace ArchiSteamFarm {
Logger.Info($"{previousMethodName}() {message}");
}
[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
internal void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(nullObjectName)) {
return;
}
LogGenericError(nullObjectName + " is null!", previousMethodName);
}
internal void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
}
Logger.Debug($"{previousMethodName}() {message}");
}
internal void LogGenericTrace(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
@ -130,5 +112,23 @@ namespace ArchiSteamFarm {
Logger.Trace($"{previousMethodName}() {message}");
}
internal void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
}
Logger.Warn($"{previousMethodName}() {message}");
}
[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
internal void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(nullObjectName)) {
return;
}
LogGenericError(nullObjectName + " is null!", previousMethodName);
}
}
}

View file

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

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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
@ -35,98 +35,14 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ConvertToConstant.Local")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class BotConfig {
internal enum EFarmingOrder : byte {
Unordered,
AppIDsAscending,
AppIDsDescending,
CardDropsAscending,
CardDropsDescending,
HoursAscending,
HoursDescending,
NamesAscending,
NamesDescending
}
[Flags]
internal enum ETradingPreferences : byte {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
None = 0,
AcceptDonations = 1,
SteamTradeMatcher = 2,
MatchEverything = 4
}
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Enabled = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Paused = false;
[JsonProperty]
internal string SteamLogin { get; set; }
[JsonProperty]
internal string SteamPassword { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
internal readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
[JsonProperty]
internal string SteamParentalPIN { get; set; } = "0";
[JsonProperty]
internal readonly string SteamApiKey = null;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly ulong SteamMasterID = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly ulong SteamMasterClanID = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool CardDropsRestricted = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool DismissInventoryNotifications = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool FarmOffline = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool HandleOfflineMessages = false;
internal readonly byte AcceptConfirmationsPeriod = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AcceptGifts = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool IsBotAccount = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool ForwardKeysToOtherBots = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool DistributeKeys = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool ShutdownOnFarmingFinished = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool SendOnFarmingFinished = false;
[JsonProperty]
internal readonly string SteamTradeToken = null;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte SendTradePeriod = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly ETradingPreferences TradingPreferences = ETradingPreferences.AcceptDonations;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte AcceptConfirmationsPeriod = 0;
internal readonly bool CardDropsRestricted = true;
[JsonProperty]
internal readonly string CustomGamePlayedWhileFarming = null;
@ -134,9 +50,75 @@ namespace ArchiSteamFarm {
[JsonProperty]
internal readonly string CustomGamePlayedWhileIdle = null;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool DismissInventoryNotifications = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool DistributeKeys = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Enabled = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool FarmOffline = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool ForwardKeysToOtherBots = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet<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) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
@ -182,7 +164,25 @@ namespace ArchiSteamFarm {
return botConfig;
}
// This constructor is used only by deserializer
private BotConfig() { }
internal enum EFarmingOrder : byte {
Unordered,
AppIDsAscending,
AppIDsDescending,
CardDropsAscending,
CardDropsDescending,
HoursAscending,
HoursDescending,
NamesAscending,
NamesDescending
}
[Flags]
internal enum ETradingPreferences : byte {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
None = 0,
AcceptDonations = 1,
SteamTradeMatcher = 2,
MatchEverything = 4
}
}
}

View file

@ -22,21 +22,18 @@
*/
using Newtonsoft.Json;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class BotDatabase {
[JsonProperty]
private string _LoginKey;
private readonly object FileLock = new object();
internal string LoginKey {
get {
return _LoginKey;
}
get { return _LoginKey; }
set {
if (_LoginKey == value) {
@ -48,13 +45,8 @@ namespace ArchiSteamFarm {
}
}
[JsonProperty]
private MobileAuthenticator _MobileAuthenticator;
internal MobileAuthenticator MobileAuthenticator {
get {
return _MobileAuthenticator;
}
get { return _MobileAuthenticator; }
set {
if (_MobileAuthenticator == value) {
@ -66,10 +58,28 @@ namespace ArchiSteamFarm {
}
}
private readonly object FileLock = new object();
[JsonProperty]
private string _LoginKey;
[JsonProperty]
private MobileAuthenticator _MobileAuthenticator;
private string FilePath;
// This constructor is used when creating new database
private BotDatabase(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(filePath));
}
FilePath = filePath;
Save();
}
// This constructor is used only by deserializer
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private BotDatabase() { }
internal static BotDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
@ -98,20 +108,6 @@ namespace ArchiSteamFarm {
return botDatabase;
}
// This constructor is used when creating new database
private BotDatabase(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(filePath));
}
FilePath = filePath;
Save();
}
// This constructor is used only by deserializer
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private BotDatabase() { }
internal void Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {

View file

@ -22,7 +22,6 @@
*/
using HtmlAgilityPack;
using System;
using System.Collections.Generic;
using System.Globalization;
@ -30,71 +29,30 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using HtmlAgilityPack;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class CardsFarmer : IDisposable {
internal sealed class Game {
[JsonProperty]
internal readonly uint AppID;
[JsonProperty]
internal readonly string GameName;
[JsonProperty]
internal float HoursPlayed { get; set; }
[JsonProperty]
internal ushort CardsRemaining { get; set; }
//internal string HeaderURL => "https://steamcdn-a.akamaihd.net/steam/apps/" + AppID + "/header.jpg";
internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining) {
if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) {
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
}
AppID = appID;
GameName = gameName;
HoursPlayed = hoursPlayed;
CardsRemaining = cardsRemaining;
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
Game game = obj as Game;
return (game != null) && Equals(game);
}
public override int GetHashCode() => (int) AppID;
private bool Equals(Game other) => AppID == other.AppID;
}
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
[JsonProperty]
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>();
[JsonProperty]
internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>();
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
[JsonProperty]
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>();
private readonly Bot Bot;
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
private readonly Timer IdleFarmingTimer;
[JsonProperty]
internal bool Paused { get; private set; }
private bool KeepFarming, NowFarming, StickyPause;
private bool KeepFarming;
private bool NowFarming;
private bool StickyPause;
internal CardsFarmer(Bot bot) {
if (bot == null) {
@ -104,16 +62,50 @@ namespace ArchiSteamFarm {
Bot = bot;
if (Program.GlobalConfig.IdleFarmingPeriod > 0) {
IdleFarmingTimer = new Timer(
e => CheckGamesForFarming(),
null,
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5 * Bot.Bots.Count), // Delay
IdleFarmingTimer = new Timer(e => CheckGamesForFarming(), null, TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5*Bot.Bots.Count), // Delay
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period
);
}
}
internal void SetInitialState(bool paused) => StickyPause = Paused = paused;
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
CurrentGamesFarming.Dispose();
GamesToFarm.Dispose();
FarmingSemaphore.Dispose();
FarmResetEvent.Dispose();
// Those are objects that might be null and the check should be in-place
IdleFarmingTimer?.Dispose();
}
internal void OnDisconnected() => StopFarming().Forget();
internal async Task OnNewGameAdded() {
if (!NowFarming) {
// If we're not farming yet, obviously it's worth it to make a check
StartFarming().Forget();
return;
}
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < 2)) {
// If we have Complex algorithm and some games to boost, it's also worth to make a check
// That's because we would check for new games after our current round anyway
await StopFarming().ConfigureAwait(false);
StartFarming().Forget();
}
}
internal async Task OnNewItemsNotification() {
if (NowFarming) {
FarmResetEvent.Set();
return;
}
// If we're not farming, and we got new items, it's likely to be a booster pack or likewise
// In this case, perform a loot if user wants to do so
await Bot.LootIfNeeded().ConfigureAwait(false);
}
internal async Task Pause(bool sticky) {
if (sticky) {
@ -142,6 +134,8 @@ namespace ArchiSteamFarm {
}
}
internal void SetInitialState(bool paused) => StickyPause = Paused = paused;
internal async Task StartFarming() {
if (NowFarming || Paused || !Bot.IsPlayingPossible) {
return;
@ -257,117 +251,12 @@ namespace ArchiSteamFarm {
}
}
internal void OnDisconnected() => StopFarming().Forget();
internal async Task OnNewItemsNotification() {
if (NowFarming) {
FarmResetEvent.Set();
private void CheckGamesForFarming() {
if (NowFarming || Paused || !Bot.IsConnectedAndLoggedOn) {
return;
}
// If we're not farming, and we got new items, it's likely to be a booster pack or likewise
// In this case, perform a loot if user wants to do so
await Bot.LootIfNeeded().ConfigureAwait(false);
}
internal async Task OnNewGameAdded() {
if (!NowFarming) {
// If we're not farming yet, obviously it's worth it to make a check
StartFarming().Forget();
return;
}
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Min(game => game.HoursPlayed) < 2)) {
// If we have Complex algorithm and some games to boost, it's also worth to make a check
// That's because we would check for new games after our current round anyway
await StopFarming().ConfigureAwait(false);
StartFarming().Forget();
}
}
private async Task<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
StartFarming().Forget();
}
private void CheckPage(HtmlDocument htmlDocument) {
@ -513,92 +402,6 @@ namespace ArchiSteamFarm {
CheckPage(htmlDocument);
}
private void CheckGamesForFarming() {
if (NowFarming || Paused || !Bot.IsConnectedAndLoggedOn) {
return;
}
StartFarming().Forget();
}
private async Task<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) {
if (game == null) {
Bot.ArchiLogger.LogNullError(nameof(game));
@ -615,7 +418,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo("Still farming: " + game.AppID + " (" + game.GameName + ")");
DateTime startFarmingPeriod = DateTime.Now;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
if (FarmResetEvent.Wait(60*1000*Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset();
success = KeepFarming;
}
@ -658,7 +461,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo("Still farming: " + string.Join(", ", games.Select(game => game.AppID)));
DateTime startFarmingPeriod = DateTime.Now;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
if (FarmResetEvent.Wait(60*1000*Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset();
success = KeepFarming;
}
@ -680,15 +483,211 @@ namespace ArchiSteamFarm {
return success;
}
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
CurrentGamesFarming.Dispose();
GamesToFarm.Dispose();
FarmingSemaphore.Dispose();
FarmResetEvent.Dispose();
private bool FarmMultiple(IEnumerable<Game> games) {
if (games == null) {
Bot.ArchiLogger.LogNullError(nameof(games));
return false;
}
// Those are objects that might be null and the check should be in-place
IdleFarmingTimer?.Dispose();
CurrentGamesFarming.ReplaceWith(games);
Bot.ArchiLogger.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming.Select(game => game.AppID)));
bool result = FarmHours(CurrentGamesFarming);
CurrentGamesFarming.ClearAndTrim();
return result;
}
private async Task<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> {
public T Current => Enumerator.Current;
object IEnumerator.Current => Current;
private readonly IEnumerator<T> Enumerator;
private readonly ReaderWriterLockSlim Lock;
object IEnumerator.Current => Current;
internal ConcurrentEnumerator(ICollection<T> collection, ReaderWriterLockSlim rwLock) {
if ((collection == null) || (rwLock == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(rwLock));
@ -47,9 +47,9 @@ namespace ArchiSteamFarm {
Enumerator = collection.GetEnumerator();
}
public void Dispose() => Lock?.ExitReadLock();
public bool MoveNext() => Enumerator.MoveNext();
public void Reset() => Enumerator.Reset();
public void Dispose() => Lock?.ExitReadLock();
}
}

View file

@ -29,15 +29,6 @@ using System.Threading;
namespace ArchiSteamFarm {
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 {
get {
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() {
Lock.EnterWriteLock();
@ -70,18 +66,6 @@ namespace ArchiSteamFarm {
}
}
public bool Remove(T item) {
Lock.EnterWriteLock();
try {
return HashSet.Remove(item);
} finally {
Lock.ExitWriteLock();
}
}
public void Dispose() => Lock.Dispose();
public void CopyTo(T[] array, int arrayIndex) {
Lock.EnterReadLock();
@ -92,6 +76,23 @@ namespace ArchiSteamFarm {
}
}
public void Dispose() => Lock.Dispose();
public IEnumerator<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) {
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) {
Lock.EnterUpgradeableReadLock();
@ -133,17 +145,6 @@ namespace ArchiSteamFarm {
}
}
internal void ClearAndTrim() {
Lock.EnterWriteLock();
try {
HashSet.Clear();
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
internal void TrimExcess() {
Lock.EnterWriteLock();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,8 +34,7 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
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 UpdateServerListAsync(IEnumerable<IPEndPoint> endPoints) {
@ -54,6 +53,6 @@ namespace ArchiSteamFarm {
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 {
#pragma warning disable 649
internal sealed class Asset {
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal readonly string Name;
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal readonly string DownloadURL;
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal readonly string Name;
}
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)]

View file

@ -33,137 +33,207 @@ using SteamKit2;
namespace ArchiSteamFarm.JSON {
internal static class Steam {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class ConfirmationDetails {
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal readonly bool Success;
#pragma warning restore 649
internal ulong OtherSteamID64 {
get {
if (_OtherSteamID64 != 0) {
return _OtherSteamID64;
}
if ((Type != EType.Trade) || (OtherSteamID3 == 0)) {
return 0;
}
_OtherSteamID64 = new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
return _OtherSteamID64;
}
}
internal ulong TradeOfferID {
get {
if (_TradeOfferID != 0) {
return _TradeOfferID;
}
if ((Type != EType.Trade) || (HtmlDocument == null)) {
return 0;
}
HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='tradeoffer']");
if (htmlNode == null) {
ASF.ArchiLogger.LogNullError(nameof(htmlNode));
return 0;
}
string id = htmlNode.GetAttributeValue("id", null);
if (string.IsNullOrEmpty(id)) {
ASF.ArchiLogger.LogNullError(nameof(id));
return 0;
}
int index = id.IndexOf('_');
if (index < 0) {
ASF.ArchiLogger.LogNullError(nameof(index));
return 0;
}
index++;
if (id.Length <= index) {
ASF.ArchiLogger.LogNullError(nameof(id.Length));
return 0;
}
id = id.Substring(index);
if (ulong.TryParse(id, out _TradeOfferID) && (_TradeOfferID != 0)) {
return _TradeOfferID;
}
ASF.ArchiLogger.LogNullError(nameof(_TradeOfferID));
return 0;
}
}
#pragma warning disable 649
[JsonProperty(PropertyName = "html", Required = Required.DisallowNull)]
private readonly string HTML;
#pragma warning restore 649
private HtmlDocument HtmlDocument {
get {
if (_HtmlDocument != null) {
return _HtmlDocument;
}
if (string.IsNullOrEmpty(HTML)) {
return null;
}
_HtmlDocument = new HtmlDocument();
_HtmlDocument.LoadHtml(WebUtility.HtmlDecode(HTML));
return _HtmlDocument;
}
}
private uint OtherSteamID3 {
get {
if (_OtherSteamID3 != 0) {
return _OtherSteamID3;
}
if ((Type != EType.Trade) || (HtmlDocument == null)) {
return 0;
}
HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//a/@data-miniprofile");
if (htmlNode == null) {
ASF.ArchiLogger.LogNullError(nameof(htmlNode));
return 0;
}
string miniProfile = htmlNode.GetAttributeValue("data-miniprofile", null);
if (string.IsNullOrEmpty(miniProfile)) {
ASF.ArchiLogger.LogNullError(nameof(miniProfile));
return 0;
}
if (uint.TryParse(miniProfile, out _OtherSteamID3) && (_OtherSteamID3 != 0)) {
return _OtherSteamID3;
}
ASF.ArchiLogger.LogNullError(nameof(_OtherSteamID3));
return 0;
}
}
private EType Type {
get {
if (_Type != EType.Unknown) {
return _Type;
}
if (HtmlDocument == null) {
return EType.Unknown;
}
HtmlNode testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_listing_prices']");
if (testNode != null) {
_Type = EType.Market;
return _Type;
}
testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_trade_area']");
if (testNode != null) {
_Type = EType.Trade;
return _Type;
}
_Type = EType.Other;
return _Type;
}
}
internal MobileAuthenticator.Confirmation Confirmation {
get { return _Confirmation; }
set {
if (value == null) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
_Confirmation = value;
}
}
private MobileAuthenticator.Confirmation _Confirmation;
private HtmlDocument _HtmlDocument;
private uint _OtherSteamID3;
private ulong _OtherSteamID64;
private ulong _TradeOfferID;
private EType _Type;
private ConfirmationDetails() { } // Deserialized from JSON
internal enum EType : byte {
Unknown,
Trade,
Market,
Other
}
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class ConfirmationResponse { // Deserialized from JSON
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal readonly bool Success;
#pragma warning restore 649
private ConfirmationResponse() { }
}
internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset | Deserialized from JSON (SteamCommunity) and constructed from code
internal const ushort SteamAppID = 753;
internal const byte SteamCommunityContextID = 6;
internal enum EType : byte {
Unknown,
BoosterPack,
Coupon,
Gift,
SteamGems,
Emoticon,
FoilTradingCard,
ProfileBackground,
TradingCard
}
internal uint Amount { get; private set; }
internal uint AppID { get; set; }
[JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string AppIDString {
get {
return AppID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
uint appID;
if (!uint.TryParse(value, out appID) || (appID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appID));
return;
}
AppID = appID;
}
}
internal ulong ClassID { get; private set; }
internal ulong ContextID { get; set; }
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ContextIDString {
get {
return ContextID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
ulong contextID;
if (!ulong.TryParse(value, out contextID) || (contextID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(contextID));
return;
}
ContextID = contextID;
}
}
internal uint RealAppID { get; set; }
internal EType Type { get; set; }
private ulong AssetID;
[JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)]
private string AssetIDString {
get {
return AssetID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
ulong assetID;
if (!ulong.TryParse(value, out assetID) || (assetID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(assetID));
return;
}
AssetID = assetID;
}
}
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ID {
get { return AssetIDString; }
set { AssetIDString = value; }
}
internal ulong ClassID { get; private set; }
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ClassIDString {
get {
return ClassID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
ulong classID;
if (!ulong.TryParse(value, out classID) || (classID == 0)) {
return;
}
ClassID = classID;
}
}
internal uint Amount { get; private set; }
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string AmountString {
get {
return Amount.ToString();
}
get { return Amount.ToString(); }
set {
if (string.IsNullOrEmpty(value)) {
@ -181,8 +251,94 @@ namespace ArchiSteamFarm.JSON {
}
}
internal uint RealAppID { get; set; }
internal EType Type { get; set; }
[JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string AppIDString {
get { return AppID.ToString(); }
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
uint appID;
if (!uint.TryParse(value, out appID) || (appID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appID));
return;
}
AppID = appID;
}
}
[JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)]
private string AssetIDString {
get { return AssetID.ToString(); }
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
ulong assetID;
if (!ulong.TryParse(value, out assetID) || (assetID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(assetID));
return;
}
AssetID = assetID;
}
}
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ClassIDString {
get { return ClassID.ToString(); }
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
ulong classID;
if (!ulong.TryParse(value, out classID) || (classID == 0)) {
return;
}
ClassID = classID;
}
}
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ContextIDString {
get { return ContextID.ToString(); }
set {
if (string.IsNullOrEmpty(value)) {
ASF.ArchiLogger.LogNullError(nameof(value));
return;
}
ulong contextID;
if (!ulong.TryParse(value, out contextID) || (contextID == 0)) {
ASF.ArchiLogger.LogNullError(nameof(contextID));
return;
}
ContextID = contextID;
}
}
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ID {
get { return AssetIDString; }
set { AssetIDString = value; }
}
// This constructor is used for constructing items in trades being received
internal Item(uint appID, ulong contextID, ulong classID, uint amount, uint realAppID, EType type) {
@ -200,33 +356,39 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private Item() { }
internal enum EType : byte {
Unknown,
BoosterPack,
Coupon,
Gift,
SteamGems,
Emoticon,
FoilTradingCard,
ProfileBackground,
TradingCard
}
}
internal sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer | Constructed from code
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum ETradeOfferState : byte {
Unknown,
Invalid,
Active,
Accepted,
Countered,
Expired,
Canceled,
Declined,
InvalidItems,
EmailPending,
EmailCanceled,
OnHold
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class RedeemWalletResponse { // Deserialized from JSON
#pragma warning disable 649
[JsonProperty(PropertyName = "detail", Required = Required.Always)]
internal readonly ArchiHandler.PurchaseResponseCallback.EPurchaseResult PurchaseResult;
#pragma warning restore 649
internal readonly ulong TradeOfferID;
internal readonly ETradeOfferState State;
private RedeemWalletResponse() { }
}
internal sealed class TradeOffer {
internal readonly HashSet<Item> ItemsToGive = 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 {
get {
if (_OtherSteamID64 != 0) {
@ -243,6 +405,10 @@ namespace ArchiSteamFarm.JSON {
}
}
private readonly uint OtherSteamID3;
private ulong _OtherSteamID64;
internal TradeOffer(ulong tradeOfferID, uint otherSteamID3, ETradeOfferState state) {
if ((tradeOfferID == 0) || (otherSteamID3 == 0) || (state == ETradeOfferState.Unknown)) {
throw new ArgumentNullException(nameof(tradeOfferID) + " || " + nameof(otherSteamID3) + " || " + nameof(state));
@ -253,8 +419,6 @@ namespace ArchiSteamFarm.JSON {
State = state;
}
internal bool IsSteamCardsRequest() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamCommunityContextID) && (item.Type == Item.EType.TradingCard));
internal bool IsFairTypesExchange() {
Dictionary<uint, Dictionary<Item.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>();
foreach (Item item in ItemsToGive) {
@ -309,217 +473,38 @@ namespace ArchiSteamFarm.JSON {
return true;
}
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class RedeemWalletResponse { // Deserialized from JSON
#pragma warning disable 649
[JsonProperty(PropertyName = "detail", Required = Required.Always)]
internal readonly ArchiHandler.PurchaseResponseCallback.EPurchaseResult PurchaseResult;
#pragma warning restore 649
internal bool IsSteamCardsRequest() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamCommunityContextID) && (item.Type == Item.EType.TradingCard)); // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer | Constructed from code
private RedeemWalletResponse() { }
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum ETradeOfferState : byte {
Unknown,
Invalid,
Active,
Accepted,
Countered,
Expired,
Canceled,
Declined,
InvalidItems,
EmailPending,
EmailCanceled,
OnHold
}
}
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class TradeOfferRequest { // Constructed from code
internal sealed class ItemList {
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal readonly HashSet<Item> Assets = new HashSet<Item>();
}
internal sealed class TradeOfferRequest {
[JsonProperty(PropertyName = "me", Required = Required.Always)]
internal readonly ItemList ItemsToGive = new ItemList();
[JsonProperty(PropertyName = "them", Required = Required.Always)]
internal readonly ItemList ItemsToReceive = new ItemList();
}
internal readonly ItemList ItemsToReceive = new ItemList(); // Constructed from code
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class ConfirmationResponse { // Deserialized from JSON
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal readonly bool Success;
#pragma warning restore 649
private ConfirmationResponse() { }
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class ConfirmationDetails { // Deserialized from JSON
internal enum EType : byte {
Unknown,
Trade,
Market,
Other
internal sealed class ItemList {
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal readonly HashSet<Item> Assets = new HashSet<Item>();
}
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 {
internal static class Logging {
private const string LayoutMessage = @"${logger}|${message}${onexception:inner= ${exception:format=toString,Data}}";
private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${processname}-${processid}|${level:uppercase=true}|" + LayoutMessage;
private const string EventLogLayout = LayoutMessage;
private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${processname}-${processid}|${level:uppercase=true}|" + LayoutMessage;
private const string LayoutMessage = @"${logger}|${message}${onexception:inner= ${exception:format=toString,Data}}";
private static readonly ConcurrentHashSet<LoggingRule> ConsoleLoggingRules = new ConcurrentHashSet<LoggingRule>();
@ -47,29 +47,18 @@ namespace ArchiSteamFarm {
LoggingConfiguration config = new LoggingConfiguration();
ColoredConsoleTarget coloredConsoleTarget = new ColoredConsoleTarget("ColoredConsole") {
DetectConsoleAvailable = false,
Layout = GeneralLayout
};
ColoredConsoleTarget coloredConsoleTarget = new ColoredConsoleTarget("ColoredConsole") { DetectConsoleAvailable = false, Layout = GeneralLayout };
config.AddTarget(coloredConsoleTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, coloredConsoleTarget));
if (Program.IsRunningAsService) {
EventLogTarget eventLogTarget = new EventLogTarget("EventLog") {
Layout = EventLogLayout,
Log = SharedInfo.EventLog,
Source = SharedInfo.EventLogSource
};
EventLogTarget eventLogTarget = new EventLogTarget("EventLog") { Layout = EventLogLayout, Log = SharedInfo.EventLog, Source = SharedInfo.EventLogSource };
config.AddTarget(eventLogTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, eventLogTarget));
} else if (!Program.Mode.HasFlag(Program.EMode.Client) || Program.Mode.HasFlag(Program.EMode.Server)) {
FileTarget fileTarget = new FileTarget("File") {
DeleteOldFileOnStartup = true,
FileName = SharedInfo.LogFile,
Layout = GeneralLayout
};
FileTarget fileTarget = new FileTarget("File") { DeleteOldFileOnStartup = true, FileName = SharedInfo.LogFile, Layout = GeneralLayout };
config.AddTarget(fileTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
@ -79,20 +68,6 @@ namespace ArchiSteamFarm {
InitConsoleLoggers();
}
internal static void OnUserInputStart() {
IsWaitingForUserInput = true;
if (ConsoleLoggingRules.Count == 0) {
return;
}
foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) {
LogManager.Configuration.LoggingRules.Remove(consoleLoggingRule);
}
LogManager.ReconfigExistingLoggers();
}
internal static void OnUserInputEnd() {
IsWaitingForUserInput = false;
@ -107,6 +82,20 @@ namespace ArchiSteamFarm {
LogManager.ReconfigExistingLoggers();
}
internal static void OnUserInputStart() {
IsWaitingForUserInput = true;
if (ConsoleLoggingRules.Count == 0) {
return;
}
foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) {
LogManager.Configuration.LoggingRules.Remove(consoleLoggingRule);
}
LogManager.ReconfigExistingLoggers();
}
private static void InitConsoleLoggers() {
ConsoleLoggingRules.Clear();
foreach (LoggingRule loggingRule in LogManager.Configuration.LoggingRules.Where(loggingRule => loggingRule.Targets.Any(target => target is ColoredConsoleTarget || target is ConsoleTarget))) {

View file

@ -37,61 +37,26 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class MobileAuthenticator : IDisposable {
internal sealed class Confirmation {
internal readonly uint ID;
internal readonly ulong Key;
internal readonly Steam.ConfirmationDetails.EType Type;
internal Confirmation(uint id, ulong key, Steam.ConfirmationDetails.EType type) {
if ((id == 0) || (key == 0) || (type == Steam.ConfirmationDetails.EType.Unknown)) {
throw new ArgumentNullException(nameof(id) + " || " + nameof(key) + " || " + nameof(type));
}
ID = id;
Key = key;
Type = type;
}
}
private const byte CodeDigits = 5;
private const byte CodeInterval = 30;
private static readonly char[] CodeCharacters = {
'2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C',
'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q',
'R', 'T', 'V', 'W', 'X', 'Y'
};
private static readonly char[] CodeCharacters = { '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y' };
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
private static short? SteamTimeDifference;
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1);
#pragma warning disable 649
[JsonProperty(PropertyName = "shared_secret", Required = Required.Always)]
private readonly string SharedSecret;
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]
private readonly string IdentitySecret;
#pragma warning restore 649
private Bot Bot;
[JsonProperty(PropertyName = "device_id")]
private string DeviceID;
private Bot Bot;
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
private MobileAuthenticator() { }
internal void Init(Bot bot) {
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
}
Bot = bot;
}
public void Dispose() => ConfirmationsSemaphore.Dispose();
internal void CorrectDeviceID(string deviceID) {
if (string.IsNullOrEmpty(deviceID)) {
@ -102,56 +67,14 @@ namespace ArchiSteamFarm {
DeviceID = deviceID;
}
internal async Task<bool> HandleConfirmations(HashSet<Confirmation> confirmations, bool accept) {
if ((confirmations == null) || (confirmations.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(confirmations));
return false;
internal async Task<string> GenerateToken() {
uint time = await GetSteamTime().ConfigureAwait(false);
if (time != 0) {
return GenerateTokenForTime(time);
}
if (!HasCorrectDeviceID) {
Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!");
return false;
}
await ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false);
try {
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Bot.ArchiLogger.LogNullError(nameof(time));
return false;
}
string confirmationHash = GenerateConfirmationKey(time, "conf");
if (string.IsNullOrEmpty(confirmationHash)) {
Bot.ArchiLogger.LogNullError(nameof(confirmationHash));
return false;
}
bool? result = await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
if (!result.HasValue) { // Request timed out
return false;
}
if (result.Value) { // Request succeeded
return true;
}
// Our multi request failed, this is almost always Steam fuckup that happens randomly
// In this case, we'll accept all pending confirmations one-by-one, synchronously (as Steam can't handle them in parallel)
// We totally ignore actual result returned by those calls, abort only if request timed out
foreach (Confirmation confirmation in confirmations) {
bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
if (!confirmationResult.HasValue) {
return false;
}
}
return true;
} finally {
ConfirmationsSemaphore.Release();
}
Bot.ArchiLogger.LogNullError(nameof(time));
return null;
}
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(Confirmation confirmation) {
@ -185,16 +108,6 @@ namespace ArchiSteamFarm {
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() {
if (!HasCorrectDeviceID) {
Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!");
@ -271,68 +184,64 @@ namespace ArchiSteamFarm {
return result;
}
private async Task<uint> GetSteamTime() {
if (SteamTimeDifference.HasValue) {
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault());
internal async Task<bool> HandleConfirmations(HashSet<Confirmation> confirmations, bool accept) {
if ((confirmations == null) || (confirmations.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(confirmations));
return false;
}
await TimeSemaphore.WaitAsync().ConfigureAwait(false);
if (!HasCorrectDeviceID) {
Bot.ArchiLogger.LogGenericWarning("Can't execute properly due to invalid DeviceID!");
return false;
}
await ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (!SteamTimeDifference.HasValue) {
uint serverTime = Bot.ArchiWebHandler.GetServerTime();
if (serverTime != 0) {
SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime());
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Bot.ArchiLogger.LogNullError(nameof(time));
return false;
}
string confirmationHash = GenerateConfirmationKey(time, "conf");
if (string.IsNullOrEmpty(confirmationHash)) {
Bot.ArchiLogger.LogNullError(nameof(confirmationHash));
return false;
}
bool? result = await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
if (!result.HasValue) { // Request timed out
return false;
}
if (result.Value) { // Request succeeded
return true;
}
// Our multi request failed, this is almost always Steam fuckup that happens randomly
// In this case, we'll accept all pending confirmations one-by-one, synchronously (as Steam can't handle them in parallel)
// We totally ignore actual result returned by those calls, abort only if request timed out
foreach (Confirmation confirmation in confirmations) {
bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
if (!confirmationResult.HasValue) {
return false;
}
}
} finally {
TimeSemaphore.Release();
}
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault());
return true;
} finally {
ConfirmationsSemaphore.Release();
}
}
private string GenerateTokenForTime(uint time) {
if (time == 0) {
Bot.ArchiLogger.LogNullError(nameof(time));
return null;
internal void Init(Bot bot) {
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
}
byte[] sharedSecret = Convert.FromBase64String(SharedSecret);
byte[] timeArray = BitConverter.GetBytes((long) time / CodeInterval);
if (BitConverter.IsLittleEndian) {
Array.Reverse(timeArray);
}
byte[] hash;
using (HMACSHA1 hmac = new HMACSHA1(sharedSecret)) {
hash = hmac.ComputeHash(timeArray);
}
// The last 4 bits of the mac say where the code starts
int start = hash[hash.Length - 1] & 0x0f;
// Extract those 4 bytes
byte[] bytes = new byte[4];
Array.Copy(hash, start, bytes, 0, 4);
if (BitConverter.IsLittleEndian) {
Array.Reverse(bytes);
}
uint fullCode = BitConverter.ToUInt32(bytes, 0) & 0x7fffffff;
// Build the alphanumeric code
StringBuilder code = new StringBuilder();
for (byte i = 0; i < CodeDigits; i++) {
code.Append(CodeCharacters[fullCode % CodeCharacters.Length]);
fullCode /= (uint) CodeCharacters.Length;
}
return code.ToString();
Bot = bot;
}
private string GenerateConfirmationKey(uint time, string tag = null) {
@ -368,6 +277,92 @@ namespace ArchiSteamFarm {
return Convert.ToBase64String(hash);
}
public void Dispose() => ConfirmationsSemaphore.Dispose();
private string GenerateTokenForTime(uint time) {
if (time == 0) {
Bot.ArchiLogger.LogNullError(nameof(time));
return null;
}
byte[] sharedSecret = Convert.FromBase64String(SharedSecret);
byte[] timeArray = BitConverter.GetBytes((long) time/CodeInterval);
if (BitConverter.IsLittleEndian) {
Array.Reverse(timeArray);
}
byte[] hash;
using (HMACSHA1 hmac = new HMACSHA1(sharedSecret)) {
hash = hmac.ComputeHash(timeArray);
}
// The last 4 bits of the mac say where the code starts
int start = hash[hash.Length - 1] & 0x0f;
// Extract those 4 bytes
byte[] bytes = new byte[4];
Array.Copy(hash, start, bytes, 0, 4);
if (BitConverter.IsLittleEndian) {
Array.Reverse(bytes);
}
uint fullCode = BitConverter.ToUInt32(bytes, 0) & 0x7fffffff;
// Build the alphanumeric code
StringBuilder code = new StringBuilder();
for (byte i = 0; i < CodeDigits; i++) {
code.Append(CodeCharacters[fullCode%CodeCharacters.Length]);
fullCode /= (uint) CodeCharacters.Length;
}
return code.ToString();
}
private async Task<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.Threading;
using System.Threading.Tasks;
using SteamKit2;
namespace ArchiSteamFarm {
internal static class Program {
[Flags]
internal enum EMode : byte {
Normal = 0, // Standard most common usage
Client = 1, // WCF client
Server = 2 // WCF server
}
internal static bool IsWCFRunning => WCF.IsServerRunning;
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static bool IsRunningAsService { get; private set; }
internal static EMode Mode { get; private set; } = EMode.Normal;
internal static WebBrowser WebBrowser { get; private set; }
private static readonly object ConsoleLock = new object();
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
private static readonly WCF WCF = new WCF();
internal static bool IsRunningAsService { get; private set; }
internal static EMode Mode { get; private set; } = EMode.Normal;
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static WebBrowser WebBrowser { get; private set; }
private static bool ShutdownSequenceInitialized;
internal static bool IsWCFRunning => WCF.IsServerRunning;
internal static void Exit(byte exitCode = 0) {
Shutdown();
Environment.Exit(exitCode);
}
internal static void Restart() {
if (!InitShutdownSequence()) {
return;
}
try {
Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
}
ShutdownResetEvent.Set();
Environment.Exit(0);
}
internal static string GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) {
if (userInputType == ASF.EUserInputType.Unknown) {
return null;
@ -137,6 +115,21 @@ namespace ArchiSteamFarm {
return !string.IsNullOrEmpty(result) ? result.Trim() : null;
}
internal static void Restart() {
if (!InitShutdownSequence()) {
return;
}
try {
Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
}
ShutdownResetEvent.Set();
Environment.Exit(0);
}
internal static void Shutdown() {
if (!InitShutdownSequence()) {
return;
@ -145,19 +138,72 @@ namespace ArchiSteamFarm {
ShutdownResetEvent.Set();
}
private static bool InitShutdownSequence() {
if (ShutdownSequenceInitialized) {
return false;
private static void Init(string[] args) {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (!string.IsNullOrEmpty(homeDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory("..");
if (Directory.Exists(SharedInfo.ConfigDirectory)) {
break;
}
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
}
}
}
ShutdownSequenceInitialized = true;
WCF.StopServer();
foreach (Bot bot in Bot.Bots.Values) {
bot.Stop();
// Parse pre-init args
if (args != null) {
ParsePreInitArgs(args);
}
return true;
Logging.InitLoggers();
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
if (!Runtime.IsRuntimeSupported) {
ASF.ArchiLogger.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!");
Thread.Sleep(10000);
}
InitServices();
// If debugging is on, we prepare debug directory prior to running
if (GlobalConfig.Debug) {
if (Directory.Exists(SharedInfo.DebugDirectory)) {
Directory.Delete(SharedInfo.DebugDirectory, true);
Thread.Sleep(1000); // Dirty workaround giving Windows some time to sync
}
Directory.CreateDirectory(SharedInfo.DebugDirectory);
DebugLog.AddListener(new Debugging.DebugListener());
DebugLog.Enabled = true;
}
// Parse post-init args
if (args != null) {
ParsePostInitArgs(args);
}
// If we ran ASF as a client, we're done by now
if (Mode.HasFlag(EMode.Client) && !Mode.HasFlag(EMode.Server)) {
Exit();
}
ASF.CheckForUpdate().Wait();
ASF.InitBots();
ASF.InitFileWatcher();
}
private static void InitServices() {
@ -186,30 +232,36 @@ namespace ArchiSteamFarm {
WebBrowser = new WebBrowser(ASF.ArchiLogger);
}
private static void ParsePreInitArgs(IEnumerable<string> args) {
if (args == null) {
ASF.ArchiLogger.LogNullError(nameof(args));
return;
private static bool InitShutdownSequence() {
if (ShutdownSequenceInitialized) {
return false;
}
foreach (string arg in args) {
switch (arg) {
case "":
break;
case "--client":
Mode |= EMode.Client;
break;
case "--server":
Mode |= EMode.Server;
break;
default:
if (arg.StartsWith("--", StringComparison.Ordinal)) {
if (arg.StartsWith("--path=", StringComparison.Ordinal) && (arg.Length > 7)) {
Directory.SetCurrentDirectory(arg.Substring(7));
}
}
ShutdownSequenceInitialized = true;
break;
WCF.StopServer();
foreach (Bot bot in Bot.Bots.Values) {
bot.Stop();
}
return true;
}
private static void Main(string[] args) {
if (Runtime.IsUserInteractive) {
// App
Init(args);
// Wait for signal to shutdown
ShutdownResetEvent.Wait();
// We got a signal to shutdown
Exit();
} else {
// Service
IsRunningAsService = true;
using (Service service = new Service()) {
ServiceBase.Run(service);
}
}
}
@ -253,6 +305,34 @@ namespace ArchiSteamFarm {
}
}
private static void ParsePreInitArgs(IEnumerable<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) {
if (args?.ExceptionObject == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
@ -271,92 +351,11 @@ namespace ArchiSteamFarm {
ASF.ArchiLogger.LogFatalException(args.Exception);
}
private static void Init(string[] args) {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (!string.IsNullOrEmpty(homeDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory("..");
if (Directory.Exists(SharedInfo.ConfigDirectory)) {
break;
}
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
}
}
}
// Parse pre-init args
if (args != null) {
ParsePreInitArgs(args);
}
Logging.InitLoggers();
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
if (!Runtime.IsRuntimeSupported) {
ASF.ArchiLogger.LogGenericError("ASF detected unsupported runtime version, program might NOT run correctly in current environment. You're running it at your own risk!");
Thread.Sleep(10000);
}
InitServices();
// If debugging is on, we prepare debug directory prior to running
if (GlobalConfig.Debug) {
if (Directory.Exists(SharedInfo.DebugDirectory)) {
Directory.Delete(SharedInfo.DebugDirectory, true);
Thread.Sleep(1000); // Dirty workaround giving Windows some time to sync
}
Directory.CreateDirectory(SharedInfo.DebugDirectory);
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener());
SteamKit2.DebugLog.Enabled = true;
}
// Parse post-init args
if (args != null) {
ParsePostInitArgs(args);
}
// If we ran ASF as a client, we're done by now
if (Mode.HasFlag(EMode.Client) && !Mode.HasFlag(EMode.Server)) {
Exit();
}
ASF.CheckForUpdate().Wait();
ASF.InitBots();
ASF.InitFileWatcher();
}
private static void Main(string[] args) {
if (Runtime.IsUserInteractive) {
// App
Init(args);
// Wait for signal to shutdown
ShutdownResetEvent.Wait();
// We got a signal to shutdown
Exit();
} else {
// Service
IsRunningAsService = true;
using (Service service = new Service()) {
ServiceBase.Run(service);
}
}
[Flags]
internal enum EMode : byte {
Normal = 0, // Standard most common usage
Client = 1, // WCF client
Server = 2 // WCF server
}
private sealed class Service : ServiceBase {
@ -373,5 +372,4 @@ namespace ArchiSteamFarm {
protected override void OnStop() => Shutdown();
}
}
}

View file

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

View file

@ -28,34 +28,8 @@ using Microsoft.Win32;
namespace ArchiSteamFarm {
internal static class Runtime {
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
internal static bool IsRunningOnMono => MonoRuntime != null;
private static bool? _IsUserInteractive;
internal static bool IsUserInteractive {
get {
if (_IsUserInteractive.HasValue) {
return _IsUserInteractive.Value;
}
if (Environment.UserInteractive) {
_IsUserInteractive = true;
} else if (!IsRunningOnMono) {
// If it's non-Mono, we can trust the result
_IsUserInteractive = false;
} else {
// In Mono, Environment.UserInteractive is always false
// There is really no reliable way for now, so assume always being interactive
// Maybe in future I find out some awful hack or workaround that could be at least semi-reliable
_IsUserInteractive = true;
}
return _IsUserInteractive.Value;
}
}
private static bool? _IsRuntimeSupported;
internal static bool IsRuntimeSupported {
get {
if (_IsRuntimeSupported.HasValue) {
@ -102,6 +76,69 @@ namespace ArchiSteamFarm {
}
}
internal static bool IsUserInteractive {
get {
if (_IsUserInteractive.HasValue) {
return _IsUserInteractive.Value;
}
if (Environment.UserInteractive) {
_IsUserInteractive = true;
} else if (!IsRunningOnMono) {
// If it's non-Mono, we can trust the result
_IsUserInteractive = false;
} else {
// In Mono, Environment.UserInteractive is always false
// There is really no reliable way for now, so assume always being interactive
// Maybe in future I find out some awful hack or workaround that could be at least semi-reliable
_IsUserInteractive = true;
}
return _IsUserInteractive.Value;
}
}
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
private static bool? _IsRuntimeSupported;
private static bool? _IsUserInteractive;
private static Version GetMonoVersion() {
if (MonoRuntime == null) {
ASF.ArchiLogger.LogNullError(nameof(MonoRuntime));
return null;
}
MethodInfo displayName = MonoRuntime.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayName == null) {
ASF.ArchiLogger.LogNullError(nameof(displayName));
return null;
}
string versionString = (string) displayName.Invoke(null, null);
if (string.IsNullOrEmpty(versionString)) {
ASF.ArchiLogger.LogNullError(nameof(versionString));
return null;
}
int index = versionString.IndexOf(' ');
if (index <= 0) {
ASF.ArchiLogger.LogNullError(nameof(index));
return null;
}
versionString = versionString.Substring(0, index);
Version version;
if (Version.TryParse(versionString, out version)) {
return version;
}
ASF.ArchiLogger.LogNullError(nameof(version));
return null;
}
private static Version GetNetVersion() {
uint release;
using (RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) {
@ -144,40 +181,5 @@ namespace ArchiSteamFarm {
return release >= 378389 ? new Version(4, 5) : null;
}
private static Version GetMonoVersion() {
if (MonoRuntime == null) {
ASF.ArchiLogger.LogNullError(nameof(MonoRuntime));
return null;
}
MethodInfo displayName = MonoRuntime.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayName == null) {
ASF.ArchiLogger.LogNullError(nameof(displayName));
return null;
}
string versionString = (string) displayName.Invoke(null, null);
if (string.IsNullOrEmpty(versionString)) {
ASF.ArchiLogger.LogNullError(nameof(versionString));
return null;
}
int index = versionString.IndexOf(' ');
if (index <= 0) {
ASF.ArchiLogger.LogNullError(nameof(index));
return null;
}
versionString = versionString.Substring(0, index);
Version version;
if (Version.TryParse(versionString, out version)) {
return version;
}
ASF.ArchiLogger.LogNullError(nameof(version));
return null;
}
}
}

View file

@ -27,29 +27,23 @@ using System.Reflection;
namespace ArchiSteamFarm {
internal static class SharedInfo {
internal const string VersionNumber = "2.1.6.9";
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
internal const string ServiceName = "ArchiSteamFarm";
internal const string ServiceDescription = "ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.";
internal const string EventLog = ServiceName;
internal const string EventLogSource = EventLog + "Logger";
internal const ulong ArchiSteamID = 76561198006963719;
internal const string ASF = "ASF";
internal const string ASFDirectory = "ArchiSteamFarm";
internal const string ConfigDirectory = "config";
internal const string DebugDirectory = "debug";
internal const string LogFile = "log.txt";
internal const ulong ArchiSteamID = 76561198006963719;
internal const ulong ASFGroupSteamID = 103582791440160998;
internal const string ConfigDirectory = "config";
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
internal const string DebugDirectory = "debug";
internal const string EventLog = ServiceName;
internal const string EventLogSource = EventLog + "Logger";
internal const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
internal const string GlobalConfigFileName = ASF + ".json";
internal const string GlobalDatabaseFileName = ASF + ".db";
internal const string LogFile = "log.txt";
internal const string ServiceDescription = "ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.";
internal const string ServiceName = "ArchiSteamFarm";
internal const string VersionNumber = "2.1.6.9";
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
}

View file

@ -31,28 +31,6 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal sealed class Trading : IDisposable {
private sealed class ParseTradeResult {
internal enum EResult : byte {
Unknown,
AcceptedWithItemLose,
AcceptedWithoutItemLose,
RejectedTemporarily,
RejectedPermanently
}
internal readonly ulong TradeID;
internal readonly EResult Result;
internal ParseTradeResult(ulong tradeID, EResult result) {
if ((tradeID == 0) || (result == EResult.Unknown)) {
throw new ArgumentNullException(nameof(tradeID) + " || " + nameof(result));
}
TradeID = tradeID;
Result = result;
}
}
internal const byte MaxItemsPerTrade = 150; // This is due to limit on POST size in WebBrowser
internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve
@ -64,14 +42,6 @@ namespace ArchiSteamFarm {
private bool ParsingScheduled;
internal static async Task LimitInventoryRequestsAsync() {
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
InventorySemaphore.Release();
}).Forget();
}
internal Trading(Bot bot) {
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
@ -85,8 +55,6 @@ namespace ArchiSteamFarm {
TradesSemaphore.Dispose();
}
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim();
internal async Task CheckTrades() {
// We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue
// This way we can call this function as many times as needed e.g. because of Steam events
@ -111,6 +79,16 @@ namespace ArchiSteamFarm {
}
}
internal static async Task LimitInventoryRequestsAsync() {
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay*1000).ConfigureAwait(false);
InventorySemaphore.Release();
}).Forget();
}
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim();
private async Task ParseActiveTrades() {
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
return;
@ -321,5 +299,28 @@ namespace ArchiSteamFarm {
// If not, we assume that the trade might be good for us in the future, unless we're bot account where we assume that inventory doesn't change
return new ParseTradeResult(tradeOffer.TradeOfferID, difference > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : (Bot.BotConfig.IsBotAccount ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily));
}
private sealed class ParseTradeResult {
internal readonly EResult Result;
internal readonly ulong TradeID;
internal ParseTradeResult(ulong tradeID, EResult result) {
if ((tradeID == 0) || (result == EResult.Unknown)) {
throw new ArgumentNullException(nameof(tradeID) + " || " + nameof(result));
}
TradeID = tradeID;
Result = result;
}
internal enum EResult : byte {
Unknown,
AcceptedWithItemLose,
AcceptedWithoutItemLose,
RejectedTemporarily,
RejectedPermanently
}
}
}
}

View file

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

View file

@ -22,15 +22,15 @@
*/
using HtmlAgilityPack;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml;
using HtmlAgilityPack;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm {
internal sealed class WebBrowser {
@ -44,12 +44,27 @@ namespace ArchiSteamFarm {
private readonly ArchiLogger ArchiLogger;
private readonly HttpClient HttpClient;
internal WebBrowser(ArchiLogger archiLogger) {
if (archiLogger == null) {
throw new ArgumentNullException(nameof(archiLogger));
}
ArchiLogger = archiLogger;
HttpClientHandler httpClientHandler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, CookieContainer = CookieContainer };
HttpClient = new HttpClient(httpClientHandler) { Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout) };
// Most web services expect that UserAgent is set, so we declare it globally
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ArchiSteamFarm/" + SharedInfo.Version);
}
internal static void Init() {
// Set max connection limit from default of 2 to desired value
ServicePointManager.DefaultConnectionLimit = MaxConnections;
// Set max idle time from default of 100 seconds (100 * 1000) to desired value
ServicePointManager.MaxServicePointIdleTime = MaxIdleTime * 1000;
ServicePointManager.MaxServicePointIdleTime = MaxIdleTime*1000;
// Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets
ServicePointManager.Expect100Continue = false;
@ -66,68 +81,6 @@ namespace ArchiSteamFarm {
#endif
}
#if !__MonoCS__
private static void InitNonMonoBehaviour() => ServicePointManager.ReusePort = true;
#endif
internal WebBrowser(ArchiLogger archiLogger) {
if (archiLogger == null) {
throw new ArgumentNullException(nameof(archiLogger));
}
ArchiLogger = archiLogger;
HttpClientHandler httpClientHandler = new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
CookieContainer = CookieContainer
};
HttpClient = new HttpClient(httpClientHandler) {
Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout)
};
// Most web services expect that UserAgent is set, so we declare it globally
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ArchiSteamFarm/" + SharedInfo.Version);
}
internal async Task<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) {
if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request));
@ -223,6 +176,44 @@ namespace ArchiSteamFarm {
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) {
if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request));
@ -277,6 +268,10 @@ namespace ArchiSteamFarm {
}
}
#if !__MonoCS__
private static void InitNonMonoBehaviour() => ServicePointManager.ReusePort = true;
#endif
private async Task<byte[]> UrlGetToBytes(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request));

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<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" />

View file

@ -22,20 +22,20 @@
*/
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using ArchiSteamFarm;
using Newtonsoft.Json;
namespace ConfigGenerator {
internal abstract class ASFConfig {
internal static readonly HashSet<ASFConfig> ASFConfigs = new HashSet<ASFConfig>();
internal string FilePath { get; set; }
private readonly object FileLock = new object();
internal string FilePath { get; set; }
protected ASFConfig() {
ASFConfigs.Add(this);
}
@ -48,16 +48,6 @@ namespace ConfigGenerator {
FilePath = filePath;
}
internal void Save() {
lock (FileLock) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented));
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
}
internal void Remove() {
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FileLock) {
@ -92,5 +82,15 @@ namespace ConfigGenerator {
FilePath = Path.Combine(SharedInfo.ConfigDirectory, botName + ".json");
}
}
internal void Save() {
lock (FileLock) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented));
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
}
}
}

View file

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

View file

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

View file

@ -36,43 +36,11 @@ namespace ConfigGenerator {
return DialogResult.Abort;
}
TextBox textBox = new TextBox {
Anchor = AnchorStyles.Right,
Bounds = new Rectangle(12, 36, 372, 20),
Width = 1000
};
Button buttonOk = new Button {
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Bounds = new Rectangle(228, 72, 75, 23),
DialogResult = DialogResult.OK,
Text = Resources.OK
};
Button buttonCancel = new Button {
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Bounds = new Rectangle(309, 72, 75, 23),
DialogResult = DialogResult.Cancel,
Text = Resources.Cancel
};
Label label = new Label {
AutoSize = true,
Bounds = new Rectangle(9, 20, 372, 13),
Text = promptText
};
Form form = new Form {
AcceptButton = buttonOk,
CancelButton = buttonCancel,
ClientSize = new Size(Math.Max(300, label.Right + 10), 107),
Controls = { label, textBox, buttonOk, buttonCancel },
FormBorderStyle = FormBorderStyle.FixedDialog,
MinimizeBox = false,
MaximizeBox = false,
StartPosition = FormStartPosition.CenterScreen,
Text = title
};
TextBox textBox = new TextBox { Anchor = AnchorStyles.Right, Bounds = new Rectangle(12, 36, 372, 20), Width = 1000 };
Button buttonOk = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(228, 72, 75, 23), DialogResult = DialogResult.OK, Text = Resources.OK };
Button buttonCancel = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(309, 72, 75, 23), DialogResult = DialogResult.Cancel, Text = Resources.Cancel };
Label label = new Label { AutoSize = true, Bounds = new Rectangle(9, 20, 372, 13), Text = promptText };
Form form = new Form { AcceptButton = buttonOk, CancelButton = buttonCancel, ClientSize = new Size(Math.Max(300, label.Right + 10), 107), Controls = { label, textBox, buttonOk, buttonCancel }, FormBorderStyle = FormBorderStyle.FixedDialog, MinimizeBox = false, MaximizeBox = false, StartPosition = FormStartPosition.CenterScreen, Text = title };
DialogResult dialogResult = form.ShowDialog();
value = textBox.Text;
@ -85,37 +53,10 @@ namespace ConfigGenerator {
return DialogResult.Abort;
}
Button buttonYes = new Button {
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Bounds = new Rectangle(228, 72, 75, 23),
DialogResult = DialogResult.Yes,
Text = Resources.Yes
};
Button buttonNo = new Button {
Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
Bounds = new Rectangle(309, 72, 75, 23),
DialogResult = DialogResult.No,
Text = Resources.No
};
Label label = new Label {
AutoSize = true,
Bounds = new Rectangle(9, 20, 372, 13),
Text = promptText
};
Form form = new Form {
AcceptButton = buttonYes,
CancelButton = buttonNo,
ClientSize = new Size(Math.Max(300, label.Right + 10), 107),
Controls = { label, buttonYes, buttonNo },
FormBorderStyle = FormBorderStyle.FixedDialog,
MinimizeBox = false,
MaximizeBox = false,
StartPosition = FormStartPosition.CenterScreen,
Text = title
};
Button buttonYes = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(228, 72, 75, 23), DialogResult = DialogResult.Yes, Text = Resources.Yes };
Button buttonNo = new Button { Anchor = AnchorStyles.Bottom | AnchorStyles.Right, Bounds = new Rectangle(309, 72, 75, 23), DialogResult = DialogResult.No, Text = Resources.No };
Label label = new Label { AutoSize = true, Bounds = new Rectangle(9, 20, 372, 13), Text = promptText };
Form form = new Form { AcceptButton = buttonYes, CancelButton = buttonNo, ClientSize = new Size(Math.Max(300, label.Right + 10), 107), Controls = { label, buttonYes, buttonNo }, FormBorderStyle = FormBorderStyle.FixedDialog, MinimizeBox = false, MaximizeBox = false, StartPosition = FormStartPosition.CenterScreen, Text = title };
DialogResult dialogResult = form.ShowDialog();
return dialogResult;

View file

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

View file

@ -7,28 +7,31 @@ using System.Windows.Forms.Design;
namespace ConfigGenerator {
internal sealed class FlagCheckedListBox : CheckedListBox {
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
internal Enum EnumValue {
get {
object e = Enum.ToObject(EnumType, GetCurrentValue());
return (Enum) e;
}
set {
Items.Clear();
_EnumValue = value; // Store the current enum value
EnumType = value.GetType(); // Store enum type
FillEnumMembers(); // Add items for enum members
ApplyEnumValue(); // Check/uncheck items depending on enum value
}
}
private Enum _EnumValue;
private Type EnumType;
private bool IsUpdatingCheckStates;
internal FlagCheckedListBox() {
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
}
#region Component Designer generated code
private void InitializeComponent() {
//
// FlaggedCheckedListBox
//
CheckOnClick = true;
}
#endregion
// Adds an integer value and its associated description
private void Add(int v, string c) {
FlagCheckedListBoxItem item = new FlagCheckedListBoxItem(v, c);
Items.Add(item);
}
protected override void OnItemCheck(ItemCheckEventArgs e) {
base.OnItemCheck(e);
@ -42,9 +45,44 @@ namespace ConfigGenerator {
UpdateCheckedItems(item, e.NewValue);
}
// Adds an integer value and its associated description
private void Add(int v, string c) {
FlagCheckedListBoxItem item = new FlagCheckedListBoxItem(v, c);
Items.Add(item);
}
// Checks/unchecks items based on the current value of the enum variable
private void ApplyEnumValue() {
int intVal = (int) Convert.ChangeType(_EnumValue, typeof(int));
UpdateCheckedItems(intVal);
}
// Adds items to the checklistbox based on the members of the enum
private void FillEnumMembers() {
foreach (string name in Enum.GetNames(EnumType)) {
object val = Enum.Parse(EnumType, name);
int intVal = (int) Convert.ChangeType(val, typeof(int));
Add(intVal, name);
}
}
// Gets the current bit value corresponding to all checked items
private int GetCurrentValue() => (from object t in Items select t as FlagCheckedListBoxItem).Where((item, i) => (item != null) && GetItemChecked(i)).Aggregate(0, (current, item) => current | item.Value);
#region Component Designer generated code
private void InitializeComponent() {
//
// FlaggedCheckedListBox
//
CheckOnClick = true;
}
#endregion
// Checks/Unchecks items depending on the give bitvalue
private void UpdateCheckedItems(int value) {
IsUpdatingCheckStates = true;
// Iterate over all items
@ -57,7 +95,6 @@ namespace ConfigGenerator {
if (item.Value == 0) {
SetItemChecked(i, value == 0);
} else {
// If the bit for the current item is on in the bitvalue, check it
if (((item.Value & value) == item.Value) && (item.Value != 0)) {
SetItemChecked(i, true);
@ -70,20 +107,17 @@ namespace ConfigGenerator {
}
IsUpdatingCheckStates = false;
}
// Updates items in the checklistbox
// composite = The item that was checked/unchecked
// cs = The check state of that item
private void UpdateCheckedItems(FlagCheckedListBoxItem composite, CheckState cs) {
// If the value of the item is 0, call directly.
if (composite.Value == 0) {
UpdateCheckedItems(0);
}
// Get the total value of all checked items
int sum = (from object t in Items select t as FlagCheckedListBoxItem).Where((item, i) => (item != null) && GetItemChecked(i)).Aggregate(0, (current, item) => current | item.Value);
@ -98,53 +132,7 @@ namespace ConfigGenerator {
// Update all items in the checklistbox based on the final bit value
UpdateCheckedItems(sum);
}
private bool IsUpdatingCheckStates;
// Gets the current bit value corresponding to all checked items
private int GetCurrentValue() => (from object t in Items select t as FlagCheckedListBoxItem).Where((item, i) => (item != null) && GetItemChecked(i)).Aggregate(0, (current, item) => current | item.Value);
private Type EnumType;
private Enum _EnumValue;
// Adds items to the checklistbox based on the members of the enum
private void FillEnumMembers() {
foreach (string name in Enum.GetNames(EnumType)) {
object val = Enum.Parse(EnumType, name);
int intVal = (int) Convert.ChangeType(val, typeof(int));
Add(intVal, name);
}
}
// Checks/unchecks items based on the current value of the enum variable
private void ApplyEnumValue() {
int intVal = (int) Convert.ChangeType(_EnumValue, typeof(int));
UpdateCheckedItems(intVal);
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
internal Enum EnumValue {
get {
object e = Enum.ToObject(EnumType, GetCurrentValue());
return (Enum) e;
}
set {
Items.Clear();
_EnumValue = value; // Store the current enum value
EnumType = value.GetType(); // Store enum type
FillEnumMembers(); // Add items for enum members
ApplyEnumValue(); // Check/uncheck items depending on enum value
}
}
}
// Represents an item in the checklistbox
@ -161,13 +149,12 @@ namespace ConfigGenerator {
public override string ToString() => Caption;
}
// UITypeEditor for flag enums
internal sealed class FlagEnumUIEditor : UITypeEditor {
internal sealed class FlagEnumUiEditor : UITypeEditor {
// The checklistbox
private readonly FlagCheckedListBox FlagEnumCb;
internal FlagEnumUIEditor() {
internal FlagEnumUiEditor() {
FlagEnumCb = new FlagCheckedListBox { BorderStyle = BorderStyle.None };
}

View file

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

View file

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

View file

@ -30,15 +30,6 @@ using ConfigGenerator.Properties;
namespace ConfigGenerator {
internal static class Logging {
internal static void LogGenericInfoWithoutStacktrace(string message) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
}
MessageBox.Show(message, Resources.Information, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
internal static void LogGenericErrorWithoutStacktrace(string message) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
@ -66,6 +57,15 @@ namespace ConfigGenerator {
}
}
internal static void LogGenericInfoWithoutStacktrace(string message) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
}
MessageBox.Show(message, Resources.Information, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
internal static void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));

View file

@ -46,6 +46,18 @@ namespace ConfigGenerator {
InitializeComponent();
}
private void MainForm_HelpButtonClicked(object sender, CancelEventArgs args) {
if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args));
return;
}
args.Cancel = true;
Tutorial.OnAction(Tutorial.EPhase.Help);
Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/wiki/Configuration");
Tutorial.OnAction(Tutorial.EPhase.HelpFinished);
}
private void MainForm_Load(object sender, EventArgs args) {
if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args));
@ -73,6 +85,24 @@ namespace ConfigGenerator {
Tutorial.OnAction(Tutorial.EPhase.Start);
}
private void MainForm_Shown(object sender, EventArgs args) {
if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args));
return;
}
Tutorial.OnAction(Tutorial.EPhase.Shown);
}
private void MainTab_Deselecting(object sender, TabControlCancelEventArgs args) {
if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args));
return;
}
OldTab = args.TabPage;
}
private void MainTab_Selected(object sender, TabControlEventArgs args) {
if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args));
@ -183,35 +213,5 @@ namespace ConfigGenerator {
Tutorial.OnAction(Tutorial.EPhase.GlobalConfigOpened);
}
}
private void MainTab_Deselecting(object sender, TabControlCancelEventArgs args) {
if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args));
return;
}
OldTab = args.TabPage;
}
private void MainForm_Shown(object sender, EventArgs args) {
if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args));
return;
}
Tutorial.OnAction(Tutorial.EPhase.Shown);
}
private void MainForm_HelpButtonClicked(object sender, CancelEventArgs args) {
if ((sender == null) || (args == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args));
return;
}
args.Cancel = true;
Tutorial.OnAction(Tutorial.EPhase.Help);
Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/wiki/Configuration");
Tutorial.OnAction(Tutorial.EPhase.HelpFinished);
}
}
}

View file

@ -34,17 +34,6 @@ namespace ConfigGenerator {
internal static class Program {
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() {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
@ -55,7 +44,6 @@ namespace ConfigGenerator {
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory("..");
@ -92,17 +80,23 @@ namespace ConfigGenerator {
return;
}
Logging.LogGenericErrorWithoutStacktrace(
"Version of ASF and ConfigGenerator doesn't match!" + Environment.NewLine +
"ASF version: " + asfVersion + " | ConfigGenerator version: " + cgVersion + Environment.NewLine +
Environment.NewLine +
"Please use ConfigGenerator from the same ASF release, I'll redirect you to appropriate ASF release..."
);
Logging.LogGenericErrorWithoutStacktrace("Version of ASF and ConfigGenerator doesn't match!" + Environment.NewLine + "ASF version: " + asfVersion + " | ConfigGenerator version: " + cgVersion + Environment.NewLine + Environment.NewLine + "Please use ConfigGenerator from the same ASF release, I'll redirect you to appropriate ASF release...");
Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/releases/tag/" + asfVersion);
Environment.Exit(1);
}
/// <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) {
if (args?.ExceptionObject == null) {
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
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle(SharedInfo.ServiceName + "-ConfigGenerator")]
[assembly: AssemblyDescription(SharedInfo.ServiceDescription)]
[assembly: AssemblyConfiguration("")]
@ -17,9 +18,11 @@ using ArchiSteamFarm;
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("c3f6fe68-5e75-415e-bea1-1e7c16d6a433")]
// Version information for an assembly consists of the following four values:
@ -32,5 +35,6 @@ using ArchiSteamFarm;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion(SharedInfo.VersionNumber)]
[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)]

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<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" />

View file

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

View file

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

View file

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

View file

@ -29,6 +29,7 @@ using NLog.Targets;
using NLog.Windows.Forms;
// ReSharper disable once CheckNamespace
namespace ArchiSteamFarm {
internal static class Logging {
private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} | ${level:uppercase=true} | ${logger} | ${message}${onexception:inner= | ${exception:format=toString,Data}}";
@ -46,10 +47,7 @@ namespace ArchiSteamFarm {
}
}
MessageBoxTarget messageBoxTarget = new MessageBoxTarget {
Name = "MessageBox",
Layout = GeneralLayout
};
MessageBoxTarget messageBoxTarget = new MessageBoxTarget { Name = "MessageBox", Layout = GeneralLayout };
LogManager.Configuration.AddTarget(messageBoxTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Fatal, messageBoxTarget));
@ -61,11 +59,7 @@ namespace ArchiSteamFarm {
return;
}
FileTarget fileTarget = new FileTarget("File") {
DeleteOldFileOnStartup = true,
FileName = SharedInfo.LogFile,
Layout = GeneralLayout
};
FileTarget fileTarget = new FileTarget("File") { DeleteOldFileOnStartup = true, FileName = SharedInfo.LogFile, Layout = GeneralLayout };
LogManager.Configuration.AddTarget(fileTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, fileTarget));
@ -74,19 +68,11 @@ namespace ArchiSteamFarm {
}
internal static void InitFormLogger() {
RichTextBoxTarget formControlTarget = new RichTextBoxTarget {
AutoScroll = true,
ControlName = "LogTextBox",
FormName = "MainForm",
Layout = GeneralLayout,
MaxLines = byte.MaxValue,
Name = "RichTextBox"
};
RichTextBoxTarget formControlTarget = new RichTextBoxTarget { AutoScroll = true, ControlName = "LogTextBox", FormName = "MainForm", Layout = GeneralLayout, MaxLines = byte.MaxValue, Name = "RichTextBox" };
formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Error", "Red", "Black"));
formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Warn", "Yellow", "Black"));
LogManager.Configuration.AddTarget(formControlTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, formControlTarget));

View file

@ -47,44 +47,20 @@ namespace GUI {
}));
}
private static Bitmap ResizeImage(Image image, int width, int height) {
if ((image == null) || (width <= 0) || (height <= 0)) {
ASF.ArchiLogger.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height));
return null;
private void BotListView_SelectedIndexChanged(object sender, EventArgs e) {
if (!string.IsNullOrEmpty(PreviouslySelectedBotName)) {
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = false;
}
Rectangle destRect = new Rectangle(0, 0, width, height);
Bitmap destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(destImage)) {
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (ImageAttributes wrapMode = new ImageAttributes()) {
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
if (BotListView.SelectedItems.Count == 0) {
return;
}
return destImage;
PreviouslySelectedBotName = BotListView.SelectedItems[0].Text;
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = true;
}
private void MainForm_Resize(object sender, EventArgs e) {
switch (WindowState) {
case FormWindowState.Minimized:
MinimizeIcon.Visible = true;
MinimizeIcon.ShowBalloonTip(5000);
break;
case FormWindowState.Normal:
MinimizeIcon.Visible = false;
break;
}
}
private void MainForm_FormClosed(object sender, FormClosedEventArgs e) => Program.InitShutdownSequence();
private async void MainForm_Load(object sender, EventArgs e) {
Logging.InitFormLogger();
@ -124,10 +100,7 @@ namespace GUI {
botStatusForm.TopLevel = false;
BotStatusPanel.Controls.Add(botStatusForm);
ListViewItem botListViewItem = new ListViewItem {
ImageIndex = BotIndexes[botName],
Text = botName
};
ListViewItem botListViewItem = new ListViewItem { ImageIndex = BotIndexes[botName], Text = botName };
BotListView.Items.Add(botListViewItem);
}
@ -138,24 +111,48 @@ namespace GUI {
}
}
private void MainForm_Resize(object sender, EventArgs e) {
switch (WindowState) {
case FormWindowState.Minimized:
MinimizeIcon.Visible = true;
MinimizeIcon.ShowBalloonTip(5000);
break;
case FormWindowState.Normal:
MinimizeIcon.Visible = false;
break;
}
}
private void MinimizeIcon_DoubleClick(object sender, EventArgs e) {
Show();
WindowState = FormWindowState.Normal;
}
private void MainForm_FormClosed(object sender, FormClosedEventArgs e) => Program.InitShutdownSequence();
private void BotListView_SelectedIndexChanged(object sender, EventArgs e) {
if (!string.IsNullOrEmpty(PreviouslySelectedBotName)) {
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = false;
private static Bitmap ResizeImage(Image image, int width, int height) {
if ((image == null) || (width <= 0) || (height <= 0)) {
ASF.ArchiLogger.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height));
return null;
}
if (BotListView.SelectedItems.Count == 0) {
return;
Rectangle destRect = new Rectangle(0, 0, width, height);
Bitmap destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(destImage)) {
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (ImageAttributes wrapMode = new ImageAttributes()) {
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
PreviouslySelectedBotName = BotListView.SelectedItems[0].Text;
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = true;
return destImage;
}
}
}

View file

@ -7,21 +7,29 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using GUI;
using SteamKit2;
// ReSharper disable once CheckNamespace
namespace ArchiSteamFarm {
internal static class Program {
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static WebBrowser WebBrowser { get; private set; }
internal static void Exit(int exitCode = 0) {
InitShutdownSequence();
Environment.Exit(exitCode);
}
internal static string GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) {
return null; // TODO
}
internal static void Exit(int exitCode = 0) {
InitShutdownSequence();
Environment.Exit(exitCode);
internal static void InitShutdownSequence() {
foreach (Bot bot in Bot.Bots.Values.Where(bot => bot.KeepRunning)) {
bot.Stop();
}
}
internal static void Restart() {
@ -36,53 +44,6 @@ namespace ArchiSteamFarm {
Environment.Exit(0);
}
internal static void InitShutdownSequence() {
foreach (Bot bot in Bot.Bots.Values.Where(bot => bot.KeepRunning)) {
bot.Stop();
}
}
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if (args?.ExceptionObject == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
return;
}
ASF.ArchiLogger.LogFatalException((Exception) args.ExceptionObject);
}
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
if (args?.Exception == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.Exception));
return;
}
ASF.ArchiLogger.LogFatalException(args.Exception);
}
private static void InitServices() {
string globalConfigFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName);
GlobalConfig = GlobalConfig.Load(globalConfigFile);
if (GlobalConfig == null) {
ASF.ArchiLogger.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!");
Exit(1);
}
string globalDatabaseFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName);
GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile);
if (GlobalDatabase == null) {
ASF.ArchiLogger.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
Exit(1);
}
ArchiWebHandler.Init();
WebBrowser.Init();
WebBrowser = new WebBrowser(ASF.ArchiLogger);
}
private static void Init() {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
@ -99,7 +60,6 @@ namespace ArchiSteamFarm {
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory("..");
@ -129,15 +89,38 @@ namespace ArchiSteamFarm {
Directory.CreateDirectory(SharedInfo.DebugDirectory);
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener());
SteamKit2.DebugLog.Enabled = true;
DebugLog.AddListener(new Debugging.DebugListener());
DebugLog.Enabled = true;
}
Logging.InitEnhancedLoggers();
}
private static void InitServices() {
string globalConfigFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName);
GlobalConfig = GlobalConfig.Load(globalConfigFile);
if (GlobalConfig == null) {
ASF.ArchiLogger.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!");
Exit(1);
}
string globalDatabaseFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName);
GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile);
if (GlobalDatabase == null) {
ASF.ArchiLogger.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
Exit(1);
}
ArchiWebHandler.Init();
WebBrowser.Init();
WebBrowser = new WebBrowser(ASF.ArchiLogger);
}
/// <summary>
/// The main entry point for the application.
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main() {
@ -146,5 +129,23 @@ namespace ArchiSteamFarm {
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if (args?.ExceptionObject == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
return;
}
ASF.ArchiLogger.LogFatalException((Exception) args.ExceptionObject);
}
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
if (args?.Exception == null) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(args.Exception));
return;
}
ASF.ArchiLogger.LogFatalException(args.Exception);
}
}
}

View file

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

View file

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

View file

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<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" />