Compare commits

..

86 Commits

Author SHA1 Message Date
JustArchi
d25d4dcba6 Always prefer alphabetical order when redeeming keys, #319 2016-08-01 18:47:44 +02:00
JustArchi
e2cf04afdd General improvements 2016-08-01 18:33:52 +02:00
JustArchi
d85d41c215 General improvements 2016-08-01 18:17:51 +02:00
JustArchi
6977343906 Refactoring 2016-08-01 17:23:40 +02:00
JustArchi
3934a3fdbc Add runtime check 2016-08-01 17:10:37 +02:00
JustArchi
66e01113bb Misc 2016-07-31 18:05:21 +02:00
JustArchi
646e52c28b Never attempt to start farming prior to ArchiWebHandler initialization 2016-07-31 18:03:06 +02:00
JustArchi
2e28319535 Misc 2016-07-31 17:50:37 +02:00
JustArchi
860b76f720 Correct statuses 2016-07-31 17:49:02 +02:00
JustArchi
82cbcecb6b Misc 2016-07-31 17:38:14 +02:00
JustArchi
fda3b1ac31 Misc 2016-07-31 15:39:46 +02:00
JustArchi
58453e72ac Misc 2016-07-31 01:25:18 +02:00
JustArchi
d8aad682d5 DeviceID hardening 2016-07-31 01:22:58 +02:00
JustArchi
8cd971825e Bump 2016-07-30 21:20:21 +02:00
JustArchi
a4b238b5a0 Bump 2016-07-30 21:18:59 +02:00
JustArchi
05f9804138 Fix deadlock 2016-07-30 21:14:55 +02:00
JustArchi
1f58c92b5e And make 0 not acceptable 2016-07-30 21:03:23 +02:00
JustArchi
198c51f3da Make cardsRemaining in CheckPage() mandatory
If this game is eligible for farm and we didn't skip it earlier, it must have cards drop remaining
2016-07-30 21:01:56 +02:00
JustArchi
d517562467 Add more farming orders 2016-07-30 20:55:43 +02:00
JustArchi
53dcd22cea Restart farming also when user registers key outside of ASF
This is now possible thanks to license callback
2016-07-30 18:28:28 +02:00
Łukasz Domeradzki
af050ae67e Merge pull request #317 from stackia/master
Revert "Make WCF interface async"
2016-07-30 06:19:28 +02:00
stackia
914936acdc Revert "Make WCF interface async"
This reverts commit fbb24506e2.
2016-07-30 12:17:01 +08:00
Łukasz Domeradzki
4bba55e8fd Merge pull request #316 from stackia/master
Make WCF interface async
2016-07-30 05:59:21 +02:00
stackia
3e1358b363 Add number of cards drop remaining in "!status" response 2016-07-30 11:52:21 +08:00
stackia
fbb24506e2 Make WCF interface async 2016-07-30 11:51:00 +08:00
JustArchi
48eae1be1a Final CodeStyle update
So it turns out that those options are under C# Text Editor, weird, at least they're exported now
2016-07-30 00:51:43 +02:00
JustArchi
6bbe543fdd Update CodeStyle
Include only options for: All languages, General and C#, I don't need to bloat anybody with other things
2016-07-30 00:47:31 +02:00
Łukasz Domeradzki
afe1d57605 Merge pull request #313 from stackia/master
Update ReSharper DotSettings file
2016-07-30 00:43:40 +02:00
stackia
5596e55c7b Update ReSharper DotSettings file
This purges all green squiggly underlines on my machine.
2016-07-30 06:39:46 +08:00
JustArchi
73cefc697e Move and regenerate CodeStyle 2016-07-30 00:32:34 +02:00
JustArchi
5680929203 Misc cleanup 2016-07-30 00:24:19 +02:00
JustArchi
c94547c68e Move FarmingOrder a bit higher and add missing example.json property
In general we should prioritize properties according to the ones user will want to modify first, but as it's unpredictable, we can only guess and do it more or less
2016-07-30 00:17:58 +02:00
Łukasz Domeradzki
11c748192f Merge pull request #312 from stackia/master
Add an option to set farming order
2016-07-30 00:09:44 +02:00
stackia
8cc3fec432 Implement Equals method on Game so it properly works with HashSet<T> 2016-07-30 05:56:31 +08:00
stackia
7baa54377a Add [JsonProperty] on Game's properties 2016-07-30 05:50:51 +08:00
stackia
4bc7fded2d Use new ConcurrentHashSet.AddRange 2016-07-30 05:49:15 +08:00
stackia
24aa1b4873 Merge remote-tracking branch 'upstream/master' 2016-07-30 05:48:02 +08:00
stackia
561f8f61df Check argument range in Game constructor 2016-07-30 05:46:21 +08:00
JustArchi
c33f575c40 Implement AddRange() + refactoring 2016-07-29 23:36:57 +02:00
stackia
2c54f6b051 Use ConcurrentHashSet<Game> for GamesToFarm and fix game didn't get sorted after first badge page 2016-07-30 05:34:57 +08:00
Stackie Jia
e0385cd343 Merge branch 'master' into master 2016-07-30 05:14:04 +08:00
stackia
80abd3ed69 Add an option to set farming order 2016-07-30 05:06:40 +08:00
JustArchi
e726558e72 Bump 2016-07-29 22:36:00 +02:00
JustArchi
07af05ad00 Revert recent confirmations rate-limiting, add debug 2016-07-29 22:34:52 +02:00
JustArchi
ef5b108b34 Correct MaxConfirmationsPerRequest 2016-07-29 14:48:34 +02:00
JustArchi
1ee9fec845 Bump 2016-07-29 03:40:41 +02:00
JustArchi
4bf1462381 Bump 2016-07-29 03:36:32 +02:00
JustArchi
e41d2cf37e Report on confirmation issues 2016-07-29 01:02:58 +02:00
JustArchi
fac5e65035 Fix accepting over 30 confirmations 2016-07-29 00:46:17 +02:00
JustArchi
b44115711b Don't stop keys forwarding if initial bot gets OnCooldown
In this case, move to the next one, try to redeem, and get the package data from it instead
2016-07-28 21:40:40 +02:00
JustArchi
a90573e0ea Implement smart algorithm of avoiding OnCooldown
I really like this approach, as it has only one caveat: memory usage.
We need to keep in memory list of all packages that our account owns, which will result of N * 32bit extra memory usage, where N is equal to number of package licenses the account owns. This results in around 16 KB extra memory usage for my 4k account.
However, apart from that there are no real caveats as checking if we own given packageID is O(1) operation, and I think that apart from this extra memory footprint there can be more benefits of having this field in future, besides, my 16 KB is extreme case, as usually nobody goes that high. Ship it!
2016-07-28 21:20:57 +02:00
JustArchi
5611694b90 Misc 2016-07-28 18:27:01 +02:00
JustArchi
efd7fbd3c0 Style fixes + move ServiceHost to try block 2016-07-28 18:07:26 +02:00
Łukasz Domeradzki
d32882d91e Merge pull request #309 from stackia/master
Add an option to switch WCF metadata publishing
2016-07-28 18:04:29 +02:00
stackia
8208e9aa77 Remove the WCFPublishMetadata config option, make it always on 2016-07-28 23:58:25 +08:00
stackia
c13eb02e51 Add an option to switch WCF metadata publishing 2016-07-28 20:34:35 +08:00
JustArchi
93ac6d5a28 Bump 2016-07-28 00:07:33 +02:00
JustArchi
413d44b42b Those should be errors 2016-07-27 04:53:23 +02:00
JustArchi
c52934ed9a Bring in small ArchiBoT trading enhancements
This mostly fixed forwarded tradeIDs to contain only trades that we indeed accepted and need to confirm, instead of all trades we're dealing with
2016-07-27 04:48:28 +02:00
JustArchi
b853c93c3f Precise Mono versions affected by bug 41701 2016-07-25 15:05:15 +02:00
JustArchi
0a86107804 Misc 2016-07-25 06:53:16 +02:00
JustArchi
cc2289798e Enable PortReuse on Windows + misc 2016-07-25 06:44:10 +02:00
JustArchi
4f01dc39fc Code review 2016-07-25 06:08:45 +02:00
JustArchi
52de999443 Bump 2016-07-25 00:10:17 +02:00
JustArchi
8998bf4832 Add extra support for fatal exceptions 2016-07-25 00:10:06 +02:00
JustArchi
49f6181263 Misc 2016-07-24 02:59:07 +02:00
JustArchi
e094a02762 Misc 2016-07-24 02:51:16 +02:00
JustArchi
410f31f995 Misc 2016-07-24 02:49:59 +02:00
JustArchi
85a70911e1 Add support of command-line-arguments to run.sh 2016-07-24 02:48:49 +02:00
JustArchi
0714c4e575 Add support for specifying --path= 2016-07-24 00:36:15 +02:00
JustArchi
b19a5c533f GetMySteamInventory() hardening 2016-07-23 01:58:56 +02:00
JustArchi
4129db0149 Bump 2016-07-22 06:27:11 +02:00
JustArchi
01c8e34b4d Remove obsolete GUI
Maybe in future I'll replace it with ASFui being developed on SG
2016-07-21 23:51:05 +02:00
JustArchi
b94bfae804 Revert "Fix compilation with Mono master"
This reverts commit b64491e284.

Didn't fix the issue as mcs.exe is bugged as well, I'll just live with Mono weekly being broken and unreliable ¯\_(ツ)_/¯
2016-07-21 23:38:33 +02:00
JustArchi
b64491e284 Fix compilation with Mono master
Bugs, bugs everywhere! https://bugzilla.xamarin.com/show_bug.cgi?id=42606
2016-07-21 23:24:03 +02:00
JustArchi
ba40448e7d Correct workaround for bug 41701
It's fixed since 4.5.3 onwards: e62c5b001d
2016-07-21 19:20:39 +02:00
JustArchi
3e8ef399dc Fix broken restart, closes #300 2016-07-19 22:18:00 +02:00
JustArchi
afbfb62bed Always expect english responses, closes #301
Also some misc fixes while I'm at it
2016-07-19 20:14:21 +02:00
JustArchi
0c709c6ca6 Bump 2016-07-19 05:09:53 +02:00
JustArchi
325529262a Revert "Modify default logic of handling keys"
This reverts commit 84271488e6.

http://steamcommunity.com/groups/ascfarm/discussions/1/352788552264319199
2016-07-19 05:05:23 +02:00
JustArchi
8cbda098de Always reset one-time-only access token after logging in
And not only if it succeeds
2016-07-19 05:01:52 +02:00
JustArchi
bfd37b4c06 Slightly modify Restart()
This fixes potential ultra-rare race condition of new process WCF being unable to bind to listening socket due to old process stil occupying it
2016-07-18 11:03:13 +02:00
JustArchi
39bc1fe719 Initialize AutoUpdatesTimer earlier
This fixes the potential issue of not initializing timer if first version check fails due to e.g. network failure
2016-07-18 10:51:25 +02:00
JustArchi
0e549985d2 Rewrite Steam crypto
This is much more readable and can be easily adapted if Valve changes anything, I never liked SDA code
2016-07-18 06:56:14 +02:00
JustArchi
496d2910e0 Move crypto to unmanaged implementation
Which is much better if Mono can handle it, should be OK
2016-07-18 06:01:21 +02:00
JustArchi
59c2e10bf1 Bump 2016-07-18 05:31:27 +02:00
48 changed files with 981 additions and 14848 deletions

1
.gitignore vendored
View File

@@ -23,6 +23,7 @@ out/
## files generated by popular Visual Studio add-ons.
# User-specific files
.vs/
*.suo
*.user
*.sln.docstates

View File

