Compare commits

..

171 Commits

Author SHA1 Message Date
JustArchi
91a96990aa Add !redeem^ 2016-09-22 19:27:16 +02:00
JustArchi
abff9bd28d Code review 2016-09-21 19:26:20 +02:00
JustArchi
e2a8c14a0d Code review 2016-09-21 18:22:27 +02:00
JustArchi
6489502be7 Misc 2016-09-21 16:10:28 +02:00
JustArchi
f6e7c06141 Code review 2016-09-21 15:56:35 +02:00
JustArchi
3548411cda Misc 2016-09-21 15:37:39 +02:00
JustArchi
43ce5eb257 Misc 2016-09-21 15:35:13 +02:00
JustArchi
2756a2ebc7 Shutdown double instances 2016-09-21 15:34:29 +02:00
JustArchi
d3470624bc Log processID too 2016-09-21 15:22:42 +02:00
JustArchi
0439656499 Misc 2016-09-20 16:29:13 +02:00
JustArchi
dc1ae32d37 Slightly improve key data updates 2016-09-20 16:05:04 +02:00
JustArchi
b3b07a465b Misc optimization 2016-09-20 15:37:40 +02:00
JustArchi
e6fff8c6bd Bump 2016-09-18 22:27:29 +02:00
JustArchi
b010009a2d AppVeyor: Skip build status changes 2016-09-18 22:15:06 +02:00
JustArchi
444eb97f49 Remove time left from !2fa
It doesn't make any sense if code works for ~2 minutes anyway
2016-09-18 22:10:11 +02:00
JustArchi
fbbe3c1d09 Partially revert < 5 seconds waits
It seems that Valve actually accepts not one but several tokens generated close to the point of request being sent, so our 2FA code will be always valid for 1-2 more minutes after supposed timeout...
2016-09-18 20:31:06 +02:00
JustArchi
69bf0022d5 Don't use MONO_DNS 2016-09-18 11:15:24 +02:00
JustArchi
ac6e9bc15a Bump 2016-09-17 05:45:07 +02:00
JustArchi
1df2c3b042 Implement my previous idea
Thanks to that, we can guarantee some room for networking, but also make users more happy as they'll never get 2FA tokens no human is capable of entering in time
2016-09-16 23:40:52 +02:00
JustArchi
160bfd612f Approach the problem in better way
The general problem is a mix of a few things: The fact that we don't have much time before steam network disconnects us, if we connect to it and not send log in request in acceptable time, the fact that Steam API might be unavailable and not provide us with server time, and the fact that we must know that time to generate valid tokens.
Previous solution would simply generate token immediately without asking Steam API, and schedule update in background for later, so even if we had incorrect time and failure of first try, second try would usually come with the right clock. If not, eventually we'd succeed anyway.
However, it's possible to slightly improve that - we can generate 2FA code BEFORE even connecting to steam network, this way we have time to ask Steam API, and in worst case of API timeout we'll simply try with our own clock anyway, and if it succeeds, timeframe before connecting and sending logon request should be small enough to fit - in worst case of being on the edge of 30 seconds, we'll simply try again later.
Perhaps it'd also make sense to modify slightly MobileAuthenticator to block and wait in case code is expiring in less than 5 seconds, that could be cool too!
2016-09-16 23:09:09 +02:00
JustArchi
feb80fbf49 Revert "Fix logging in when steam API is unavailable"
This reverts commit 5a267eb225.
2016-09-16 22:46:10 +02:00
JustArchi
5a267eb225 Fix logging in when steam API is unavailable
Or at least part of it...
2016-09-14 19:00:03 +02:00
JustArchi
ccfad31053 Misc 2016-09-14 18:43:34 +02:00
JustArchi
bd3dfa3664 Bump 2016-09-13 03:15:55 +02:00
JustArchi
18c6fd639a Misc 2016-09-13 03:12:58 +02:00
JustArchi
75e3ef9818 SteamClient.IsConnected hardening 2016-09-13 02:58:22 +02:00
JustArchi
44b401aabd Never override existing values 2016-09-12 21:01:09 +02:00
JustArchi
4d6f2811bb Unify mono params 2016-09-12 20:52:30 +02:00
JustArchi
11d4430bbc Rename Steam contextID to match ArchiBoT 2016-09-12 05:54:34 +02:00
JustArchi
0cea70ef6a Rename card checks methods to match ArchiBoT 2016-09-12 03:49:17 +02:00
JustArchi
e4df12fc5b Misc 2016-09-11 16:37:18 +02:00
JustArchi
19818f126f Bump 2016-09-09 02:53:33 +02:00
JustArchi
f2a2f93273 Allow an hour of time difference with offline messages 2016-09-09 02:50:13 +02:00
JustArchi
e6d4304ca1 Misc consistency 2016-09-05 20:21:50 +02:00
JustArchi
e020f54753 Optimize SteamTimeDifference
In very unlikely case of actually having exactly the same time difference, notice the difference between no value and value of 0
2016-09-05 18:46:00 +02:00
JustArchi
cada3fa920 Don't use bashisms in envsetup 2016-09-02 18:57:13 +02:00
JustArchi
2c99c4b433 Misc 2016-09-02 18:41:40 +02:00
JustArchi
a25a181cd9 Don't use bashisms in post-build scripts
They're always run with /bin/sh and that doesn't always equal to bash
2016-09-02 11:40:30 +02:00
Łukasz Domeradzki
5f3a6d4c10 Update CONTRIBUTING.md 2016-09-02 10:58:38 +02:00
JustArchi
3e82c9f11e Misc 2016-08-31 14:19:27 +02:00
JustArchi
bed87c4c80 Integrate DebugListener with Logger 2016-08-31 13:56:09 +02:00
JustArchi
37f5f82cf8 Bump 2016-08-31 09:27:33 +02:00
JustArchi
ff00b3049a Correct LoginID to use 0
It seems that some broken OSes actually can lead to result of obfuscation mask instead of real value, so ASF shouldn't use it
2016-08-30 17:42:01 +02:00
JustArchi
b316d3b03a Don't attempt to farm hours if we have only 1 game left to farm
FarmSolo() function allows more precise tracking, and FarmMultiple() shouldn't be used only with 1 game
2016-08-30 14:05:03 +02:00
JustArchi
3f735d165c Misc 2016-08-30 13:54:40 +02:00
JustArchi
abde04cc83 Misc 2016-08-30 13:46:42 +02:00
JustArchi
e606ca05ed Bump 2016-08-30 12:53:42 +02:00
JustArchi
e0dbd70b37 Final attempt to fix #318 2016-08-30 12:45:02 +02:00
JustArchi
d37f5bb250 Maintain debug directory for each bot 2016-08-30 10:16:22 +02:00
JustArchi
fdd9744556 Add standard PP donate option
As per request
2016-08-28 22:58:42 +02:00
JustArchi
3814bd64f0 Bump 2016-08-27 10:15:11 +02:00
JustArchi
98679545d3 Give up, Mono in travis is truly broken 2016-08-27 10:02:05 +02:00
JustArchi
49b1259817 Misc 2016-08-27 09:56:52 +02:00
JustArchi
0c14128b63 Final try 2016-08-27 09:53:30 +02:00
JustArchi
c115158279 More debug 2016-08-27 09:45:55 +02:00
JustArchi
2a61ecb681 Attempt to fix Mono weekly in travis (once again) 2016-08-27 09:34:08 +02:00
JustArchi
d06fbda6d6 Prefer 64-bit ASF
ASF is AnyCPU binary which means it'll run fine on both 32-bit as well as 64-bit OSes.
Up to today 32-bit runtime was preferred for ASF, as it uses less memory and can be faster than 64-bit equivalent.
However, in very busy environments it's easily possible for ASF to require even more than 2 GB of memory, and in addition to that extending our userspace to 64-bit can bring benefits on more modern setups.
Therefore, keep being compatible on both 32-bit and 64-bit OSes, but on 64-bit ones use actual 64-bit runtime instead of preferred 32-bit one.
In addition to that, optimize some unused references and other things while we're at it.
2016-08-27 08:40:40 +02:00
JustArchi
7ba2e829d3 Code review 2016-08-22 00:10:29 +02:00
JustArchi
58ff2a2a4d Bump 2016-08-21 22:41:19 +02:00
JustArchi
d6c9fe3cde Add workarounds for #335 2016-08-21 22:35:31 +02:00
JustArchi
4b782bd10d Bump 2016-08-21 18:15:03 +02:00
JustArchi
1a1e48a33d Bump 2016-08-21 18:06:50 +02:00
JustArchi
f7d7c559b8 Derp 2016-08-21 18:04:48 +02:00
JustArchi
602cda73b7 Closes #335
We need to alter the flow in order to check for result updates on each loop instead of the beginning
2016-08-21 18:03:56 +02:00
Łukasz Domeradzki
f579462f60 Update CONTRIBUTING.md 2016-08-21 03:50:54 +02:00
JustArchi
8066f1a7c0 Don't attempt to loot foils if !IsBotAccount
Rationale: Foil cards are excluded from STM, as the price varies. For most people with !IsBotAccount that will be main account, which should never be lootable, and for people running bots for friends, making them keep foils makes sense. The main reason for this change is my own setup in which I'd like to automatically send all cards to ArchiBoT for 1:1 matching, but keep foils for selling.
2016-08-20 00:35:33 +02:00
JustArchi
f8409e1be6 Misc 2016-08-19 17:49:56 +02:00
Łukasz Domeradzki
c36eeb8c28 Update CONTRIBUTING.md 2016-08-19 15:11:49 +02:00
JustArchi
d0344a7ab9 Misc code review 2016-08-19 05:25:08 +02:00
JustArchi
2c767bfe85 Misc 2016-08-19 05:08:37 +02:00
JustArchi
2816ecaa90 Further optimize InMemoryServerListProvider and make it thread-safe 2016-08-19 04:57:21 +02:00
JustArchi
214746bca2 Optimize updating of server list
We can compare new endpoints firstly, to save Save() call if they're equal values-wise.
2016-08-19 04:10:49 +02:00
JustArchi
134aa62952 Seems to work properly 2016-08-15 22:00:32 +02:00
JustArchi
5a4132a679 More tests 2016-08-15 21:57:45 +02:00
JustArchi
edb047980e Extend logic for trades 2016-08-15 21:47:31 +02:00
JustArchi
7d32adac13 Perform loot also on new items received, if we're not farming 2016-08-15 21:35:19 +02:00
JustArchi
95637ea3a7 Improve trading failure handling
It seems that even if Steam responds with e.g. internal server error (500), the trade gets accepted 20-30 seconds later, which doesn't make ANY sense, but does anything in Steam do?
Let's improve the logic a bit by returning result even if we in fact failed in Accept/Decline function, this will allow us to deal with confirmations even if failed trade in fact succeeded.
2016-08-14 00:19:01 +02:00
JustArchi
02a547e7d2 Misc 2016-08-13 15:58:00 +02:00
JustArchi
9594357d56 Misc code analysis fixes 2016-08-13 04:39:17 +02:00
JustArchi
ce166baab6 Bump 2016-08-13 04:27:04 +02:00
JustArchi
1ec0b20604 Misc 2016-08-13 04:19:20 +02:00
JustArchi
26bd76cc4a Make debugging easier for me
Modification of ASF.json is troublesome when I work with GitHub tree, therefore make it possible for me to execute and test commands but only in debugging builds - public ASF releases are always compiled in release mode.
2016-08-13 04:12:39 +02:00
JustArchi
8e1d02f43f Implement !ownsall, closes #330 2016-08-13 04:04:47 +02:00
JustArchi
b802822699 Correct #329 a bit 2016-08-12 23:07:19 +02:00
JustArchi
be77e8d380 Update README.md
Drop support for Vista, as it's not supported in .NET 4.6.1+
2016-08-10 22:03:31 +02:00
JustArchi
000b902ced Categorize options in ConfigGenerator
Preview: http://i.imgur.com/Noc8qbf.png
2016-08-10 18:03:14 +02:00
JustArchi
d09be453f3 Misc 2016-08-09 04:05:01 +02:00
JustArchi
5f1342ae26 Add extra check after waiting in OnDisconnected()
If for some reason this callback gets executed twice, we don't want to issue second connect request in any case
2016-08-09 04:04:22 +02:00
JustArchi
00b4c28843 Respect LimitLoginRequestsAsync() in HeartBeat() 2016-08-09 03:46:45 +02:00
JustArchi
cb6cfd08c2 Improve load-balancing 2016-08-08 20:10:04 +02:00
JustArchi
f53911bd9a Misc 2016-08-08 20:07:40 +02:00
JustArchi
527641439b Implement enhanced HeartBeat
The objective of this feature is to detect network malfunctions as well as SK2 connection issues early and initiate a reconnect as soon as possible, instead of relying on failures in SK2 code.
This is because those failures are very usually coming too late, when connection was already lost for a dozen or more minutes behind, and it also increases likehood of getting weird SK2 freezes like the one in #318.
Therefore, let's see how it works, it's possible that I'll revert it later when SK2 code improves or we find a better way to do that. The introduced overhead both CPU-wise and bandwidth-wise is negligible.
2016-08-08 20:06:20 +02:00
JustArchi
647a0ee865 Revert "Prepare for custom HeartBeat handling"
This reverts commit b9f2dd1292.
2016-08-08 18:47:23 +02:00
JustArchi
b9f2dd1292 Prepare for custom HeartBeat handling 2016-08-08 18:23:15 +02:00
JustArchi
e675a3a488 Enhance startup sequence a bit 2016-08-06 22:16:46 +02:00
JustArchi
fb8692d28c Misc enhancements 2016-08-06 16:29:05 +02:00
JustArchi
cf4141dde7 Remove debug routines 2016-08-05 20:43:30 +02:00
JustArchi
963f56ccf2 Bump 2016-08-05 03:16:12 +02:00
JustArchi
c754a18603 Fix games with over 255 card drops not being recognized
I never expected somebody to reach that many
2016-08-05 03:11:27 +02:00
JustArchi
eb886e8ca8 Move logging module initialization after setting home directory 2016-08-04 22:44:17 +02:00
JustArchi
e8889fb087 Add one more status case 2016-08-04 15:11:23 +02:00
JustArchi
d627a5ee9d Require .NET 4.6.1+ 2016-08-03 20:07:46 +02:00
JustArchi
35bd36bbd9 Change download count to latest stable only 2016-08-03 18:03:02 +02:00
JustArchi
d79944085f Fix Mono compilation 2016-08-03 17:38:25 +02:00
JustArchi
4e191367da Bump 2016-08-03 17:32:06 +02:00
JustArchi
f88bfe9f83 Make CardDropsRestricted true by default
After evaluation, it seems that more accounts have card drops restricted rather than not, and having it as true when in reality it's false results in less performance degradation than the other way
2016-08-03 17:16:11 +02:00
JustArchi
86aa9e781d Travis: Allow failure on Mono weekly
It's broken more often than it works, I don't need to be informed about that
2016-08-03 03:20:42 +02:00
JustArchi
6ae7e74daf Bump 2016-08-03 02:53:47 +02:00
JustArchi
4481fb3a86 Work work 2016-08-02 22:51:09 +02:00
JustArchi
6bd161359f Work on GUI
There is still a long way till it's done...
2016-08-02 12:13:15 +02:00
JustArchi
612abef327 Work on GUI
There is still a long way till it's done...
2016-08-02 08:50:31 +02:00
JustArchi
97224aafaf Bring initial GUI project 2016-08-02 06:13:58 +02:00
JustArchi
7025659151 Refactoring for upcoming GUI app 2016-08-02 06:04:44 +02:00
JustArchi
9918861c66 Misc 2016-08-02 05:11:35 +02:00
JustArchi
fcfe4495ae Misc 2016-08-02 05:09:53 +02:00
JustArchi
2c10ebe8b3 Never attempt to loot non-connected bot instance 2016-08-02 05:06:52 +02:00
JustArchi
e2dca65165 Misc 2016-08-02 02:17:06 +02:00
JustArchi
beef5ec8ed Order also !statusall 2016-08-01 22:25:21 +02:00
JustArchi
14fc01d4ef Improve !status 2016-08-01 20:27:26 +02:00
JustArchi
7bd8b8f965 Fix !status 2016-08-01 20:01:20 +02:00
JustArchi
85fd18d621 Bump 2016-08-01 19:01:44 +02:00
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
70 changed files with 16490 additions and 944 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

@@ -6,8 +6,19 @@ git:
mono:
- weekly
# - alpha
# - beta
- latest
matrix:
allow_failures:
- mono: weekly
# - mono: alpha
# - mono: beta
before_script:
- source mono_envsetup.sh
notifications:
email: false
webhooks:

View File

@@ -10,6 +10,11 @@ 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", "{13949B41-787C-4558-90AE-A9F9E7F86B1F}"
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
@@ -24,6 +29,10 @@ 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
{13949B41-787C-4558-90AE-A9F9E7F86B1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{13949B41-787C-4558-90AE-A9F9E7F86B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13949B41-787C-4558-90AE-A9F9E7F86B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13949B41-787C-4558-90AE-A9F9E7F86B1F}.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>

199
ArchiSteamFarm/ASF.cs Normal file
View File

@@ -0,0 +1,199 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal static class ASF {
private static Timer AutoUpdatesTimer;
internal static async Task CheckForUpdate(bool updateOverride = false) {
string exeFile = Assembly.GetEntryAssembly().Location;
string oldExeFile = exeFile + ".old";
// We booted successfully so we can now remove old exe file
if (File.Exists(oldExeFile)) {
// It's entirely possible that old process is still running, allow at least a second before trying to remove the file
await Task.Delay(1000).ConfigureAwait(false);
try {
File.Delete(oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
Logging.LogGenericError("Could not remove old ASF binary, please remove " + oldExeFile + " manually in order for update function to work!");
}
}
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Unknown) {
return;
}
if ((AutoUpdatesTimer == null) && Program.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 = SharedInfo.GithubReleaseURL;
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
releaseURL += "/latest";
}
Logging.LogGenericInfo("Checking new version...");
GitHub.ReleaseResponse releaseResponse;
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
releaseResponse = await Program.WebBrowser.UrlGetToJsonResultRetry<GitHub.ReleaseResponse>(releaseURL).ConfigureAwait(false);
if (releaseResponse == null) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
} else {
List<GitHub.ReleaseResponse> releases = await Program.WebBrowser.UrlGetToJsonResultRetry<List<GitHub.ReleaseResponse>>(releaseURL).ConfigureAwait(false);
if ((releases == null) || (releases.Count == 0)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
releaseResponse = releases[0];
}
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
Version newVersion = new Version(releaseResponse.Tag);
Logging.LogGenericInfo("Local version: " + SharedInfo.Version + " | Remote version: " + newVersion);
if (SharedInfo.Version.CompareTo(newVersion) >= 0) { // If local version is the same or newer than remote version
return;
}
if (!updateOverride && !Program.GlobalConfig.AutoUpdates) {
Logging.LogGenericInfo("New version is available!");
Logging.LogGenericInfo("Consider updating yourself!");
await Task.Delay(5000).ConfigureAwait(false);
return;
}
if (File.Exists(oldExeFile)) {
Logging.LogGenericWarning("Refusing to proceed with auto update as old " + oldExeFile + " binary could not be removed, please remove it manually");
return;
}
// Auto update logic starts here
if (releaseResponse.Assets == null) {
Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
return;
}
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!");
return;
}
if (string.IsNullOrEmpty(binaryAsset.DownloadURL)) {
Logging.LogGenericWarning("Could not proceed with update because download URL is empty!");
return;
}
Logging.LogGenericInfo("Downloading new version...");
Logging.LogGenericInfo("While waiting, consider donating if you appreciate the work being done :)");
byte[] result = await Program.WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
if (result == null) {
return;
}
string newExeFile = exeFile + ".new";
// Firstly we create new exec
try {
File.WriteAllBytes(newExeFile, result);
} catch (Exception e) {
Logging.LogGenericException(e);
return;
}
// Now we move current -> old
try {
File.Move(exeFile, oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Delete(newExeFile);
} catch {
// Ignored
}
return;
}
// Now we move new -> current
try {
File.Move(newExeFile, exeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Move(oldExeFile, exeFile);
File.Delete(newExeFile);
} catch {
// Ignored
}
return;
}
Logging.LogGenericInfo("Update process finished!");
if (Program.GlobalConfig.AutoRestart) {
Logging.LogGenericInfo("Restarting...");
await Task.Delay(5000).ConfigureAwait(false);
Program.Restart();
} else {
Logging.LogGenericInfo("Exiting...");
await Task.Delay(5000).ConfigureAwait(false);
Program.Exit();
}
}
}
}

View File

@@ -75,10 +75,7 @@ namespace ArchiSteamFarm {
return;
}
Notifications = new HashSet<ENotification>();
foreach (CMsgClientUserNotifications.Notification notification in msg.notifications) {
Notifications.Add((ENotification) notification.user_notification_type);
}
Notifications = new HashSet<ENotification>(msg.notifications.Select(notification => (ENotification) notification.user_notification_type));
}
internal NotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) {
@@ -223,7 +220,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,8 +38,9 @@
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>3</WarningLevel>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -53,7 +54,7 @@
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<DocumentationFile>
</DocumentationFile>
<Prefer32Bit>true</Prefer32Bit>
<Prefer32Bit>false</Prefer32Bit>
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
<UseVSHostingProcess>false</UseVSHostingProcess>
</PropertyGroup>
@@ -61,7 +62,7 @@
<ApplicationIcon>cirno.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>OnBuildSuccess</RunPostBuildEvent>
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>false</SignAssembly>
@@ -95,18 +96,14 @@
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Security" />
<Reference Include="System.ServiceModel" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Net.Http" />
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
@@ -115,11 +112,13 @@
<Compile Include="ArchiServiceInstaller.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="ASF.cs" />
<Compile Include="Bot.cs" />
<Compile Include="BotConfig.cs" />
<Compile Include="ConcurrentEnumerator.cs" />
<Compile Include="ConcurrentHashSet.cs" />
<Compile Include="CryptoHelper.cs" />
<Compile Include="Events.cs" />
<Compile Include="GlobalDatabase.cs" />
<Compile Include="BotDatabase.cs" />
<Compile Include="CardsFarmer.cs" />
@@ -189,11 +188,15 @@
copy "$(TargetDir)$(TargetName).exe" "$(SolutionDir)out\ASF.exe"
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
if [ -f "$(SolutionDir)mono_envsetup.sh" ]; then
. "$(SolutionDir)mono_envsetup.sh"
fi
mkdir -p "$(SolutionDir)out/config"
cp "$(TargetDir)config/ASF.json" "$(SolutionDir)out/config"
cp "$(TargetDir)config/example.json" "$(SolutionDir)out/config"
cp "$(TargetDir)config/minimal.json" "$(SolutionDir)out/config"
mono --llvm --server -O=all "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-Service.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
mono "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-Service.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
rm "$(SolutionDir)out/ASF-Service.exe.config"
cp "$(SolutionDir)out/ASF-Service.exe" "$(SolutionDir)out/ASF.exe"
</PostBuildEvent>

View File

@@ -166,8 +166,8 @@ namespace ArchiSteamFarm {
internal void OnDisconnected() => Ready = false;
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) {
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce)) {
Logging.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce), Bot.BotName);
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) {
Logging.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin), Bot.BotName);
return false;
}
@@ -217,19 +217,29 @@ namespace ArchiSteamFarm {
return false;
}
Logging.LogGenericInfo("Success!", Bot.BotName);
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
string steamLogin = authResult["token"].Value;
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
if (string.IsNullOrEmpty(steamLogin)) {
Logging.LogNullError(nameof(steamLogin), Bot.BotName);
return false;
}
string steamLoginSecure = authResult["tokensecure"].Value;
if (string.IsNullOrEmpty(steamLoginSecure)) {
Logging.LogNullError(nameof(steamLoginSecure), Bot.BotName);
return false;
}
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunityHost));
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunityHost));
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
Logging.LogGenericInfo("Success!", Bot.BotName);
// Unlock Steam Parental if needed
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
return false;
if (!parentalPin.Equals("0")) {
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
return false;
}
}
Ready = true;
@@ -312,22 +322,8 @@ namespace ArchiSteamFarm {
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)) {
return null;
}
Steam.ConfirmationDetails response;
try {
response = JsonConvert.DeserializeObject<Steam.ConfirmationDetails>(json);
} catch (JsonException e) {
Logging.LogGenericException(e, Bot.BotName);
return null;
}
if (response == null) {
Logging.LogNullError(nameof(response), Bot.BotName);
Steam.ConfirmationDetails response = await WebBrowser.UrlGetToJsonResultRetry<Steam.ConfirmationDetails>(request).ConfigureAwait(false);
if ((response == null) || !response.Success) {
return null;
}
@@ -335,14 +331,30 @@ namespace ArchiSteamFarm {
return response;
}
internal async Task<bool> HandleConfirmations(string deviceID, string confirmationHash, uint time, HashSet<MobileAuthenticator.Confirmation> confirmations, bool accept) {
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmations == null) || (confirmations.Count == 0)) {
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmations), Bot.BotName);
return false;
internal async Task<bool?> HandleConfirmation(string deviceID, string confirmationHash, uint time, uint confirmationID, ulong confirmationKey, bool accept) {
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0) || (confirmationKey == 0)) {
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID) + " || " + nameof(confirmationKey), Bot.BotName);
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
return null;
}
string request = SteamCommunityURL + "/mobileconf/ajaxop?op=" + (accept ? "allow" : "cancel") + "&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf&cid=" + confirmationID + "&ck=" + confirmationKey;
Steam.ConfirmationResponse response = await WebBrowser.UrlGetToJsonResultRetry<Steam.ConfirmationResponse>(request).ConfigureAwait(false);
return response?.Success;
}
internal async Task<bool?> HandleConfirmations(string deviceID, string confirmationHash, uint time, HashSet<MobileAuthenticator.Confirmation> confirmations, bool accept) {
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmations == null) || (confirmations.Count == 0)) {
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmations), Bot.BotName);
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
string request = SteamCommunityURL + "/mobileconf/multiajaxop";
@@ -362,26 +374,8 @@ namespace ArchiSteamFarm {
data.Add(new KeyValuePair<string, string>("ck[]", confirmation.Key.ToString()));
}
string json = await WebBrowser.UrlPostToContentRetry(request, data).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) {
return false;
}
Steam.ConfirmationResponse response;
try {
response = JsonConvert.DeserializeObject<Steam.ConfirmationResponse>(json);
} catch (JsonException e) {
Logging.LogGenericException(e, Bot.BotName);
return false;
}
if (response != null) {
return response.Success;
}
Logging.LogNullError(nameof(response), Bot.BotName);
return false;
Steam.ConfirmationResponse response = await WebBrowser.UrlPostToJsonResultRetry<Steam.ConfirmationResponse>(request, data).ConfigureAwait(false);
return response?.Success;
}
internal async Task<Dictionary<uint, string>> GetOwnedGames() {
@@ -652,37 +646,38 @@ namespace ArchiSteamFarm {
return result;
}
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
internal async Task AcceptTradeOffer(ulong tradeID) {
if (tradeID == 0) {
Logging.LogNullError(nameof(tradeID), Bot.BotName);
return false;
return;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
return;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError(nameof(sessionID), Bot.BotName);
return false;
return;
}
string referer = SteamCommunityURL + "/tradeoffer/" + tradeID;
string request = referer + "/accept";
Dictionary<string, string> data = new Dictionary<string, string>(3) {
{ "sessionid", sessionID },
{ "serverid", "1" },
{ "tradeofferid", tradeID.ToString() }
};
return await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
await WebBrowser.UrlPostRetry(request, data, referer).ConfigureAwait(false);
}
internal bool DeclineTradeOffer(ulong tradeID) {
internal void DeclineTradeOffer(ulong tradeID) {
if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
Logging.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
return false;
return;
}
KeyValue response = null;
@@ -702,12 +697,9 @@ namespace ArchiSteamFarm {
}
}
if (response != null) {
return true;
if (response == null) {
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
}
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable) {
@@ -717,7 +709,7 @@ namespace ArchiSteamFarm {
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=";
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamCommunityContextID + "?l=english&trading=" + (tradable ? "1" : "0") + "&start=";
uint currentPage = 0;
while (true) {
@@ -854,7 +846,7 @@ namespace ArchiSteamFarm {
itemID = 0;
}
singleTrade.ItemsToGive.Assets.Add(new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamContextID, item.AssetID, item.Amount));
singleTrade.ItemsToGive.Assets.Add(new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, item.AssetID, item.Amount));
itemID++;
}
@@ -929,24 +921,22 @@ namespace ArchiSteamFarm {
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
try {
if (DateTime.Now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
return true;
}
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.GetValueOrDefault(true)) {
LastSessionRefreshCheck = DateTime.Now;
return true;
} else {
Logging.LogGenericInfo("Refreshing our session!", Bot.BotName);
return await Bot.RefreshSession().ConfigureAwait(false);
}
} finally {
SessionSemaphore.Release();
return true;
}
bool result;
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.GetValueOrDefault(true)) {
result = true;
LastSessionRefreshCheck = DateTime.Now;
} else {
Logging.LogGenericInfo("Refreshing our session!", Bot.BotName);
result = await Bot.RefreshSession().ConfigureAwait(false);
}
SessionSemaphore.Release();
return result;
}
private async Task<bool> UnlockParentalAccount(string parentalPin) {
@@ -955,10 +945,6 @@ namespace ArchiSteamFarm {
return false;
}
if (parentalPin.Equals("0")) {
return true;
}
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
string request = SteamCommunityURL + "/parental/ajaxunlock";

File diff suppressed because it is too large Load Diff

View File

@@ -32,8 +32,21 @@ using System.Linq;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "ConvertToConstant.Local")]
[SuppressMessage("ReSharper", "ConvertToConstant.Global")]
internal sealed class BotConfig {
internal enum EFarmingOrder : byte {
Unordered,
AppIDsAscending,
AppIDsDescending,
CardDropsAscending,
CardDropsDescending,
HoursAscending,
HoursDescending,
NamesAscending,
NamesDescending
}
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool Enabled = false;
@@ -46,10 +59,6 @@ namespace ArchiSteamFarm {
[JsonProperty]
internal string SteamPassword { get; set; }
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "ConvertToConstant.Local")]
private readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
[JsonProperty]
internal string SteamParentalPIN { get; set; } = "0";
@@ -63,11 +72,14 @@ namespace ArchiSteamFarm {
internal readonly ulong SteamMasterClanID = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool CardDropsRestricted = false;
internal readonly bool CardDropsRestricted = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool DismissInventoryNotifications = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly EFarmingOrder FarmingOrder = EFarmingOrder.Unordered;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool FarmOffline = false;
@@ -113,6 +125,8 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet<uint> GamesPlayedWhileIdle = new HashSet<uint>();
[JsonProperty(Required = Required.DisallowNull)]
private readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
@@ -133,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

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,18 +34,61 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class CardsFarmer : IDisposable {
internal sealed class Game {
[JsonProperty]
internal readonly uint AppID;
[JsonProperty]
internal readonly string GameName;
[JsonProperty]
internal float HoursPlayed { get; set; }
[JsonProperty]
internal ushort CardsRemaining { get; set; }
//internal string HeaderURL => "https://steamcdn-a.akamaihd.net/steam/apps/" + AppID + "/header.jpg";
internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining) {
if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) {
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
}
AppID = appID;
GameName = gameName;
HoursPlayed = hoursPlayed;
CardsRemaining = cardsRemaining;
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
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);
private readonly Bot Bot;
private readonly Timer Timer;
private readonly Timer IdleFarmingTimer;
[JsonProperty]
internal bool ManualMode { get; private set; }
@@ -60,22 +102,25 @@ namespace ArchiSteamFarm {
Bot = bot;
if ((Timer == null) && (Program.GlobalConfig.IdleFarmingPeriod > 0)) {
Timer = new Timer(
if (Program.GlobalConfig.IdleFarmingPeriod > 0) {
IdleFarmingTimer = new Timer(
e => CheckGamesForFarming(),
null,
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(Bot.Bots.Count), // Delay
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromMinutes(0.5 * Bot.Bots.Count), // Delay
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period
);
}
}
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
CurrentGamesFarming.Dispose();
FarmResetEvent.Dispose();
GamesToFarm.Dispose();
FarmingSemaphore.Dispose();
FarmResetEvent.Dispose();
Timer?.Dispose();
// Those are objects that might be null and the check should be in-place
IdleFarmingTimer?.Dispose();
}
internal async Task SwitchToManualMode(bool manualMode) {
@@ -101,49 +146,49 @@ namespace ArchiSteamFarm {
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
if (NowFarming || ManualMode || Bot.PlayingBlocked) {
FarmingSemaphore.Release(); // We have nothing to do, don't forget to release semaphore
return;
try {
if (NowFarming || ManualMode || Bot.PlayingBlocked) {
return;
}
if (!await IsAnythingToFarm().ConfigureAwait(false)) {
Logging.LogGenericInfo("We don't have anything to farm on this account!", Bot.BotName);
await Bot.OnFarmingFinished(false).ConfigureAwait(false);
return;
}
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) {
Logging.LogGenericInfo("But account is currently occupied, so farming is stopped!", Bot.BotName);
return;
}
KeepFarming = NowFarming = true;
} finally {
FarmingSemaphore.Release();
}
if (!await IsAnythingToFarm().ConfigureAwait(false)) {
FarmingSemaphore.Release(); // We have nothing to do, don't forget to release semaphore
Logging.LogGenericInfo("We don't have anything to farm on this account!", Bot.BotName);
await Bot.OnFarmingFinished(false).ConfigureAwait(false);
return;
}
Logging.LogGenericInfo("We have a total of " + GamesToFarm.Count + " games to farm on this account...", Bot.BotName);
// This is the last moment for final check if we can farm
if (Bot.PlayingBlocked) {
Logging.LogGenericInfo("But account is currently occupied, so farming is stopped!", Bot.BotName);
FarmingSemaphore.Release(); // We have nothing to do, don't forget to release semaphore
return;
}
KeepFarming = NowFarming = true;
FarmingSemaphore.Release(); // From this point we allow other calls to shut us down
do {
// Now the algorithm used for farming depends on whether account is restricted or not
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 = GamesToFarm.Count > 1 ? new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= 2)) : new HashSet<Game>(GamesToFarm);
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 +198,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;
}
@@ -178,37 +223,42 @@ namespace ArchiSteamFarm {
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
if (!NowFarming) {
try {
if (!NowFarming) {
return;
}
Logging.LogGenericInfo("Sending signal to stop farming", Bot.BotName);
KeepFarming = false;
FarmResetEvent.Set();
Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName);
for (byte i = 0; (i < 5) && NowFarming; i++) {
await Task.Delay(1000).ConfigureAwait(false);
}
if (NowFarming) {
Logging.LogGenericWarning("Timed out!", Bot.BotName);
}
Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
Bot.OnFarmingStopped();
} finally {
FarmingSemaphore.Release();
return;
}
Logging.LogGenericInfo("Sending signal to stop farming", Bot.BotName);
KeepFarming = false;
FarmResetEvent.Set();
Logging.LogGenericInfo("Waiting for reaction...", Bot.BotName);
for (byte i = 0; (i < 5) && NowFarming; i++) {
await Task.Delay(1000).ConfigureAwait(false);
}
if (NowFarming) {
Logging.LogGenericWarning("Timed out!", Bot.BotName);
}
Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
Bot.OnFarmingStopped();
FarmingSemaphore.Release();
}
internal void OnDisconnected() => StopFarming().Forget();
internal void OnNewItemsNotification() {
if (!NowFarming) {
internal async Task OnNewItemsNotification() {
if (NowFarming) {
FarmResetEvent.Set();
return;
}
FarmResetEvent.Set();
// If we're not farming, and we got new items, it's likely to be a booster pack or likewise
// In this case, perform a loot if user wants to do so
await Bot.LootIfNeeded().ConfigureAwait(false);
}
internal async Task OnNewGameAdded() {
@@ -218,7 +268,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 +276,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 +303,11 @@ namespace ArchiSteamFarm {
}
}
GamesToFarm.Clear();
GamesToFarm.ClearAndTrim();
CheckPage(htmlDocument);
if (maxPages == 1) {
SortGamesToFarm();
return GamesToFarm.Count > 0;
}
@@ -283,9 +320,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 +368,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 +378,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 +414,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 +429,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;
}
ushort cardsRemaining;
if (!ushort.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));
}
}
@@ -374,20 +505,20 @@ namespace ArchiSteamFarm {
}
private void CheckGamesForFarming() {
if (NowFarming || ManualMode || !Bot.SteamClient.IsConnected) {
if (NowFarming || ManualMode || !Bot.IsConnectedAndLoggedOn) {
return;
}
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;
}
@@ -404,90 +535,75 @@ namespace ArchiSteamFarm {
return null;
}
byte cardsRemaining = 0;
ushort cardsRemaining = 0;
Match match = Regex.Match(progress, @"\d+");
if (match.Success) {
if (!byte.TryParse(match.Value, out cardsRemaining)) {
if (!ushort.TryParse(match.Value, out cardsRemaining)) {
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
return null;
}
}
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 +612,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 +656,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 +667,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,16 +80,6 @@ namespace ArchiSteamFarm {
}
}
public void TrimExcess() {
Lock.EnterWriteLock();
try {
HashSet.TrimExcess();
} finally {
Lock.ExitWriteLock();
}
}
public void Dispose() => Lock.Dispose();
public void CopyTo(T[] array, int arrayIndex) {
@@ -122,8 +92,66 @@ 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 bool ReplaceIfNeededWith(HashSet<T> items) {
Lock.EnterUpgradeableReadLock();
try {
if (HashSet.SetEquals(items)) {
return false;
}
ReplaceWith(items);
return true;
} finally {
Lock.ExitUpgradeableReadLock();
}
}
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

@@ -23,9 +23,7 @@
*/
using SteamKit2;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace ArchiSteamFarm {
internal static class Debugging {
@@ -37,33 +35,14 @@ namespace ArchiSteamFarm {
internal static readonly bool IsDebugBuild = false;
#endif
internal static bool NetHookAlreadyInitialized { get; set; }
internal sealed class DebugListener : IDebugListener {
private readonly object FileLock = new object();
private readonly string FilePath;
internal DebugListener(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(filePath));
}
FilePath = filePath;
}
public void WriteLine(string category, string msg) {
if (string.IsNullOrEmpty(category) && string.IsNullOrEmpty(msg)) {
Logging.LogNullError(nameof(category) + " && " + nameof(msg));
return;
}
lock (FileLock) {
try {
File.AppendAllText(FilePath, category + " | " + msg + Environment.NewLine);
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
Logging.LogGenericDebug(category + " | " + msg, nameof(DebugListener));
}
}
}

20
ArchiSteamFarm/Events.cs Normal file
View File

@@ -0,0 +1,20 @@
using System.Linq;
using System.Threading.Tasks;
using SteamKit2;
namespace ArchiSteamFarm {
internal static class Events {
internal static void OnBotShutdown() {
if (Program.ShutdownSequenceInitialized || Program.WCF.IsServerRunning() || Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
return;
}
Logging.LogGenericInfo("No bots are running, exiting");
Task.Delay(5000).Wait();
Program.Shutdown();
}
internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) {
}
}
}

View File

@@ -25,14 +25,13 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class GlobalDatabase {
internal sealed class GlobalDatabase : IDisposable {
private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings {
Converters = new List<JsonConverter> {
Converters = new List<JsonConverter>(2) {
new IPAddressConverter(),
new IPEndPointConverter()
}
@@ -56,7 +55,6 @@ namespace ArchiSteamFarm {
}
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Local")]
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
private readonly object FileLock = new object();
@@ -91,7 +89,10 @@ namespace ArchiSteamFarm {
return globalDatabase;
}
private void OnServerListUpdated(object sender, EventArgs e) => Save();
public void Dispose() {
ServerListProvider.ServerListUpdated -= OnServerListUpdated;
ServerListProvider.Dispose();
}
// This constructor is used when creating new database
private GlobalDatabase(string filePath) : this() {
@@ -104,11 +105,12 @@ namespace ArchiSteamFarm {
}
// This constructor is used only by deserializer
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private GlobalDatabase() {
ServerListProvider.ServerListUpdated += OnServerListUpdated;
}
private void OnServerListUpdated(object sender, EventArgs e) => Save();
private void Save() {
string json = JsonConvert.SerializeObject(this, CustomSerializerSettings);
if (string.IsNullOrEmpty(json)) {

View File

@@ -24,36 +24,36 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SteamKit2.Discovery;
namespace ArchiSteamFarm {
internal sealed class InMemoryServerListProvider : IServerListProvider {
internal sealed class InMemoryServerListProvider : IDisposable, IServerListProvider {
[JsonProperty(Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "FieldCanBeMadeReadOnly.Local")]
private HashSet<IPEndPoint> Servers = new HashSet<IPEndPoint>();
private readonly ConcurrentHashSet<IPEndPoint> Servers = new ConcurrentHashSet<IPEndPoint>();
internal event EventHandler ServerListUpdated = delegate { };
public Task<IEnumerable<IPEndPoint>> FetchServerListAsync() => Task.FromResult<IEnumerable<IPEndPoint>>(Servers);
public Task UpdateServerListAsync(IEnumerable<IPEndPoint> endpoints) {
if (endpoints == null) {
Logging.LogNullError(nameof(endpoints));
public Task UpdateServerListAsync(IEnumerable<IPEndPoint> endPoints) {
if (endPoints == null) {
Logging.LogNullError(nameof(endPoints));
return Task.Delay(0);
}
Servers.Clear();
foreach (IPEndPoint endpoint in endpoints) {
Servers.Add(endpoint);
HashSet<IPEndPoint> newServers = new HashSet<IPEndPoint>(endPoints);
if (!Servers.ReplaceIfNeededWith(newServers)) {
return Task.Delay(0);
}
ServerListUpdated(this, EventArgs.Empty);
return Task.Delay(0);
}
public void Dispose() => Servers.Dispose();
}
}

View File

@@ -29,7 +29,6 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm.JSON {
internal static class GitHub {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ReleaseResponse {
#pragma warning disable 649
internal sealed class Asset {

View File

@@ -35,7 +35,7 @@ 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 const ushort SteamAppID = 753;
internal const byte SteamContextID = 6;
internal const byte SteamCommunityContextID = 6;
internal enum EType : byte {
Unknown,
@@ -269,9 +269,9 @@ namespace ArchiSteamFarm.JSON {
State = state;
}
internal bool IsSteamCardsOnlyTradeForUs() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamContextID) && (item.Type == Item.EType.TradingCard));
internal bool IsSteamCardsRequest() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamCommunityContextID) && (item.Type == Item.EType.TradingCard));
internal bool IsPotentiallyDupesTradeForUs() {
internal bool IsFairTypesExchange() {
Dictionary<uint, Dictionary<Item.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>();
foreach (Item item in ItemsToGive) {
Dictionary<Item.EType, uint> itemsPerType;
@@ -343,7 +343,6 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationResponse { // Deserialized from JSON
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
@@ -355,7 +354,6 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationDetails { // Deserialized from JSON
internal enum EType : byte {
Unknown,

View File

@@ -23,7 +23,6 @@
*/
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -35,18 +34,17 @@ using NLog.Targets;
namespace ArchiSteamFarm {
internal static class Logging {
private const string LayoutMessage = @"${message}${onexception:inner= ${exception:format=toString,Data}}";
private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${level:uppercase=true}|" + LayoutMessage;
private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${processname}-${processid}|${level:uppercase=true}|" + LayoutMessage;
private const string EventLogLayout = LayoutMessage;
private static readonly ConcurrentHashSet<LoggingRule> ConsoleLoggingRules = new ConcurrentHashSet<LoggingRule>();
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static bool IsUsingCustomConfiguration, IsWaitingForUserInput;
private static bool IsWaitingForUserInput;
internal static void InitCoreLoggers() {
internal static void InitLoggers() {
if (LogManager.Configuration != null) {
// User provided custom NLog config, or we have it set already, so don't override it
IsUsingCustomConfiguration = true;
InitConsoleLoggers();
LogManager.ConfigurationChanged += OnConfigurationChanged;
return;
@@ -54,21 +52,12 @@ namespace ArchiSteamFarm {
LoggingConfiguration config = new LoggingConfiguration();
ColoredConsoleTarget consoleTarget = new ColoredConsoleTarget("Console") {
ColoredConsoleTarget coloredConsoleTarget = new ColoredConsoleTarget("ColoredConsole") {
Layout = GeneralLayout
};
config.AddTarget(consoleTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, consoleTarget));
LogManager.Configuration = config;
InitConsoleLoggers();
}
internal static void InitEnhancedLoggers() {
if (IsUsingCustomConfiguration) {
return;
}
config.AddTarget(coloredConsoleTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, coloredConsoleTarget));
if (Program.IsRunningAsService) {
EventLogTarget eventLogTarget = new EventLogTarget("EventLog") {
@@ -77,21 +66,21 @@ namespace ArchiSteamFarm {
Source = SharedInfo.EventLogSource
};
LogManager.Configuration.AddTarget(eventLogTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, eventLogTarget));
} else {
config.AddTarget(eventLogTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, eventLogTarget));
} else if (Program.Mode != Program.EMode.Client) {
FileTarget fileTarget = new FileTarget("File") {
DeleteOldFileOnStartup = true,
FileName = Program.LogFile,
FileName = SharedInfo.LogFile,
Layout = GeneralLayout
};
LogManager.Configuration.AddTarget(fileTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget));
config.AddTarget(fileTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget));
}
LogManager.ReconfigExistingLoggers();
LogGenericInfo("Logging module initialized!");
LogManager.Configuration = config;
InitConsoleLoggers();
}
internal static void OnUserInputStart() {
@@ -122,7 +111,7 @@ namespace ArchiSteamFarm {
LogManager.ReconfigExistingLoggers();
}
internal static void LogGenericError(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
internal static void LogGenericError(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
@@ -131,7 +120,7 @@ namespace ArchiSteamFarm {
Logger.Error($"{botName}|{previousMethodName}() {message}");
}
internal static void LogGenericException(Exception exception, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
internal static void LogGenericException(Exception exception, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception), botName);
return;
@@ -140,7 +129,7 @@ namespace ArchiSteamFarm {
Logger.Error(exception, $"{botName}|{previousMethodName}()");
}
internal static void LogFatalException(Exception exception, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
internal static void LogFatalException(Exception exception, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception), botName);
return;
@@ -154,10 +143,10 @@ namespace ArchiSteamFarm {
}
// 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);
File.WriteAllText(SharedInfo.LogFile, DateTime.Now + " ASF V" + SharedInfo.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);
File.AppendAllText(SharedInfo.LogFile, "[!] EXCEPTION: " + previousMethodName + "() " + exception.Message + Environment.NewLine + "StackTrace:" + Environment.NewLine + exception.StackTrace);
if (exception.InnerException != null) {
exception = exception.InnerException;
continue;
@@ -167,7 +156,7 @@ namespace ArchiSteamFarm {
}
}
internal static void LogGenericWarning(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
internal static void LogGenericWarning(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
@@ -176,7 +165,7 @@ namespace ArchiSteamFarm {
Logger.Warn($"{botName}|{previousMethodName}() {message}");
}
internal static void LogGenericInfo(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
internal static void LogGenericInfo(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
@@ -186,7 +175,7 @@ namespace ArchiSteamFarm {
}
[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
internal static void LogNullError(string nullObjectName, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
internal static void LogNullError(string nullObjectName, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(nullObjectName)) {
return;
}
@@ -194,9 +183,8 @@ namespace ArchiSteamFarm {
LogGenericError(nullObjectName + " is null!", botName, previousMethodName);
}
[Conditional("DEBUG")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal static void LogGenericDebug(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
internal static void LogGenericDebug(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
@@ -207,7 +195,7 @@ namespace ArchiSteamFarm {
private static void InitConsoleLoggers() {
ConsoleLoggingRules.Clear();
foreach (LoggingRule loggingRule in from loggingRule in LogManager.Configuration.LoggingRules from target in loggingRule.Targets where target is ColoredConsoleTarget || target is ConsoleTarget select loggingRule) {
foreach (LoggingRule loggingRule in LogManager.Configuration.LoggingRules.Where(loggingRule => loggingRule.Targets.Any(target => target is ColoredConsoleTarget || target is ConsoleTarget))) {
ConsoleLoggingRules.Add(loggingRule);
}
}

View File

@@ -36,7 +36,7 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class MobileAuthenticator {
internal sealed class MobileAuthenticator : IDisposable {
internal sealed class Confirmation {
internal readonly uint ID;
internal readonly ulong Key;
@@ -64,9 +64,9 @@ namespace ArchiSteamFarm {
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
private static short SteamTimeDifference;
private static short? SteamTimeDifference;
internal bool HasDeviceID => !string.IsNullOrEmpty(DeviceID);
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1);
#pragma warning disable 649
[JsonProperty(PropertyName = "shared_secret", Required = Required.Always)]
@@ -81,9 +81,9 @@ namespace ArchiSteamFarm {
private Bot Bot;
private MobileAuthenticator() {
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); // "ERROR" is being used by SteamDesktopAuthenticator
}
private MobileAuthenticator() { }
internal void Init(Bot bot) {
if (bot == null) {
@@ -108,19 +108,50 @@ namespace ArchiSteamFarm {
return false;
}
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
if (!HasCorrectDeviceID) {
Logging.LogGenericWarning("Can't execute properly due to invalid DeviceID!", Bot.BotName);
return false;
}
string confirmationHash = GenerateConfirmationKey(time, "conf");
if (!string.IsNullOrEmpty(confirmationHash)) {
return await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
}
await ConfirmationsSemaphore.WaitAsync().ConfigureAwait(false);
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
return false;
try {
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
return false;
}
string confirmationHash = GenerateConfirmationKey(time, "conf");
if (string.IsNullOrEmpty(confirmationHash)) {
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
return false;
}
bool? result = await Bot.ArchiWebHandler.HandleConfirmations(DeviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
if (!result.HasValue) { // Request timed out
return false;
}
if (result.Value) { // Request succeeded
return true;
}
// Our multi request failed, this is almost always Steam fuckup that happens randomly
// In this case, we'll accept all pending confirmations one-by-one, synchronously (as Steam can't handle them in parallel)
// We totally ignore actual result returned by those calls, abort only if request timed out
foreach (Confirmation confirmation in confirmations) {
bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
if (!confirmationResult.HasValue) {
return false;
}
}
return true;
} finally {
ConfirmationsSemaphore.Release();
}
}
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(Confirmation confirmation) {
@@ -129,6 +160,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);
@@ -160,6 +196,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);
@@ -228,22 +269,25 @@ namespace ArchiSteamFarm {
return result;
}
internal async Task<uint> GetSteamTime() {
if (SteamTimeDifference != 0) {
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference);
private async Task<uint> GetSteamTime() {
if (SteamTimeDifference.HasValue) {
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault());
}
await TimeSemaphore.WaitAsync().ConfigureAwait(false);
if (SteamTimeDifference == 0) {
uint serverTime = Bot.ArchiWebHandler.GetServerTime();
if (serverTime != 0) {
SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime());
try {
if (!SteamTimeDifference.HasValue) {
uint serverTime = Bot.ArchiWebHandler.GetServerTime();
if (serverTime != 0) {
SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime());
}
}
} finally {
TimeSemaphore.Release();
}
TimeSemaphore.Release();
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference);
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.GetValueOrDefault());
}
private string GenerateTokenForTime(uint time) {
@@ -319,7 +363,9 @@ namespace ArchiSteamFarm {
hash = hmac.ComputeHash(buffer);
}
return Convert.ToBase64String(hash, Base64FormattingOptions.None);
return Convert.ToBase64String(hash);
}
public void Dispose() => ConfirmationsSemaphore.Dispose();
}
}

View File

@@ -22,240 +22,35 @@
*/
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal static class Program {
internal enum EUserInputType : byte {
Unknown,
DeviceID,
Login,
Password,
PhoneNumber,
SMS,
SteamGuard,
SteamParentalPIN,
RevocationCode,
TwoFactorAuthentication,
WCFHostname
}
private enum EMode : byte {
[SuppressMessage("ReSharper", "UnusedMember.Local")]
Unknown,
internal enum EMode : byte {
Normal, // Standard most common usage
Client, // WCF client only
Server // Normal + WCF server
}
internal const string ASF = "ASF";
internal const string ConfigDirectory = "config";
internal const string DebugDirectory = "debug";
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";
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
internal static readonly WCF WCF = new WCF();
private static readonly object ConsoleLock = new object();
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
private static readonly WCF WCF = new WCF();
internal static bool IsRunningAsService { get; private set; }
internal static bool ShutdownSequenceInitialized { get; private set; }
internal static EMode Mode { get; private set; } = EMode.Normal;
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
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 exeFile = Assembly.GetEntryAssembly().Location;
string oldExeFile = exeFile + ".old";
// We booted successfully so we can now remove old exe file
if (File.Exists(oldExeFile)) {
// It's entirely possible that old process is still running, allow at least a second before trying to remove the file
await Task.Delay(1000).ConfigureAwait(false);
try {
File.Delete(oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
Logging.LogGenericError("Could not remove old ASF binary, please remove " + oldExeFile + " manually in order for update function to work!");
}
}
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Unknown) {
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";
}
Logging.LogGenericInfo("Checking new version...");
string response = await WebBrowser.UrlGetToContentRetry(releaseURL).ConfigureAwait(false);
if (string.IsNullOrEmpty(response)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
GitHub.ReleaseResponse releaseResponse;
if (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
try {
releaseResponse = JsonConvert.DeserializeObject<GitHub.ReleaseResponse>(response);
} catch (JsonException e) {
Logging.LogGenericException(e);
return;
}
} else {
List<GitHub.ReleaseResponse> releases;
try {
releases = JsonConvert.DeserializeObject<List<GitHub.ReleaseResponse>>(response);
} catch (JsonException e) {
Logging.LogGenericException(e);
return;
}
if ((releases == null) || (releases.Count == 0)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
releaseResponse = releases[0];
}
if (string.IsNullOrEmpty(releaseResponse.Tag)) {
Logging.LogGenericWarning("Could not check latest version!");
return;
}
Version newVersion = new Version(releaseResponse.Tag);
Logging.LogGenericInfo("Local version: " + Version + " | Remote version: " + newVersion);
if (Version.CompareTo(newVersion) >= 0) { // If local version is the same or newer than remote version
return;
}
if (!updateOverride && !GlobalConfig.AutoUpdates) {
Logging.LogGenericInfo("New version is available!");
Logging.LogGenericInfo("Consider updating yourself!");
await Task.Delay(5000).ConfigureAwait(false);
return;
}
if (File.Exists(oldExeFile)) {
Logging.LogGenericWarning("Refusing to proceed with auto update as old " + oldExeFile + " binary could not be removed, please remove it manually");
return;
}
// Auto update logic starts here
if (releaseResponse.Assets == null) {
Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
return;
}
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!");
return;
}
if (string.IsNullOrEmpty(binaryAsset.DownloadURL)) {
Logging.LogGenericWarning("Could not proceed with update because download URL is empty!");
return;
}
Logging.LogGenericInfo("Downloading new version...");
Logging.LogGenericInfo("While waiting, consider donating if you appreciate the work being done :)");
byte[] result = await WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
if (result == null) {
return;
}
string newExeFile = exeFile + ".new";
// Firstly we create new exec
try {
File.WriteAllBytes(newExeFile, result);
} catch (Exception e) {
Logging.LogGenericException(e);
return;
}
// Now we move current -> old
try {
File.Move(exeFile, oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Delete(newExeFile);
} catch {
// Ignored
}
return;
}
// Now we move new -> current
try {
File.Move(newExeFile, exeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
try {
// Cleanup
File.Move(oldExeFile, exeFile);
File.Delete(newExeFile);
} catch {
// Ignored
}
return;
}
Logging.LogGenericInfo("Update process finished!");
if (GlobalConfig.AutoRestart) {
Logging.LogGenericInfo("Restarting...");
await Task.Delay(5000).ConfigureAwait(false);
Restart();
} else {
Logging.LogGenericInfo("Exiting...");
await Task.Delay(5000).ConfigureAwait(false);
Exit();
}
}
internal static WebBrowser WebBrowser { get; private set; }
internal static void Exit(byte exitCode = 0) {
Shutdown();
@@ -274,8 +69,8 @@ namespace ArchiSteamFarm {
Environment.Exit(0);
}
internal static string GetUserInput(EUserInputType userInputType, string botName = ASF, string extraInformation = null) {
if (userInputType == EUserInputType.Unknown) {
internal static string GetUserInput(SharedInfo.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) {
if (userInputType == SharedInfo.EUserInputType.Unknown) {
return null;
}
@@ -288,35 +83,35 @@ namespace ArchiSteamFarm {
lock (ConsoleLock) {
Logging.OnUserInputStart();
switch (userInputType) {
case EUserInputType.DeviceID:
case SharedInfo.EUserInputType.DeviceID:
Console.Write("<" + botName + "> Please enter your Device ID (including \"android:\"): ");
break;
case EUserInputType.Login:
case SharedInfo.EUserInputType.Login:
Console.Write("<" + botName + "> Please enter your login: ");
break;
case EUserInputType.Password:
case SharedInfo.EUserInputType.Password:
Console.Write("<" + botName + "> Please enter your password: ");
break;
case EUserInputType.PhoneNumber:
case SharedInfo.EUserInputType.PhoneNumber:
Console.Write("<" + botName + "> Please enter your full phone number (e.g. +1234567890): ");
break;
case EUserInputType.SMS:
case SharedInfo.EUserInputType.SMS:
Console.Write("<" + botName + "> Please enter SMS code sent on your mobile: ");
break;
case EUserInputType.SteamGuard:
case SharedInfo.EUserInputType.SteamGuard:
Console.Write("<" + botName + "> Please enter the auth code sent to your email: ");
break;
case EUserInputType.SteamParentalPIN:
case SharedInfo.EUserInputType.SteamParentalPIN:
Console.Write("<" + botName + "> Please enter steam parental PIN: ");
break;
case EUserInputType.RevocationCode:
case SharedInfo.EUserInputType.RevocationCode:
Console.WriteLine("<" + botName + "> PLEASE WRITE DOWN YOUR REVOCATION CODE: " + extraInformation);
Console.Write("<" + botName + "> Hit enter once ready...");
break;
case EUserInputType.TwoFactorAuthentication:
case SharedInfo.EUserInputType.TwoFactorAuthentication:
Console.Write("<" + botName + "> Please enter your 2 factor auth code from your authenticator app: ");
break;
case EUserInputType.WCFHostname:
case SharedInfo.EUserInputType.WCFHostname:
Console.Write("<" + botName + "> Please enter your WCF hostname: ");
break;
default:
@@ -336,25 +131,7 @@ namespace ArchiSteamFarm {
return !string.IsNullOrEmpty(result) ? result.Trim() : null;
}
internal static void OnBotShutdown() {
if (ShutdownSequenceInitialized) {
return;
}
if (Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
return;
}
if (WCF.IsServerRunning()) {
return;
}
Logging.LogGenericInfo("No bots are running, exiting");
Thread.Sleep(5000);
ShutdownResetEvent.Set();
}
private static void Shutdown() {
internal static void Shutdown() {
if (!InitShutdownSequence()) {
return;
}
@@ -378,16 +155,20 @@ namespace ArchiSteamFarm {
}
private static void InitServices() {
GlobalConfig = GlobalConfig.Load(Path.Combine(ConfigDirectory, GlobalConfigFile));
string globalConfigFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.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(SharedInfo.ConfigDirectory, SharedInfo.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);
}
@@ -396,7 +177,7 @@ namespace ArchiSteamFarm {
WebBrowser.Init();
WCF.Init();
WebBrowser = new WebBrowser(ASF);
WebBrowser = new WebBrowser(SharedInfo.ASF);
}
private static void ParsePreInitArgs(IEnumerable<string> args) {
@@ -409,6 +190,12 @@ namespace ArchiSteamFarm {
switch (arg) {
case "":
break;
case "--client":
Mode = EMode.Client;
break;
case "--server":
Mode = EMode.Server;
break;
default:
if (arg.StartsWith("--", StringComparison.Ordinal)) {
if (arg.StartsWith("--path=", StringComparison.Ordinal) && (arg.Length > 7)) {
@@ -481,9 +268,6 @@ namespace ArchiSteamFarm {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
Logging.InitCoreLoggers();
Logging.LogGenericInfo("ASF V" + Version);
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (!string.IsNullOrEmpty(homeDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
@@ -494,13 +278,13 @@ namespace ArchiSteamFarm {
// 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)) {
if (Directory.Exists(SharedInfo.ConfigDirectory)) {
break;
}
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
}
}
@@ -511,17 +295,26 @@ namespace ArchiSteamFarm {
ParsePreInitArgs(args);
}
Logging.InitLoggers();
Logging.LogGenericInfo("ASF V" + SharedInfo.Version);
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);
}
InitServices();
// If debugging is on, we prepare debug directory prior to running
if (GlobalConfig.Debug) {
if (Directory.Exists(DebugDirectory)) {
Directory.Delete(DebugDirectory, true);
if (Directory.Exists(SharedInfo.DebugDirectory)) {
Directory.Delete(SharedInfo.DebugDirectory, true);
Thread.Sleep(1000); // Dirty workaround giving Windows some time to sync
}
Directory.CreateDirectory(DebugDirectory);
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener(Path.Combine(DebugDirectory, "debug.txt")));
Directory.CreateDirectory(SharedInfo.DebugDirectory);
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener());
SteamKit2.DebugLog.Enabled = true;
}
@@ -536,24 +329,22 @@ namespace ArchiSteamFarm {
}
// From now on it's server mode
Logging.InitEnhancedLoggers();
if (!Directory.Exists(ConfigDirectory)) {
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
Logging.LogGenericError("Config directory doesn't exist!");
Thread.Sleep(5000);
Exit(1);
}
CheckForUpdate().Wait();
ASF.CheckForUpdate().Wait();
// Before attempting to connect, initialize our list of CMs
Bot.InitializeCMs(GlobalDatabase.CellID, GlobalDatabase.ServerListProvider);
bool isRunning = false;
foreach (string botName in Directory.EnumerateFiles(ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
switch (botName) {
case ASF:
case SharedInfo.ASF:
case "example":
case "minimal":
continue;
@@ -571,7 +362,7 @@ namespace ArchiSteamFarm {
// Check if we got any bots running
if (!isRunning) {
OnBotShutdown();
Events.OnBotShutdown();
}
}

View File

@@ -5,11 +5,11 @@ using ArchiSteamFarm;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ArchiSteamFarm")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyTitle(SharedInfo.ServiceName)]
[assembly: AssemblyDescription(SharedInfo.ServiceDescription)]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ArchiSteamFarm")]
[assembly: AssemblyProduct(SharedInfo.ServiceName)]
[assembly: AssemblyCopyright(SharedInfo.Copyright)]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@ using ArchiSteamFarm;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion(SharedInfo.Version)]
[assembly: AssemblyFileVersion(SharedInfo.Version)]
[assembly: AssemblyVersion(SharedInfo.VersionNumber)]
[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)]

View File

@@ -24,6 +24,7 @@
using System;
using System.Reflection;
using Microsoft.Win32;
namespace ArchiSteamFarm {
internal static class Runtime {
@@ -54,6 +55,53 @@ namespace ArchiSteamFarm {
}
}
private static bool? _IsRuntimeSupported;
internal static bool IsRuntimeSupported {
get {
if (_IsRuntimeSupported.HasValue) {
return _IsRuntimeSupported.Value;
}
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);
_IsRuntimeSupported = true;
return true;
}
Logging.LogGenericWarning("Your Mono version is too old. Required: " + minMonoVersion + " | Found: " + monoVersion);
_IsRuntimeSupported = false;
return false;
}
Version netVersion = GetNetVersion();
if (netVersion == null) {
Logging.LogNullError(nameof(netVersion));
return false;
}
Version minNetVersion = new Version(4, 6, 1);
if (netVersion >= minNetVersion) {
Logging.LogGenericInfo("Your .NET version is OK. Required: " + minNetVersion + " | Found: " + netVersion);
_IsRuntimeSupported = true;
return true;
}
Logging.LogGenericWarning("Your .NET version is too old. Required: " + minNetVersion + " | Found: " + netVersion);
_IsRuntimeSupported = false;
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
@@ -79,6 +127,49 @@ namespace ArchiSteamFarm {
}
}
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() {
if (MonoRuntime == null) {
Logging.LogNullError(nameof(MonoRuntime));

View File

@@ -22,9 +22,26 @@
*/
using System;
using System.Reflection;
namespace ArchiSteamFarm {
internal static class SharedInfo {
internal const string Version = "2.1.3.2";
internal enum EUserInputType : byte {
Unknown,
DeviceID,
Login,
Password,
PhoneNumber,
SMS,
SteamGuard,
SteamParentalPIN,
RevocationCode,
TwoFactorAuthentication,
WCFHostname
}
internal const string VersionNumber = "2.1.5.2";
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
@@ -34,5 +51,20 @@ namespace ArchiSteamFarm {
internal const string EventLog = ServiceName;
internal const string EventLogSource = EventLog + "Logger";
internal const string ASF = "ASF";
internal const string ASFDirectory = "ArchiSteamFarm";
internal const string ConfigDirectory = "config";
internal const string DebugDirectory = "debug";
internal const string LogFile = "log.txt";
internal const ulong ArchiSteamID = 76561198006963719;
internal const ulong ASFGroupSteamID = 103582791440160998;
internal const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
internal const string GlobalConfigFileName = ASF + ".json";
internal const string GlobalDatabaseFileName = ASF + ".db";
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
}
}

View File

@@ -129,6 +129,11 @@ namespace ArchiSteamFarm {
await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, acceptedTradeIDs).ConfigureAwait(false);
}
if (results.Any(result => (result != null) && ((result.Result == ParseTradeResult.EResult.AcceptedWithItemLose) || (result.Result == ParseTradeResult.EResult.AcceptedWithoutItemLose)))) {
// If we finished a trade, perform a loot if user wants to do so
await Bot.LootIfNeeded().ConfigureAwait(false);
}
}
private async Task<ParseTradeResult> ParseTrade(Steam.TradeOffer tradeOffer) {
@@ -152,23 +157,25 @@ namespace ArchiSteamFarm {
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 : null;
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
break;
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 : null;
Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID);
break;
}
IgnoredTrades.Add(tradeOffer.TradeOfferID);
}
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return result;
default:
return result;
break;
}
return result;
}
private async Task<ParseTradeResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
@@ -199,7 +206,7 @@ namespace ArchiSteamFarm {
}
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade
if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) {
if (!tradeOffer.IsSteamCardsRequest() || !tradeOffer.IsFairTypesExchange()) {
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}

View File

@@ -26,10 +26,12 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal static class Utilities {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
internal static void Forget(this Task task) { }

View File

@@ -31,6 +31,9 @@ using System.ServiceModel.Description;
namespace ArchiSteamFarm {
[ServiceContract]
internal interface IWCF {
[OperationContract]
string GetStatus();
[OperationContract]
string HandleCommand(string input);
}
@@ -44,7 +47,7 @@ namespace ArchiSteamFarm {
internal static void Init() {
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
Program.GlobalConfig.WCFHostname = Program.GetUserInput(Program.EUserInputType.WCFHostname);
Program.GlobalConfig.WCFHostname = Program.GetUserInput(SharedInfo.EUserInputType.WCFHostname);
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
return;
}
@@ -59,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
@@ -75,8 +78,10 @@ namespace ArchiSteamFarm {
return output;
}
public string GetStatus() => Program.GlobalConfig.SteamOwnerID == 0 ? "{}" : Bot.GetAPIStatus();
public void Dispose() {
Client?.Close();
StopClient();
StopServer();
}
@@ -136,12 +141,24 @@ namespace ArchiSteamFarm {
return Client.HandleCommand(input);
}
private void StopClient() {
if (Client == null) {
return;
}
if (Client.State != CommunicationState.Closed) {
Client.Close();
}
Client = null;
}
}
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

@@ -60,7 +60,7 @@ namespace ArchiSteamFarm {
// 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) {
if (Runtime.IsRuntimeSupported && !Runtime.IsRunningOnMono) {
InitNonMonoBehaviour();
}
#endif
@@ -87,7 +87,7 @@ namespace ArchiSteamFarm {
};
// Most web services expect that UserAgent is set, so we declare it globally
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ArchiSteamFarm/" + Program.Version);
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ArchiSteamFarm/" + SharedInfo.Version);
}
internal async Task<bool> UrlHeadRetry(string request, string referer = null) {
@@ -147,25 +147,6 @@ namespace ArchiSteamFarm {
return null;
}
internal async Task<string> UrlGetToContentRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request), Identifier);
return null;
}
string result = null;
for (byte i = 0; (i < MaxRetries) && string.IsNullOrEmpty(result); i++) {
result = await UrlGetToContent(request, referer).ConfigureAwait(false);
}
if (!string.IsNullOrEmpty(result)) {
return result;
}
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return null;
}
internal async Task<HtmlDocument> UrlGetToHtmlDocumentRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request), Identifier);
@@ -204,6 +185,25 @@ namespace ArchiSteamFarm {
return null;
}
internal async Task<T> UrlGetToJsonResultRetry<T>(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request), Identifier);
return default(T);
}
string json = await UrlGetToContentRetry(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) {
return default(T);
}
try {
return JsonConvert.DeserializeObject<T>(json);
} catch (JsonException e) {
Logging.LogGenericException(e, Identifier);
return default(T);
}
}
internal async Task<XmlDocument> UrlGetToXMLRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request), Identifier);
@@ -242,23 +242,23 @@ namespace ArchiSteamFarm {
return false;
}
internal async Task<string> UrlPostToContentRetry(string request, ICollection<KeyValuePair<string, string>> data = null, string referer = null) {
internal async Task<T> UrlPostToJsonResultRetry<T>(string request, ICollection<KeyValuePair<string, string>> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request), Identifier);
return null;
return default(T);
}
string result = null;
for (byte i = 0; (i < MaxRetries) && string.IsNullOrEmpty(result); i++) {
result = await UrlPostToContent(request, data, referer).ConfigureAwait(false);
string json = await UrlPostToContentRetry(request, data, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) {
return default(T);
}
if (!string.IsNullOrEmpty(result)) {
return result;
try {
return JsonConvert.DeserializeObject<T>(json);
} catch (JsonException e) {
Logging.LogGenericException(e, Identifier);
return default(T);
}
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return null;
}
private async Task<byte[]> UrlGetToBytes(string request, string referer = null) {
@@ -291,6 +291,25 @@ namespace ArchiSteamFarm {
}
}
private async Task<string> UrlGetToContentRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request), Identifier);
return null;
}
string result = null;
for (byte i = 0; (i < MaxRetries) && string.IsNullOrEmpty(result); i++) {
result = await UrlGetToContent(request, referer).ConfigureAwait(false);
}
if (!string.IsNullOrEmpty(result)) {
return result;
}
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return null;
}
private async Task<HtmlDocument> UrlGetToHtmlDocument(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request), Identifier);
@@ -419,6 +438,25 @@ namespace ArchiSteamFarm {
}
}
private async Task<string> UrlPostToContentRetry(string request, ICollection<KeyValuePair<string, string>> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request), Identifier);
return null;
}
string result = null;
for (byte i = 0; (i < MaxRetries) && string.IsNullOrEmpty(result); i++) {
result = await UrlPostToContent(request, data, referer).ConfigureAwait(false);
}
if (!string.IsNullOrEmpty(result)) {
return result;
}
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return null;
}
private async Task<HttpResponseMessage> UrlPostToResponse(string request, IEnumerable<KeyValuePair<string, string>> data = null, string referer = null) {
if (!string.IsNullOrEmpty(request)) {
return await UrlRequest(request, HttpMethod.Post, data, referer).ConfigureAwait(false);

View File

@@ -8,8 +8,9 @@
"SteamApiKey": null,
"SteamMasterID": 0,
"SteamMasterClanID": 0,
"CardDropsRestricted": false,
"CardDropsRestricted": true,
"DismissInventoryNotifications": true,
"FarmingOrder": 0,
"FarmOffline": false,
"HandleOfflineMessages": false,
"AcceptGifts": false,

View File

@@ -1,16 +1,20 @@
# Contributing
Before making an issue or pull request, you should carefully read **[ASF wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki)** first.
Before making an issue or pull request, you should carefully read **[ASF wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki)** first. At least reading **[FAQ](https://github.com/JustArchi/ArchiSteamFarm/wiki/FAQ)** is mandatory.
## Issues
GitHub **[issues](https://github.com/JustArchi/ArchiSteamFarm/issues)** page is being used for ASF TODO list, regarding both features and bugs. It has rather strict policy - GitHub is not technical support and all cases that are not suggestions or bug reports should NOT be posted there. You have **[ASF chat](https://gitter.im/JustArchi/ArchiSteamFarm)** and **[Steam group](http://steamcommunity.com/groups/ascfarm/discussions/1/)** for general discussion, questions or technical issues. Please avoid using GitHub issues, unless you indeed want to report a bug or suggest an enhancement. Even prior to doing that, please make sure that you're indeed dealing with a bug, or your suggestion makes sense, preferably by asking on chat/steam group first. Invalid issues will be closed immediately.
GitHub **[issues](https://github.com/JustArchi/ArchiSteamFarm/issues)** page is being used for ASF TODO list, regarding both features and bugs. It has rather strict policy - GitHub is NOT technical support and all cases that are not suggestions neither bug reports should NOT be posted there. You have **[ASF chat](https://gitter.im/JustArchi/ArchiSteamFarm)** and **[Steam group](http://steamcommunity.com/groups/ascfarm/discussions/1/)** for general discussion, questions or technical issues. Please avoid using GitHub issues, unless you indeed want to report a bug or suggest an enhancement. Even prior to doing that, please make sure that you're indeed dealing with a bug, or your suggestion makes sense, preferably by asking on chat/steam group first. Invalid issues will be closed immediately and won't be answered.
---
### Bugs
Posting a log is **mandatory**, regardless if it contains information that is relevant or not. You're allowed to make small modifications such as changing bot names to something more generic, but you should not be doing anything else. You want us to fix the bug you've encountered, then help us instead of making it harder - we're not being paid for that, and we're not forced to fix the bug you've encountered. Include as much relevant info as possible - if bug is reproducable, when it happens, if it's a result of a command - which one, does it happen always or only sometimes, with one account or all of them - everything you consider appropriate, that would help us reproduce the bug and fix it. The more information you include, the higher the chance of bug getting fixed. And this is probably what you want, right?
Before reporting a bug you should carefully check if the "bug" you're encountering is in fact ASF bug and not technical issue that is answered in the **[FAQ](https://github.com/JustArchi/ArchiSteamFarm/wiki/FAQ#issues)** or in other place on the wiki. Typically technical issue is intentional ASF behaviour which might not match your expectations, e.g. failing to send or accept steam trades - logic for accepting and sending steam trades is outside of the ASF, as stated in the FAQ, and there is no bug related to that because it's up to Steam to accept such request, or not. If you're not sure if you're encountering ASF bug or technical issue, please use **[ASF chat](https://gitter.im/JustArchi/ArchiSteamFarm)** or **[Steam group](http://steamcommunity.com/groups/ascfarm/discussions/1/)** and avoid GitHub issues.
Regarding ASF bugs - Posting a log is **mandatory**, regardless if it contains information that is relevant or not. You're allowed to make small modifications such as changing bot names to something more generic, but you should not be doing anything else. You want us to fix the bug you've encountered, then help us instead of making it harder - we're not being paid for that, and we're not forced to fix the bug you've encountered. Include as much relevant info as possible - if bug is reproducable, when it happens, if it's a result of a command - which one, does it happen always or only sometimes, with one account or all of them - everything you consider appropriate, that would help us reproduce the bug and fix it. The more information you include, the higher the chance of bug getting fixed. If nobody is able to reproduce your bug, there is also no way of blindly fixing it.
It would also be cool if you could reproduce your issue on latest pre-release (and not stable) version, as this is most recent codebase that might include not-yet-released fix for your issue already. Of course, that is not mandatory, as ASF offers support for both latest pre-release as well as latest stable versions.
---
@@ -28,3 +32,22 @@ In general any pull request is welcome and should be accepted, unless there is a
Every pull request is carefully examined by our continuous integration system - it won't be accepted if it doesn't compile properly or causes any test to fail. We also expect that you at least barely tested the modification you're trying to add, and not blindly editing the file without even checking if it compiles. Consider the fact that you're not coding it only for yourself, but for thousands of users.
---
### License
ASF is using **[Apache License 2.0](https://github.com/JustArchi/ArchiSteamFarm/blob/master/LICENSE-2.0.txt)**.
> Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions.
The license also permits you to:
> You may add Your own copyright statement to Your modifications(...)
Adding your own copyright statement is totally fine and it should be in format of **[copyright and contact](https://github.com/JustArchi/ArchiSteamFarm/blob/master/ArchiSteamFarm/Program.cs#L8-L9)**, specified below all currently existing statements of the file you're modifying. Adding such statement is not mandatory when sending PRs, and it's up to you to decide if you want to put one, or not.
---
### Code style
Please stick with ASF code style when submitting PRs. In repo you can find standard **[VS settings](https://github.com/JustArchi/ArchiSteamFarm/blob/master/CodeStyle.vssettings)** file that you can use in Visual Studio for import. In addition to that, there is also **[DotSettings](https://github.com/JustArchi/ArchiSteamFarm/blob/master/ArchiSteamFarm.sln.DotSettings)** file for **[ReSharper](https://www.jetbrains.com/resharper/)** (optional). Consistency is the key.

38
CodeStyle.vssettings Normal file

File diff suppressed because one or more lines are too long

View File

@@ -26,6 +26,7 @@ using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using ArchiSteamFarm;
namespace ConfigGenerator {
internal abstract class ASFConfig {
@@ -60,7 +61,7 @@ namespace ConfigGenerator {
internal void Remove() {
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FileLock) {
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
foreach (string botFile in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, queryPath + ".*")) {
try {
File.Delete(botFile);
} catch (Exception e) {
@@ -80,15 +81,15 @@ namespace ConfigGenerator {
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FileLock) {
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
foreach (string botFile in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, queryPath + ".*")) {
try {
File.Move(botFile, Path.Combine(Program.ConfigDirectory, botName + Path.GetExtension(botFile)));
File.Move(botFile, Path.Combine(SharedInfo.ConfigDirectory, botName + Path.GetExtension(botFile)));
} catch (Exception e) {
Logging.LogGenericException(e);
}
}
FilePath = Path.Combine(Program.ConfigDirectory, botName + ".json");
FilePath = Path.Combine(SharedInfo.ConfigDirectory, botName + ".json");
}
}
}

View File

@@ -41,52 +41,80 @@ namespace ConfigGenerator {
ProtectedDataForCurrentUser
}
internal enum EFarmingOrder : byte {
Unordered,
AppIDsAscending,
AppIDsDescending,
CardDropsAscending,
CardDropsDescending,
HoursAscending,
HoursDescending,
NamesAscending,
NamesDescending
}
[Category("\t\tCore")]
[JsonProperty(Required = Required.DisallowNull)]
public bool Enabled { get; set; } = false;
[Category("\tAdvanced")]
[JsonProperty(Required = Required.DisallowNull)]
public bool StartOnLaunch { get; set; } = true;
[Category("\t\tCore")]
[JsonProperty]
public string SteamLogin { get; set; } = null;
[Category("\t\tCore")]
[JsonProperty]
[PasswordPropertyText(true)]
public string SteamPassword { get; set; } = null;
[Category("\tAccess")]
[JsonProperty(Required = Required.DisallowNull)]
public ECryptoMethod PasswordFormat { get; set; } = ECryptoMethod.PlainText;
[Category("\tAccess")]
[JsonProperty]
public string SteamParentalPIN { get; set; } = "0";
[Category("\tAccess")]
[JsonProperty]
public string SteamApiKey { get; set; } = null;
[Category("\tAccess")]
[JsonProperty(Required = Required.DisallowNull)]
public ulong SteamMasterID { get; set; } = 0;
[Category("\tAccess")]
[JsonProperty(Required = Required.DisallowNull)]
public ulong SteamMasterClanID { get; set; } = 0;
[Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)]
public bool CardDropsRestricted { get; set; } = false;
public bool CardDropsRestricted { get; set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
public bool DismissInventoryNotifications { get; set; } = true;
[JsonProperty(Required = Required.DisallowNull)]
public EFarmingOrder FarmingOrder { get; set; } = EFarmingOrder.Unordered;
[JsonProperty(Required = Required.DisallowNull)]
public bool FarmOffline { get; set; } = false;
[Category("\tAdvanced")]
[JsonProperty(Required = Required.DisallowNull)]
public bool HandleOfflineMessages { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool AcceptGifts { get; set; } = false;
[Category("\tAdvanced")]
[JsonProperty(Required = Required.DisallowNull)]
public bool IsBotAccount { get; set; } = false;
[Category("\tAdvanced")]
[JsonProperty(Required = Required.DisallowNull)]
public bool SteamTradeMatcher { get; set; } = false;
@@ -102,12 +130,14 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool SendOnFarmingFinished { get; set; } = false;
[Category("\tAccess")]
[JsonProperty]
public string SteamTradeToken { get; set; } = null;
[JsonProperty(Required = Required.DisallowNull)]
public byte SendTradePeriod { get; set; } = 0;
[Category("\tAdvanced")]
[JsonProperty(Required = Required.DisallowNull)]
public byte AcceptConfirmationsPeriod { get; set; } = 0;

View File

@@ -25,6 +25,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -36,10 +37,14 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>cirno.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
@@ -74,6 +79,7 @@
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RunTime.cs" />
<Compile Include="Tutorial.cs" />
<EmbeddedResource Include="ConfigPage.resx">
<DependentUpon>ConfigPage.cs</DependentUpon>
@@ -106,16 +112,22 @@
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Content Include="cirno.ico" />
<None Include="FodyWeavers.xml" />
</ItemGroup>
<ItemGroup>
<Content Include="cirno.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
copy "$(TargetDir)$(TargetName).exe" "$(SolutionDir)out\ASF-ConfigGenerator.exe"
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mono --llvm --server -O=all "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-ConfigGenerator.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
if [ -f "$(SolutionDir)mono_envsetup.sh" ]; then
. "$(SolutionDir)mono_envsetup.sh"
fi
mono "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-ConfigGenerator.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
rm "$(SolutionDir)out/ASF-ConfigGenerator.exe.config"
</PostBuildEvent>
</PropertyGroup>

View File

@@ -25,6 +25,7 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Sockets;
@@ -50,57 +51,74 @@ namespace ConfigGenerator {
// This is hardcoded blacklist which should not be possible to change
private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
[Category("\tDebugging")]
[JsonProperty(Required = Required.DisallowNull)]
public bool Debug { get; set; } = false;
[Category("\tAdvanced")]
[JsonProperty(Required = Required.DisallowNull)]
public bool Headless { get; set; } = false;
[Category("\tUpdates")]
[JsonProperty(Required = Required.DisallowNull)]
public bool AutoUpdates { get; set; } = true;
[Category("\tUpdates")]
[JsonProperty(Required = Required.DisallowNull)]
public bool AutoRestart { get; set; } = true;
[Category("\tUpdates")]
[JsonProperty(Required = Required.DisallowNull)]
public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable;
[Category("\tAdvanced")]
[JsonProperty(Required = Required.DisallowNull)]
public ProtocolType SteamProtocol { get; set; } = DefaultSteamProtocol;
[Category("\tAccess")]
[JsonProperty(Required = Required.DisallowNull)]
public ulong SteamOwnerID { get; set; } = 0;
[Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)]
public byte MaxFarmingTime { get; set; } = DefaultMaxFarmingTime;
[Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)]
public byte IdleFarmingPeriod { get; set; } = 3;
[Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)]
public byte FarmingDelay { get; set; } = DefaultFarmingDelay;
[Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)]
public byte LoginLimiterDelay { get; set; } = 10;
[Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)]
public byte InventoryLimiterDelay { get; set; } = 3;
[Category("\tPerformance")]
[JsonProperty(Required = Required.DisallowNull)]
public byte GiftsLimiterDelay { get; set; } = 1;
[JsonProperty(Required = Required.DisallowNull)]
public byte MaxTradeHoldDuration { get; set; } = 15;
[Category("\tDebugging")]
[JsonProperty(Required = Required.DisallowNull)]
public bool ForceHttp { get; set; } = false;
[Category("\tDebugging")]
[JsonProperty(Required = Required.DisallowNull)]
public byte HttpTimeout { get; set; } = DefaultHttpTimeout;
[Category("\tAccess")]
[JsonProperty]
public string WCFHostname { get; set; } = "localhost";
[Category("\tAccess")]
[JsonProperty(Required = Required.DisallowNull)]
public ushort WCFPort { get; set; } = DefaultWCFPort;

View File

@@ -36,7 +36,7 @@
this.MainTab.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.MainTab.HotTrack = true;
this.MainTab.Location = new System.Drawing.Point(14, 14);
this.MainTab.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.MainTab.Margin = new System.Windows.Forms.Padding(4);
this.MainTab.Multiline = true;
this.MainTab.Name = "MainTab";
this.MainTab.SelectedIndex = 0;
@@ -58,7 +58,7 @@
this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.HelpButton = true;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.Margin = new System.Windows.Forms.Padding(4);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "MainForm";

View File

@@ -52,14 +52,14 @@ namespace ConfigGenerator {
return;
}
ASFTab = new ConfigPage(GlobalConfig.Load(Path.Combine(Program.ConfigDirectory, Program.GlobalConfigFile)));
ASFTab = new ConfigPage(GlobalConfig.Load(Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName)));
MainTab.TabPages.Add(ASFTab);
foreach (string configFile in Directory.EnumerateFiles(Program.ConfigDirectory, "*.json")) {
foreach (string configFile in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json")) {
string botName = Path.GetFileNameWithoutExtension(configFile);
switch (botName) {
case Program.ASF:
case SharedInfo.ASF:
case "example":
case "minimal":
continue;
@@ -161,7 +161,7 @@ namespace ConfigGenerator {
}
switch (input) {
case Program.ASF:
case SharedInfo.ASF:
case "example":
case "minimal":
Logging.LogGenericErrorWithoutStacktrace("This name is reserved!");
@@ -173,7 +173,7 @@ namespace ConfigGenerator {
return;
}
input = Path.Combine(Program.ConfigDirectory, input + ".json");
input = Path.Combine(SharedInfo.ConfigDirectory, input + ".json");
ConfigPage newConfigPage = new ConfigPage(BotConfig.Load(input));
MainTab.TabPages.Insert(MainTab.TabPages.Count - ReservedTabs, newConfigPage);

View File

@@ -32,12 +32,7 @@ using ArchiSteamFarm;
namespace ConfigGenerator {
internal static class Program {
internal const string ASF = "ASF";
internal const string ConfigDirectory = "config";
internal const string GlobalConfigFile = ASF + ".json";
private const string ASFDirectory = "ArchiSteamFarm";
private const string ASFExecutableFile = ASF + ".exe";
private const string ASFExecutableFile = SharedInfo.ASF + ".exe";
/// <summary>
/// The main entry point for the application.
@@ -64,22 +59,22 @@ namespace ConfigGenerator {
// 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)) {
if (!Directory.Exists(SharedInfo.ASFDirectory)) {
continue;
}
Directory.SetCurrentDirectory(ASFDirectory);
Directory.SetCurrentDirectory(SharedInfo.ASFDirectory);
break;
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(ConfigDirectory)) {
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
}
}
}
if (!Directory.Exists(ConfigDirectory)) {
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
Logging.LogGenericErrorWithoutStacktrace("Config directory could not be found!");
Environment.Exit(1);
}

View File

@@ -5,11 +5,11 @@ using ArchiSteamFarm;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ConfigGenerator")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyTitle(SharedInfo.ServiceName + "-ConfigGenerator")]
[assembly: AssemblyDescription(SharedInfo.ServiceDescription)]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConfigGenerator")]
[assembly: AssemblyProduct(SharedInfo.ServiceName + "-ConfigGenerator")]
[assembly: AssemblyCopyright(SharedInfo.Copyright)]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@ using ArchiSteamFarm;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion(SharedInfo.Version)]
[assembly: AssemblyFileVersion(SharedInfo.Version)]
[assembly: AssemblyVersion(SharedInfo.VersionNumber)]
[assembly: AssemblyFileVersion(SharedInfo.VersionNumber)]

View File

@@ -0,0 +1,33 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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;
namespace ConfigGenerator {
internal static class Runtime {
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
internal static bool IsRunningOnMono => MonoRuntime != null;
}
}

View File

@@ -57,8 +57,14 @@ namespace ConfigGenerator {
Logging.LogGenericInfoWithoutStacktrace("You can now notice the main ASF Config Generator screen, it's really easy to use!");
Logging.LogGenericInfoWithoutStacktrace("At the top of the window you can notice currently loaded configs, and 3 extra buttons for removing, renaming and adding new ones.");
Logging.LogGenericInfoWithoutStacktrace("In the middle of the window you will be able to configure all config properties that are available for you.");
Logging.LogGenericInfoWithoutStacktrace("In the top right corner you can find help button [?] which will redirect you to ASF wiki where you can find more information.");
Logging.LogGenericInfoWithoutStacktrace("Please click the help button to continue.");
if (!Runtime.IsRunningOnMono) {
Logging.LogGenericInfoWithoutStacktrace("In the top right corner you can find help button [?] which will redirect you to ASF wiki where you can find more information.");
Logging.LogGenericInfoWithoutStacktrace("Please click the help button to continue.");
} else {
Logging.LogGenericInfoWithoutStacktrace("Please visit ASF wiki if you're in doubt - you can find more information there.");
Logging.LogGenericInfoWithoutStacktrace("Alright, let's start configuring our ASF. Click on the plus [+] button to add your first steam account to ASF!");
NextPhase = EPhase.HelpFinished;
}
break;
case EPhase.Help:
Logging.LogGenericInfoWithoutStacktrace("Well done! On ASF wiki you can find detailed help about every config property you're going to configure in a moment.");

6
GUI/App.config Normal file
View File

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

63
GUI/BotStatusForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,63 @@
namespace GUI {
sealed partial class BotStatusForm {
/// <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(BotStatusForm));
this.AvatarPictureBox = new System.Windows.Forms.PictureBox();
((System.ComponentModel.ISupportInitialize)(this.AvatarPictureBox)).BeginInit();
this.SuspendLayout();
//
// AvatarPictureBox
//
this.AvatarPictureBox.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.AvatarPictureBox.ErrorImage = ((System.Drawing.Image)(resources.GetObject("AvatarPictureBox.ErrorImage")));
this.AvatarPictureBox.Image = ((System.Drawing.Image)(resources.GetObject("AvatarPictureBox.Image")));
this.AvatarPictureBox.Location = new System.Drawing.Point(12, 12);
this.AvatarPictureBox.Name = "AvatarPictureBox";
this.AvatarPictureBox.Size = new System.Drawing.Size(184, 184);
this.AvatarPictureBox.TabIndex = 0;
this.AvatarPictureBox.TabStop = false;
this.AvatarPictureBox.LoadCompleted += new System.ComponentModel.AsyncCompletedEventHandler(this.AvatarPictureBox_LoadCompleted);
//
// BotStatusForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoScroll = true;
this.ClientSize = new System.Drawing.Size(651, 513);
this.Controls.Add(this.AvatarPictureBox);
this.DoubleBuffered = true;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.Name = "BotStatusForm";
this.Text = "BotStatusForm";
((System.ComponentModel.ISupportInitialize)(this.AvatarPictureBox)).EndInit();
this.ResumeLayout(false);
}
#endregion
internal System.Windows.Forms.PictureBox AvatarPictureBox;
}
}

46
GUI/BotStatusForm.cs Normal file
View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Windows.Forms;
using ArchiSteamFarm;
using SteamKit2;
namespace GUI {
internal sealed partial class BotStatusForm : Form {
internal static readonly ConcurrentDictionary<string, BotStatusForm> BotForms = new ConcurrentDictionary<string, BotStatusForm>();
private readonly Bot Bot;
internal BotStatusForm(Bot bot) {
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
}
Bot = bot;
BotForms[bot.BotName] = this;
InitializeComponent();
Dock = DockStyle.Fill;
}
internal void OnStateUpdated(SteamFriends.PersonaStateCallback callback) {
if (callback == null) {
Logging.LogNullError(nameof(callback));
return;
}
if (callback.AvatarHash != null) {
string avatarHash = BitConverter.ToString(callback.AvatarHash).Replace("-", "").ToLowerInvariant();
string avatarURL = "https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/" + avatarHash.Substring(0, 2) + "/" + avatarHash + "_full.jpg";
AvatarPictureBox.ImageLocation = avatarURL;
AvatarPictureBox.LoadAsync();
}
}
private void AvatarPictureBox_LoadCompleted(object sender, AsyncCompletedEventArgs e) {
MainForm.UpdateBotAvatar(Bot.BotName, AvatarPictureBox.Image);
}
}
}

247
GUI/BotStatusForm.resx Normal file
View File

@@ -0,0 +1,247 @@
<?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.Runtime.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:import namespace="http://www.w3.org/XML/1998/namespace" />
<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" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</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" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="AvatarPictureBox.ErrorImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
/9j/4AAQSkZJRgABAQEAAAAAAAD/4QCqRXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAZKG
AAcAAAB0AAAALAAAAABDAFIARQBBAFQATwBSADoAIABnAGQALQBqAHAAZQBnACAAdgAxAC4AMAAgACgA
dQBzAGkAbgBnACAASQBKAEcAIABKAFAARQBHACAAdgA2ADIAKQAsACAAcQB1AGEAbABpAHQAeQAgAD0A
IAA4ADAACgAAAAAA/9sAQwAGBAUGBQQGBgUGBwcGCAoQCgoJCQoUDg8MEBcUGBgXFBYWGh0lHxobIxwW
FiAsICMmJykqKRkfLTAtKDAlKCko/9sAQwEHBwcKCAoTCgoTKBoWGigoKCgoKCgoKCgoKCgoKCgoKCgo
KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo/8AAEQgAuAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEB
AQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQci
cRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVm
Z2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV
1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//E
ALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDTh
JfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5
+v/aAAwDAQACEQMRAD8A8V1G9u4dQuYobmeONJWVVWQgAAn3qt/aN9/z+3P/AH9b/GjVv+Qre/8AXZ//
AEI1VoAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAt
f2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/t
z/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj
+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9u
f+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooA09OvbubULaKa5nkjeVVZWkJBBI96KraT/
AMhWy/67J/6EKKADVv8AkK3v/XZ//QjVWrWrf8hW9/67P/6Eaq0AFFFFABRRRQAUUUUAFFFFABRRRQAU
UUUAFFFFABRRRQAUUUUAFFFFAFrSf+QrZf8AXZP/AEIUUaT/AMhWy/67J/6EKKADVv8AkK3v/XZ//QjV
WrWrf8hW9/67P/6Eaq0AFFFFABRRRQAUUUUAFFFbXhTwvq/irURZaJaPO4wZHPCRD1Zuw/U9s0AYtFfS
Hhn4AaZBEkniPUZru4xkxWuI4we43EFm+vFdavwc8CqoB0QsR1Ju58n/AMfoA+QqK+oNc+Avhu8jJ0q5
vdOmx8vzCZPxVuT+BFeJ+PfhvrvgxvNvYhc6cThbyDJTPYMMZU/Xj0JoA4uiiigAooooAKKKKACiiigC
1pP/ACFbL/rsn/oQoo0n/kK2X/XZP/QhRQAat/yFb3/rs/8A6Eaq1a1b/kK3v/XZ/wD0I1VoAKKKKACi
iigAooooA2/Bnhy78V+I7TSbH5XmbLyEZESDlmP0/U8V9leEvDeneFdFh03SYQkSAF3IG+V8YLMe5OP6
DivK/wBmHQUt9B1HXJE/f3cv2eMntGgBOPqx/wDHa9toAKK8U+N/xSu/D96dA8OOkd+EDXNyQGMIIyFU
f3sHOSOAeOengNx4k1y4nM8+sai8xOd7XLk5+uaAPumo7mCK6t5Le5ijmglUq8cigqwIwQR3FfMvww+M
ep6Vfw2Pii5kv9KkIUzyktLAem4t1ZfUHJ9PQ/TqOrorowZSAwKnII65BoA+S/jX8P8A/hDtZS605SdF
vWPlA5JhfvGT6dwT2+ma82r7S+Kugp4i8B6tZFd0yRGeA9xIg3Lj0zgj6Gvi2gAooooAKKKKACiiigC1
pP8AyFbL/rsn/oQoo0n/AJCtl/12T/0IUUAGrf8AIVvf+uz/APoRqrVrVv8AkK3v/XZ//QjVWgAooooA
KKKKACiiigD64+A9xbRfCrRVeaFHJnLAsAc+e/Xn0xXffbbX/n5h/wC+x/jXwTRQBr+ML5tS8V6xeu28
z3crg5yMFjgA+mOKyKKKACvtP4UzTz/Djw89znzPsaLk9SoGFP5AV8ofD/wpd+MfEtvplqCsRO+4mA4h
jBG5vr2A7mvtSxtYbGyt7S1QJBBGsUaDoqKAAP0oAlZQylWAZWGCDyCK+Aq+5PGurpoXhLV9Sdgpt7Z2
UnjL4wg/Fior4boAKKKKACiiigAooooAtaT/AMhWy/67J/6EKKNJ/wCQrZf9dk/9CFFABq3/ACFb3/rs
/wD6Eaq1a1b/AJCt7/12f/0I1VoAKKKKACiiigAooooAKKKKACpbW3mu7qG3tY3luJXEccaDJdieAB3O
TUVfQ/7O3gHyIl8V6tD+9kUiwjccqveXHqeg9snuKAPQvhR4Jh8FeG0gcI+p3GJLuVecvjhQf7q5x78n
vXa0V5r8bfHw8I6H9j0+T/idXyFYsdYU6GQ+/Ye/PY0Aec/tFeOk1K8HhjTJN1taybruRTw8ozhB7Lnn
3/3a8RpWYsxZiWZjkk8kn1NJQAUUUUAFFFFABRRRQBa0n/kK2X/XZP8A0IUUaT/yFbL/AK7J/wChCigA
1b/kK3v/AF2f/wBCNVatat/yFb3/AK7P/wChGqtABRRRQAUUUUAFFFFABRRRQB0Pw+0NfEnjTSNJkz5N
xMPNA6mNQWcA+u1TX23FGkMSRxIEjRQqqowFAGAAOwxXyV+z2P8Ai6Wm/wDXKb/0W1fW9AGN4w8RWfhX
w9datqBzFCvyoDgyueFUe5P5DntXxd4n1298Sa5darqUm+4uH3EDOEHQKo7ADivdf2qbt00vw9ZhiElm
mmK9iUVQCf8Avs187UAFFFFABRRRQAUUUUAFFFFAFrSf+QrZf9dk/wDQhRRpP/IVsv8Arsn/AKEKKADV
v+Qre/8AXZ//AEI1Vq1q3/IVvf8Ars//AKEaq0AFFFFABRRRQAUUUUAFFFFAHdfBTVrHRfiFY3uq3Mdt
aJHMGlk4AJjIH6mvpX/hZfg3/oYbH/vo/wCFfGFFAHs/7RvibRvEX/CPf2JqMN75H2jzfLJOzd5W3PH+
ya8YoooAKKKKACiiigAooooAKKKKALWk/wDIVsv+uyf+hCijSf8AkK2X/XZP/QhRQAat/wAhW9/67P8A
+hGqtWtW/wCQre/9dn/9CNVaACiiigAooooAKKKKACiiigD0P4BwQ3PxN0+K5ijljMcxKSKGB/dkjivq
v+xdK/6Blj/4Dr/hXyf8DL+z034kWFzqN3b2lsscwaWeQRoCYyACxIA5r6g/4TXwt/0Muif+DCL/AOKo
A8Y/aisrSz/4Rn7JbQwbvtW7y0C7v9VjOBz1rwivb/2l9a0rWP8AhHP7I1Oxv/K+0+Z9luFl2Z8rG7aT
jOD19K8QoAKKKKACiiigAooooAKKKKALWk/8hWy/67J/6EKKNJ/5Ctl/12T/ANCFFABq3/IVvf8Ars//
AKEaq1a1b/kK3v8A12f/ANCNVaACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
C1pP/IVsv+uyf+hCijSf+QrZf9dk/wDQhRQAat/yFb3/AK7P/wChGqtWtW/5Ct7/ANdn/wDQjVWgAooo
oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAtaT/yFbL/rsn/oQoo0n/kK2X/XZP8A
0IUUAWdRsrubULmWG2nkjeVmVljJBBJ9qrf2dff8+Vz/AN+m/wAKKKAD+zr7/nyuf+/Tf4Uf2dff8+Vz
/wB+m/woooAP7Ovv+fK5/wC/Tf4Uf2dff8+Vz/36b/CiigA/s6+/58rn/v03+FH9nX3/AD5XP/fpv8KK
KAD+zr7/AJ8rn/v03+FH9nX3/Plc/wDfpv8ACiigA/s6+/58rn/v03+FH9nX3/Plc/8Afpv8KKKAD+zr
7/nyuf8Av03+FH9nX3/Plc/9+m/woooAP7Ovv+fK5/79N/hR/Z19/wA+Vz/36b/CiigA/s6+/wCfK5/7
9N/hR/Z19/z5XP8A36b/AAoooAP7Ovv+fK5/79N/hR/Z19/z5XP/AH6b/CiigA/s6+/58rn/AL9N/hR/
Z19/z5XP/fpv8KKKALOnWV3DqFtLNbTxxpKrMzRkAAEe1FFFAH//2Q==
</value>
</data>
<data name="AvatarPictureBox.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
/9j/4AAQSkZJRgABAQEAAAAAAAD/4QCqRXhpZgAATU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAZKG
AAcAAAB0AAAALAAAAABDAFIARQBBAFQATwBSADoAIABnAGQALQBqAHAAZQBnACAAdgAxAC4AMAAgACgA
dQBzAGkAbgBnACAASQBKAEcAIABKAFAARQBHACAAdgA2ADIAKQAsACAAcQB1AGEAbABpAHQAeQAgAD0A
IAA4ADAACgAAAAAA/9sAQwAGBAUGBQQGBgUGBwcGCAoQCgoJCQoUDg8MEBcUGBgXFBYWGh0lHxobIxwW
FiAsICMmJykqKRkfLTAtKDAlKCko/9sAQwEHBwcKCAoTCgoTKBoWGigoKCgoKCgoKCgoKCgoKCgoKCgo
KCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgo/8AAEQgAuAC4AwEiAAIRAQMRAf/EAB8AAAEFAQEB
AQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQci
cRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVm
Z2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV
1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//E
ALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDTh
JfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT
lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5
+v/aAAwDAQACEQMRAD8A8V1G9u4dQuYobmeONJWVVWQgAAn3qt/aN9/z+3P/AH9b/GjVv+Qre/8AXZ//
AEI1VoAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAt
f2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/t
z/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj
+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9u
f+/rf41VooAtf2jff8/tz/39b/Gj+0b7/n9uf+/rf41VooA09OvbubULaKa5nkjeVVZWkJBBI96KraT/
AMhWy/67J/6EKKADVv8AkK3v/XZ//QjVWrWrf8hW9/67P/6Eaq0AFFFFABRRRQAUUUUAFFFFABRRRQAU
UUUAFFFFABRRRQAUUUUAFFFFAFrSf+QrZf8AXZP/AEIUUaT/AMhWy/67J/6EKKADVv8AkK3v/XZ//QjV
WrWrf8hW9/67P/6Eaq0AFFFFABRRRQAUUUUAFFFbXhTwvq/irURZaJaPO4wZHPCRD1Zuw/U9s0AYtFfS
Hhn4AaZBEkniPUZru4xkxWuI4we43EFm+vFdavwc8CqoB0QsR1Ju58n/AMfoA+QqK+oNc+Avhu8jJ0q5
vdOmx8vzCZPxVuT+BFeJ+PfhvrvgxvNvYhc6cThbyDJTPYMMZU/Xj0JoA4uiiigAooooAKKKKACiiigC
1pP/ACFbL/rsn/oQoo0n/kK2X/XZP/QhRQAat/yFb3/rs/8A6Eaq1a1b/kK3v/XZ/wD0I1VoAKKKKACi
iigAooooA2/Bnhy78V+I7TSbH5XmbLyEZESDlmP0/U8V9leEvDeneFdFh03SYQkSAF3IG+V8YLMe5OP6
DivK/wBmHQUt9B1HXJE/f3cv2eMntGgBOPqx/wDHa9toAKK8U+N/xSu/D96dA8OOkd+EDXNyQGMIIyFU
f3sHOSOAeOengNx4k1y4nM8+sai8xOd7XLk5+uaAPumo7mCK6t5Le5ijmglUq8cigqwIwQR3FfMvww+M
ep6Vfw2Pii5kv9KkIUzyktLAem4t1ZfUHJ9PQ/TqOrorowZSAwKnII65BoA+S/jX8P8A/hDtZS605SdF
vWPlA5JhfvGT6dwT2+ma82r7S+Kugp4i8B6tZFd0yRGeA9xIg3Lj0zgj6Gvi2gAooooAKKKKACiiigC1
pP8AyFbL/rsn/oQoo0n/AJCtl/12T/0IUUAGrf8AIVvf+uz/APoRqrVrVv8AkK3v/XZ//QjVWgAooooA
KKKKACiiigD64+A9xbRfCrRVeaFHJnLAsAc+e/Xn0xXffbbX/n5h/wC+x/jXwTRQBr+ML5tS8V6xeu28
z3crg5yMFjgA+mOKyKKKACvtP4UzTz/Djw89znzPsaLk9SoGFP5AV8ofD/wpd+MfEtvplqCsRO+4mA4h
jBG5vr2A7mvtSxtYbGyt7S1QJBBGsUaDoqKAAP0oAlZQylWAZWGCDyCK+Aq+5PGurpoXhLV9Sdgpt7Z2
UnjL4wg/Fior4boAKKKKACiiigAooooAtaT/AMhWy/67J/6EKKNJ/wCQrZf9dk/9CFFABq3/ACFb3/rs
/wD6Eaq1a1b/AJCt7/12f/0I1VoAKKKKACiiigAooooAKKKKACpbW3mu7qG3tY3luJXEccaDJdieAB3O
TUVfQ/7O3gHyIl8V6tD+9kUiwjccqveXHqeg9snuKAPQvhR4Jh8FeG0gcI+p3GJLuVecvjhQf7q5x78n
vXa0V5r8bfHw8I6H9j0+T/idXyFYsdYU6GQ+/Ye/PY0Aec/tFeOk1K8HhjTJN1taybruRTw8ozhB7Lnn
3/3a8RpWYsxZiWZjkk8kn1NJQAUUUUAFFFFABRRRQBa0n/kK2X/XZP8A0IUUaT/yFbL/AK7J/wChCigA
1b/kK3v/AF2f/wBCNVatat/yFb3/AK7P/wChGqtABRRRQAUUUUAFFFFABRRRQB0Pw+0NfEnjTSNJkz5N
xMPNA6mNQWcA+u1TX23FGkMSRxIEjRQqqowFAGAAOwxXyV+z2P8Ai6Wm/wDXKb/0W1fW9AGN4w8RWfhX
w9datqBzFCvyoDgyueFUe5P5DntXxd4n1298Sa5darqUm+4uH3EDOEHQKo7ADivdf2qbt00vw9ZhiElm
mmK9iUVQCf8Avs187UAFFFFABRRRQAUUUUAFFFFAFrSf+QrZf9dk/wDQhRRpP/IVsv8Arsn/AKEKKADV
v+Qre/8AXZ//AEI1Vq1q3/IVvf8Ars//AKEaq0AFFFFABRRRQAUUUUAFFFFAHdfBTVrHRfiFY3uq3Mdt
aJHMGlk4AJjIH6mvpX/hZfg3/oYbH/vo/wCFfGFFAHs/7RvibRvEX/CPf2JqMN75H2jzfLJOzd5W3PH+
ya8YoooAKKKKACiiigAooooAKKKKALWk/wDIVsv+uyf+hCijSf8AkK2X/XZP/QhRQAat/wAhW9/67P8A
+hGqtWtW/wCQre/9dn/9CNVaACiiigAooooAKKKKACiiigD0P4BwQ3PxN0+K5ijljMcxKSKGB/dkjivq
v+xdK/6Blj/4Dr/hXyf8DL+z034kWFzqN3b2lsscwaWeQRoCYyACxIA5r6g/4TXwt/0Muif+DCL/AOKo
A8Y/aisrSz/4Rn7JbQwbvtW7y0C7v9VjOBz1rwivb/2l9a0rWP8AhHP7I1Oxv/K+0+Z9luFl2Z8rG7aT
jOD19K8QoAKKKKACiiigAooooAKKKKALWk/8hWy/67J/6EKKNJ/5Ctl/12T/ANCFFABq3/IVvf8Ars//
AKEaq1a1b/kK3v8A12f/ANCNVaACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiig
C1pP/IVsv+uyf+hCijSf+QrZf9dk/wDQhRQAat/yFb3/AK7P/wChGqtWtW/5Ct7/ANdn/wDQjVWgAooo
oAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAooooAtaT/yFbL/rsn/oQoo0n/kK2X/XZP8A
0IUUAWdRsrubULmWG2nkjeVmVljJBBJ9qrf2dff8+Vz/AN+m/wAKKKAD+zr7/nyuf+/Tf4Uf2dff8+Vz
/wB+m/woooAP7Ovv+fK5/wC/Tf4Uf2dff8+Vz/36b/CiigA/s6+/58rn/v03+FH9nX3/AD5XP/fpv8KK
KAD+zr7/AJ8rn/v03+FH9nX3/Plc/wDfpv8ACiigA/s6+/58rn/v03+FH9nX3/Plc/8Afpv8KKKAD+zr
7/nyuf8Av03+FH9nX3/Plc/9+m/woooAP7Ovv+fK5/79N/hR/Z19/wA+Vz/36b/CiigA/s6+/wCfK5/7
9N/hR/Z19/z5XP8A36b/AAoooAP7Ovv+fK5/79N/hR/Z19/z5XP/AH6b/CiigA/s6+/58rn/AL9N/hR/
Z19/z5XP/fpv8KKKALOnWV3DqFtLNbTxxpKrMzRkAAEe1FFFAH//2Q==
</value>
</data>
</root>

23
GUI/Events.cs Normal file
View File

@@ -0,0 +1,23 @@
using GUI;
using SteamKit2;
// ReSharper disable once CheckNamespace
namespace ArchiSteamFarm {
internal static class Events {
internal static void OnBotShutdown() { }
internal static void OnStateUpdated(Bot bot, SteamFriends.PersonaStateCallback callback) {
if ((bot == null) || (callback == null)) {
Logging.LogNullError(nameof(bot) + " || " + nameof(callback));
return;
}
BotStatusForm form;
if (!BotStatusForm.BotForms.TryGetValue(bot.BotName, out form)) {
return;
}
form.OnStateUpdated(callback);
}
}
}

4
GUI/FodyWeavers.xml Normal file
View File

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

235
GUI/GUI.csproj Normal file
View File

@@ -0,0 +1,235 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.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>{13949B41-787C-4558-90AE-A9F9E7F86B1F}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GUI</RootNamespace>
<AssemblyName>GUI</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</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>
<Prefer32Bit>false</Prefer32Bit>
</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>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>cirno.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<Reference Include="HtmlAgilityPack, Version=1.4.9.5, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<HintPath>..\packages\HtmlAgilityPack.1.4.9.5\lib\Net45\HtmlAgilityPack.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.Windows.Forms.4.2.3\lib\net35\NLog.Windows.Forms.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="protobuf-net, Version=2.0.0.668, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
<HintPath>..\packages\protobuf-net.2.0.0.668\lib\net40\protobuf-net.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SteamKit2, Version=1.8.0.26737, Culture=neutral, PublicKeyToken=ed3ce47ed5aad940, processorArchitecture=MSIL">
<HintPath>..\packages\SteamKit2.1.8.0\lib\net45\SteamKit2.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Security" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\ArchiSteamFarm\ArchiHandler.cs">
<Link>ArchiHandler.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\ArchiWebHandler.cs">
<Link>ArchiWebHandler.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\ASF.cs">
<Link>ASF.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\Bot.cs">
<Link>Bot.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\BotConfig.cs">
<Link>BotConfig.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\BotDatabase.cs">
<Link>BotDatabase.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\CardsFarmer.cs">
<Link>CardsFarmer.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\ConcurrentEnumerator.cs">
<Link>ConcurrentEnumerator.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\ConcurrentHashSet.cs">
<Link>ConcurrentHashSet.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\CryptoHelper.cs">
<Link>CryptoHelper.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\Debugging.cs">
<Link>Debugging.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\GlobalConfig.cs">
<Link>GlobalConfig.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\GlobalDatabase.cs">
<Link>GlobalDatabase.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\InMemoryServerListProvider.cs">
<Link>InMemoryServerListProvider.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\IPAddressConverter.cs">
<Link>IPAddressConverter.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\IPEndPointConverter.cs">
<Link>IPEndPointConverter.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\JSON\GitHub.cs">
<Link>JSON\GitHub.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\JSON\Steam.cs">
<Link>JSON\Steam.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\MobileAuthenticator.cs">
<Link>MobileAuthenticator.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\Runtime.cs">
<Link>Runtime.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\SharedInfo.cs">
<Link>SharedInfo.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\Trading.cs">
<Link>Trading.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\Utilities.cs">
<Link>Utilities.cs</Link>
</Compile>
<Compile Include="..\ArchiSteamFarm\WebBrowser.cs">
<Link>WebBrowser.cs</Link>
</Compile>
<Compile Include="BotStatusForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="BotStatusForm.Designer.cs">
<DependentUpon>BotStatusForm.cs</DependentUpon>
</Compile>
<Compile Include="Events.cs" />
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Logging.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="BotStatusForm.resx">
<DependentUpon>BotStatusForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.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="packages.config" />
<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>
<Content Include="cirno.ico" />
<None Include="FodyWeavers.xml" />
<None Include="Resources\SteamUnknownAvatar.jpg" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
copy "$(TargetDir)$(TargetName).exe" "$(SolutionDir)out\ASF-GUI.exe"
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
if [ -f "$(SolutionDir)mono_envsetup.sh" ]; then
. "$(SolutionDir)mono_envsetup.sh"
fi
mono "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
rm "$(SolutionDir)out/ASF-GUI.exe.config"
</PostBuildEvent>
</PropertyGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Fody.1.30.0-beta01\build\dotnet\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.30.0-beta01\build\dotnet\Fody.targets'))" />
<Error Condition="!Exists('..\packages\Costura.Fody.2.0.0-beta0018\build\Costura.Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Costura.Fody.2.0.0-beta0018\build\Costura.Fody.targets'))" />
</Target>
<Import Project="..\packages\Fody.1.30.0-beta01\build\dotnet\Fody.targets" Condition="'$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' AND Exists('..\packages\Fody.1.30.0-beta01\build\dotnet\Fody.targets')" />
<Import Project="..\packages\Costura.Fody.2.0.0-beta0018\build\Costura.Fody.targets" Condition="'$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' AND Exists('..\packages\Costura.Fody.2.0.0-beta0018\build\Costura.Fody.targets')" />
<!-- 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>

184
GUI/Logging.cs Normal file
View File

@@ -0,0 +1,184 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using NLog;
using NLog.Config;
using NLog.Targets;
using NLog.Windows.Forms;
// ReSharper disable once CheckNamespace
namespace ArchiSteamFarm {
internal static class Logging {
private const string GeneralLayout = @"${date:format=yyyy-MM-dd HH\:mm\:ss} | ${level:uppercase=true} | ${message}${onexception:inner= | ${exception:format=toString,Data}}";
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static bool IsUsingCustomConfiguration;
internal static void InitCoreLoggers() {
if (LogManager.Configuration == null) {
LogManager.Configuration = new LoggingConfiguration();
} else {
// User provided custom NLog config, but we still need to define our own logger
IsUsingCustomConfiguration = true;
if (LogManager.Configuration.AllTargets.Any(target => target is MessageBoxTarget)) {
return;
}
}
MessageBoxTarget messageBoxTarget = new MessageBoxTarget {
Name = "MessageBox",
Layout = GeneralLayout
};
LogManager.Configuration.AddTarget(messageBoxTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Fatal, messageBoxTarget));
LogManager.ReconfigExistingLoggers();
}
internal static void InitEnhancedLoggers() {
if (IsUsingCustomConfiguration) {
return;
}
FileTarget fileTarget = new FileTarget("File") {
DeleteOldFileOnStartup = true,
FileName = SharedInfo.LogFile,
Layout = GeneralLayout
};
LogManager.Configuration.AddTarget(fileTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget));
LogManager.ReconfigExistingLoggers();
}
internal static void InitFormLogger() {
RichTextBoxTarget formControlTarget = new RichTextBoxTarget {
AutoScroll = true,
ControlName = "LogTextBox",
FormName = "MainForm",
Layout = GeneralLayout,
MaxLines = byte.MaxValue,
Name = "RichTextBox"
};
formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Error", "Red", "Black"));
formControlTarget.RowColoringRules.Add(new RichTextBoxRowColoringRule("level >= LogLevel.Warn", "Yellow", "Black"));
LogManager.Configuration.AddTarget(formControlTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, formControlTarget));
LogManager.ReconfigExistingLoggers();
}
internal static void LogGenericError(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
}
Logger.Error($"{botName}|{previousMethodName}() {message}");
}
internal static void LogGenericException(Exception exception, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception), botName);
return;
}
Logger.Error(exception, $"{botName}|{previousMethodName}()");
}
internal static void LogFatalException(Exception exception, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception), botName);
return;
}
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(SharedInfo.LogFile, DateTime.Now + " ASF V" + SharedInfo.Version + " has run into fatal exception before core logging module was even able to initialize!" + Environment.NewLine);
while (true) {
File.AppendAllText(SharedInfo.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 = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
}
Logger.Warn($"{botName}|{previousMethodName}() {message}");
}
internal static void LogGenericInfo(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
}
Logger.Info($"{botName}|{previousMethodName}() {message}");
}
[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
internal static void LogNullError(string nullObjectName, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(nullObjectName)) {
return;
}
LogGenericError(nullObjectName + " is null!", botName, previousMethodName);
}
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal static void LogGenericDebug(string message, string botName = SharedInfo.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
}
Logger.Debug($"{botName}|{previousMethodName}() {message}");
}
}
}

128
GUI/MainForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,128 @@
namespace GUI {
internal sealed partial class MainForm {
/// <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() {
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.BotListView = new System.Windows.Forms.ListView();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.MinimizeIcon = new System.Windows.Forms.NotifyIcon(this.components);
this.LogTextBox = new System.Windows.Forms.RichTextBox();
this.BotStatusPanel = new System.Windows.Forms.Panel();
this.AvatarImageList = new System.Windows.Forms.ImageList(this.components);
this.SuspendLayout();
//
// BotListView
//
this.BotListView.Dock = System.Windows.Forms.DockStyle.Left;
this.BotListView.GridLines = true;
this.BotListView.Location = new System.Drawing.Point(0, 24);
this.BotListView.MultiSelect = false;
this.BotListView.Name = "BotListView";
this.BotListView.ShowGroups = false;
this.BotListView.Size = new System.Drawing.Size(150, 705);
this.BotListView.TabIndex = 0;
this.BotListView.UseCompatibleStateImageBehavior = false;
this.BotListView.View = System.Windows.Forms.View.SmallIcon;
this.BotListView.SelectedIndexChanged += new System.EventHandler(this.BotListView_SelectedIndexChanged);
//
// menuStrip1
//
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(1008, 24);
this.menuStrip1.TabIndex = 1;
this.menuStrip1.Text = "menuStrip1";
//
// MinimizeIcon
//
this.MinimizeIcon.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info;
this.MinimizeIcon.BalloonTipText = "ASF will keep working in the background...";
this.MinimizeIcon.BalloonTipTitle = "ASF";
this.MinimizeIcon.Icon = ((System.Drawing.Icon)(resources.GetObject("MinimizeIcon.Icon")));
this.MinimizeIcon.Text = "MinimizeIcon";
this.MinimizeIcon.Visible = true;
this.MinimizeIcon.DoubleClick += new System.EventHandler(this.MinimizeIcon_DoubleClick);
//
// LogTextBox
//
this.LogTextBox.BackColor = System.Drawing.Color.Black;
this.LogTextBox.CausesValidation = false;
this.LogTextBox.Dock = System.Windows.Forms.DockStyle.Bottom;
this.LogTextBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.LogTextBox.ForeColor = System.Drawing.Color.White;
this.LogTextBox.Location = new System.Drawing.Point(150, 529);
this.LogTextBox.Name = "LogTextBox";
this.LogTextBox.ReadOnly = true;
this.LogTextBox.Size = new System.Drawing.Size(858, 200);
this.LogTextBox.TabIndex = 2;
this.LogTextBox.Text = "";
//
// BotStatusPanel
//
this.BotStatusPanel.Dock = System.Windows.Forms.DockStyle.Top;
this.BotStatusPanel.Location = new System.Drawing.Point(150, 24);
this.BotStatusPanel.Name = "BotStatusPanel";
this.BotStatusPanel.Size = new System.Drawing.Size(858, 496);
this.BotStatusPanel.TabIndex = 3;
//
// AvatarImageList
//
this.AvatarImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth24Bit;
this.AvatarImageList.ImageSize = new System.Drawing.Size(46, 46);
this.AvatarImageList.TransparentColor = System.Drawing.Color.Transparent;
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1008, 729);
this.Controls.Add(this.BotStatusPanel);
this.Controls.Add(this.LogTextBox);
this.Controls.Add(this.BotListView);
this.Controls.Add(this.menuStrip1);
this.DoubleBuffered = true;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MainMenuStrip = this.menuStrip1;
this.Name = "MainForm";
this.Text = "ArchiSteamFarm";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainForm_FormClosed);
this.Load += new System.EventHandler(this.MainForm_Load);
this.Resize += new System.EventHandler(this.MainForm_Resize);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ListView BotListView;
private System.Windows.Forms.MenuStrip menuStrip1;
private System.Windows.Forms.NotifyIcon MinimizeIcon;
private System.Windows.Forms.RichTextBox LogTextBox;
private System.Windows.Forms.Panel BotStatusPanel;
private System.Windows.Forms.ImageList AvatarImageList;
}
}

161
GUI/MainForm.cs Normal file
View File

@@ -0,0 +1,161 @@
using System;
using System.Collections.Concurrent;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using ArchiSteamFarm;
namespace GUI {
internal sealed partial class MainForm : Form {
private static readonly ConcurrentDictionary<string, int> BotIndexes = new ConcurrentDictionary<string, int>();
private static MainForm Form;
private string PreviouslySelectedBotName;
internal MainForm() {
Form = this;
InitializeComponent();
}
internal static void UpdateBotAvatar(string botName, Image image) {
if (string.IsNullOrEmpty(botName) || (image == null)) {
Logging.LogNullError(nameof(botName) + " || " + nameof(image));
return;
}
if (Form == null) {
return;
}
int index;
if (!BotIndexes.TryGetValue(botName, out index)) {
return;
}
Bitmap resizedImage = ResizeImage(image, Form.AvatarImageList.ImageSize.Width, Form.AvatarImageList.ImageSize.Height);
if (resizedImage == null) {
return;
}
Form.Invoke((MethodInvoker) (() => {
Form.AvatarImageList.Images[index] = resizedImage;
Form.BotListView.Refresh();
}));
}
private static Bitmap ResizeImage(Image image, int width, int height) {
if ((image == null) || (width <= 0) || (height <= 0)) {
Logging.LogNullError(nameof(image) + " || " + nameof(width) + " || " + nameof(height));
return null;
}
Rectangle destRect = new Rectangle(0, 0, width, height);
Bitmap destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(destImage)) {
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (ImageAttributes wrapMode = new ImageAttributes()) {
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
private void MainForm_Resize(object sender, EventArgs e) {
switch (WindowState) {
case FormWindowState.Minimized:
MinimizeIcon.Visible = true;
MinimizeIcon.ShowBalloonTip(5000);
break;
case FormWindowState.Normal:
MinimizeIcon.Visible = false;
break;
}
}
private async void MainForm_Load(object sender, EventArgs e) {
Logging.InitFormLogger();
BotListView.LargeImageList = BotListView.SmallImageList = AvatarImageList;
await Task.Run(async () => {
Logging.LogGenericInfo("ASF V" + SharedInfo.Version);
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
Logging.LogGenericError("Config directory could not be found!");
Environment.Exit(1);
}
await ASF.CheckForUpdate().ConfigureAwait(false);
// Before attempting to connect, initialize our list of CMs
Bot.InitializeCMs(Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider);
});
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension)) {
switch (botName) {
case SharedInfo.ASF:
case "example":
case "minimal":
continue;
}
Bot bot = new Bot(botName);
BotStatusForm botStatusForm = new BotStatusForm(bot);
BotIndexes[botName] = AvatarImageList.Images.Count;
AvatarImageList.Images.Add(botName, botStatusForm.AvatarPictureBox.Image);
botStatusForm.TopLevel = false;
BotStatusPanel.Controls.Add(botStatusForm);
ListViewItem botListViewItem = new ListViewItem {
ImageIndex = BotIndexes[botName],
Text = botName
};
BotListView.Items.Add(botListViewItem);
}
if (BotListView.Items.Count > 0) {
BotListView.Items[0].Selected = true;
BotListView.Select();
}
}
private void MinimizeIcon_DoubleClick(object sender, EventArgs e) {
Show();
WindowState = FormWindowState.Normal;
}
private void MainForm_FormClosed(object sender, FormClosedEventArgs e) => Program.InitShutdownSequence();
private void BotListView_SelectedIndexChanged(object sender, EventArgs e) {
if (!string.IsNullOrEmpty(PreviouslySelectedBotName)) {
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = false;
}
if (BotListView.SelectedItems.Count == 0) {
return;
}
PreviouslySelectedBotName = BotListView.SelectedItems[0].Text;
BotStatusForm.BotForms[PreviouslySelectedBotName].Visible = true;
}
}
}

12477
GUI/MainForm.resx Normal file

File diff suppressed because it is too large Load Diff

150
GUI/Program.cs Normal file
View File

@@ -0,0 +1,150 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using GUI;
// ReSharper disable once CheckNamespace
namespace ArchiSteamFarm {
internal static class Program {
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static WebBrowser WebBrowser { get; private set; }
internal static string GetUserInput(SharedInfo.EUserInputType userInputType, string botName = SharedInfo.ASF, string extraInformation = null) {
return null; // TODO
}
internal static void Exit(int exitCode = 0) {
InitShutdownSequence();
Environment.Exit(exitCode);
}
internal static void Restart() {
InitShutdownSequence();
try {
Process.Start(Assembly.GetEntryAssembly().Location, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
} catch (Exception e) {
Logging.LogGenericException(e);
}
Environment.Exit(0);
}
internal static void InitShutdownSequence() {
foreach (Bot bot in Bot.Bots.Values.Where(bot => bot.KeepRunning)) {
bot.Stop();
}
}
private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args) {
if (args?.ExceptionObject == null) {
Logging.LogNullError(nameof(args) + " || " + nameof(args.ExceptionObject));
return;
}
Logging.LogFatalException((Exception) args.ExceptionObject);
}
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
if (args?.Exception == null) {
Logging.LogNullError(nameof(args) + " || " + nameof(args.Exception));
return;
}
Logging.LogFatalException(args.Exception);
}
private static void InitServices() {
string globalConfigFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName);
GlobalConfig = GlobalConfig.Load(globalConfigFile);
if (GlobalConfig == null) {
Logging.LogGenericError("Global config could not be loaded, please make sure that " + globalConfigFile + " exists and is valid!");
Exit(1);
}
string globalDatabaseFile = Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName);
GlobalDatabase = GlobalDatabase.Load(globalDatabaseFile);
if (GlobalDatabase == null) {
Logging.LogGenericError("Global database could not be loaded, if issue persists, please remove " + globalDatabaseFile + " in order to recreate database!");
Exit(1);
}
ArchiWebHandler.Init();
WebBrowser.Init();
WebBrowser = new WebBrowser(SharedInfo.ASF);
}
private static void Init() {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
Logging.InitCoreLoggers();
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!");
}
string homeDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (!string.IsNullOrEmpty(homeDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
// Allow loading configs from source tree if it's a debug build
if (Debugging.IsDebugBuild) {
// Common structure is bin/(x64/)Debug/ArchiSteamFarm.exe, so we allow up to 4 directories up
for (byte i = 0; i < 4; i++) {
Directory.SetCurrentDirectory("..");
if (!Directory.Exists(SharedInfo.ASFDirectory)) {
continue;
}
Directory.SetCurrentDirectory(SharedInfo.ASFDirectory);
break;
}
// If config directory doesn't exist after our adjustment, abort all of that
if (!Directory.Exists(SharedInfo.ConfigDirectory)) {
Directory.SetCurrentDirectory(homeDirectory);
}
}
}
InitServices();
// If debugging is on, we prepare debug directory prior to running
if (GlobalConfig.Debug) {
if (Directory.Exists(SharedInfo.DebugDirectory)) {
Directory.Delete(SharedInfo.DebugDirectory, true);
Thread.Sleep(1000); // Dirty workaround giving Windows some time to sync
}
Directory.CreateDirectory(SharedInfo.DebugDirectory);
SteamKit2.DebugLog.AddListener(new Debugging.DebugListener());
SteamKit2.DebugLog.Enabled = true;
}
Logging.InitEnhancedLoggers();
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main() {
Init();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}

View File

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

73
GUI/Properties/Resources.Designer.cs generated Normal file
View File

@@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// <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;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap SteamUnknownAvatar {
get {
object obj = ResourceManager.GetObject("SteamUnknownAvatar", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

View File

@@ -0,0 +1,124 @@
<?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.Runtime.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:import namespace="http://www.w3.org/XML/1998/namespace" />
<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" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</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" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="SteamUnknownAvatar" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\SteamUnknownAvatar.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

26
GUI/Properties/Settings.Designer.cs generated Normal file
View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <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", "11.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

@@ -0,0 +1,7 @@
<?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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
GUI/cirno.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

11
GUI/packages.config Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Costura.Fody" version="2.0.0-beta0018" targetFramework="net461" developmentDependency="true" />
<package id="Fody" version="1.30.0-beta01" targetFramework="net461" developmentDependency="true" />
<package id="HtmlAgilityPack" version="1.4.9.5" targetFramework="net461" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
<package id="NLog" version="4.4.0-betaV15" targetFramework="net461" />
<package id="NLog.Windows.Forms" version="4.2.3" targetFramework="net461" />
<package id="protobuf-net" version="2.0.0.668" targetFramework="net461" />
<package id="SteamKit2" version="1.8.0" targetFramework="net461" />
</packages>

View File

@@ -6,9 +6,10 @@ ArchiSteamFarm
[![Build Status (Mono)](https://img.shields.io/travis/JustArchi/ArchiSteamFarm.svg?label=Mono&maxAge=60)](https://travis-ci.org/JustArchi/ArchiSteamFarm)
[![License](https://img.shields.io/github/license/JustArchi/ArchiSteamFarm.svg?label=License&maxAge=86400)](./LICENSE-2.0.txt)
[![GitHub Release](https://img.shields.io/github/release/JustArchi/ArchiSteamFarm.svg?label=Latest&maxAge=60)](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
[![Github All Releases](https://img.shields.io/github/downloads/JustArchi/ArchiSteamFarm/total.svg?label=Downloads&maxAge=60)](https://github.com/JustArchi/ArchiSteamFarm/releases)
[![Github Downloads](https://img.shields.io/github/downloads/JustArchi/ArchiSteamFarm/latest/total.svg?label=Downloads&maxAge=60)](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
[![Paypal Donate](https://img.shields.io/badge/PayPal-donate-yellow.svg)](https://www.paypal.me/JustArchi/1usd)
[![Paypal.me Donate](https://img.shields.io/badge/Paypal.me-donate-yellow.svg)](https://www.paypal.me/JustArchi/1usd)
[![Paypal Donate](https://img.shields.io/badge/Paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4)
[![Steam Donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
---
@@ -42,8 +43,10 @@ ASF officially supports Windows, Linux and OS X operating systems, including fol
- Windows 10 (Native)
- Windows 8.1 (Native)
- Windows 7 (Native)
- Windows Vista (Native)
- Windows 8 (Native)
- Windows 7 SP1 (Native)
- Windows Server 2012 R2 (Native)
- Windows Server 2008 R2 SP1 (Native)
- Debian 9 Stretch (Mono)
- Debian 8 Jessie (Mono)
- Ubuntu 16.04 (Mono)

View File

@@ -13,4 +13,4 @@ notifications:
method: POST
on_build_success: true
on_build_failure: true
on_build_status_changed: true
on_build_status_changed: false

9
cc.sh
View File

@@ -5,7 +5,6 @@ BUILD="Release"
AOT=0
CLEAN=0
MONO_ARGS=("--aot" "--llvm" "--server" "-O=all")
XBUILD_ARGS=("/nologo")
BINARIES=("ArchiSteamFarm/bin/Release/ArchiSteamFarm.exe")
SOLUTION="ArchiSteamFarm.sln"
@@ -29,6 +28,12 @@ XBUILD_ARGS+=("/p:Configuration=$BUILD")
cd "$(dirname "$(readlink -f "$0")")"
if [[ -f "mono_envsetup.sh" ]]; then
set +u
source "mono_envsetup.sh"
set -u
fi
if [[ -d ".git" ]] && hash git &>/dev/null; then
git pull || true
fi
@@ -60,7 +65,7 @@ if [[ "$AOT" -eq 1 && "$BUILD" = "Release" ]]; then
continue
fi
mono "${MONO_ARGS[@]}" "$BINARY"
mono --aot "$BINARY"
done
fi

File diff suppressed because one or more lines are too long

68
mono_envsetup.sh Normal file
View File

@@ -0,0 +1,68 @@
MONO_DEBUG_IF_AVAILABLE() {
local PREVIOUS_MONO_DEBUG="$MONO_DEBUG"
# Add change if needed
if [ -z "$PREVIOUS_MONO_DEBUG" ]; then
export MONO_DEBUG="$1"
elif echo "$PREVIOUS_MONO_DEBUG" | grep -Fq "$1"; then
echo "Success: $1 already exists"
return 0
else
export MONO_DEBUG="${PREVIOUS_MONO_DEBUG},${1}"
fi
# If we did a change, check if Mono supports that option
# If not, it will be listed as invalid option on line 1
if mono "" 2>&1 | head -n 1 | grep -Fq "$1"; then
echo "Failure: $1"
export MONO_DEBUG="$PREVIOUS_MONO_DEBUG"
return 1
fi
echo "Success: $1"
return 0
}
VERSION_GREATER() {
if [ "$1" = "$2" ]; then
return 1
fi
! VERSION_LESS_EQUAL "$1" "$2"
}
VERSION_GREATER_EQUAL() {
! VERSION_LESS "$1" "$2"
}
VERSION_LESS() {
if [ "$1" = "$2" ]; then
return 1
fi
VERSION_LESS_EQUAL "$1" "$2"
}
VERSION_LESS_EQUAL() {
[ "$1" = "$(echo -e "$1\n$2" | sort -V | head -n 1)" ]
}
echo "Mono environment setup executed!"
MONO_VERSION="$(mono -V | head -n 1 | cut -d ' ' -f 5)"
echo "Mono version: $MONO_VERSION"
if VERSION_GREATER_EQUAL "$MONO_VERSION" "4.6.0"; then
echo "INFO: Appending no-compact-seq-points to MONO_DEBUG..."
MONO_DEBUG_IF_AVAILABLE "no-compact-seq-points"
fi
if [ -z "$MONO_ENV_OPTIONS" ]; then
echo "INFO: Setting MONO_ENV_OPTIONS to: --server -O=all"
export MONO_ENV_OPTIONS="--server -O=all"
else
echo "INFO: Skipping setting of MONO_ENV_OPTIONS as it's already declared with value: $MONO_ENV_OPTIONS"
fi
echo "Mono environment setup finished!"

Binary file not shown.

View File

@@ -0,0 +1,768 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>NLog.Windows.Forms</name>
</assembly>
<members>
<member name="T:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer">
<summary>
Strings rendered with this rendrer would convert to links in the control. <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/>
</summary>
<remarks>
Internally this renderer replaces the rendered text with a GUID and stores the info in <see cref="P:NLog.LogEventInfo.Properties"/> by <see cref="F:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.LinkInfo.PropertyName"/> as a key
Actual rendering is done in <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.SendTheMessageToRichTextBox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)"/>
</remarks>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.Append(System.Text.StringBuilder,NLog.LogEventInfo)">
<summary>
Implementation of a <see cref="M:NLog.LayoutRenderers.LayoutRenderer.Append(System.Text.StringBuilder,NLog.LogEventInfo)"/>
</summary>
<param name="builder"></param>
<param name="logEvent"></param>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.Inner">
<summary>
Inner layout that actually provides text
</summary>
</member>
<member name="T:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.LinkInfo">
<summary>
Inernal class storing the captured link info, used by <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.SendTheMessageToRichTextBox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)"/> to convert the text to link and then identify the logEvent
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer.LinkInfo.PropertyName">
<summary>
Used as a key in <see cref="P:NLog.LogEventInfo.Properties"/>
</summary>
</member>
<member name="T:NLog.Windows.Forms.RichTextBoxWordColoringRule">
<summary>
Highlighting rule for Win32 colorful console.
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxWordColoringRule.#ctor">
<summary>
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxWordColoringRule"/> class.
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxWordColoringRule.#ctor(System.String,System.String,System.String)">
<summary>
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxWordColoringRule"/> class.
</summary>
<param name="text">The text to be matched..</param><param name="fontColor">Color of the text.</param><param name="backgroundColor">Color of the background.</param>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxWordColoringRule.#ctor(System.String,System.String,System.String,System.Drawing.FontStyle)">
<summary>
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxWordColoringRule"/> class.
</summary>
<param name="text">The text to be matched..</param><param name="textColor">Color of the text.</param><param name="backgroundColor">Color of the background.</param><param name="fontStyle">The font style.</param>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.Regex">
<summary>
Gets or sets the regular expression to be matched. You must specify either <c>text</c> or <c>regex</c>.
</summary>
<docgen category="Rule Matching Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.Text">
<summary>
Gets or sets the text to be matched. You must specify either <c>text</c> or <c>regex</c>.
</summary>
<docgen category="Rule Matching Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.WholeWords">
<summary>
Gets or sets a value indicating whether to match whole words only.
</summary>
<docgen category="Rule Matching Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.IgnoreCase">
<summary>
Gets or sets a value indicating whether to ignore case when comparing texts.
</summary>
<docgen category="Rule Matching Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.Style">
<summary>
Gets or sets the font style of matched text.
Possible values are the same as in <c>FontStyle</c> enum in <c>System.Drawing</c>.
</summary>
<docgen category="Formatting Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.CompiledRegex">
<summary>
Gets the compiled regular expression that matches either Text or Regex property.
</summary>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.FontColor">
<summary>
Gets or sets the font color.
Names are identical with KnownColor enum extended with Empty value which means that font color won't be changed.
</summary>
<docgen category="Formatting Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxWordColoringRule.BackgroundColor">
<summary>
Gets or sets the background color.
Names are identical with KnownColor enum extended with Empty value which means that background color won't be changed.
</summary>
<docgen category="Formatting Options" order="10"/>
</member>
<member name="T:NLog.Windows.Forms.RichTextBoxRowColoringRule">
<summary>
The row-coloring condition.
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.#cctor">
<summary>
Initializes static members of the RichTextBoxRowColoringRule class.
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.#ctor">
<summary>
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxRowColoringRule"/> class.
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.#ctor(System.String,System.String,System.String,System.Drawing.FontStyle)">
<summary>
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxRowColoringRule"/> class.
</summary>
<param name="condition">The condition.</param><param name="fontColor">Color of the foreground text.</param><param name="backColor">Color of the background text.</param><param name="fontStyle">The font style.</param>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.#ctor(System.String,System.String,System.String)">
<summary>
Initializes a new instance of the <see cref="T:NLog.Targets.RichTextBoxRowColoringRule"/> class.
</summary>
<param name="condition">The condition.</param><param name="fontColor">Color of the text.</param><param name="backColor">Color of the background.</param>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxRowColoringRule.CheckCondition(NLog.LogEventInfo)">
<summary>
Checks whether the specified log event matches the condition (if any).
</summary>
<param name="logEvent">Log event.
</param>
<returns>
A value of <see langword="true"/> if the condition is not defined or
if it matches, <see langword="false"/> otherwise.
</returns>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.Default">
<summary>
Gets the default highlighting rule. Doesn't change the color.
</summary>
<docgen category="Rule Matching Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.Condition">
<summary>
Gets or sets the condition that must be met in order to set the specified font color.
</summary>
<docgen category="Rule Matching Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.FontColor">
<summary>
Gets or sets the font color.
</summary>
<remarks>
Names are identical with KnownColor enum extended with Empty value which means that background color won't be changed.
</remarks>
<docgen category="Formatting Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.BackgroundColor">
<summary>
Gets or sets the background color.
</summary>
<remarks>
Names are identical with KnownColor enum extended with Empty value which means that background color won't be changed.
</remarks>
<docgen category="Formatting Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxRowColoringRule.Style">
<summary>
Gets or sets the font style of matched text.
</summary>
<remarks>
Possible values are the same as in <c>FontStyle</c> enum in <c>System.Drawing</c>
</remarks>
<docgen category="Formatting Options" order="10"/>
</member>
<member name="T:NLog.Windows.Forms.FormControlTarget">
<summary>
Logs text to Windows.Forms.Control.Text property control of specified Name.
</summary>
<example>
<p>
To set up the target in the <a href="config.html">configuration file</a>,
use the following syntax:
</p>
<code lang="XML" source="examples/targets/Configuration File/FormControl/NLog.config" />
<p>
The result is:
</p>
<img src="examples/targets/Screenshots/FormControl/FormControl.gif" />
<p>
To set up the log target programmatically similar to above use code like this:
</p>
<code lang="C#" source="examples/targets/Configuration API/FormControl/Form1.cs" />,
</example>
</member>
<member name="M:NLog.Windows.Forms.FormControlTarget.#ctor">
<summary>
Initializes a new instance of the <see cref="T:NLog.Windows.Forms.FormControlTarget"/> class.
</summary>
<remarks>
The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
</remarks>
</member>
<member name="M:NLog.Windows.Forms.FormControlTarget.Write(NLog.LogEventInfo)">
<summary>
Log message to control.
</summary>
<param name="logEvent">
The logging event.
</param>
</member>
<member name="P:NLog.Windows.Forms.FormControlTarget.ControlName">
<summary>
Gets or sets the name of control to which NLog will log write log text.
</summary>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.FormControlTarget.Append">
<summary>
Gets or sets a value indicating whether log text should be appended to the text of the control instead of overwriting it. </summary>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.FormControlTarget.FormName">
<summary>
Gets or sets the name of the Form on which the control is located.
</summary>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.FormControlTarget.ReverseOrder">
<summary>
Gets or sets whether new log entry are added to the start or the end of the control
</summary>
</member>
<member name="T:NLog.Windows.Forms.FormHelper">
<summary>
Form helper methods.
</summary>
</member>
<member name="M:NLog.Windows.Forms.FormHelper.CreateRichTextBox(System.String,System.Windows.Forms.Form)">
<summary>
Creates RichTextBox and docks in parentForm.
</summary>
<param name="name">Name of RichTextBox.</param>
<param name="parentForm">Form to dock RichTextBox.</param>
<returns>Created RichTextBox.</returns>
</member>
<member name="M:NLog.Windows.Forms.FormHelper.FindControl(System.String,System.Windows.Forms.Control)">
<summary>
Finds control embedded on searchControl.
</summary>
<param name="name">Name of the control.</param>
<param name="searchControl">Control in which we're searching for control.</param>
<returns>A value of null if no control has been found.</returns>
</member>
<member name="M:NLog.Windows.Forms.FormHelper.FindControl``1(System.String,System.Windows.Forms.Control)">
<summary>
Finds control of specified type embended on searchControl.
</summary>
<typeparam name="TControl">The type of the control.</typeparam>
<param name="name">Name of the control.</param>
<param name="searchControl">Control in which we're searching for control.</param>
<returns>
A value of null if no control has been found.
</returns>
</member>
<member name="M:NLog.Windows.Forms.FormHelper.CreateForm(System.String,System.Int32,System.Int32,System.Boolean,System.Boolean,System.Boolean)">
<summary>
Creates a form.
</summary>
<param name="name">Name of form.</param>
<param name="width">Width of form.</param>
<param name="height">Height of form.</param>
<param name="show">Auto show form.</param>
<param name="showMinimized">If set to <c>true</c> the form will be minimized.</param>
<param name="toolWindow">If set to <c>true</c> the form will be created as tool window.</param>
<returns>Created form.</returns>
</member>
<member name="M:NLog.Windows.Forms.FormHelper.ChangeSelectionToLink(System.Windows.Forms.RichTextBox,System.String,System.String)">
<summary>
Replaces currently selected text in the RTB control with a link
</summary>
<param name="textBox">target control</param>
<param name="text">visible text of the new link</param>
<param name="hyperlink">hidden part of the new link</param>
<remarks>
Based on http://www.codeproject.com/info/cpol10.aspx
</remarks>
</member>
<member name="M:NLog.Windows.Forms.FormHelper.SetSelectionStyle(System.Windows.Forms.RichTextBox,System.UInt32,System.UInt32)">
<summary>
Sets selection style for RichTextBox
https://msdn.microsoft.com/en-us/library/windows/desktop/bb787883(v=vs.85).aspx
</summary>
<param name="textBox">target control</param>
<param name="mask">Specifies the parts of the CHARFORMAT2 structure that contain valid information.</param>
<param name="effect">A set of bit flags that specify character effects.</param>
<remarks>
Based on http://www.codeproject.com/info/cpol10.aspx
</remarks>
</member>
<member name="T:NLog.Windows.Forms.FormHelper.CHARFORMAT2_STRUCT">
<summary>
CHARFORMAT2 structure, contains information about character formatting in a rich edit control.
</summary>
see https://msdn.microsoft.com/en-us/library/windows/desktop/bb787883(v=vs.85).aspx
</member>
<member name="T:NLog.Windows.Forms.MessageBoxTarget">
<summary>
Pops up log messages as message boxes.
</summary>
<seealso href="https://github.com/nlog/nlog/wiki/MessageBox-target">Documentation on NLog Wiki</seealso>
<example>
<p>
To set up the target in the <a href="config.html">configuration file</a>,
use the following syntax:
</p>
<code lang="XML" source="examples/targets/Configuration File/MessageBox/NLog.config" />
<p>
This assumes just one target and a single rule. More configuration
options are described <a href="config.html">here</a>.
</p>
<p>
The result is a message box:
</p>
<img src="examples/targets/Screenshots/MessageBox/MessageBoxTarget.gif" />
<p>
To set up the log target programmatically use code like this:
</p>
<code lang="C#" source="examples/targets/Configuration API/MessageBox/Simple/Example.cs" />
</example>
</member>
<member name="M:NLog.Windows.Forms.MessageBoxTarget.#ctor">
<summary>
Initializes a new instance of the <see cref="T:NLog.Windows.Forms.MessageBoxTarget"/> class.
</summary>
<remarks>
The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
</remarks>
</member>
<member name="M:NLog.Windows.Forms.MessageBoxTarget.Write(NLog.LogEventInfo)">
<summary>
Displays the message box with the log message and caption specified in the Caption
parameter.
</summary>
<param name="logEvent">The logging event.</param>
</member>
<member name="M:NLog.Windows.Forms.MessageBoxTarget.Write(NLog.Common.AsyncLogEventInfo[])">
<summary>
Displays the message box with the array of rendered logs messages and caption specified in the Caption
parameter.
</summary>
<param name="logEvents">The array of logging events.</param>
</member>
<member name="P:NLog.Windows.Forms.MessageBoxTarget.Caption">
<summary>
Gets or sets the message box title.
</summary>
<docgen category='UI Options' order='10' />
</member>
<member name="T:NLog.Windows.Forms.RichTextBoxTarget">
<summary>
Log text a Rich Text Box control in an existing or new form.
</summary>
<seealso href="https://github.com/nlog/nlog/wiki/RichTextBox-target">Documentation on NLog Wiki</seealso>
<example>
<p>
To set up the target in the <a href="config.html">configuration file</a>,
use the following syntax:
</p><code lang="XML" source="examples/targets/Configuration File/RichTextBox/Simple/NLog.config">
</code>
<p>
The result is:
</p><img src="examples/targets/Screenshots/RichTextBox/Simple.gif"/><p>
To set up the target with coloring rules in the <a href="config.html">configuration file</a>,
use the following syntax:
</p><code lang="XML" source="examples/targets/Configuration File/RichTextBox/RowColoring/NLog.config">
</code>
<code lang="XML" source="examples/targets/Configuration File/RichTextBox/WordColoring/NLog.config">
</code>
<p>
The result is:
</p><img src="examples/targets/Screenshots/RichTextBox/RowColoring.gif"/><img src="examples/targets/Screenshots/RichTextBox/WordColoring.gif"/><p>
To set up the log target programmatically similar to above use code like this:
</p><code lang="C#" source="examples/targets/Configuration API/RichTextBox/Simple/Form1.cs">
</code>
,
<code lang="C#" source="examples/targets/Configuration API/RichTextBox/RowColoring/Form1.cs">
</code>
for RowColoring,
<code lang="C#" source="examples/targets/Configuration API/RichTextBox/WordColoring/Form1.cs">
</code>
for WordColoring
</example>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.LinkPrefix">
<summary>
Internal prefix that is added to the link id in RTF
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.#cctor">
<summary>
Initializes static members of the RichTextBoxTarget class.
</summary>
<remarks>
The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
</remarks>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.ReInitializeAllTextboxes(System.Windows.Forms.Form)">
<summary>
Attempts to attach existing targets that have yet no textboxes to controls that exist on specified form if appropriate
</summary>
<remarks>
Setting <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.AllowAccessoryFormCreation"/> to true (default) actually causes target to always have a textbox
(after having <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.InitializeTarget"/> called), so such targets are not affected by this method.
</remarks>
<param name="form">a Form to check for RichTextBoxes</param>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.GetTargetByControl(System.Windows.Forms.RichTextBox)">
<summary>
Returns a target attached to a given RichTextBox control
</summary>
<param name="control">a RichTextBox control for which the target is to be returned</param>
<returns>A RichTextBoxTarget attached to a given control or <code>null</code> if no target is attached</returns>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.#ctor">
<summary>
Initializes a new instance of the <see cref="T:NLog.Windows.Forms.RichTextBoxTarget"/> class.
</summary>
<remarks>
The default value of the layout is: <code>${longdate}|${level:uppercase=true}|${logger}|${message}</code>
</remarks>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.messageRetention">
<summary>
Actual value of the <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.MessageRetention"/>.
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.lastLoggedTextBoxControl">
<summary>
A textbox to which we have logged last time. Used to prevent duplicating messages in the same textbox in case of config reload and RichTextBoxTargetMessageRetentionStrategy.All
see https://github.com/NLog/NLog.Windows.Forms/pull/22
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.messageQueueLock">
<summary>
a lock object used to synchronize access to <see cref="F:NLog.Windows.Forms.RichTextBoxTarget.messageQueue"/>
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.messageQueue">
<summary>
A queue used to store messages based on <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.MessageRetention"/>.
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.supportLinks">
<summary>
Actual value of the <see cref="E:NLog.Windows.Forms.RichTextBoxTarget.LinkClicked"/> property
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkedEventsLock">
<summary>
Lock for <see cref="F:NLog.Windows.Forms.RichTextBoxTarget.linkedEvents"/> dictionary access
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkedEvents">
<summary>
A map from link id to a corresponding log event
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkRegexLock">
<summary>
Used to synchronize lazy initialization of <see cref="F:NLog.Windows.Forms.RichTextBoxTarget.linkAddRegex"/> and <see cref="F:NLog.Windows.Forms.RichTextBoxTarget.linkRemoveRtfRegex"/> in <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/>.set
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkAddRegex">
<summary>
Used to capture link placeholders in <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.SendTheMessageToRichTextBox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)"/>
Lazily initialized in <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/>.set(true). Assure checking <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/> before accessing the field
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTarget.linkRemoveRtfRegex">
<summary>
Used to parse RTF with links when removing excess lines in <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.SendTheMessageToRichTextBox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)"/>
Lazily initialized in <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/>.set(true). Assure checking <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks"/> before accessing the field
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.InitializeTarget">
<summary>
Initializes the target. Can be used by inheriting classes
to initialize logging.
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.HandleError(System.String,System.Object[])">
<summary>
Called from constructor when error is detected. In case LogManager.ThrowExceptions is enabled, throws the exception, otherwise - logs the problem message
</summary>
<param name="message">exception/log message format</param>
<param name="args">message format arguments</param>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.CreateAccessoryForm">
<summary>
Used to create accessory form with textbox in case specified form or control were not found during InitializeTarget() and AllowAccessoryFormCreation==true
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.AttachToControl(System.Windows.Forms.Form,System.Windows.Forms.RichTextBox)">
<summary>
Used to (re)initialize target when attaching it to another RTB control
</summary>
<param name="form">form owning textboxControl</param>
<param name="textboxControl">a new control to attach to</param>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox_LinkClicked(System.Object,System.Windows.Forms.LinkClickedEventArgs)">
<summary>
Attached to RTB-control's LinkClicked event to convert link text to logEvent
</summary>
<param name="sender"></param>
<param name="e"></param>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.DetachFromControl">
<summary>
if <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.CreatedForm"/> is true, then destroys created form. Resets <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.CreatedForm"/>, <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.TargetForm"/> and <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox"/> to default values
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.CloseTarget">
<summary>
Closes the target and releases any unmanaged resources.
</summary>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.Write(NLog.LogEventInfo)">
<summary>
Log message to RichTextBox.
</summary>
<param name="logEvent">The logging event.</param>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.DoSendMessageToTextbox(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)">
<summary>
Actually sends log message to <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox"/>
</summary>
<param name="logMessage">a message to send</param>
<param name="rule">matching coloring rule</param>
<param name="logEvent">original logEvent</param>
<returns>true if the message was actually sent (i.e. <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox"/> is not null and not disposed, and no exception happened during message send)</returns>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.FindMatchingRule(NLog.LogEventInfo)">
<summary>
Find first matching rule
</summary>
<param name="logEvent"></param>
<returns></returns>
</member>
<member name="M:NLog.Windows.Forms.RichTextBoxTarget.StoreMessage(System.String,NLog.Windows.Forms.RichTextBoxRowColoringRule,NLog.LogEventInfo)">
<summary>
Stores a new message in internal queue, if it exists. Removes overflowing messages.
</summary>
<param name="logMessage">a message to store</param>
<param name="rule">a corresponding coloring rule</param>
<param name="logEvent">original LogEvent</param>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.DefaultRowColoringRules">
<summary>
Gets the default set of row coloring rules which applies when <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.UseDefaultRowColoringRules"/> is set to true.
</summary>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.ControlName">
<summary>
Gets or sets the Name of RichTextBox to which Nlog will write.
</summary>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.FormName">
<summary>
Gets or sets the name of the Form on which the control is located.
If there is no open form of a specified name than NLog will create a new one.
</summary>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.UseDefaultRowColoringRules">
<summary>
Gets or sets a value indicating whether to use default coloring rules.
</summary>
<docgen category='Highlighting Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.RowColoringRules">
<summary>
Gets the row coloring rules.
</summary>
<docgen category='Highlighting Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.WordColoringRules">
<summary>
Gets the word highlighting rules.
</summary>
<docgen category='Highlighting Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.ToolWindow">
<summary>
Gets or sets a value indicating whether the created window will be a tool window.
</summary>
<remarks>
This parameter is ignored when logging to existing form control.
Tool windows have thin border, and do not show up in the task bar.
</remarks>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.ShowMinimized">
<summary>
Gets or sets a value indicating whether the created form will be initially minimized.
</summary>
<remarks>
This parameter is ignored when logging to existing form control.
</remarks>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.Width">
<summary>
Gets or sets the initial width of the form with rich text box.
</summary>
<remarks>
This parameter is ignored when logging to existing form control.
</remarks>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.Height">
<summary>
Gets or sets the initial height of the form with rich text box.
</summary>
<remarks>
This parameter is ignored when logging to existing form control.
</remarks>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.AutoScroll">
<summary>
Gets or sets a value indicating whether scroll bar will be moved automatically to show most recent log entries.
</summary>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.MaxLines">
<summary>
Gets or sets the maximum number of lines the rich text box will store (or 0 to disable this feature).
</summary>
<remarks>
After exceeding the maximum number, first line will be deleted.
</remarks>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.TargetForm">
<summary>
Gets or sets the form to log to.
</summary>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.TargetRichTextBox">
<summary>
Gets or sets the rich text box to log to.
</summary>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.CreatedForm">
<summary>
Form created (true) or used an existing (false). Set after <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.InitializeTarget"/>. Can be true only if <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.AllowAccessoryFormCreation"/> is set to true (default).
</summary>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.AllowAccessoryFormCreation">
<summary>
Gets or sets a value indicating whether to create accessory form if the specified form/control combination was not found during target initialization.
</summary>
<remarks>
If set to false and the control was not found during target initialiation, the target would skip events until the control is found during <see cref="M:NLog.Windows.Forms.RichTextBoxTarget.ReInitializeAllTextboxes(System.Windows.Forms.Form)"/> call
</remarks>
<docgen category="Form Options" order="10"/>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.MessageRetention">
<summary>
gets or sets the message retention strategy which determines how the target handles messages when there's no control attached, or when switching between controls
</summary>
<remarks>
</remarks>
<docgen category='Form Options' order='10' />
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.SupportLinks">
<summary>
If set to true, using "rtb-link" renderer (<see cref="T:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer"/>) would create clickable links in the control.
<seealso cref="E:NLog.Windows.Forms.RichTextBoxTarget.LinkClicked"/>
</summary>
</member>
<member name="E:NLog.Windows.Forms.RichTextBoxTarget.LinkClicked">
<summary>
Event fired when the user clicks on a link in the control created by the "rtb-link" renderer (<see cref="T:NLog.Windows.Forms.RichTextBoxLinkLayoutRenderer"/>).
<seealso cref="T:NLog.Windows.Forms.RichTextBoxTarget.DelLinkClicked"/>
</summary>
</member>
<member name="P:NLog.Windows.Forms.RichTextBoxTarget.LinkedEventsCount">
<summary>
Returns number of events stored for active links in the control.
Used only in tests to check that non needed events are removed.
</summary>
</member>
<member name="T:NLog.Windows.Forms.RichTextBoxTarget.DelLinkClicked">
<summary>
Type of delegate for <see cref="E:NLog.Windows.Forms.RichTextBoxTarget.LinkClicked"/> event.
</summary>
<param name="sender">The target that caused the event</param>
<param name="linkText">Visible text of the link being clicked</param>
<param name="logEvent">Original log event that caused a line with the link</param>
</member>
<member name="T:NLog.Windows.Forms.RichTextBoxTargetMessageRetentionStrategy">
<summary>
How to handle messages when switching between target controls or no control is attached at all
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTargetMessageRetentionStrategy.None">
<summary>
Just skip logging events when no target control attached. Only new messages would be sent to rich text box after attachement.
No additional resources spent on this.
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTargetMessageRetentionStrategy.OnlyMissed">
<summary>
Store logging events only during periods when no target control attached. Only these messages would be sent to rich text box after attachment. Messages that were sent to previous textbox are not stored and would not be shown.
Number of events stored is limited by <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.MaxLines"/>.
</summary>
</member>
<member name="F:NLog.Windows.Forms.RichTextBoxTargetMessageRetentionStrategy.All">
<summary>
Store all events in internal queue. After attaching to a new control all the stored messages would be repeated in it, including messages that were sent to previous textbox.
Number of messages stored is limited by <see cref="P:NLog.Windows.Forms.RichTextBoxTarget.MaxLines"/>.
</summary>
</member>
</members>
</doc>

Binary file not shown.

15
run.sh
View File

@@ -6,7 +6,6 @@ BUILD="Release"
UNTIL_CLEAN_EXIT=0
ASF_ARGS=("")
MONO_ARGS=("--llvm" "--server" "-O=all")
PRINT_USAGE() {
echo "Usage: $0 [--until-clean-exit] [--cryptkey=] [--path=] [--server] [debug/release]"
@@ -25,12 +24,14 @@ for ARG in "$@"; do
esac
done
if [[ "$BUILD" = "Debug" ]]; then
MONO_ARGS+=("--debug")
fi
cd "$(dirname "$(readlink -f "$0")")"
if [[ -f "mono_envsetup.sh" ]]; then
set +u
source "mono_envsetup.sh"
set -u
fi
BINARY="ArchiSteamFarm/bin/$BUILD/ArchiSteamFarm.exe"
if [[ ! -f "$BINARY" ]]; then
@@ -39,12 +40,12 @@ if [[ ! -f "$BINARY" ]]; then
fi
if [[ "$UNTIL_CLEAN_EXIT" -eq 0 ]]; then
mono "${MONO_ARGS[@]}" "$BINARY" "${ASF_ARGS[@]}"
mono "$BINARY" "${ASF_ARGS[@]}"
exit $?
fi
while [[ -f "$BINARY" ]]; do
if mono "${MONO_ARGS[@]}" "$BINARY" "${ASF_ARGS[@]}"; then
if mono "$BINARY" "${ASF_ARGS[@]}"; then
break
fi
sleep 1