@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm", "ArchiSteamFarm\ArchiSteamFarm.csproj", "{35AF7887-08B9-40E8-A5EA-797D8B60B30C}"
EndProject
@@ -10,11 +10,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigGenerator", "ConfigGe
{35AF7887-08B9-40E8-A5EA-797D8B60B30C} = {35AF7887-08B9-40E8-A5EA-797D8B60B30C}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GUI", "GUI\GUI.csproj", "{599121A9-5887-4522-A3D6-61470B90BAD4}"
ProjectSection(ProjectDependencies) = postProject
{35AF7887-08B9-40E8-A5EA-797D8B60B30C} = {35AF7887-08B9-40E8-A5EA-797D8B60B30C}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -29,10 +24,6 @@ Global
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Release|Any CPU.Build.0 = Release|Any CPU
{599121A9-5887-4522-A3D6-61470B90BAD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{599121A9-5887-4522-A3D6-61470B90BAD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{599121A9-5887-4522-A3D6-61470B90BAD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{599121A9-5887-4522-A3D6-61470B90BAD4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,8 +1,22 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_OWNER_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/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: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>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FINALLY_ON_NEW_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHING_EMPTY_BRACES/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/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>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=AES/@EntryIndexedValue">AES</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ASF/@EntryIndexedValue">ASF</s:String>
@@ -12,6 +26,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IP/@EntryIndexedValue">IP</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=OK/@EntryIndexedValue">OK</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PICS/@EntryIndexedValue">PICS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PIN/@EntryIndexedValue">PIN</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SC/@EntryIndexedValue">SC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SMS/@EntryIndexedValue">SMS</s:String>

View File

@@ -223,7 +223,7 @@ namespace ArchiSteamFarm {
PlayGames(new List<uint> { gameID }, gameName);
}
internal void PlayGames(ICollection<uint> gameIDs, string gameName = null) {
internal void PlayGames(IEnumerable<uint> gameIDs, string gameName = null) {
if (gameIDs == null) {
Logging.LogNullError(nameof(gameIDs), Bot.BotName);
return;

View File

@@ -38,10 +38,10 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler : IDisposable {
private const string SteamCommunityHost = "steamcommunity.com";
private const byte MinSessionTTL = 15; // Assume session is valid for at least that amount of seconds
private const byte MinSessionTTL = GlobalConfig.DefaultHttpTimeout / 4; // Assume session is valid for at least that amount of seconds
private static string SteamCommunityURL = "https://" + SteamCommunityHost;
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000;
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000; // This must be int type
private readonly Bot Bot;
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
@@ -310,7 +310,7 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/mobileconf/details/" + confirmation.ID + "?p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
string request = SteamCommunityURL + "/mobileconf/details/" + confirmation.ID + "?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
string json = await WebBrowser.UrlGetToContentRetry(request).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) {
@@ -377,6 +377,11 @@ namespace ArchiSteamFarm {
}
if (response != null) {
// TODO: Remove me
if (!response.Success) {
Logging.LogGenericError("HandleConfirmations() debug content: " + json, Bot.BotName);
}
return response.Success;
}
@@ -710,18 +715,18 @@ namespace ArchiSteamFarm {
return false;
}
internal async Task<HashSet<Steam.Item>> GetMyInventory(bool tradable) {
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable) {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?l=english&trading=" + (tradable ? "1" : "0") + "&start=";
uint currentPage = 0;
while (true) {
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=" + (tradable ? "1" : "0") + "&start=" + currentPage;
JObject jObject = await WebBrowser.UrlGetToJObjectRetry(request).ConfigureAwait(false);
while (true) {
JObject jObject = await WebBrowser.UrlGetToJObjectRetry(request + currentPage).ConfigureAwait(false);
IEnumerable<JToken> descriptions = jObject?.SelectTokens("$.rgDescriptions.*");
if (descriptions == null) {
@@ -729,7 +734,7 @@ namespace ArchiSteamFarm {
}
Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptionMap = new Dictionary<ulong, Tuple<uint, Steam.Item.EType>>();
foreach (JToken description in descriptions) {
foreach (JToken description in descriptions.Where(description => description != null)) {
string classIDString = description["classid"].ToString();
if (string.IsNullOrEmpty(classIDString)) {
Logging.LogNullError(nameof(classIDString), Bot.BotName);
@@ -782,7 +787,7 @@ namespace ArchiSteamFarm {
return null;
}
foreach (JToken item in items) {
foreach (JToken item in items.Where(item => item != null)) {
Steam.Item steamItem;
try {

View File

@@ -26,6 +26,7 @@ using Newtonsoft.Json;
using SteamKit2;
using SteamKit2.Internal;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -43,7 +44,7 @@ namespace ArchiSteamFarm {
private const ushort CallbackSleep = 500; // In miliseconds
private const ushort MaxSteamMessageLength = 2048;
internal static readonly Dictionary<string, Bot> Bots = new Dictionary<string, Bot>();
internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
private static readonly uint LoginID = MsgClientLogon.ObfuscationMask; // This must be the same for all ASF bots and all ASF processes
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
@@ -63,6 +64,7 @@ namespace ArchiSteamFarm {
private readonly CardsFarmer CardsFarmer;
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
private readonly ConcurrentHashSet<uint> OwnedPackageIDs = new ConcurrentHashSet<uint>();
private readonly SteamApps SteamApps;
private readonly SteamFriends SteamFriends;
private readonly SteamUser SteamUser;
@@ -77,6 +79,19 @@ namespace ArchiSteamFarm {
private bool FirstTradeSent, InvalidPassword, SkipFirstShutdown;
private string AuthCode, TwoFactorCode;
internal static string GetAPIStatus() {
var response = new {
Bots
};
try {
return JsonConvert.SerializeObject(response);
} catch (JsonException e) {
Logging.LogGenericException(e);
return null;
}
}
internal static void InitializeCMs(uint cellID, IServerListProvider serverListProvider) {
if (serverListProvider == null) {
Logging.LogNullError(nameof(serverListProvider));
@@ -135,9 +150,11 @@ namespace ArchiSteamFarm {
BotName = botName;
SentryFile = botPath + ".bin";
BotConfig = BotConfig.Load(botPath + ".json");
string botConfigFile = botPath + ".json";
BotConfig = BotConfig.Load(botConfigFile);
if (BotConfig == null) {
Logging.LogGenericError("Your bot config is invalid, refusing to start this bot instance!", botName);
Logging.LogGenericError("Your bot config is invalid, please verify content of " + botConfigFile + " and try again!", botName);
return;
}
@@ -146,9 +163,11 @@ namespace ArchiSteamFarm {
return;
}
BotDatabase = BotDatabase.Load(botPath + ".db");
string botDatabaseFile = botPath + ".db";
BotDatabase = BotDatabase.Load(botDatabaseFile);
if (BotDatabase == null) {
Logging.LogGenericError("Bot database could not be loaded, refusing to start this bot instance!", botName);
Logging.LogGenericError("Bot database could not be loaded, refusing to create this bot instance! In order to recreate it, remove " + botDatabaseFile + " and try again!", botName);
return;
}
@@ -184,6 +203,7 @@ namespace ArchiSteamFarm {
SteamApps = SteamClient.GetHandler<SteamApps>();
CallbackManager.Subscribe<SteamApps.FreeLicenseCallback>(OnFreeLicense);
CallbackManager.Subscribe<SteamApps.GuestPassListCallback>(OnGuestPassList);
CallbackManager.Subscribe<SteamApps.LicenseListCallback>(OnLicenseList);
SteamFriends = SteamClient.GetHandler<SteamFriends>();
CallbackManager.Subscribe<SteamFriends.ChatInviteCallback>(OnChatInvite);
@@ -250,41 +270,45 @@ namespace ArchiSteamFarm {
Trading?.Dispose();
}
internal async Task AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
internal async Task<bool> AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
if (BotDatabase.MobileAuthenticator == null) {
return;
return false;
}
HashSet<MobileAuthenticator.Confirmation> confirmations = await BotDatabase.MobileAuthenticator.GetConfirmations().ConfigureAwait(false);
if ((confirmations == null) || (confirmations.Count == 0)) {
return;
return true;
}
if (acceptedType != Steam.ConfirmationDetails.EType.Unknown) {
if (confirmations.RemoveWhere(confirmation => (confirmation.Type != acceptedType) && (confirmation.Type != Steam.ConfirmationDetails.EType.Other)) > 0) {
if (confirmations.Count == 0) {
return;
return true;
}
}
}
if ((acceptedSteamID != 0) || ((acceptedTradeIDs != null) && (acceptedTradeIDs.Count > 0))) {
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
HashSet<MobileAuthenticator.Confirmation> ignoredConfirmations = new HashSet<MobileAuthenticator.Confirmation>(detailsResults.Where(details => (details != null) && (
((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) ||
((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID))
)).Select(details => details.Confirmation));
if (ignoredConfirmations.Count > 0) {
confirmations.ExceptWith(ignoredConfirmations);
if (confirmations.Count == 0) {
return;
}
}
if ((acceptedSteamID == 0) && ((acceptedTradeIDs == null) || (acceptedTradeIDs.Count == 0))) {
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
}
await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
Steam.ConfirmationDetails[] detailsResults = await Task.WhenAll(confirmations.Select(BotDatabase.MobileAuthenticator.GetConfirmationDetails)).ConfigureAwait(false);
HashSet<MobileAuthenticator.Confirmation> ignoredConfirmations = new HashSet<MobileAuthenticator.Confirmation>(detailsResults.Where(details => (details != null) && (
((acceptedSteamID != 0) && (details.OtherSteamID64 != 0) && (acceptedSteamID != details.OtherSteamID64)) ||
((acceptedTradeIDs != null) && (details.TradeOfferID != 0) && !acceptedTradeIDs.Contains(details.TradeOfferID))
)).Select(details => details.Confirmation));
if (ignoredConfirmations.Count == 0) {
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
}
confirmations.ExceptWith(ignoredConfirmations);
if (confirmations.Count == 0) {
return true;
}
return await BotDatabase.MobileAuthenticator.HandleConfirmations(confirmations, accept).ConfigureAwait(false);
}
internal async Task<bool> RefreshSession() {
@@ -507,7 +531,8 @@ namespace ArchiSteamFarm {
BotDatabase.MobileAuthenticator.Init(this);
if (!BotDatabase.MobileAuthenticator.HasDeviceID) {
if (!BotDatabase.MobileAuthenticator.HasCorrectDeviceID) {
Logging.LogGenericWarning("Your DeviceID is incorrect or doesn't exist", BotName);
string deviceID = Program.GetUserInput(Program.EUserInputType.DeviceID, BotName);
if (string.IsNullOrEmpty(deviceID)) {
BotDatabase.MobileAuthenticator = null;
@@ -626,7 +651,7 @@ namespace ArchiSteamFarm {
}
if (CardsFarmer.CurrentGamesFarming.Count > 0) {
return "Bot " + BotName + " is farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games left to farm.";
return "Bot " + BotName + " is farming appIDs: " + string.Join(", ", CardsFarmer.CurrentGamesFarming) + " and has a total of " + CardsFarmer.GamesToFarm.Count + " games (" + CardsFarmer.GamesToFarm.Sum(game => game.CardsRemaining) + " cards) to farm.";
}
return "Bot " + BotName + " is not farming anything.";
@@ -694,7 +719,7 @@ namespace ArchiSteamFarm {
await Trading.LimitInventoryRequestsAsync().ConfigureAwait(false);
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMyInventory(true).ConfigureAwait(false);
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMySteamInventory(true).ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) {
return "Nothing to send, inventory seems empty!";
}
@@ -797,8 +822,11 @@ namespace ArchiSteamFarm {
return "That bot doesn't have ASF 2FA enabled!";
}
await AcceptConfirmations(confirm).ConfigureAwait(false);
return "Done!";
if (await AcceptConfirmations(confirm).ConfigureAwait(false)) {
return "Success!";
}
return "Something went wrong!";
}
private static async Task<string> Response2FAConfirm(ulong steamID, string botName, bool confirm) {
@@ -820,25 +848,12 @@ namespace ArchiSteamFarm {
}
private static string ResponseAPI(ulong steamID) {
if (steamID == 0) {
Logging.LogNullError(nameof(steamID));
return null;
if (steamID != 0) {
return !IsOwner(steamID) ? null : GetAPIStatus();
}
if (!IsOwner(steamID)) {
return null;
}
var response = new {
Bots
};
try {
return JsonConvert.SerializeObject(response);
} catch (JsonException e) {
Logging.LogGenericException(e);
return null;
}
Logging.LogNullError(nameof(steamID));
return null;
}
private static string ResponseExit(ulong steamID) {
@@ -924,11 +939,9 @@ namespace ArchiSteamFarm {
StringBuilder response = new StringBuilder();
using (StringReader reader = new StringReader(message))
using (IEnumerator<Bot> iterator = Bots.Values.GetEnumerator()) {
using (IEnumerator<Bot> iterator = Bots.OrderBy(bot => bot.Key).Select(bot => bot.Value).GetEnumerator()) {
string key = reader.ReadLine();
Bot currentBot = this;
bool startHandling = false;
while (!string.IsNullOrEmpty(key) && (currentBot != null)) {
if (validate && !IsValidCdKey(key)) {
key = reader.ReadLine(); // Next key
@@ -971,17 +984,8 @@ namespace ArchiSteamFarm {
break; // Next bot, without changing key
}
bool startInnerHandling = false;
bool alreadyInnerHandled = false;
foreach (Bot bot in Bots.Values) {
if (bot == this) {
startInnerHandling = true;
continue;
}
if (!startInnerHandling || !bot.SteamClient.IsConnected) {
continue;
}
bool alreadyHandled = false;
foreach (Bot bot in Bots.Where(bot => (bot.Value != this) && bot.Value.SteamClient.IsConnected && ((result.Items.Count == 0) || result.Items.Keys.Any(packageID => !bot.Value.OwnedPackageIDs.Contains(packageID)))).OrderBy(bot => bot.Key).Select(bot => bot.Value)) {
ArchiHandler.PurchaseResponseCallback otherResult = await bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
if (otherResult == null) {
@@ -993,15 +997,23 @@ namespace ArchiSteamFarm {
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.DuplicatedKey:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.InvalidKey:
case ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK:
alreadyInnerHandled = true; // This key is already handled, as we either redeemed it or we're sure it's dupe/invalid
alreadyHandled = true; // This key is already handled, as we either redeemed it or we're sure it's dupe/invalid
break;
}
response.Append(Environment.NewLine + "<" + bot.BotName + "> Key: " + key + " | Status: " + otherResult.PurchaseResult + " | Items: " + string.Join("", otherResult.Items));
if (alreadyInnerHandled) {
if (alreadyHandled) {
break;
}
if (result.Items.Count != 0) {
continue;
}
foreach (KeyValuePair<uint, string> item in otherResult.Items) {
result.Items[item.Key] = item.Value;
}
}
key = reader.ReadLine(); // Next key
@@ -1016,10 +1028,7 @@ namespace ArchiSteamFarm {
do {
currentBot = iterator.MoveNext() ? iterator.Current : null;
if (currentBot == this) {
startHandling = true;
}
} while (!startHandling || (currentBot == this) || ((currentBot != null) && !currentBot.SteamClient.IsConnected));
} while ((currentBot == this) || ((currentBot != null) && !currentBot.SteamClient.IsConnected));
}
}
@@ -1605,15 +1614,16 @@ namespace ArchiSteamFarm {
return;
}
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
await Task.Delay(1000).ConfigureAwait(false);
}
if (!ArchiWebHandler.Ready) {
return;
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
await Task.Delay(1000).ConfigureAwait(false);
}
if (!ArchiWebHandler.Ready) {
return;
}
}
bool acceptedSomething = false;
foreach (ulong gid in callback.GuestPasses.Select(guestPass => guestPass["gid"].AsUnsignedLong()).Where(gid => (gid != 0) && !HandledGifts.Contains(gid))) {
HandledGifts.Add(gid);
@@ -1621,16 +1631,40 @@ namespace ArchiSteamFarm {
await LimitGiftsRequestsAsync().ConfigureAwait(false);
if (await ArchiWebHandler.AcceptGift(gid).ConfigureAwait(false)) {
acceptedSomething = true;
Logging.LogGenericInfo("Success!", BotName);
} else {
Logging.LogGenericInfo("Failed!", BotName);
}
}
}
if (acceptedSomething) {
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
private async void OnLicenseList(SteamApps.LicenseListCallback callback) {
if (callback?.LicenseList == null) {
Logging.LogNullError(nameof(callback) + " || " + nameof(callback.LicenseList), BotName);
return;
}
OwnedPackageIDs.Clear();
foreach (SteamApps.LicenseListCallback.License license in callback.LicenseList) {
OwnedPackageIDs.Add(license.PackageID);
}
OwnedPackageIDs.TrimExcess();
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
if (!ArchiWebHandler.Ready) {
for (byte i = 0; (i < Program.GlobalConfig.HttpTimeout) && !ArchiWebHandler.Ready; i++) {
await Task.Delay(1000).ConfigureAwait(false);
}
if (!ArchiWebHandler.Ready) {
return;
}
}
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
}
private void OnChatInvite(SteamFriends.ChatInviteCallback callback) {
@@ -1761,6 +1795,9 @@ namespace ArchiSteamFarm {
return;
}
// Reset one-time-only access tokens
AuthCode = TwoFactorCode = null;
switch (callback.Result) {
case EResult.AccountLogonDenied:
AuthCode = Program.GetUserInput(Program.EUserInputType.SteamGuard, BotName);
@@ -1801,9 +1838,6 @@ namespace ArchiSteamFarm {
}
}
// Reset one-time-only access tokens
AuthCode = TwoFactorCode = null;
if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
BotConfig.SteamParentalPIN = Program.GetUserInput(Program.EUserInputType.SteamParentalPIN, BotName);
if (string.IsNullOrEmpty(BotConfig.SteamParentalPIN)) {
@@ -1834,9 +1868,6 @@ namespace ArchiSteamFarm {
}
Trading.CheckTrades().Forget();
await Task.Delay(1000).ConfigureAwait(false); // Wait a second for eventual PlayingSessionStateCallback
CardsFarmer.StartFarming().Forget();
break;
case EResult.NoConnection:
case EResult.ServiceUnavailable:
@@ -1964,14 +1995,9 @@ namespace ArchiSteamFarm {
}
}
private async void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
private void OnPurchaseResponse(ArchiHandler.PurchaseResponseCallback callback) {
if (callback == null) {
Logging.LogNullError(nameof(callback), BotName);
return;
}
if (callback.PurchaseResult == ArchiHandler.PurchaseResponseCallback.EPurchaseResult.OK) {
await CardsFarmer.OnNewGameAdded().ConfigureAwait(false);
}
}
}

View File

@@ -32,12 +32,25 @@ using System.Linq;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class BotConfig {
[JsonProperty(Required = Required.DisallowNull)]
internal bool Enabled { get; private set; } = false;
internal enum EFarmingOrder : byte {
Unordered,
AppIDsAscending,
AppIDsDescending,
CardDropsAscending,
CardDropsDescending,
HoursAscending,
HoursDescending,
NamesAscending,
NamesDescending
}
[JsonProperty(Required = Required.DisallowNull)]
internal bool StartOnLaunch { get; private set; } = true;
internal readonly bool Enabled = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool StartOnLaunch = true;
[JsonProperty]
internal string SteamLogin { get; set; }
@@ -53,65 +66,67 @@ namespace ArchiSteamFarm {
internal string SteamParentalPIN { get; set; } = "0";
[JsonProperty]
internal string SteamApiKey { get; private set; } = null;
internal readonly string SteamApiKey = null;
[JsonProperty(Required = Required.DisallowNull)]
internal ulong SteamMasterID { get; private set; } = 0;
internal readonly ulong SteamMasterID = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal ulong SteamMasterClanID { get; private set; } = 0;
internal readonly ulong SteamMasterClanID = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal bool CardDropsRestricted { get; private set; } = false;
internal readonly bool CardDropsRestricted = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool DismissInventoryNotifications { get; private set; } = true;
internal readonly bool DismissInventoryNotifications = true;
[JsonProperty(Required = Required.DisallowNull)]
internal bool FarmOffline { get; private set; } = false;
internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered;
[JsonProperty(Required = Required.DisallowNull)]
internal bool HandleOfflineMessages { get; private set; } = false;
internal readonly bool FarmOffline = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AcceptGifts { get; private set; } = false;
internal readonly bool HandleOfflineMessages = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool IsBotAccount { get; private set; } = false;
internal readonly bool AcceptGifts = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool SteamTradeMatcher { get; private set; } = false;
internal readonly bool IsBotAccount = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ForwardKeysToOtherBots { get; private set; } = false;
internal readonly bool SteamTradeMatcher = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool DistributeKeys { get; private set; } = false;
internal readonly bool ForwardKeysToOtherBots = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
internal readonly bool DistributeKeys = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool SendOnFarmingFinished { get; private set; } = false;
internal readonly bool ShutdownOnFarmingFinished = false;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool SendOnFarmingFinished = false;
[JsonProperty]
internal string SteamTradeToken { get; private set; } = null;
internal readonly string SteamTradeToken = null;
[JsonProperty(Required = Required.DisallowNull)]
internal byte SendTradePeriod { get; private set; } = 0;
internal readonly byte SendTradePeriod = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal byte AcceptConfirmationsPeriod { get; private set; } = 0;
internal readonly byte AcceptConfirmationsPeriod = 0;
[JsonProperty]
internal string CustomGamePlayedWhileFarming { get; private set; } = null;
internal readonly string CustomGamePlayedWhileFarming = null;
[JsonProperty]
internal string CustomGamePlayedWhileIdle { get; private set; } = null;
internal readonly string CustomGamePlayedWhileIdle = null;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>();
internal readonly HashSet<uint> GamesPlayedWhileIdle = new HashSet<uint>();
internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
@@ -132,6 +147,11 @@ namespace ArchiSteamFarm {
return null;
}
if (botConfig == null) {
Logging.LogNullError(nameof(botConfig));
return null;
}
// Support encrypted passwords
if ((botConfig.PasswordFormat != CryptoHelper.ECryptoMethod.PlainText) && !string.IsNullOrEmpty(botConfig.SteamPassword)) {
// In worst case password will result in null, which will have to be corrected by user during runtime
@@ -145,7 +165,10 @@ namespace ArchiSteamFarm {
}
Logging.LogGenericWarning("Playing more than " + CardsFarmer.MaxGamesPlayedConcurrently + " games concurrently is not possible, only first " + CardsFarmer.MaxGamesPlayedConcurrently + " entries from GamesPlayedWhileIdle will be used");
botConfig.GamesPlayedWhileIdle = new HashSet<uint>(botConfig.GamesPlayedWhileIdle.Take(CardsFarmer.MaxGamesPlayedConcurrently));
HashSet<uint> validGames = new HashSet<uint>(botConfig.GamesPlayedWhileIdle.Take(CardsFarmer.MaxGamesPlayedConcurrently));
botConfig.GamesPlayedWhileIdle.IntersectWith(validGames);
botConfig.GamesPlayedWhileIdle.TrimExcess();
return botConfig;
}

View File

@@ -24,7 +24,6 @@
using HtmlAgilityPack;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
@@ -35,13 +34,54 @@ 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 byte CardsRemaining { get; set; }
internal Game(uint appID, string gameName, float hoursPlayed, byte 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;
}
return obj is Game && Equals((Game) obj);
}
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 ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>();
[JsonProperty]
internal readonly ConcurrentHashSet<uint> CurrentGamesFarming = new ConcurrentHashSet<uint>();
internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>();
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
@@ -113,7 +153,7 @@ namespace ArchiSteamFarm {
return;
}
Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games to farm on this account...", Bot.BotName);
Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games (" + GamesToFarm.Sum(game => game.CardsRemaining) + " cards) to farm on this account...", Bot.BotName);
// This is the last moment for final check if we can farm
if (Bot.PlayingBlocked) {
@@ -130,20 +170,20 @@ namespace ArchiSteamFarm {
if (Bot.BotConfig.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm
Logging.LogGenericInfo("Chosen farming algorithm: Complex", Bot.BotName);
while (GamesToFarm.Count > 0) {
HashSet<uint> gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm);
HashSet<Game> gamesToFarmSolo = new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= 2));
if (gamesToFarmSolo.Count > 0) {
while (gamesToFarmSolo.Count > 0) {
uint appID = gamesToFarmSolo.First();
if (await FarmSolo(appID).ConfigureAwait(false)) {
gamesToFarmSolo.Remove(appID);
Game game = gamesToFarmSolo.First();
if (await FarmSolo(game).ConfigureAwait(false)) {
gamesToFarmSolo.Remove(game);
} else {
NowFarming = false;
return;
}
}
} else {
if (FarmMultiple()) {
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName);
if (FarmMultiple(GamesToFarm.OrderByDescending(game => game.HoursPlayed).Take(MaxGamesPlayedConcurrently))) {
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Select(game => game.AppID)), Bot.BotName);
} else {
NowFarming = false;
return;
@@ -153,8 +193,8 @@ namespace ArchiSteamFarm {
} else { // If we have unrestricted card drops, we use simple algorithm
Logging.LogGenericInfo("Chosen farming algorithm: Simple", Bot.BotName);
while (GamesToFarm.Count > 0) {
uint appID = GamesToFarm.Keys.FirstOrDefault();
if (await FarmSolo(appID).ConfigureAwait(false)) {
Game game = GamesToFarm.First();
if (await FarmSolo(game).ConfigureAwait(false)) {
continue;
}
@@ -218,7 +258,7 @@ namespace ArchiSteamFarm {
return;
}
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Values.Min() < 2)) {
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);
@@ -226,20 +266,6 @@ namespace ArchiSteamFarm {
}
}
private static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
if (gamesToFarm == null) {
Logging.LogNullError(nameof(gamesToFarm));
return null;
}
HashSet<uint> result = new HashSet<uint>();
foreach (KeyValuePair<uint, float> keyValue in gamesToFarm.Where(keyValue => keyValue.Value >= 2)) {
result.Add(keyValue.Key);
}
return result;
}
private async Task<bool> IsAnythingToFarm() {
Logging.LogGenericInfo("Checking badges...", Bot.BotName);
@@ -267,10 +293,11 @@ namespace ArchiSteamFarm {
}
}
GamesToFarm.Clear();
GamesToFarm.ClearAndTrim();
CheckPage(htmlDocument);
if (maxPages == 1) {
SortGamesToFarm();
return GamesToFarm.Count > 0;
}
@@ -283,9 +310,47 @@ namespace ArchiSteamFarm {
}
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:
Logging.LogGenericError("Unhandled case: " + Bot.BotConfig.FarmingOrder, Bot.BotName);
return;
}
GamesToFarm.ReplaceWith(gamesToFarm.ToList()); // We must call ToList() here as we can't enumerate during replacing
}
private void CheckPage(HtmlDocument htmlDocument) {
if (htmlDocument == null) {
Logging.LogNullError(nameof(htmlDocument), Bot.BotName);
@@ -293,7 +358,7 @@ namespace ArchiSteamFarm {
}
HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_title_stats']");
if (htmlNodes == null) { // For example a page full of non-games badges
if (htmlNodes == null) { // No eligible badges
return;
}
@@ -303,6 +368,12 @@ namespace ArchiSteamFarm {
continue; // This game is not needed for farming
}
HtmlNode progressNode = htmlNode.SelectSingleNode(".//span[@class='progress_info_bold']");
if (progressNode == null) {
continue; // e.g. Holiday Sale 2015
}
// AppIDs
string steamLink = farmingNode.GetAttributeValue("href", null);
if (string.IsNullOrEmpty(steamLink)) {
Logging.LogNullError(nameof(steamLink), Bot.BotName);
@@ -333,6 +404,7 @@ namespace ArchiSteamFarm {
continue;
}
// Hours
HtmlNode timeNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
if (timeNode == null) {
Logging.LogNullError(nameof(timeNode), Bot.BotName);
@@ -347,15 +419,64 @@ namespace ArchiSteamFarm {
float hours = 0;
Match match = Regex.Match(hoursString, @"[0-9\.,]+");
if (match.Success) {
if (!float.TryParse(match.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
Match hoursMatch = Regex.Match(hoursString, @"[0-9\.,]+");
if (hoursMatch.Success) { // Might fail if we have 0.0 hours played
if (!float.TryParse(hoursMatch.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
Logging.LogNullError(nameof(hours), Bot.BotName);
return;
}
}
GamesToFarm[appID] = hours;
// Cards
string progress = progressNode.InnerText;
if (string.IsNullOrEmpty(progress)) {
Logging.LogNullError(nameof(progress), Bot.BotName);
return;
}
Match progressMatch = Regex.Match(progress, @"\d+");
if (!progressMatch.Success) {
Logging.LogNullError(nameof(progressMatch), Bot.BotName);
return;
}
byte cardsRemaining;
if (!byte.TryParse(progressMatch.Value, out cardsRemaining) || (cardsRemaining == 0)) {
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
return;
}
// Names
HtmlNode nameNode = htmlNode.SelectSingleNode("(.//div[@class='card_drop_info_body'])[last()]");
if (nameNode == null) {
Logging.LogNullError(nameof(nameNode), Bot.BotName);
return;
}
string name = nameNode.InnerText;
if (string.IsNullOrEmpty(name)) {
Logging.LogNullError(nameof(name), Bot.BotName);
return;
}
int nameStartIndex = name.IndexOf(" by playing ", StringComparison.Ordinal);
if (nameStartIndex <= 0) {
Logging.LogNullError(nameof(nameStartIndex));
return;
}
nameStartIndex += 12;
int nameEndIndex = name.LastIndexOf('.');
if (nameEndIndex <= nameStartIndex) {
Logging.LogNullError(nameof(nameEndIndex));
return;
}
name = name.Substring(nameStartIndex, nameEndIndex - nameStartIndex);
// Final result
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining));
}
}
@@ -381,13 +502,13 @@ namespace ArchiSteamFarm {
StartFarming().Forget();
}
private async Task<bool?> ShouldFarm(uint appID) {
if (appID == 0) {
Logging.LogNullError(nameof(appID), Bot.BotName);
private async Task<bool?> ShouldFarm(Game game) {
if (game == null) {
Logging.LogNullError(nameof(game), Bot.BotName);
return false;
}
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(game.AppID).ConfigureAwait(false);
if (htmlDocument == null) {
return null;
}
@@ -414,80 +535,65 @@ namespace ArchiSteamFarm {
}
}
Logging.LogGenericInfo("Status for " + appID + ": " + cardsRemaining + " cards remaining", Bot.BotName);
game.CardsRemaining = cardsRemaining;
Logging.LogGenericInfo("Status for " + game.AppID + " (" + game.GameName + "): " + cardsRemaining + " cards remaining", Bot.BotName);
return cardsRemaining > 0;
}
private bool FarmMultiple() {
if (GamesToFarm.Count == 0) {
return true;
private bool FarmMultiple(IEnumerable<Game> games) {
if (games == null) {
Logging.LogNullError(nameof(games));
return false;
}
float maxHour = 0;
foreach (KeyValuePair<uint, float> game in GamesToFarm) {
CurrentGamesFarming.Add(game.Key);
if (game.Value > maxHour) {
maxHour = game.Value;
}
CurrentGamesFarming.ReplaceWith(games);
if (CurrentGamesFarming.Count >= MaxGamesPlayedConcurrently) {
break;
}
}
Logging.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming.Select(game => game.AppID)), Bot.BotName);
if (maxHour >= 2) {
CurrentGamesFarming.ClearAndTrim();
return true;
}
Logging.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming), Bot.BotName);
bool result = FarmHours(maxHour, CurrentGamesFarming);
bool result = FarmHours(CurrentGamesFarming);
CurrentGamesFarming.ClearAndTrim();
return result;
}
private async Task<bool> FarmSolo(uint appID) {
if (appID == 0) {
Logging.LogNullError(nameof(appID), Bot.BotName);
private async Task<bool> FarmSolo(Game game) {
if (game == null) {
Logging.LogNullError(nameof(game), Bot.BotName);
return true;
}
CurrentGamesFarming.Add(appID);
CurrentGamesFarming.Add(game);
Logging.LogGenericInfo("Now farming: " + appID, Bot.BotName);
Logging.LogGenericInfo("Now farming: " + game.AppID + " (" + game.GameName + ")", Bot.BotName);
bool result = await Farm(appID).ConfigureAwait(false);
bool result = await Farm(game).ConfigureAwait(false);
CurrentGamesFarming.ClearAndTrim();
if (!result) {
return false;
}
float hours;
if (!GamesToFarm.TryRemove(appID, out hours)) {
return false;
}
GamesToFarm.Remove(game);
TimeSpan timeSpan = TimeSpan.FromHours(hours);
Logging.LogGenericInfo("Done farming: " + appID + " after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!", Bot.BotName);
TimeSpan timeSpan = TimeSpan.FromHours(game.HoursPlayed);
Logging.LogGenericInfo("Done farming: " + game.AppID + " (" + game.GameName + ") after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!", Bot.BotName);
return true;
}
private async Task<bool> Farm(uint appID) {
if (appID == 0) {
Logging.LogNullError(nameof(appID), Bot.BotName);
private async Task<bool> Farm(Game game) {
if (game == null) {
Logging.LogNullError(nameof(game), Bot.BotName);
return false;
}
Bot.ArchiHandler.PlayGame(appID, Bot.BotConfig.CustomGamePlayedWhileFarming);
Bot.ArchiHandler.PlayGame(game.AppID, Bot.BotConfig.CustomGamePlayedWhileFarming);
DateTime endFarmingDate = DateTime.Now.AddHours(Program.GlobalConfig.MaxFarmingTime);
bool success = true;
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
bool? keepFarming = await ShouldFarm(game).ConfigureAwait(false);
while (keepFarming.GetValueOrDefault(true) && (DateTime.Now < endFarmingDate)) {
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
Logging.LogGenericInfo("Still farming: " + game.AppID + " (" + game.GameName + ")", Bot.BotName);
DateTime startFarmingPeriod = DateTime.Now;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
@@ -496,30 +602,41 @@ namespace ArchiSteamFarm {
}
// Don't forget to update our GamesToFarm hours
GamesToFarm[appID] += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
game.HoursPlayed += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
if (!success) {
break;
}
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
keepFarming = await ShouldFarm(game).ConfigureAwait(false);
}
Logging.LogGenericInfo("Stopped farming: " + appID, Bot.BotName);
Logging.LogGenericInfo("Stopped farming: " + game.AppID + " (" + game.GameName + ")", Bot.BotName);
return success;
}
private bool FarmHours(float maxHour, ConcurrentHashSet<uint> appIDs) {
if ((maxHour < 0) || (appIDs == null) || (appIDs.Count == 0)) {
Logging.LogNullError(nameof(maxHour) + " || " + nameof(appIDs) + " || " + nameof(appIDs.Count), Bot.BotName);
private bool FarmHours(ConcurrentHashSet<Game> games) {
if ((games == null) || (games.Count == 0)) {
Logging.LogNullError(nameof(games), Bot.BotName);
return false;
}
Bot.ArchiHandler.PlayGames(appIDs, Bot.BotConfig.CustomGamePlayedWhileFarming);
float maxHour = games.Max(game => game.HoursPlayed);
if (maxHour < 0) {
Logging.LogNullError(nameof(maxHour), Bot.BotName);
return false;
}
if (maxHour >= 2) {
Logging.LogGenericError("Received request for past-2h games!", Bot.BotName);
return true;
}
Bot.ArchiHandler.PlayGames(games.Select(game => game.AppID), Bot.BotConfig.CustomGamePlayedWhileFarming);
bool success = true;
while (maxHour < 2) {
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
Logging.LogGenericInfo("Still farming: " + string.Join(", ", games.Select(game => game.AppID)), Bot.BotName);
DateTime startFarmingPeriod = DateTime.Now;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
@@ -529,8 +646,8 @@ namespace ArchiSteamFarm {
// Don't forget to update our GamesToFarm hours
float timePlayed = (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
foreach (uint appID in appIDs) {
GamesToFarm[appID] += timePlayed;
foreach (Game game in games) {
game.HoursPlayed += timePlayed;
}
if (!success) {
@@ -540,7 +657,7 @@ namespace ArchiSteamFarm {
maxHour += timePlayed;
}
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", appIDs), Bot.BotName);
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", games.Select(game => game.AppID)), Bot.BotName);
return success;
}
}

View File

@@ -25,7 +25,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace ArchiSteamFarm {
@@ -36,6 +35,9 @@ namespace ArchiSteamFarm {
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();
@@ -48,17 +50,6 @@ namespace ArchiSteamFarm {
}
}
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
public bool Add(T item) {
Lock.EnterWriteLock();
try {
return HashSet.Add(item);
} finally {
Lock.ExitWriteLock();
}
}
public void Clear() {
Lock.EnterWriteLock();
@@ -69,17 +60,6 @@ namespace ArchiSteamFarm {
}
}
public void ClearAndTrim() {
Lock.EnterWriteLock();
try {
HashSet.Clear();
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
public bool Contains(T item) {
Lock.EnterReadLock();
@@ -100,7 +80,7 @@ namespace ArchiSteamFarm {
}
}
public void Dispose() => Lock?.Dispose();
public void Dispose() => Lock.Dispose();
public void CopyTo(T[] array, int arrayIndex) {
Lock.EnterReadLock();
@@ -112,8 +92,51 @@ namespace ArchiSteamFarm {
}
}
void ICollection<T>.Add(T item) => Add(item);
internal void Add(T item) {
Lock.EnterWriteLock();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
try {
HashSet.Add(item);
} finally {
Lock.ExitWriteLock();
}
}
internal void ReplaceWith(IEnumerable<T> items) {
Lock.EnterWriteLock();
try {
HashSet.Clear();
foreach (T item in items) {
HashSet.Add(item);
}
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
internal void ClearAndTrim() {
Lock.EnterWriteLock();
try {
HashSet.Clear();
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
internal void TrimExcess() {
Lock.EnterWriteLock();
try {
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
}
}

View File

@@ -89,7 +89,7 @@ namespace ArchiSteamFarm {
try {
byte[] key;
using (SHA256Managed sha256 = new SHA256Managed()) {
using (SHA256Cng sha256 = new SHA256Cng()) {
key = sha256.ComputeHash(EncryptionKey);
}
@@ -110,7 +110,7 @@ namespace ArchiSteamFarm {
try {
byte[] key;
using (SHA256Managed sha256 = new SHA256Managed()) {
using (SHA256Cng sha256 = new SHA256Cng()) {
key = sha256.ComputeHash(EncryptionKey);
}

View File

@@ -32,6 +32,7 @@ using System.Net.Sockets;
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 {
@@ -51,64 +52,64 @@ namespace ArchiSteamFarm {
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
[JsonProperty(Required = Required.DisallowNull)]
internal bool Debug { get; private set; } = false;
internal readonly bool Debug = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool Headless { get; private set; } = false;
internal readonly bool Headless = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoUpdates { get; private set; } = true;
internal readonly bool AutoUpdates = true;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoRestart { get; private set; } = true;
internal readonly bool AutoRestart = true;
[JsonProperty(Required = Required.DisallowNull)]
internal EUpdateChannel UpdateChannel { get; private set; } = EUpdateChannel.Stable;
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;
[JsonProperty(Required = Required.DisallowNull)]
internal ProtocolType SteamProtocol { get; private set; } = DefaultSteamProtocol;
internal readonly ProtocolType SteamProtocol = DefaultSteamProtocol;
[JsonProperty(Required = Required.DisallowNull)]
internal ulong SteamOwnerID { get; private set; } = 0;
internal readonly ulong SteamOwnerID = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal byte MaxFarmingTime { get; private set; } = DefaultMaxFarmingTime;
internal readonly byte MaxFarmingTime = DefaultMaxFarmingTime;
[JsonProperty(Required = Required.DisallowNull)]
internal byte IdleFarmingPeriod { get; private set; } = 3;
internal readonly byte IdleFarmingPeriod = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte FarmingDelay { get; private set; } = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
internal byte LoginLimiterDelay { get; private set; } = 10;
internal readonly byte LoginLimiterDelay = 10;
[JsonProperty(Required = Required.DisallowNull)]
internal byte InventoryLimiterDelay { get; private set; } = 3;
internal readonly byte InventoryLimiterDelay = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte GiftsLimiterDelay { get; private set; } = 1;
internal readonly byte GiftsLimiterDelay = 1;
[JsonProperty(Required = Required.DisallowNull)]
internal byte MaxTradeHoldDuration { get; private set; } = 15;
internal readonly byte MaxTradeHoldDuration = 15;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ForceHttp { get; private set; } = false;
internal readonly bool ForceHttp = false;
[JsonProperty(Required = Required.DisallowNull)]
internal byte HttpTimeout { get; private set; } = DefaultHttpTimeout;
internal readonly byte HttpTimeout = DefaultHttpTimeout;
[JsonProperty]
internal string WCFHostname { get; set; } = "localhost";
[JsonProperty(Required = Required.DisallowNull)]
internal ushort WCFPort { get; private set; } = DefaultWCFPort;
internal readonly ushort WCFPort = DefaultWCFPort;
[JsonProperty(Required = Required.DisallowNull)]
internal bool Statistics { get; private set; } = true;
internal readonly bool Statistics = true;
[JsonProperty(Required = Required.DisallowNull)]
internal HashSet<uint> Blacklist { get; private set; } = new HashSet<uint>(GlobalBlacklist);
internal readonly HashSet<uint> Blacklist = new HashSet<uint>(GlobalBlacklist);
internal static GlobalConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
@@ -141,21 +142,20 @@ namespace ArchiSteamFarm {
case ProtocolType.Udp:
break;
default:
Logging.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol + ". Value of " + DefaultSteamProtocol + " will be used instead");
globalConfig.SteamProtocol = DefaultSteamProtocol;
break;
Logging.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol);
return null;
}
// User might not know what he's doing
// Ensure that he can't screw core ASF variables
if (globalConfig.MaxFarmingTime == 0) {
Logging.LogGenericWarning("Configured MaxFarmingTime is invalid: " + globalConfig.MaxFarmingTime + ". Value of " + DefaultMaxFarmingTime + " will be used instead");
globalConfig.MaxFarmingTime = DefaultMaxFarmingTime;
Logging.LogGenericWarning("Configured MaxFarmingTime is invalid: " + globalConfig.MaxFarmingTime);
return null;
}
if (globalConfig.FarmingDelay == 0) {
Logging.LogGenericWarning("Configured FarmingDelay is invalid: " + globalConfig.FarmingDelay + ". Value of " + DefaultFarmingDelay + " will be used instead");
globalConfig.FarmingDelay = DefaultFarmingDelay;
Logging.LogGenericWarning("Configured FarmingDelay is invalid: " + globalConfig.FarmingDelay);
return null;
}
if ((globalConfig.FarmingDelay > 5) && Runtime.RequiresWorkaroundForMonoBug41701()) {
@@ -164,18 +164,16 @@ namespace ArchiSteamFarm {
}
if (globalConfig.HttpTimeout == 0) {
Logging.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout + ". Value of " + DefaultHttpTimeout + " will be used instead");
globalConfig.HttpTimeout = DefaultHttpTimeout;
Logging.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout);
return null;
}
if (globalConfig.WCFPort != 0) {
return globalConfig;
}
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort + ". Value of " + DefaultWCFPort + " will be used instead");
globalConfig.WCFPort = DefaultWCFPort;
return globalConfig;
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort);
return null;
}
// This constructor is used only by deserializer

View File

@@ -57,7 +57,7 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
internal InMemoryServerListProvider ServerListProvider { get; private set; } = new InMemoryServerListProvider();
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
private readonly object FileLock = new object();

View File

@@ -31,19 +31,21 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ReleaseResponse {
#pragma warning disable 649
internal sealed class Asset {
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal string Name { get; private set; }
internal readonly string Name;
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal string DownloadURL { get; private set; }
internal readonly string DownloadURL;
}
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)]
internal string Tag { get; private set; }
internal readonly string Tag;
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal List<Asset> Assets { get; private set; }
internal readonly List<Asset> Assets;
#pragma warning restore 649
}
}
}

View File

@@ -33,8 +33,7 @@ using SteamKit2;
namespace ArchiSteamFarm.JSON {
internal static class Steam {
internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
// Deserialized from JSON (SteamCommunity) and constructed from code
internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset | Deserialized from JSON (SteamCommunity) and constructed from code
internal const ushort SteamAppID = 753;
internal const byte SteamContextID = 6;
@@ -219,8 +218,7 @@ namespace ArchiSteamFarm.JSON {
}
}
internal sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
// Constructed from code
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,
@@ -330,8 +328,7 @@ namespace ArchiSteamFarm.JSON {
}
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class TradeOfferRequest {
// Constructed from code
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>();
@@ -347,10 +344,11 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationResponse {
// Deserialized from JSON
internal sealed class ConfirmationResponse { // Deserialized from JSON
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal bool Success { get; private set; }
internal readonly bool Success;
#pragma warning restore 649
private ConfirmationResponse() { }
}
@@ -358,8 +356,7 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationDetails {
// Deserialized from JSON
internal sealed class ConfirmationDetails { // Deserialized from JSON
internal enum EType : byte {
Unknown,
Trade,
@@ -381,8 +378,10 @@ namespace ArchiSteamFarm.JSON {
}
}
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal bool Success { get; private set; }
internal readonly bool Success;
#pragma warning restore 649
private EType _Type;
private EType Type {
@@ -474,8 +473,8 @@ namespace ArchiSteamFarm.JSON {
}
#pragma warning disable 649
[JsonProperty(PropertyName = "html")]
private string HTML;
[JsonProperty(PropertyName = "html", Required = Required.DisallowNull)]
private readonly string HTML;
#pragma warning restore 649
private uint _OtherSteamID3;

View File

@@ -25,6 +25,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using NLog;
@@ -146,6 +147,24 @@ namespace ArchiSteamFarm {
}
Logger.Fatal(exception, $"{botName}|{previousMethodName}()");
// If LogManager has been initialized already, don't do anything else
if (LogManager.Configuration != null) {
return;
}
// Otherwise, if we run into fatal exception before logging module is even initialized, write exception to classic log file
File.WriteAllText(Program.LogFile, DateTime.Now + " ASF V" + Program.Version + " has run into fatal exception before core logging module was even able to initialize!" + Environment.NewLine);
while (true) {
File.AppendAllText(Program.LogFile, "[!] EXCEPTION: " + previousMethodName + "() " + exception.Message + Environment.NewLine + "StackTrace:" + Environment.NewLine + exception.StackTrace);
if (exception.InnerException != null) {
exception = exception.InnerException;
continue;
}
break;
}
}
internal static void LogGenericWarning(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {

View File

@@ -53,19 +53,27 @@ namespace ArchiSteamFarm {
}
}
private static readonly byte[] TokenCharacters = { 50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89 };
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 SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
private static short SteamTimeDifference;
internal bool HasDeviceID => !string.IsNullOrEmpty(DeviceID);
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
#pragma warning disable 649
[JsonProperty(PropertyName = "shared_secret", Required = Required.DisallowNull)]
private string SharedSecret;
[JsonProperty(PropertyName = "shared_secret", Required = Required.Always)]
private readonly string SharedSecret;
[JsonProperty(PropertyName = "identity_secret", Required = Required.DisallowNull)]
private string IdentitySecret;
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]
private readonly string IdentitySecret;
#pragma warning restore 649
[JsonProperty(PropertyName = "device_id")]
@@ -100,6 +108,11 @@ namespace ArchiSteamFarm {
return false;
}
if (!HasCorrectDeviceID) {
Logging.LogGenericWarning("Can't execute properly due to invalid DeviceID!", Bot.BotName);
return false;
}
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
@@ -121,6 +134,11 @@ namespace ArchiSteamFarm {
return null;
}
if (!HasCorrectDeviceID) {
Logging.LogGenericWarning("Can't execute properly due to invalid DeviceID!", Bot.BotName);
return null;
}
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
@@ -152,6 +170,11 @@ namespace ArchiSteamFarm {
}
internal async Task<HashSet<Confirmation>> GetConfirmations() {
if (!HasCorrectDeviceID) {
Logging.LogGenericWarning("Can't execute properly due to invalid DeviceID!", Bot.BotName);
return null;
}
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
@@ -238,37 +261,47 @@ namespace ArchiSteamFarm {
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference);
}
private string GenerateTokenForTime(long time) {
private string GenerateTokenForTime(uint time) {
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
return null;
}
byte[] sharedSecretArray = Convert.FromBase64String(SharedSecret);
byte[] timeArray = new byte[8];
byte[] sharedSecret = Convert.FromBase64String(SharedSecret);
time /= 30L;
for (int i = 8; i > 0; i--) {
timeArray[i - 1] = (byte) time;
time >>= 8;
byte[] timeArray = BitConverter.GetBytes((long) time / CodeInterval);
if (BitConverter.IsLittleEndian) {
Array.Reverse(timeArray);
}
byte[] hashedData;
using (HMACSHA1 hmacGenerator = new HMACSHA1(sharedSecretArray, true)) {
hashedData = hmacGenerator.ComputeHash(timeArray);
byte[] hash;
using (HMACSHA1 hmac = new HMACSHA1(sharedSecret)) {
hash = hmac.ComputeHash(timeArray);
}
byte b = (byte) (hashedData[19] & 0xF);
int codePoint = ((hashedData[b] & 0x7F) << 24) | ((hashedData[b + 1] & 0xFF) << 16) | ((hashedData[b + 2] & 0xFF) << 8) | (hashedData[b + 3] & 0xFF);
// The last 4 bits of the mac say where the code starts
int start = hash[hash.Length - 1] & 0x0f;
byte[] codeArray = new byte[5];
for (int i = 0; i < 5; ++i) {
codeArray[i] = TokenCharacters[codePoint % TokenCharacters.Length];
codePoint /= TokenCharacters.Length;
// Extract those 4 bytes
byte[] bytes = new byte[4];
Array.Copy(hash, start, bytes, 0, 4);
if (BitConverter.IsLittleEndian) {
Array.Reverse(bytes);
}
return Encoding.UTF8.GetString(codeArray);
uint fullCode = BitConverter.ToUInt32(bytes, 0) & 0x7fffffff;
// Build the alphanumeric code
StringBuilder code = new StringBuilder();
for (byte i = 0; i < CodeDigits; i++) {
code.Append(CodeCharacters[fullCode % CodeCharacters.Length]);
fullCode /= (uint) CodeCharacters.Length;
}
return code.ToString();
}
private string GenerateConfirmationKey(uint time, string tag = null) {
@@ -277,27 +310,27 @@ namespace ArchiSteamFarm {
return null;
}
byte[] b64Secret = Convert.FromBase64String(IdentitySecret);
byte[] identitySecret = Convert.FromBase64String(IdentitySecret);
int bufferSize = 8;
if (string.IsNullOrEmpty(tag) == false) {
bufferSize += Math.Min(32, tag.Length);
byte bufferSize = 8;
if (!string.IsNullOrEmpty(tag)) {
bufferSize += (byte) Math.Min(32, tag.Length);
}
byte[] buffer = new byte[bufferSize];
byte[] timeArray = BitConverter.GetBytes((long) time);
if (BitConverter.IsLittleEndian) {
Array.Reverse(timeArray);
}
byte[] buffer = new byte[bufferSize];
Array.Copy(timeArray, buffer, 8);
if (string.IsNullOrEmpty(tag) == false) {
if (!string.IsNullOrEmpty(tag)) {
Array.Copy(Encoding.UTF8.GetBytes(tag), 0, buffer, 8, bufferSize - 8);
}
byte[] hash;
using (HMACSHA1 hmac = new HMACSHA1(b64Secret, true)) {
using (HMACSHA1 hmac = new HMACSHA1(identitySecret)) {
hash = hmac.ComputeHash(buffer);
}

View File

@@ -65,28 +65,27 @@ namespace ArchiSteamFarm {
internal const string LogFile = "log.txt";
private const string GithubReleaseURL = "https://api.github.com/repos/" + SharedInfo.GithubRepo + "/releases"; // GitHub API is HTTPS only
private const string GlobalConfigFile = ASF + ".json";
private const string GlobalDatabaseFile = ASF + ".db";
private const string GlobalConfigFileName = ASF + ".json";
private const string GlobalDatabaseFileName = ASF + ".db";
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
private static readonly object ConsoleLock = new object();
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
private static readonly string ExecutableFile = Assembly.GetEntryAssembly().Location;
private static readonly string ExecutableName = Path.GetFileName(ExecutableFile);
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
private static readonly WCF WCF = new WCF();
internal static bool IsRunningAsService { get; private set; }
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
private static bool ShutdownSequenceInitialized;
private static Timer AutoUpdatesTimer;
private static EMode Mode = EMode.Normal;
private static WebBrowser WebBrowser;
internal static async Task CheckForUpdate(bool updateOverride = false) {
string oldExeFile = ExecutableFile + ".old";
string exeFile = Assembly.GetEntryAssembly().Location;
string oldExeFile = exeFile + ".old";
// We booted successfully so we can now remove old exe file
if (File.Exists(oldExeFile)) {
@@ -105,6 +104,17 @@ namespace ArchiSteamFarm {
return;
}
if ((AutoUpdatesTimer == null) && GlobalConfig.AutoUpdates) {
AutoUpdatesTimer = new Timer(
async e => await CheckForUpdate().ConfigureAwait(false),
null,
TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period
);
Logging.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
}
string releaseURL = GithubReleaseURL;
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
releaseURL += "/latest";
@@ -153,19 +163,6 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Local version: " + Version + " | Remote version: " + newVersion);
if (Version.CompareTo(newVersion) >= 0) { // If local version is the same or newer than remote version
if ((AutoUpdatesTimer != null) || !GlobalConfig.AutoUpdates) {
return;
}
Logging.LogGenericInfo("ASF will automatically check for new versions every 24 hours");
AutoUpdatesTimer = new Timer(
async e => await CheckForUpdate().ConfigureAwait(false),
null,
TimeSpan.FromDays(1), // Delay
TimeSpan.FromDays(1) // Period
);
return;
}
@@ -187,7 +184,8 @@ namespace ArchiSteamFarm {
return;
}
GitHub.ReleaseResponse.Asset binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(ExecutableName, StringComparison.OrdinalIgnoreCase));
string exeFileName = Path.GetFileName(exeFile);
GitHub.ReleaseResponse.Asset binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name.Equals(exeFileName, StringComparison.OrdinalIgnoreCase));
if (binaryAsset == null) {
Logging.LogGenericWarning("Could not proceed with update because there is no asset that relates to currently running binary!");
@@ -207,7 +205,7 @@ namespace ArchiSteamFarm {
return;
}
string newExeFile = ExecutableFile + ".new";
string newExeFile = exeFile + ".new";
// Firstly we create new exec
try {
@@ -219,7 +217,7 @@ namespace ArchiSteamFarm {
// Now we move current -> old
try {
File.Move(ExecutableFile, oldExeFile);
File.Move(exeFile, oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
@@ -233,12 +231,12 @@ namespace ArchiSteamFarm {
// Now we move new -> current
try {
File.Move(newExeFile, ExecutableFile);
File.Move(newExeFile, exeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Move(oldExeFile, ExecutableFile);
File.Move(oldExeFile, exeFile);
File.Delete(newExeFile);
} catch {
// Ignored
@@ -259,19 +257,21 @@ namespace ArchiSteamFarm {
}
}
internal static void Exit(int exitCode = 0) {
internal static void Exit(byte exitCode = 0) {
Shutdown();
Environment.Exit(exitCode);
}
internal static void Restart() {
InitShutdownSequence();
try {
Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
} catch (Exception e) {
Logging.LogGenericException(e);
}
Exit();
Environment.Exit(0);
}
internal static string GetUserInput(EUserInputType userInputType, string botName = ASF, string extraInformation = null) {
@@ -337,7 +337,7 @@ namespace ArchiSteamFarm {
}
internal static void OnBotShutdown() {
if (ShutdownResetEvent.IsSet) {
if (ShutdownSequenceInitialized) {
return;
}
@@ -355,29 +355,43 @@ namespace ArchiSteamFarm {
}
private static void Shutdown() {
if (ShutdownResetEvent.IsSet) {
if (!InitShutdownSequence()) {
return;
}
ShutdownResetEvent.Set();
WCF.StopServer();
}
private static bool InitShutdownSequence() {
if (ShutdownSequenceInitialized) {
return false;
}
ShutdownSequenceInitialized = true;
WCF.StopServer();
foreach (Bot bot in Bot.Bots.Values) {
bot.Stop();
}
return true;
}
private static void InitServices() {
GlobalConfig = GlobalConfig.Load(Path.Combine(ConfigDirectory, GlobalConfigFile));
string globalConfigFile = Path.Combine(ConfigDirectory, GlobalConfigFileName);
GlobalConfig = GlobalConfig.Load(globalConfigFile);
if (GlobalConfig == null) {
Logging.LogGenericError("Global config could not be loaded, please make sure that ASF.json exists and is valid!");
Logging.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!");
Thread.Sleep(5000);
Exit(1);
}
GlobalDatabase = GlobalDatabase.Load(Path.Combine(ConfigDirectory, GlobalDatabaseFile));
string globalDatabaseFile = Path.Combine(ConfigDirectory, GlobalDatabaseFileName);
GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile);
if (GlobalDatabase == null) {
Logging.LogGenericError("Global database could not be loaded!");
Logging.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
Thread.Sleep(5000);
Exit(1);
}
@@ -389,7 +403,29 @@ namespace ArchiSteamFarm {
WebBrowser = new WebBrowser(ASF);
}
private static void ParseArgs(IEnumerable<string> args) {
private static void ParsePreInitArgs(IEnumerable<string> args) {
if (args == null) {
Logging.LogNullError(nameof(args));
return;
}
foreach (string arg in args) {
switch (arg) {
case "":
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 ParsePostInitArgs(IEnumerable<string> args) {
if (args == null) {
Logging.LogNullError(nameof(args));
return;
@@ -410,8 +446,6 @@ namespace ArchiSteamFarm {
if (arg.StartsWith("--", StringComparison.Ordinal)) {
if (arg.StartsWith("--cryptkey=", StringComparison.Ordinal) && (arg.Length > 11)) {
CryptoHelper.SetEncryptionKey(arg.Substring(11));
} else {
Logging.LogGenericWarning("Unrecognized parameter: " + arg);
}
break;
@@ -423,14 +457,7 @@ namespace ArchiSteamFarm {
}
Logging.LogGenericInfo("Command sent: " + arg);
// We intentionally execute this async block synchronously
Logging.LogGenericInfo("Response received: " + WCF.SendCommand(arg));
/*
Task.Run(async () => {
Logging.LogGenericNotice("WCF", "Response received: " + await WCF.SendCommand(arg).ConfigureAwait(false));
}).Wait();
*/
break;
}
}
@@ -454,30 +481,43 @@ namespace ArchiSteamFarm {
Logging.LogFatalException(args.Exception);
}
private static void Init(IEnumerable<string> args) {
private static void Init(string[] args) {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
Logging.InitCoreLoggers();
Logging.LogGenericInfo("ASF V" + Version);
Directory.SetCurrentDirectory(ExecutableDirectory);
if (!Runtime.IsRuntimeSupported()) {
Logging.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);
}
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (!string.IsNullOrEmpty(homeDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
// 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(ConfigDirectory)) {
break;
// 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(ConfigDirectory)) {
break;
}
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
}
}
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
Directory.SetCurrentDirectory(ExecutableDirectory);
}
// Parse pre-init args
if (args != null) {
ParsePreInitArgs(args);
}
InitServices();
@@ -494,9 +534,9 @@ namespace ArchiSteamFarm {
SteamKit2.DebugLog.Enabled = true;
}
// Parse args
// Parse post-init args
if (args != null) {
ParseArgs(args);
ParsePostInitArgs(args);
}
// If we ran ASF as a client, we're done by now

View File

@@ -24,11 +24,13 @@
using System;
using System.Reflection;
using Microsoft.Win32;
namespace ArchiSteamFarm {
internal static class Runtime {
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
private static bool IsRunningOnMono => MonoRuntime != null;
internal static bool IsRunningOnMono => MonoRuntime != null;
private static bool? _IsUserInteractive;
internal static bool IsUserInteractive {
@@ -39,23 +41,57 @@ namespace ArchiSteamFarm {
if (Environment.UserInteractive) {
_IsUserInteractive = true;
return true;
}
// If it's non-Mono, we can trust the result
if (!IsRunningOnMono) {
} else if (!IsRunningOnMono) {
// If it's non-Mono, we can trust the result
_IsUserInteractive = false;
return 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;
}
// 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 true;
return _IsUserInteractive.Value;
}
}
internal static bool IsRuntimeSupported() {
if (IsRunningOnMono) {
Version monoVersion = GetMonoVersion();
if (monoVersion == null) {
Logging.LogNullError(nameof(monoVersion));
return false;
}
Version minMonoVersion = new Version(4, 4);
if (monoVersion >= minMonoVersion) {
Logging.LogGenericInfo("Your Mono version is OK. Required: " + minMonoVersion + " | Found: " + monoVersion);
return true;
}
Logging.LogGenericWarning("Your Mono version is too old. Required: " + minMonoVersion + " | Found: " + monoVersion);
return false;
}
Version netVersion = GetNetVersion();
if (netVersion == null) {
Logging.LogNullError(nameof(netVersion));
return false;
}
Version minNetVersion = new Version(4, 6);
if (netVersion >= minNetVersion) {
Logging.LogGenericInfo("Your .NET version is OK. Required: " + minNetVersion + " | Found: " + netVersion);
return true;
}
Logging.LogGenericWarning("Your .NET version is too old. Required: " + minNetVersion + " | Found: " + netVersion);
return false;
}
// TODO: Remove me once Mono 4.6 is released
internal static bool RequiresWorkaroundForMonoBug41701() {
// Mono only, https://bugzilla.xamarin.com/show_bug.cgi?id=41701
if (!IsRunningOnMono) {
@@ -63,11 +99,64 @@ namespace ArchiSteamFarm {
}
Version monoVersion = GetMonoVersion();
if (monoVersion == null) {
// The issue affects Mono versions from 4.3.2, 4.4.0 to 4.4.2 (inclusive) and from 4.5.0 to 4.5.2 (inclusive)
if (monoVersion?.Major != 4) {
return false;
}
return monoVersion >= new Version(4, 4);
switch (monoVersion.Minor) {
case 3:
return monoVersion.Build >= 2;
case 4:
case 5:
return monoVersion.Build <= 2;
default:
return false;
}
}
private static Version GetNetVersion() {
uint release;
using (RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\v4\\Full\\")) {
if (registryKey == null) {
Logging.LogNullError(nameof(registryKey));
return null;
}
object releaseObj = registryKey.GetValue("Release");
if (releaseObj == null) {
Logging.LogNullError(nameof(releaseObj));
return null;
}
if (!uint.TryParse(releaseObj.ToString(), out release) || (release == 0)) {
Logging.LogNullError(nameof(release));
return null;
}
}
if (release >= 394747) {
return new Version(4, 6, 2);
}
if (release >= 394254) {
return new Version(4, 6, 1);
}
if (release >= 393295) {
return new Version(4, 6);
}
if (release >= 379893) {
return new Version(4, 5, 2);
}
if (release >= 378675) {
return new Version(4, 5, 1);
}
return release >= 378389 ? new Version(4, 5) : null;
}
private static Version GetMonoVersion() {

View File

@@ -24,7 +24,7 @@
namespace ArchiSteamFarm {
internal static class SharedInfo {
internal const string Version = "2.1.2.7";
internal const string Version = "2.1.3.7";
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";

View File

@@ -24,7 +24,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -32,14 +31,26 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal sealed class Trading : IDisposable {
private enum ParseTradeResult : byte {
[SuppressMessage("ReSharper", "UnusedMember.Local")]
Unknown,
Error,
AcceptedWithItemLose,
AcceptedWithoutItemLose,
RejectedTemporarily,
RejectedPermanently
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
@@ -112,35 +123,42 @@ namespace ArchiSteamFarm {
}
ParseTradeResult[] results = await Task.WhenAll(tradeOffers.Select(ParseTrade)).ConfigureAwait(false);
if (results.Any(result => result == ParseTradeResult.AcceptedWithItemLose)) {
HashSet<ulong> acceptedTradeIDs = new HashSet<ulong>(results.Where(result => (result != null) && (result.Result == ParseTradeResult.EResult.AcceptedWithItemLose)).Select(result => result.TradeID));
if (acceptedTradeIDs.Count > 0) {
await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
HashSet<ulong> tradeIDs = new HashSet<ulong>(tradeOffers.Select(tradeOffer => tradeOffer.TradeOfferID));
await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, tradeIDs).ConfigureAwait(false);
await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, acceptedTradeIDs).ConfigureAwait(false);
}
}
private async Task<ParseTradeResult> ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
return ParseTradeResult.Error;
return null;
}
if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) {
return ParseTradeResult.Error;
Logging.LogGenericError("Ignoring trade in non-active state!", Bot.BotName);
return null;
}
ParseTradeResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false);
switch (result) {
case ParseTradeResult.AcceptedWithItemLose:
case ParseTradeResult.AcceptedWithoutItemLose:
if (result == null) {
Logging.LogNullError(nameof(result), Bot.BotName);
return null;
}
switch (result.Result) {
case ParseTradeResult.EResult.AcceptedWithItemLose:
case ParseTradeResult.EResult.AcceptedWithoutItemLose:
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false) ? result : ParseTradeResult.Error;
case ParseTradeResult.RejectedPermanently:
case ParseTradeResult.RejectedTemporarily:
if (result == ParseTradeResult.RejectedPermanently) {
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false) ? result : null;
case ParseTradeResult.EResult.RejectedPermanently:
case ParseTradeResult.EResult.RejectedTemporarily:
if (result.Result == ParseTradeResult.EResult.RejectedPermanently) {
if (Bot.BotConfig.IsBotAccount) {
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID) ? result : ParseTradeResult.Error;
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID) ? result : null;
}
IgnoredTrades.Add(tradeOffer.TradeOfferID);
@@ -156,33 +174,33 @@ namespace ArchiSteamFarm {
private async Task<ParseTradeResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
return ParseTradeResult.Error;
return null;
}
// Always accept trades when we're not losing anything
if (tradeOffer.ItemsToGive.Count == 0) {
// Unless it's steam fuckup and we're dealing with broken trade
return tradeOffer.ItemsToReceive.Count > 0 ? ParseTradeResult.AcceptedWithoutItemLose : ParseTradeResult.RejectedTemporarily;
return tradeOffer.ItemsToReceive.Count > 0 ? new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithoutItemLose) : new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily);
}
// Always accept trades from SteamMasterID
if ((tradeOffer.OtherSteamID64 != 0) && (tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID)) {
return ParseTradeResult.AcceptedWithItemLose;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose);
}
// If we don't have SteamTradeMatcher enabled, this is the end for us
if (!Bot.BotConfig.SteamTradeMatcher) {
return ParseTradeResult.RejectedPermanently;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}
// Decline trade if we're giving more count-wise
if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) {
return ParseTradeResult.RejectedPermanently;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade
if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) {
return ParseTradeResult.RejectedPermanently;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}
// At this point we're sure that STM trade is valid
@@ -191,23 +209,23 @@ namespace ArchiSteamFarm {
byte? holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDuration(tradeOffer.TradeOfferID).ConfigureAwait(false);
if (!holdDuration.HasValue) {
// If we can't get trade hold duration, reject trade temporarily
return ParseTradeResult.RejectedTemporarily;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily);
}
// If user has a trade hold, we add extra logic
if (holdDuration.Value > 0) {
// If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) {
return ParseTradeResult.RejectedPermanently;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}
}
// Now check if it's worth for us to do the trade
await LimitInventoryRequestsAsync().ConfigureAwait(false);
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyInventory(false).ConfigureAwait(false);
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false).ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) {
return ParseTradeResult.AcceptedWithItemLose; // OK, assume that this trade is valid, we can't check our EQ
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose); // OK, assume that this trade is valid, we can't check our EQ
}
// Get appIDs we're interested in
@@ -218,7 +236,7 @@ namespace ArchiSteamFarm {
// If for some reason Valve is talking crap and we can't find mentioned items, assume OK
if (inventory.Count == 0) {
return ParseTradeResult.AcceptedWithItemLose;
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose);
}
// Now let's create a map which maps items to their amount in our EQ
@@ -271,7 +289,7 @@ namespace ArchiSteamFarm {
int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum();
// Trade is worth for us if the difference is greater than 0
return difference > 0 ? ParseTradeResult.AcceptedWithItemLose : ParseTradeResult.RejectedTemporarily;
return difference > 0 ? new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose) : new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily);
}
}
}

View File

@@ -26,10 +26,14 @@ using System;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
namespace ArchiSteamFarm {
[ServiceContract]
internal interface IWCF {
[OperationContract]
string GetStatus();
[OperationContract]
string HandleCommand(string input);
}
@@ -58,15 +62,15 @@ namespace ArchiSteamFarm {
return null;
}
if (Program.GlobalConfig.SteamOwnerID == 0) {
return "Refusing to handle request because SteamOwnerID is not set!";
}
Bot bot = Bot.Bots.Values.FirstOrDefault();
if (bot == null) {
return "ERROR: No bots are enabled!";
}
if (Program.GlobalConfig.SteamOwnerID == 0) {
return "Refusing to handle request because SteamOwnerID is not set!";
}
string command = "!" + input;
string output = bot.Response(Program.GlobalConfig.SteamOwnerID, command).Result; // TODO: This should be asynchronous
@@ -74,9 +78,11 @@ namespace ArchiSteamFarm {
return output;
}
public string GetStatus() => Program.GlobalConfig.SteamOwnerID == 0 ? "{}" : Bot.GetAPIStatus();
public void Dispose() {
ServiceHost?.Close();
Client?.Close();
StopServer();
}
internal bool IsServerRunning() => ServiceHost != null;
@@ -87,15 +93,18 @@ namespace ArchiSteamFarm {
}
Logging.LogGenericInfo("Starting WCF server...");
ServiceHost = new ServiceHost(typeof(WCF));
ServiceHost.AddServiceEndpoint(typeof(IWCF), new BasicHttpBinding(), URL);
try {
ServiceHost = new ServiceHost(typeof(WCF), new Uri(URL));
ServiceHost.Description.Behaviors.Add(new ServiceMetadataBehavior {
HttpGetEnabled = true
});
ServiceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex");
ServiceHost.AddServiceEndpoint(typeof(IWCF), new BasicHttpBinding(), string.Empty);
ServiceHost.Open();
} catch (AddressAccessDeniedException) {
Logging.LogGenericWarning("WCF service could not be started because of AddressAccessDeniedException");
Logging.LogGenericWarning("If you want to use WCF service provided by ASF, consider starting ASF as administrator, or giving proper permissions");
return;
} catch (Exception e) {
Logging.LogGenericException(e);
return;
@@ -109,7 +118,14 @@ namespace ArchiSteamFarm {
return;
}
ServiceHost.Close();
if (ServiceHost.State != CommunicationState.Closed) {
try {
ServiceHost.Close();
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
ServiceHost = null;
}
@@ -127,10 +143,10 @@ namespace ArchiSteamFarm {
}
}
internal sealed class Client : ClientBase<IWCF>, IWCF {
internal sealed class Client : ClientBase<IWCF> {
internal Client(Binding binding, EndpointAddress address) : base(binding, address) { }
public string HandleCommand(string input) {
internal string HandleCommand(string input) {
if (string.IsNullOrEmpty(input)) {
Logging.LogNullError(nameof(input));
return null;

View File

@@ -39,8 +39,6 @@ namespace ArchiSteamFarm {
private const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state
private const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
private static readonly string DefaultUserAgent = "ArchiSteamFarm/" + Program.Version;
internal readonly CookieContainer CookieContainer = new CookieContainer();
private readonly HttpClient HttpClient;
@@ -57,11 +55,21 @@ namespace ArchiSteamFarm {
ServicePointManager.Expect100Continue = false;
#if !__MonoCS__
// Reuse ports if possible (since .NET 4.6+)
//ServicePointManager.ReusePort = true;
// We run Windows-compiled ASF on both Windows and Mono. Normally we'd simply put code in the if
// However, if we did that, then we would still crash on Mono due to potentially calling non-existing methods
// Therefore, call mono-incompatible options in their own function to avoid that, and just leave the function call here
// When compiling on Mono, this section is omitted entirely as we never run Mono-compiled ASF on Windows
// Moreover, Mono compiler doesn't even include ReusePort field in ServicePointManager, so it's crucial to avoid compilation error
if (!Runtime.IsRunningOnMono) {
InitNonMonoBehaviour();
}
#endif
}
#if !__MonoCS__
private static void InitNonMonoBehaviour() => ServicePointManager.ReusePort = true;
#endif
internal WebBrowser(string identifier) {
if (string.IsNullOrEmpty(identifier)) {
throw new ArgumentNullException(nameof(identifier));
@@ -79,10 +87,7 @@ namespace ArchiSteamFarm {
};
// Most web services expect that UserAgent is set, so we declare it globally
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
// We should always operate in English language, declare it globally
HttpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.8,en-GB;q=0.6");
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ArchiSteamFarm/" + Program.Version);
}
internal async Task<bool> UrlHeadRetry(string request, string referer = null) {
@@ -308,15 +313,15 @@ namespace ArchiSteamFarm {
return null;
}
string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) {
string json = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) {
return null;
}
JObject jObject;
try {
jObject = JObject.Parse(content);
jObject = JObject.Parse(json);
} catch (JsonException e) {
Logging.LogGenericException(e, Identifier);
return null;
@@ -340,15 +345,15 @@ namespace ArchiSteamFarm {
return null;
}
string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) {
string xml = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(xml)) {
return null;
}
XmlDocument xmlDocument = new XmlDocument();
try {
xmlDocument.LoadXml(content);
xmlDocument.LoadXml(xml);
} catch (XmlException e) {
Logging.LogGenericException(e, Identifier);
return null;

View File

@@ -10,6 +10,7 @@
"SteamMasterClanID": 0,
"CardDropsRestricted": false,
"DismissInventoryNotifications": true,
"FarmingOrder": 0,
"FarmOffline": false,
"HandleOfflineMessages": false,
"AcceptGifts": false,

38
CodeStyle.vssettings Normal file

File diff suppressed because one or more lines are too long

View File

@@ -33,6 +33,8 @@ namespace ConfigGenerator {
internal string FilePath { get; set; }
private readonly object FileLock = new object();
protected ASFConfig() {
ASFConfigs.Add(this);
}
@@ -46,7 +48,7 @@ namespace ConfigGenerator {
}
internal void Save() {
lock (FilePath) {
lock (FileLock) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this, Formatting.Indented));
} catch (Exception e) {
@@ -57,7 +59,7 @@ namespace ConfigGenerator {
internal void Remove() {
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FilePath) {
lock (FileLock) {
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
try {
File.Delete(botFile);
@@ -77,7 +79,7 @@ namespace ConfigGenerator {
}
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FilePath) {
lock (FileLock) {
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
try {
File.Move(botFile, Path.Combine(Program.ConfigDirectory, botName + Path.GetExtension(botFile)));

View File

@@ -41,6 +41,18 @@ namespace ConfigGenerator {
ProtectedDataForCurrentUser
}
internal enum EFarmingOrder : byte {
Unordered,
AppIDsAscending,
AppIDsDescending,
CardDropsAscending,
CardDropsDescending,
HoursAscending,
HoursDescending,
NamesAscending,
NamesDescending
}
[JsonProperty(Required = Required.DisallowNull)]
public bool Enabled { get; set; } = false;
@@ -75,6 +87,9 @@ namespace ConfigGenerator {
[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;

View File

@@ -39,9 +39,6 @@ namespace ConfigGenerator {
private const string ASFDirectory = "ArchiSteamFarm";
private const string ASFExecutableFile = ASF + ".exe";
private static readonly string ExecutableDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
private static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
/// <summary>
/// The main entry point for the application.
/// </summary>
@@ -57,25 +54,28 @@ namespace ConfigGenerator {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
Directory.SetCurrentDirectory(ExecutableDirectory);
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) {
// 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(ASFDirectory)) {
continue;
// 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(ASFDirectory)) {
continue;
}
Directory.SetCurrentDirectory(ASFDirectory);
break;
}
Directory.SetCurrentDirectory(ASFDirectory);
break;
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
Directory.SetCurrentDirectory(ExecutableDirectory);
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
}
}
}
@@ -89,15 +89,17 @@ namespace ConfigGenerator {
}
FileVersionInfo asfVersionInfo = FileVersionInfo.GetVersionInfo(ASFExecutableFile);
Version asfVersion = new Version(asfVersionInfo.ProductVersion);
if (Version == asfVersion) {
Version cgVersion = Assembly.GetEntryAssembly().GetName().Version;
if (asfVersion == cgVersion) {
return;
}
Logging.LogGenericErrorWithoutStacktrace(
"Version of ASF and ConfigGenerator doesn't match!" + Environment.NewLine +
"ASF version: " + asfVersion + " | ConfigGenerator version: " + Version + 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..."
);
@@ -107,8 +109,8 @@ namespace ConfigGenerator {
}
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if ((sender == null) || (args == null) || (args.ExceptionObject == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args) + " || " + nameof(args.ExceptionObject));
if (args?.ExceptionObject == null) {
Logging.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
return;
}
@@ -116,8 +118,8 @@ namespace ConfigGenerator {
}
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
if ((sender == null) || (args == null) || (args.Exception == null)) {
Logging.LogNullError(nameof(sender) + " || " + nameof(args) + " || " + nameof(args.Exception));
if (args?.Exception == null) {
Logging.LogNullError(nameof(args) + " || " + nameof(args.Exception));
return;
}

View File

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

View File

@@ -1,35 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace GUI {
internal static class Debugging {
#if DEBUG
internal static readonly bool IsDebugBuild = true;
#else
internal static readonly bool IsDebugBuild = false;
#endif
internal static bool IsReleaseBuild => !IsDebugBuild;
}
}

513
GUI/Form1.Designer.cs generated
View File

@@ -1,513 +0,0 @@
namespace GUI {
partial class Form1 {
/// <summary>
/// Erforderliche Designervariable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Verwendete Ressourcen bereinigen.
/// </summary>
/// <param name="disposing">True, wenn verwaltete Ressourcen gelöscht werden sollen; andernfalls False.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Vom Windows Form-Designer generierter Code
/// <summary>
/// Erforderliche Methode für die Designerunterstützung.
/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
this.button5 = new System.Windows.Forms.Button();
this.button4 = new System.Windows.Forms.Button();
this.button3 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.button1 = new System.Windows.Forms.Button();
this.label2 = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.textBox2 = new System.Windows.Forms.TextBox();
this.textBox1 = new System.Windows.Forms.TextBox();
this.comboBox1 = new System.Windows.Forms.ComboBox();
this.checkBox3 = new System.Windows.Forms.CheckBox();
this.checkBox2 = new System.Windows.Forms.CheckBox();
this.checkBox1 = new System.Windows.Forms.CheckBox();
this.textBox3 = new System.Windows.Forms.TextBox();
this.ASFGUI = new System.Windows.Forms.NotifyIcon(this.components);
this.checkBox4 = new System.Windows.Forms.CheckBox();
this.button6 = new System.Windows.Forms.Button();
this.button7 = new System.Windows.Forms.Button();
this.button8 = new System.Windows.Forms.Button();
this.button9 = new System.Windows.Forms.Button();
this.button10 = new System.Windows.Forms.Button();
this.button11 = new System.Windows.Forms.Button();
this.button12 = new System.Windows.Forms.Button();
this.button13 = new System.Windows.Forms.Button();
this.button14 = new System.Windows.Forms.Button();
this.button15 = new System.Windows.Forms.Button();
this.button16 = new System.Windows.Forms.Button();
this.button17 = new System.Windows.Forms.Button();
this.button18 = new System.Windows.Forms.Button();
this.button19 = new System.Windows.Forms.Button();
this.label3 = new System.Windows.Forms.Label();
this.label4 = new System.Windows.Forms.Label();
this.button20 = new System.Windows.Forms.Button();
this.label5 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// button5
//
this.button5.Location = new System.Drawing.Point(86, 204);
this.button5.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button5.Name = "button5";
this.button5.Size = new System.Drawing.Size(56, 19);
this.button5.TabIndex = 27;
this.button5.Text = "2fa ok";
this.button5.UseVisualStyleBackColor = true;
this.button5.Click += new System.EventHandler(this.button5_Click);
//
// button4
//
this.button4.Location = new System.Drawing.Point(26, 204);
this.button4.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button4.Name = "button4";
this.button4.Size = new System.Drawing.Size(56, 19);
this.button4.TabIndex = 26;
this.button4.Text = "2fa code";
this.button4.UseVisualStyleBackColor = true;
this.button4.Click += new System.EventHandler(this.button4_Click);
//
// button3
//
this.button3.Location = new System.Drawing.Point(26, 286);
this.button3.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button3.Name = "button3";
this.button3.Size = new System.Drawing.Size(56, 19);
this.button3.TabIndex = 24;
this.button3.Text = "Redeem";
this.button3.UseVisualStyleBackColor = true;
this.button3.Click += new System.EventHandler(this.button3_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(26, 171);
this.button2.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(56, 19);
this.button2.TabIndex = 23;
this.button2.Text = "Loot";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// button1
//
this.button1.Location = new System.Drawing.Point(169, 130);
this.button1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(56, 19);
this.button1.TabIndex = 22;
this.button1.Text = "Send";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(579, 13);
this.label2.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(39, 13);
this.label2.TabIndex = 21;
this.label2.Text = "Output";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(258, 39);
this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(31, 13);
this.label1.TabIndex = 20;
this.label1.Text = "Input";
//
// textBox2
//
this.textBox2.Location = new System.Drawing.Point(413, 34);
this.textBox2.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.textBox2.Multiline = true;
this.textBox2.Name = "textBox2";
this.textBox2.ReadOnly = true;
this.textBox2.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBox2.Size = new System.Drawing.Size(432, 370);
this.textBox2.TabIndex = 19;
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(169, 61);
this.textBox1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(223, 65);
this.textBox1.TabIndex = 18;
//
// comboBox1
//
this.comboBox1.FormattingEnabled = true;
this.comboBox1.Location = new System.Drawing.Point(26, 116);
this.comboBox1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.comboBox1.Name = "comboBox1";
this.comboBox1.Size = new System.Drawing.Size(92, 21);
this.comboBox1.TabIndex = 17;
this.comboBox1.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged);
//
// checkBox3
//
this.checkBox3.AutoSize = true;
this.checkBox3.Location = new System.Drawing.Point(26, 94);
this.checkBox3.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.checkBox3.Name = "checkBox3";
this.checkBox3.Size = new System.Drawing.Size(102, 17);
this.checkBox3.TabIndex = 16;
this.checkBox3.Text = "Send to specific";
this.checkBox3.UseVisualStyleBackColor = true;
this.checkBox3.CheckedChanged += new System.EventHandler(this.checkBox3_CheckedChanged);
//
// checkBox2
//
this.checkBox2.AutoSize = true;
this.checkBox2.Location = new System.Drawing.Point(26, 72);
this.checkBox2.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.checkBox2.Name = "checkBox2";
this.checkBox2.Size = new System.Drawing.Size(100, 17);
this.checkBox2.TabIndex = 15;
this.checkBox2.Text = "Send to all Bots";
this.checkBox2.UseVisualStyleBackColor = true;
this.checkBox2.CheckedChanged += new System.EventHandler(this.checkBox2_CheckedChanged);
//
// checkBox1
//
this.checkBox1.AutoSize = true;
this.checkBox1.Location = new System.Drawing.Point(107, 11);
this.checkBox1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.checkBox1.Name = "checkBox1";
this.checkBox1.Size = new System.Drawing.Size(74, 17);
this.checkBox1.TabIndex = 14;
this.checkBox1.Text = "Safemode";
this.checkBox1.UseVisualStyleBackColor = true;
this.checkBox1.CheckedChanged += new System.EventHandler(this.checkBox1_CheckedChanged);
//
// textBox3
//
this.textBox3.Location = new System.Drawing.Point(26, 387);
this.textBox3.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.textBox3.Name = "textBox3";
this.textBox3.Size = new System.Drawing.Size(366, 20);
this.textBox3.TabIndex = 28;
this.textBox3.TextChanged += new System.EventHandler(this.textBox3_TextChanged);
//
// ASFGUI
//
this.ASFGUI.Text = "notifyIcon1";
this.ASFGUI.Visible = true;
this.ASFGUI.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.ASFGUI_MouseDoubleClick);
//
// checkBox4
//
this.checkBox4.AutoSize = true;
this.checkBox4.Location = new System.Drawing.Point(26, 11);
this.checkBox4.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.checkBox4.Name = "checkBox4";
this.checkBox4.Size = new System.Drawing.Size(83, 17);
this.checkBox4.TabIndex = 29;
this.checkBox4.Text = "Send to any";
this.checkBox4.UseVisualStyleBackColor = true;
this.checkBox4.CheckedChanged += new System.EventHandler(this.checkBox4_CheckedChanged);
//
// button6
//
this.button6.Location = new System.Drawing.Point(26, 41);
this.button6.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button6.Name = "button6";
this.button6.Size = new System.Drawing.Size(94, 27);
this.button6.TabIndex = 30;
this.button6.Text = "generate Botlist";
this.button6.UseVisualStyleBackColor = true;
this.button6.Click += new System.EventHandler(this.button6_Click);
//
// button7
//
this.button7.Location = new System.Drawing.Point(147, 204);
this.button7.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button7.Name = "button7";
this.button7.Size = new System.Drawing.Size(56, 19);
this.button7.TabIndex = 31;
this.button7.Text = "2fano";
this.button7.UseVisualStyleBackColor = true;
this.button7.Click += new System.EventHandler(this.button7_Click);
//
// button8
//
this.button8.Location = new System.Drawing.Point(87, 286);
this.button8.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button8.Name = "button8";
this.button8.Size = new System.Drawing.Size(56, 19);
this.button8.TabIndex = 32;
this.button8.Text = "2faoff";
this.button8.UseVisualStyleBackColor = true;
this.button8.Click += new System.EventHandler(this.button8_Click);
//
// button9
//
this.button9.Location = new System.Drawing.Point(148, 286);
this.button9.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button9.Name = "button9";
this.button9.Size = new System.Drawing.Size(56, 19);
this.button9.TabIndex = 33;
this.button9.Text = "exit";
this.button9.UseVisualStyleBackColor = true;
this.button9.Click += new System.EventHandler(this.button9_Click);
//
// button10
//
this.button10.Location = new System.Drawing.Point(86, 171);
this.button10.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button10.Name = "button10";
this.button10.Size = new System.Drawing.Size(56, 19);
this.button10.TabIndex = 34;
this.button10.Text = "farm";
this.button10.UseVisualStyleBackColor = true;
this.button10.Click += new System.EventHandler(this.button10_Click);
//
// button11
//
this.button11.Location = new System.Drawing.Point(147, 171);
this.button11.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button11.Name = "button11";
this.button11.Size = new System.Drawing.Size(56, 19);
this.button11.TabIndex = 35;
this.button11.Text = "help";
this.button11.UseVisualStyleBackColor = true;
this.button11.Click += new System.EventHandler(this.button11_Click);
//
// button12
//
this.button12.Location = new System.Drawing.Point(208, 171);
this.button12.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button12.Name = "button12";
this.button12.Size = new System.Drawing.Size(56, 19);
this.button12.TabIndex = 36;
this.button12.Text = "start";
this.button12.UseVisualStyleBackColor = true;
this.button12.Click += new System.EventHandler(this.button12_Click);
//
// button13
//
this.button13.Location = new System.Drawing.Point(268, 171);
this.button13.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button13.Name = "button13";
this.button13.Size = new System.Drawing.Size(56, 19);
this.button13.TabIndex = 37;
this.button13.Text = "stop";
this.button13.UseVisualStyleBackColor = true;
this.button13.Click += new System.EventHandler(this.button13_Click);
//
// button14
//
this.button14.Location = new System.Drawing.Point(329, 171);
this.button14.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button14.Name = "button14";
this.button14.Size = new System.Drawing.Size(56, 19);
this.button14.TabIndex = 38;
this.button14.Text = "pause";
this.button14.UseVisualStyleBackColor = true;
this.button14.Click += new System.EventHandler(this.button14_Click);
//
// button15
//
this.button15.Location = new System.Drawing.Point(268, 204);
this.button15.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button15.Name = "button15";
this.button15.Size = new System.Drawing.Size(56, 19);
this.button15.TabIndex = 39;
this.button15.Text = "status";
this.button15.UseVisualStyleBackColor = true;
this.button15.Click += new System.EventHandler(this.button15_Click);
//
// button16
//
this.button16.Location = new System.Drawing.Point(329, 204);
this.button16.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button16.Name = "button16";
this.button16.Size = new System.Drawing.Size(56, 19);
this.button16.TabIndex = 40;
this.button16.Text = "status all";
this.button16.UseVisualStyleBackColor = true;
this.button16.Click += new System.EventHandler(this.button16_Click);
//
// button17
//
this.button17.Location = new System.Drawing.Point(26, 318);
this.button17.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button17.Name = "button17";
this.button17.Size = new System.Drawing.Size(56, 19);
this.button17.TabIndex = 41;
this.button17.Text = "owns";
this.button17.UseVisualStyleBackColor = true;
this.button17.Click += new System.EventHandler(this.button17_Click);
//
// button18
//
this.button18.Location = new System.Drawing.Point(147, 318);
this.button18.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button18.Name = "button18";
this.button18.Size = new System.Drawing.Size(56, 19);
this.button18.TabIndex = 42;
this.button18.Text = "addlicense";
this.button18.UseVisualStyleBackColor = true;
this.button18.Click += new System.EventHandler(this.button18_Click);
//
// button19
//
this.button19.Location = new System.Drawing.Point(87, 318);
this.button19.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button19.Name = "button19";
this.button19.Size = new System.Drawing.Size(56, 19);
this.button19.TabIndex = 43;
this.button19.Text = "play";
this.button19.UseVisualStyleBackColor = true;
this.button19.Click += new System.EventHandler(this.button19_Click);
//
// label3
//
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(24, 245);
this.label3.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(235, 13);
this.label3.TabIndex = 44;
this.label3.Text = "The following do not work with \"Send to all\" and";
//
// label4
//
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(24, 259);
this.label4.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(224, 13);
this.label4.TabIndex = 45;
this.label4.Text = "require confirmation even without \"Safemode\"";
//
// button20
//
this.button20.Location = new System.Drawing.Point(231, 130);
this.button20.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button20.Name = "button20";
this.button20.Size = new System.Drawing.Size(56, 19);
this.button20.TabIndex = 46;
this.button20.Text = "clear";
this.button20.UseVisualStyleBackColor = true;
this.button20.Click += new System.EventHandler(this.button20_Click);
//
// label5
//
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(29, 370);
this.label5.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(221, 13);
this.label5.TabIndex = 47;
this.label5.Text = "If you don\'t know what this is... Don\'t touch it!";
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(854, 414);
this.Controls.Add(this.label5);
this.Controls.Add(this.button20);
this.Controls.Add(this.label4);
this.Controls.Add(this.label3);
this.Controls.Add(this.button19);
this.Controls.Add(this.button18);
this.Controls.Add(this.button17);
this.Controls.Add(this.button16);
this.Controls.Add(this.button15);
this.Controls.Add(this.button14);
this.Controls.Add(this.button13);
this.Controls.Add(this.button12);
this.Controls.Add(this.button11);
this.Controls.Add(this.button10);
this.Controls.Add(this.button9);
this.Controls.Add(this.button8);
this.Controls.Add(this.button7);
this.Controls.Add(this.button6);
this.Controls.Add(this.checkBox4);
this.Controls.Add(this.textBox3);
this.Controls.Add(this.button5);
this.Controls.Add(this.button4);
this.Controls.Add(this.button3);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.textBox2);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.comboBox1);
this.Controls.Add(this.checkBox3);
this.Controls.Add(this.checkBox2);
this.Controls.Add(this.checkBox1);
this.Icon = ((System.Drawing.Icon) (resources.GetObject("$this.Icon")));
this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button button5;
private System.Windows.Forms.Button button4;
private System.Windows.Forms.Button button3;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.ComboBox comboBox1;
private System.Windows.Forms.CheckBox checkBox3;
private System.Windows.Forms.CheckBox checkBox2;
private System.Windows.Forms.CheckBox checkBox1;
private System.Windows.Forms.TextBox textBox3;
private System.Windows.Forms.NotifyIcon ASFGUI;
private System.Windows.Forms.CheckBox checkBox4;
private System.Windows.Forms.Button button6;
private System.Windows.Forms.Button button7;
private System.Windows.Forms.Button button8;
private System.Windows.Forms.Button button9;
private System.Windows.Forms.Button button10;
private System.Windows.Forms.Button button11;
private System.Windows.Forms.Button button12;
private System.Windows.Forms.Button button13;
private System.Windows.Forms.Button button14;
private System.Windows.Forms.Button button15;
private System.Windows.Forms.Button button16;
private System.Windows.Forms.Button button17;
private System.Windows.Forms.Button button18;
private System.Windows.Forms.Button button19;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label4;
private System.Windows.Forms.Button button20;
private System.Windows.Forms.Label label5;
}
}

View File

@@ -1,310 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Florian "KlappPC" Lang
Contact: ichhoeremusik@gmx.net
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Windows.Forms;
using System.ServiceModel;
using System.IO;
namespace GUI {
public partial class Form1 : Form {
private bool safeMode = false;
private bool sendAll = false;
private bool sendAny = true;
private bool sendOne = false;
private string botName = "";
string[] botList;
private string URL = "";
ServerProcess proc;
private Client Client;
public Form1() {
InitializeComponent();
// So either the ASF.exe is in the same directory, or we assume development environment.
string ASF = "ASF.exe";
if (!File.Exists(ASF)) {
ASF = "../../../ArchiSteamFarm/bin/" + (Debugging.IsDebugBuild ? "Debug" : "Release") + "/ArchiSteamFarm.exe";
if (!File.Exists(ASF)) {
Logging.LogGenericError("ASF binary could not be found!");
Environment.Exit(1);
}
}
proc = new ServerProcess(ASF, "--server", textBox2);
proc.Start();
}
protected override void OnFormClosing(FormClosingEventArgs e) {
proc.Stop();
base.OnFormClosing(e);
}
/**
* Sends a single command. can be lead by a ! but it does not have to.
*/
private string sendCommand(string command) {
if (command.StartsWith("!")) {
command = command.Substring(1);
}
if (Client == null) {
Client = new Client(new BasicHttpBinding(), new EndpointAddress(URL));
}
return Client.HandleCommand(command);
}
/**
* Maximize again when double clicked on tray icon
*/
private void ASFGUI_MouseDoubleClick(object sender, MouseEventArgs e) {
this.Show();
this.WindowState = FormWindowState.Normal;
}
private void Form1_Load(object sender, System.EventArgs e) {
this.Resize += new System.EventHandler(this.Form1_Resize);
textBox2.ScrollBars = ScrollBars.Vertical;
textBox1.ScrollBars = ScrollBars.Vertical;
checkBox4.Checked = true;
textBox3.Text = "http://localhost:1242/ASF";
URL = "http://localhost:1242/ASF";
textBox2.Anchor = (AnchorStyles.Right | AnchorStyles.Left);
}
/**
* Minimize to tray instead of taskbar
*/
private void Form1_Resize(object sender, EventArgs e) {
if (FormWindowState.Minimized == this.WindowState) {
ASFGUI.Visible = true;
this.Hide();
} else if (FormWindowState.Normal == this.WindowState) {
ASFGUI.Visible = false;
}
}
/**
* generate a command from a simple command
* That means, adds a botName or makes multiple commands for multiple bots.
*/
private string generateCommand(string command, string arg = "") {
if (sendOne)
return command + " " + botName + " " + arg;
if (sendAll) {
string ret = "";
foreach (string str in botList) {
ret = ret + command + " " + str + " " + arg + "\r\n";
}
return ret;
}
return command + arg;
}
/**
* One of the simple buttons got pressed
*/
private void buttonPressed(string command) {
textBox1.Text = generateCommand(command);
if (!safeMode)
button1_Click(this, null);
}
/**
* One of the complicated buttons was pressed
* We get an argumentlist
*/
private void multiCommand(string command) {
if (sendAll)
return;
string[] arr = textBox1.Lines;
string cmd = "";
for (int i = 0; i < arr.Length; i++) {
if (!String.IsNullOrEmpty(arr[i].Trim())) {
cmd = cmd + generateCommand(command, arr[i].Trim()) + "\r\n";
}
}
textBox1.Text = cmd;
}
/**
* updates the WCF URL in case of custom URL
*/
private void textBox3_TextChanged(object sender, EventArgs e) {
URL = textBox3.Text;
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) {
botName = comboBox1.SelectedItem.ToString();
}
//Ok, radiobuttons would have been better I guess, to lazy to change now.
private void checkBox3_CheckedChanged(object sender, EventArgs e) {
//specific
if (checkBox3.Checked) {
sendAll = false;
sendAny = false;
sendOne = true;
checkBox2.Checked = false;
checkBox4.Checked = false;
}
}
private void checkBox2_CheckedChanged(object sender, EventArgs e) {
//all
if (checkBox2.Checked) {
sendAll = true;
sendAny = false;
sendOne = false;
checkBox3.Checked = false;
checkBox4.Checked = false;
}
}
private void checkBox4_CheckedChanged(object sender, EventArgs e) {
//any
if (checkBox4.Checked) {
sendAll = false;
sendAny = true;
sendOne = false;
checkBox2.Checked = false;
checkBox3.Checked = false;
}
}
private void checkBox1_CheckedChanged(object sender, EventArgs e) {
safeMode = checkBox1.Checked;
}
/**
* Send command button.
*/
private void button1_Click(object sender, System.EventArgs e) {
for (int i = 0; i < textBox1.Lines.Length; i++) {
string command = textBox1.Lines[i];
if (!String.IsNullOrEmpty(command.Trim())) {
sendCommand(command);
}
}
}
/**
* Update /Generate Botlist button
*/
private void button6_Click(object sender, EventArgs e) {
string ret = sendCommand("statusall");
string[] arr = ret.Split('\n');
int botAmount = Convert.ToInt16(arr[arr.Length - 1].Split('/')[1].Trim().Split(' ')[0]);
botList = new string[botAmount];
for (int i = 0; i < botAmount; i++) {
botList[i] = arr[arr.Length - 2 - i].Substring(3).Trim().Split(' ')[0];
}
comboBox1.Items.AddRange(botList);
}
//The Rest are simple buttons.
private void button3_Click(object sender, EventArgs e) {
multiCommand("redeem");
}
private void button2_Click(object sender, EventArgs e) {
textBox1.Text = generateCommand("loot");
if (!safeMode)
button1_Click(this, null);
}
private void button4_Click(object sender, EventArgs e) {
//2fa
textBox1.Text = generateCommand("2fa");
if (!safeMode)
button1_Click(this, null);
}
private void button5_Click(object sender, EventArgs e) {
buttonPressed("2faok");
}
private void button7_Click(object sender, EventArgs e) {
buttonPressed("2fano");
}
private void button8_Click(object sender, EventArgs e) {
if (sendAll)
return;
textBox1.Text = generateCommand("2faoff");
}
private void button9_Click(object sender, EventArgs e) {
textBox1.Text = "exit";
}
private void button10_Click(object sender, EventArgs e) {
buttonPressed("farm");
}
private void button11_Click(object sender, EventArgs e) {
buttonPressed("help");
}
private void button12_Click(object sender, EventArgs e) {
buttonPressed("start");
}
private void button13_Click(object sender, EventArgs e) {
buttonPressed("stop");
}
private void button14_Click(object sender, EventArgs e) {
buttonPressed("pause");
}
private void button15_Click(object sender, EventArgs e) {
buttonPressed("status");
}
private void button16_Click(object sender, EventArgs e) {
textBox1.Text = "statusall";
if (!safeMode)
button1_Click(this, null);
}
private void button17_Click(object sender, EventArgs e) {
multiCommand("owns");
}
private void button18_Click(object sender, EventArgs e) {
multiCommand("addlicense");
}
private void button19_Click(object sender, EventArgs e) {
multiCommand("play");
}
private void button20_Click(object sender, EventArgs e) {
textBox1.Text = "";
}
}
//############### After this point copied from Archie's WCF ###################
[ServiceContract]
interface IWCF {
[OperationContract]
string HandleCommand(string input);
}
class Client : ClientBase<IWCF>, IWCF {
internal Client(System.ServiceModel.Channels.Binding binding, EndpointAddress address) : base(binding, address) { }
public string HandleCommand(string input) {
try {
return Channel.HandleCommand(input);
} catch (Exception e) {
//Logging.LogGenericException(e);
return null;
}
}
}
}

File diff suppressed because it is too large Load Diff

84
GUI/Form2.Designer.cs generated
View File

@@ -1,84 +0,0 @@
namespace GUI {
partial class Form2 {
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form2));
this.button1 = new System.Windows.Forms.Button();
this.textBox1 = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(134, 93);
this.button1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(87, 28);
this.button1.TabIndex = 0;
this.button1.Text = "OK";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// textBox1
//
this.textBox1.Location = new System.Drawing.Point(12, 63);
this.textBox1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(336, 20);
this.textBox1.TabIndex = 1;
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(10, 7);
this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(35, 13);
this.label1.TabIndex = 2;
this.label1.Text = "label1";
//
// Form2
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(356, 132);
this.Controls.Add(this.label1);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.button1);
this.Icon = ((System.Drawing.Icon) (resources.GetObject("$this.Icon")));
this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2);
this.Name = "Form2";
this.Text = "Input";
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Label label1;
}
}

View File

@@ -1,44 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Florian "KlappPC" Lang
Contact: ichhoeremusik@gmx.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Windows.Forms;
namespace GUI {
/**
* popup Message when Input is required
*/
public partial class Form2 : Form {
ServerProcess proc;
public Form2(ServerProcess proc, string msg) {
this.proc = proc;
InitializeComponent();
label1.Text = msg;
button1.DialogResult = DialogResult.OK;
}
private void button1_Click(object sender, EventArgs e) {
proc.Write(textBox1.Text);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,146 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{599121A9-5887-4522-A3D6-61470B90BAD4}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GUI</RootNamespace>
<AssemblyName>GUI</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>
</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>cirno.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.ServiceModel.Web" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="Debugging.cs" />
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Form2.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form2.Designer.cs">
<DependentUpon>Form2.cs</DependentUpon>
</Compile>
<Compile Include="Logging.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServerProcess.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Form2.resx">
<DependentUpon>Form2.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="app.manifest" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Service References\" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.5">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.5 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Content Include="cirno.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -1,93 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
namespace GUI {
internal static class Logging {
internal static void LogGenericInfo(string message) {
if (string.IsNullOrEmpty(message)) {
return;
}
MessageBox.Show(message, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
internal static void LogGenericWTF(string message, [CallerMemberName] string previousMethodName = "") {
if (string.IsNullOrEmpty(message)) {
return;
}
MessageBox.Show(previousMethodName + "() " + message, "WTF", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
internal static void LogGenericError(string message, [CallerMemberName] string previousMethodName = "") {
if (string.IsNullOrEmpty(message)) {
return;
}
MessageBox.Show(previousMethodName + "() " + message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
internal static void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = "") {
if (exception == null) {
return;
}
MessageBox.Show(previousMethodName + "() " + exception.Message + Environment.NewLine + exception.StackTrace, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
if (exception.InnerException != null) {
LogGenericException(exception.InnerException, previousMethodName);
}
}
internal static void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = "") {
if (string.IsNullOrEmpty(message)) {
return;
}
MessageBox.Show(previousMethodName + "() " + message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
internal static void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = "") {
if (string.IsNullOrEmpty(nullObjectName)) {
return;
}
LogGenericError(nullObjectName + " is null!", previousMethodName);
}
[Conditional("DEBUG")]
internal static void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = "") {
if (string.IsNullOrEmpty(message)) {
return;
}
MessageBox.Show(previousMethodName + "() " + message, "Debug", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using System.Windows.Forms;
namespace GUI {
static class Program {
/// <summary>
/// Der Haupteinstiegspunkt für die Anwendung.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die mit einer Assembly verknüpft sind.
[assembly: AssemblyTitle("GUI")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GUI")]
[assembly: AssemblyCopyright("Copyright © ArchiSteamFarm 2015-2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("18b85645-1c80-4e25-9dcb-e01684a48fca")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
//
// Hauptversion
// Nebenversion
// Buildnummer
// Revision
//
// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -1,63 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GUI.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GUI.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@@ -1,117 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -1,26 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace GUI.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

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

View File

@@ -1,180 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Florian "KlappPC" Lang
Contact: ichhoeremusik@gmx.net
This file is mostly done by a friend who explicitly does not want to get mentioned in any way.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System.Threading;
using System.Diagnostics;
using System.Windows.Forms;
namespace GUI {
/*basically a class to run executables as controlled prozess in the background*/
public class ServerProcess {
//ASF.exe in our case
protected Process process;
//handling the output.
protected Thread outputThread;
protected bool stopping;
//the textbox from our Form, where we want to display output.
private TextBox output;
private object lockObj = new object();
/**
* New SeverProcess for filename with arguments and output to textBox.
* Console is hidden and IO redirected.
*/
public ServerProcess(string fileName, string argumants, TextBox textBox) {
output = textBox;
process = new System.Diagnostics.Process();
process.StartInfo.FileName = fileName;
process.StartInfo.Arguments = argumants;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
}
//needed for realizing when input is needed.
private int dotcounter = 0;
/**
* I'm not quite happy with this. I could not figure a way to notice when input is required
* besides reading char by char and searching for keywords. Will stop working, if the "Please enter"
* lines gets changed.
* Only tested for "Enter Password."
*/
private void NewOutput(object sender, char e) {
MethodInvoker mi = delegate {
output.AppendText(e.ToString());
};
if (e == '.') {
dotcounter++;
} else if (e == ':') {
dotcounter = 3;
} else {
dotcounter = 0;
}
if (dotcounter == 3) {
string[] arr = output.Lines;
string str = arr[arr.Length - 1];
if (str.Contains("Hit enter")) {
str = arr[arr.Length - 2] + " | " + str;
Form f = new Form2(this, str);
f.ShowDialog();
mi = delegate { output.AppendText(e.ToString() + "\n"); };
}
if (str.Contains("Please enter")) {
Form f = new Form2(this, str);
f.ShowDialog();
mi = delegate { output.AppendText(e.ToString() + "\n"); };
}
dotcounter = 0;
}
output.Invoke(mi);
}
private void NewOutput(object sender, string e) {
MethodInvoker mi = delegate {
output.AppendText(e + "\n");
};
output.Invoke(mi);
}
private void printOutPut() {
char str;
int i;
string s;
while (!stopping) {
//thats ugly, but when using readline we can't catch input.
while (((i = process.StandardOutput.Read()) != 0)) {
str = System.Convert.ToChar(i);
NewOutput(this, str);
if (stopping)
break;
}
while (((s = process.StandardError.ReadLine()) != null)) {
NewOutput(this, s);
if (stopping)
break;
}
}
}
public void Write(string msg) {
process.StandardInput.WriteLine(msg);
process.StandardInput.Flush();
}
public void Stop() {
Thread stopThread = new Thread(StopProcess);
stopThread.Start();
}
private void StopProcess() {
if (process == null)
return;
stopping = true;
outputThread.Abort();
Thread.Sleep(1000);
if (process == null)
return;
if (process.HasExited)
process.Close();
else
process.Kill();
process = null;
}
/**
* starts the process and a second thread to listen for output.
*/
public void Start() {
outputThread = new Thread(printOutPut);
process.Start();
outputThread.Start();
}
public Process Process {
get {
return process;
}
}
}
}

View File

@@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC-Manifestoptionen
Wenn Sie die Zugangsebene für das Windows-Benutzerkonto ändern möchten, ersetzen Sie den
requestedExecutionLevel-Knoten durch eines der folgenden Elemente.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Durch Angeben des requestedExecutionLevel-Knotens wird die Datei- und Registrierungsvirtualisierung deaktiviert.
Wenn Sie Datei- und Registrierungsvirtualisierung für Abwärts-
kompatibilität verwenden möchten, löschen Sie den requestedExecutionLevel-Knoten.
-->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Eine Liste aller Windows-Versionen, mit denen die Anwendung kompatibel ist.
Windows wählt automatisch die am stärksten kompatible Umgebung aus.-->
<!-- Wenn die Anwendung mit Windows Vista kompatibel ist, heben Sie die Auskommentierung des folgenden supportedOS-Knotens auf-->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"></supportedOS>-->
<!-- Wenn die Anwendung mit Windows 7 kompatibel ist, heben Sie die Kommentierung des folgenden supportedOS-Knotens auf.-->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>-->
<!-- Wenn die Anwendung mit Windows 8 kompatibel ist, heben Sie die Auskommentierung des folgenden supportedOS-Knotens auf-->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></supportedOS>-->
<!-- Wenn die Anwendung mit Windows 8.1 kompatibel ist, die Kommentierung des folgenden supportedOS-Knotens aufheben.-->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>-->
</application>
</compatibility>
<!-- Designs für allgemeine Windows-Steuerelemente und -Dialogfelder (Windows XP und höher) aktivieren -->
<!-- <dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>-->
</asmv1:assembly>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

File diff suppressed because one or more lines are too long

11
run.sh
View File

@@ -2,12 +2,14 @@
set -eu
BUILD="Release"
UNTIL_CLEAN_EXIT=0
ASF_ARGS=("")
MONO_ARGS=("--llvm" "--server" "-O=all")
PRINT_USAGE() {
echo "Usage: $0 [--until-clean-exit] [debug/release]"
echo "Usage: $0 [--until-clean-exit] [--cryptkey=] [--path=] [--server] [debug/release]"
exit 1
}
@@ -15,6 +17,9 @@ for ARG in "$@"; do
case "$ARG" in
release|Release) BUILD="Release" ;;
debug|Debug) BUILD="Debug" ;;
--cryptkey=*) ASF_ARGS+=("$ARG") ;;
--path=*) ASF_ARGS+=("$ARG") ;;
--server) ASF_ARGS+=("$ARG") ;;
--until-clean-exit) UNTIL_CLEAN_EXIT=1 ;;
*) PRINT_USAGE
esac
@@ -34,12 +39,12 @@ if [[ ! -f "$BINARY" ]]; then
fi
if [[ "$UNTIL_CLEAN_EXIT" -eq 0 ]]; then
mono "${MONO_ARGS[@]}" "$BINARY"
mono "${MONO_ARGS[@]}" "$BINARY" "${ASF_ARGS[@]}"
exit $?
fi
while [[ -f "$BINARY" ]]; do
if mono "${MONO_ARGS[@]}" "$BINARY"; then
if mono "${MONO_ARGS[@]}" "$BINARY" "${ASF_ARGS[@]}"; then
break
fi
sleep 1