Compare commits

..

176 Commits

Author SHA1 Message Date
JustArchi
69d6d6e10b Use ASF instead of Main for global logging routines, closes #280 2016-07-04 22:46:08 +02:00
JustArchi
38eb130217 Add support for logging/catching also very early messages 2016-07-04 22:30:29 +02:00
JustArchi
c00943bb74 Initialize console loggers only once 2016-07-04 22:03:12 +02:00
JustArchi
e134d6f6b6 So happy to fix this 2016-07-04 21:58:50 +02:00
JustArchi
79b1019ec3 Allow overriding default ASF NLog settings, #279 2016-07-04 21:40:09 +02:00
JustArchi
4b0e65b76b Treat timeouts as warnings 2016-07-04 20:57:33 +02:00
JustArchi
49c4f94bba But don't commit my tests 2016-07-04 19:35:50 +02:00
JustArchi
58cc203577 Use fatal level for unhandled exceptions 2016-07-04 19:33:54 +02:00
JustArchi
6128d87cbf Always delete old log file 2016-07-04 19:25:01 +02:00
JustArchi
6ada40fc2f Implement NLog for ASF logging, closes #274, #279 2016-07-04 19:22:46 +02:00
JustArchi
f8ab06871f Bump 2016-07-03 09:41:51 +02:00
JustArchi
066c1358db Resurrect ASF Windows Service as ASF-Service.exe, #268 2016-07-03 09:36:42 +02:00
JustArchi
0f3b7becdf Statistics: Don't join group chat anymore
It was really cool in first ASF days, when it actually made sense. Once we reached 1k+ active accounts, it doesn't make sense anymore as group chat has 1k limit anyway.
2016-07-03 05:57:33 +02:00
JustArchi
dcda639cb1 Misc 2016-07-03 01:19:31 +02:00
JustArchi
27a555dde6 And back to fixing Mono 2016-07-03 01:18:09 +02:00
JustArchi
0e699eb95b Commit missing files 2016-07-03 00:52:45 +02:00
JustArchi
17dc0e3911 Bump 2016-07-03 00:46:27 +02:00
JustArchi
cd462f607e Move to Fody.Costura for repacking
This should be the best way for both Windows and Mono, plus is much more cleaner, get rid of those crappy tools
2016-07-03 00:46:16 +02:00
JustArchi
4e6723a7e2 Partially revert 9cc7e30
Indeed LibZ seems to not being functional on Mono, so we keep using ILRepack for Mono, and LibZ for Windows
Final binary is compiled on Windows anyway, and it seems to work properly on both Windows and Mono, so this is purely for making people using self-compiled binaries happy
2016-07-02 23:40:57 +02:00
JustArchi
9cc7e30c98 Switch from ILRepack to LibZ
We tried ILMerge, we tried ILRepack, so now maybe LibZ will do it's thing?
The objective is solving false-positives of AVs, it might get reverted if there is no improvement
2016-07-02 23:31:37 +02:00
JustArchi
cbf212aff5 Don't launch all parallel tasks immediately
It seems that the runtime is actually smarter than I thought, and can optimize tasks creation for maximum performance better than we'd do
2016-07-02 22:05:29 +02:00
JustArchi
ab4630c191 Add CodeStyle settings
In case somebody would like to stick with mine
2016-07-02 05:50:22 +02:00
JustArchi
e1e68d2d6a Enable greed mode
Barely anybody donates 😢
2016-07-02 05:46:23 +02:00
JustArchi
b594af5c44 Bump 2016-07-02 05:30:05 +02:00
JustArchi
d243bf260f ILRepack bump 2016-07-02 02:22:52 +02:00
JustArchi
af956f22e9 Allow empty descriptions 2016-07-01 16:34:32 +02:00
JustArchi
aac72d4c5b Avoid code duplication 2016-07-01 16:33:27 +02:00
JustArchi
dc98d794b8 Misc 2016-06-30 18:20:26 +02:00
JustArchi
831856cf15 Misc 2016-06-30 18:13:22 +02:00
Łukasz Domeradzki
686c0aaf8b Update CONTRIBUTING.md 2016-06-30 11:48:04 +02:00
JustArchi
9eadf805b5 Bump 2016-06-30 09:46:32 +02:00
JustArchi
3b8315073b Fix regressions 2016-06-30 09:33:22 +02:00
JustArchi
bd028ba459 Packages update 2016-06-29 21:33:38 +02:00
JustArchi
49344751af Add missing copyright notice 2016-06-29 21:24:05 +02:00
JustArchi
97724c95a7 Bump 2016-06-29 20:42:53 +02:00
JustArchi
627c4576f5 Misc 2016-06-29 20:35:33 +02:00
JustArchi
a90b5adaf5 Further hardening 2016-06-29 20:33:52 +02:00
JustArchi
1c74fddbac Further hardening 2016-06-29 20:20:38 +02:00
JustArchi
4380679df6 JSON code review 2016-06-29 20:18:47 +02:00
JustArchi
e3030dccdb Fix GetTradeHoldDuration() sometimes failing
Forgot to ensure the session is active
2016-06-29 09:11:59 +02:00
JustArchi
e6509ae1a3 Use timeouts instead 2016-06-28 23:25:18 +02:00
JustArchi
2bc202dd3a Revert "Respond with Error on Volvo fuckup, #266"
This reverts commit 5bc6af718e.

https://www.steamgifts.com/go/comment/6Fc717c
2016-06-28 22:57:54 +02:00
JustArchi
b9e8823bf1 Bump 2016-06-28 10:58:16 +02:00
JustArchi
e7cd488435 Give up for now 2016-06-28 10:54:39 +02:00
JustArchi
4c09d95b9a Let's test something else 2016-06-28 10:44:07 +02:00
JustArchi
1fe5cfff49 Fix broken Mono after #268 2016-06-28 10:36:28 +02:00
JustArchi
57014aab6d Warn also on empty (invalid) cryptkey 2016-06-28 09:26:11 +02:00
JustArchi
4a5bff1b84 Add --cryptkey, #267 2016-06-28 09:24:43 +02:00
JustArchi
5f0ce543ae Bump 2016-06-28 08:03:48 +02:00
JustArchi
25094592c9 Misc 2016-06-28 07:50:44 +02:00
JustArchi
8f75042b54 Silence resharper over obsolete code that will be removed soon 2016-06-28 07:21:03 +02:00
JustArchi
34c1016218 Misc 2016-06-28 07:18:39 +02:00
JustArchi
7351d07518 Improve ArchiService
- Correct DisplayName
- Implement proper Shutdown sequence instead of 20 seconds timeout-kill
- Misc
2016-06-28 07:14:51 +02:00
JustArchi
5a8701444a Formatting + code review 2016-06-28 06:58:59 +02:00
Łukasz Domeradzki
8db29fa5a7 Merge pull request #268 from justin-gerhardt/master
adding ability to run as a windows service
2016-06-28 06:28:54 +02:00
Justin Gerhardt
056b793262 adding ability to run as a windows service 2016-06-28 00:09:42 -04:00
JustArchi
a4383cdb89 Implement superior ProtectedDataForCurrentUser encryption 2016-06-28 05:24:30 +02:00
JustArchi
d5514422b6 Kill Base64 encryption
It doesn't make any sense when AES is available, not to mention that it's not even proper encryption...
2016-06-28 04:52:00 +02:00
JustArchi
3627a01f59 Correct example.json, default is 0 2016-06-28 04:36:31 +02:00
JustArchi
0dc131b79b Misc 2016-06-28 04:34:51 +02:00
JustArchi
8d9fbce2ed Add support for encrypted passwords, closes #267 2016-06-28 04:32:48 +02:00
JustArchi
a01e718e28 Misc 2016-06-27 21:28:06 +02:00
JustArchi
9bed5f013a Misc 2016-06-27 21:21:22 +02:00
JustArchi
2cf2df62c5 This is no longer required 2016-06-27 21:17:23 +02:00
JustArchi
9bac3915da Move MaxGamesPlayedConcurrently logic higher 2016-06-27 21:15:30 +02:00
JustArchi
93191f9066 Add logic for MaxGamesPlayedConcurrently
This is a limit introduced by Steam Network, limit for Steam Client is 30, but we don't have to respect that one
2016-06-27 21:09:58 +02:00
JustArchi
ebc8c1674b Prefer fully synchronous code during saving
We must ensure no race condition during lock statement and exiting lock clause to run async code - Thread.Sleep() is bad, but we never expect to reach that point in normal conditions, and in faulty ones, it improves the chances of things succeeding in next try
2016-06-27 19:05:24 +02:00
JustArchi
5bc6af718e Respond with Error on Volvo fuckup, #266 2016-06-27 18:56:11 +02:00
JustArchi
579d9e1cbf What the fuck VS 2016-06-27 18:48:25 +02:00
JustArchi
c7d0fb1aac Fix formatting 2016-06-27 18:46:50 +02:00
JustArchi
1f4a4cc6b7 Database saving hardening, #265 2016-06-27 18:44:48 +02:00
JustArchi
612ab87626 Bump 2016-06-27 02:31:25 +02:00
JustArchi
951d58161f Use SharedInfo + Add ConfigGenerator version check 2016-06-27 02:07:27 +02:00
JustArchi
d68bb01174 Add MaxTradeHoldDuration 2016-06-27 01:45:41 +02:00
JustArchi
da411b81b9 Bump 2016-06-27 01:01:55 +02:00
JustArchi
82c2d3ec15 Never register bot with broken database, closes #265 2016-06-27 00:38:38 +02:00
JustArchi
788f5c94a2 Reduce likeness of accepting/rejecting outdated trades 2016-06-26 23:01:30 +02:00
JustArchi
4d37a775cf Add case-insensitivity to commands 2016-06-26 22:38:03 +02:00
JustArchi
766a638f0d Misc 2016-06-26 20:41:21 +02:00
JustArchi
12aa933355 Misc 2016-06-26 20:29:56 +02:00
JustArchi
9bc76ca1fe Misc 2016-06-25 07:03:39 +02:00
JustArchi
339a56dc80 Bump 2016-06-25 04:10:13 +02:00
JustArchi
d16228b2a5 Steam talks more crap than I thought 2016-06-25 04:05:30 +02:00
JustArchi
99dbca1b36 Code review 2016-06-25 03:56:49 +02:00
JustArchi
943bc7e3d9 Further optimize confirmations related to trading
Thanks to that we're in fact asking for confirmations only if we accepted something, request succeeded, and we lost items
2016-06-25 03:52:02 +02:00
JustArchi
9535479602 Bump 2016-06-24 22:29:30 +02:00
JustArchi
90ade53ae7 Compilation fix 2016-06-24 22:27:49 +02:00
JustArchi
9a51386b7e Bump 2016-06-24 22:27:22 +02:00
JustArchi
03ee96057f Add GiftsLimiterDelay 2016-06-24 22:26:52 +02:00
JustArchi
a23bca7960 Actually handle gift only one time regardless 2016-06-24 20:54:21 +02:00
JustArchi
6b4ae6a4d7 Do not attempt to handle the same gift more than one time 2016-06-24 20:27:23 +02:00
JustArchi
5c80fd158d Misc 2016-06-24 19:26:21 +02:00
JustArchi
885800c539 Closes #263 2016-06-24 16:58:49 +02:00
JustArchi
920d4b9ed6 Misc 2016-06-24 05:41:32 +02:00
JustArchi
2d02bd609e Add CONTRIBUTING.md 2016-06-24 05:39:32 +02:00
JustArchi
e658ae33b1 Misc 2016-06-24 03:17:11 +02:00
JustArchi
379018866b Bump 2016-06-24 02:40:44 +02:00
JustArchi
1ad1e8b792 Use better format for API 2016-06-24 01:55:39 +02:00
JustArchi
c3dde4c822 Final fix 2016-06-24 01:44:58 +02:00
JustArchi
b40dc2e572 Calculate proper dupe values for the same cards 2016-06-24 01:27:11 +02:00
JustArchi
70bdd34d66 Increase default LoginLimiterDelay to 10
Seems to fix semi-rare 429 rate-limiting errors. Maybe GabeN increased limits?
2016-06-23 16:00:15 +02:00
JustArchi
56a6e10189 Correct retry logic of API requests
Timeout will result in WebClient does not support concurrent I/O operations for concurrent calls
Therefore, retry with new WebClient instead
2016-06-22 02:02:43 +02:00
JustArchi
41f8a0a474 Add !api 2016-06-20 21:20:38 +02:00
JustArchi
92f347e28b Bump 2016-06-20 20:47:09 +02:00
JustArchi
1a1914540c Fix !2faok with many confirmations 2016-06-20 20:29:12 +02:00
JustArchi
449e4f9511 Bump 2016-06-20 17:04:07 +02:00
JustArchi
959bf98039 Divide !pause into !pause and !resume 2016-06-20 17:03:55 +02:00
JustArchi
017c5eb7ef Fix misc issue with steam gifts in trades 2016-06-20 15:02:26 +02:00
JustArchi
9e575584a8 Bump 2016-06-20 14:41:14 +02:00
JustArchi
19da8c6d11 Try to solve language problem in non-invasive way 2016-06-20 14:40:14 +02:00
JustArchi
3d19a69c60 Let's hope this is the last one 2016-06-20 13:51:17 +02:00
JustArchi
f6a8d16c85 More fixes 2016-06-20 13:45:12 +02:00
JustArchi
f13991c2da Bump... 2016-06-20 13:26:43 +02:00
JustArchi
40a3d6558d GabeN broke more than I thought 2016-06-20 13:26:30 +02:00
JustArchi
fea76a3dda Derp 2016-06-20 13:18:06 +02:00
JustArchi
1087c01a2c And don't you dare to break again 2016-06-20 13:15:35 +02:00
JustArchi
fd49ff5483 Thanks GabeN 2016-06-20 13:09:27 +02:00
JustArchi
0f5d9a665c Bump 2016-06-20 13:01:42 +02:00
JustArchi
ad63432645 I must stop forgetting about bot identifiers 2016-06-20 12:53:17 +02:00
JustArchi
f36681ea18 Be more verbose on progress 2016-06-20 12:52:28 +02:00
JustArchi
ce94035d98 Bump 2016-06-20 08:37:31 +02:00
JustArchi
7583e50cf3 Bump 2016-06-20 08:35:47 +02:00
JustArchi
687de60476 Final performance improvements of new ASF 2FA 2016-06-20 06:51:42 +02:00
JustArchi
e181eb354b Revert "Closes #253"
This reverts commit 4e5ddefac9.

I didn't like the new format.
2016-06-20 06:32:40 +02:00
JustArchi
4e5ddefac9 Closes #253 2016-06-19 22:25:53 +02:00
JustArchi
e588ba3d2c Misc 2016-06-19 15:03:42 +02:00
JustArchi
6d087a9ac9 Misc 2016-06-19 13:59:56 +02:00
JustArchi
529d366b6c Revert "Try to fix Travis"
This reverts commit 4657d00d11.
2016-06-19 12:53:04 +02:00
JustArchi
4657d00d11 Try to fix Travis 2016-06-19 12:47:27 +02:00
JustArchi
3a5edab651 Code review 2016-06-19 12:20:12 +02:00
JustArchi
d662d9dd6a Misc 2016-06-19 09:34:09 +02:00
JustArchi
e508e99d14 Be smarter ASF 2016-06-19 07:37:31 +02:00
JustArchi
fdc705e955 Further enhance new 2FA, closes #161 2016-06-19 07:25:02 +02:00
JustArchi
38f48841bd Time to break things, closes #169 2016-06-19 05:57:29 +02:00
JustArchi
2ebce59ee7 WIP: Gigantic work on #252
TODO: Market confirmations, cleanup, code review, shitload of tests...
2016-06-19 05:40:46 +02:00
JustArchi
adefa6446d Fix SteamAuth memory leak
I'll need to rewrite the entire module sooner or later anyway...
2016-06-18 22:45:44 +02:00
JustArchi
f18c3b301e Uh 2016-06-15 03:39:16 +02:00
JustArchi
b79265e74a Misc 2016-06-15 03:38:51 +02:00
JustArchi
50853d8d7e Add license badge 2016-06-15 03:36:50 +02:00
JustArchi
7e084bf50b Code review 2016-06-13 18:08:19 +02:00
JustArchi
60a02a4c6a Misc 2016-06-12 23:01:17 +02:00
JustArchi
fcfdbdd220 Bump 2016-06-12 03:06:31 +02:00
JustArchi
ac10f32431 Forward ArchiBoT logic of trade holds for steam sale cards 2016-06-12 02:18:18 +02:00
JustArchi
5d7e0290d7 Bump 2016-06-11 03:42:53 +02:00
JustArchi
f170f16919 Catch SendAsync() exception when debugging 2016-06-11 03:39:25 +02:00
JustArchi
96d9ea6056 Bump 2016-06-11 02:02:01 +02:00
JustArchi
d8bf424ac3 Make STM accept trading cards only, #245 2016-06-11 00:45:07 +02:00
JustArchi
95d2860afd Add AppVeyor/Travis notifications to ASF chat 2016-06-10 21:45:51 +02:00
JustArchi
a75ed7047b Misc 2016-06-10 21:11:18 +02:00
JustArchi
d627570cc9 Bump 2016-06-10 21:10:43 +02:00
JustArchi
4e22d7fcd1 Disgusting fix for broken Mono 2016-06-10 20:51:10 +02:00
JustArchi
bb05f4c67a Misc 2016-06-10 17:40:37 +02:00
JustArchi
238838b0fd Misc 2016-06-10 17:06:20 +02:00
JustArchi
ae8413b72b Add chat link 2016-06-10 16:48:46 +02:00
JustArchi
027e301420 Turns out to be mono bug, hooray! 2016-06-10 01:27:07 +02:00
JustArchi
1a6c5a3cff Fix trades with non-steam items 2016-06-10 01:16:05 +02:00
JustArchi
378a87bc86 Fix broken tabs 2016-06-10 00:49:44 +02:00
JustArchi
27635d260b Code review + some extra debug 2016-06-10 00:47:38 +02:00
JustArchi
8f99620598 Bump 2016-06-09 23:53:21 +02:00
JustArchi
bb285512d1 Update ILRepack to another self-compiled version, #243 2016-06-09 23:49:01 +02:00
JustArchi
88690d8c09 Bump 2016-06-09 19:20:06 +02:00
JustArchi
0ff442e3e1 Bump 2016-06-09 19:16:51 +02:00
JustArchi
fedc3268b6 Misc 2016-06-09 19:13:20 +02:00
JustArchi
fc0d0abaaf Update blacklist, closes #232 2016-06-09 19:12:16 +02:00
JustArchi
95df9057c2 Update NetHook2 tool
Especially because 189d066cd5
2016-06-09 18:50:26 +02:00
JustArchi
ed6e35da85 Misc 2016-06-09 18:23:29 +02:00
JustArchi
64f424e474 Misc 2016-06-09 03:18:56 +02:00
JustArchi
7b67755932 Improve cards farming restart module logic 2016-06-09 03:13:35 +02:00
JustArchi
a5f7e7988c Change default FarmingDelay from 5 to 15
Now that we have event-based mechanism, we don't need to check that often, but still keep fuckups in mind
2016-06-09 01:15:48 +02:00
JustArchi
80ed0e66bb Never restart cards farming module
There is enough logic to handle games added in the meantime already
2016-06-09 00:49:52 +02:00
JustArchi
5529a8e1f0 Fix more regressions 2016-06-09 00:35:54 +02:00
JustArchi
496bea5ac5 Fix regression 2016-06-09 00:01:13 +02:00
JustArchi
52f3a86255 EXPERIMENTAL: Closes #238
Needs further tests
2016-06-08 23:26:37 +02:00
Łukasz Domeradzki
7d205cfa42 Update README.md 2016-06-08 19:02:34 +02:00
JustArchi
c4f47c56da Misc 2016-06-08 13:01:41 +02:00
JustArchi
546440d9dc Bump 2016-06-06 18:09:50 +02:00
146 changed files with 10000 additions and 11851 deletions

64
.gitignore vendored
View File

@@ -15,37 +15,6 @@ ArchiSteamFarm/debug/*
# Ignore out
out/
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
@@ -63,7 +32,6 @@ local.properties
[Dd]ebug/
[Rr]elease/
x64/
build/
[Bb]in/
[Oo]bj/
@@ -199,35 +167,3 @@ $RECYCLE.BIN/
# Mac crap
.DS_Store
#############
## Python
#############
*.py[cod]
# Packages
*.egg
*.egg-info
dist/
build/
eggs/
parts/
var/
sdist/
develop-eggs/
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg

View File

@@ -10,3 +10,9 @@ mono:
notifications:
email: false
webhooks:
urls:
- https://webhooks.gitter.im/e/df82484f12510c3f2516
on_success: always # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View File

@@ -1,12 +1,10 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm", "ArchiSteamFarm\ArchiSteamFarm.csproj", "{35AF7887-08B9-40E8-A5EA-797D8B60B30C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamAuth", "SteamAuth\SteamAuth.csproj", "{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigGenerator", "ConfigGenerator\ConfigGenerator.csproj", "{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}"
ProjectSection(ProjectDependencies) = postProject
{35AF7887-08B9-40E8-A5EA-797D8B60B30C} = {35AF7887-08B9-40E8-A5EA-797D8B60B30C}
@@ -27,10 +25,6 @@ Global
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{35AF7887-08B9-40E8-A5EA-797D8B60B30C}.Release|Any CPU.Build.0 = Release|Any CPU
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.Build.0 = Release|Any CPU
{C3F6FE68-5E75-415E-BEA1-1E7C16D6A433}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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

View File

@@ -1,8 +1,14 @@
<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: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_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/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>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FA/@EntryIndexedValue">FA</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=FS/@EntryIndexedValue">FS</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HTML/@EntryIndexedValue">HTML</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</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/=PIN/@EntryIndexedValue">PIN</s:String>
@@ -14,4 +20,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=WTF/@EntryIndexedValue">WTF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XML/@EntryIndexedValue">XML</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="AaBb" /&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="AaBb" /&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

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

View File

@@ -59,7 +59,7 @@ namespace ArchiSteamFarm {
Unknown = 0,
Trading = 1,
// Only custom below, different than ones available as user_notification_type
Items = 255
Items = 254
}
internal readonly HashSet<ENotification> Notifications;
@@ -71,6 +71,10 @@ namespace ArchiSteamFarm {
JobID = jobID;
if (msg.notifications.Count == 0) {
return;
}
Notifications = new HashSet<ENotification>();
foreach (CMsgClientUserNotifications.Notification notification in msg.notifications) {
Notifications.Add((ENotification) notification.user_notification_type);
@@ -85,9 +89,7 @@ namespace ArchiSteamFarm {
JobID = jobID;
if (msg.count_new_items > 0) {
Notifications = new HashSet<ENotification> {
ENotification.Items
};
Notifications = new HashSet<ENotification> { ENotification.Items };
}
}
}

View File

@@ -0,0 +1,52 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics.CodeAnalysis;
using System.ServiceProcess;
namespace ArchiSteamFarm {
[RunInstaller(true)]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public sealed class ArchiServiceInstaller : Installer {
public ArchiServiceInstaller() {
ServiceInstaller serviceInstaller = new ServiceInstaller();
ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
serviceInstaller.ServiceName = SharedInfo.ServiceName;
serviceInstaller.DisplayName = SharedInfo.ServiceName;
serviceInstaller.Description = SharedInfo.ServiceDescription;
// Defaulting to only starting when a user starts it, can be easily changed after install
serviceInstaller.StartType = ServiceStartMode.Manual;
// System account, requires admin privilege to install
serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
Installers.Add(serviceInstaller);
Installers.Add(serviceProcessInstaller);
}
}
}

View File

@@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ArchiSteamFarm</RootNamespace>
<AssemblyName>ArchiSteamFarm</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<IsWebBootstrapper>false</IsWebBootstrapper>
<TargetFrameworkProfile />
@@ -27,6 +27,8 @@
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -71,12 +73,16 @@
<DelaySign>false</DelaySign>
</PropertyGroup>
<ItemGroup>
<Reference Include="HtmlAgilityPack, Version=1.4.9.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<HintPath>..\packages\HtmlAgilityPack.1.4.9\lib\Net45\HtmlAgilityPack.dll</HintPath>
<Reference Include="HtmlAgilityPack, Version=1.4.9.4, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<HintPath>..\packages\HtmlAgilityPack.1.4.9.4\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-beta1\lib\net45\Newtonsoft.Json.dll</HintPath>
<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-beta13\lib\net45\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="protobuf-net, Version=2.0.0.668, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
@@ -88,19 +94,31 @@
<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>
<Compile Include="ArchiHandler.cs" />
<Compile Include="ArchiWebHandler.cs" />
<Compile Include="ArchiServiceInstaller.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Bot.cs" />
<Compile Include="BotConfig.cs" />
<Compile Include="ConcurrentEnumerator.cs" />
<Compile Include="ConcurrentHashSet.cs" />
<Compile Include="CryptoHelper.cs" />
<Compile Include="GlobalDatabase.cs" />
<Compile Include="BotDatabase.cs" />
<Compile Include="CardsFarmer.cs" />
@@ -109,15 +127,21 @@
<Compile Include="JSON\GitHub.cs" />
<Compile Include="JSON\Steam.cs" />
<Compile Include="Logging.cs" />
<Compile Include="MobileAuthenticator.cs" />
<Compile Include="Runtime.cs" />
<Compile Include="ObsoleteSteamGuardAccount.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SharedInfo.cs" />
<Compile Include="Trading.cs" />
<Compile Include="Utilities.cs" />
<Compile Include="WCF.cs" />
<Compile Include="WebBrowser.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="App.config">
<SubType>Designer</SubType>
</None>
<None Include="config\ASF.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@@ -143,12 +167,7 @@
</ItemGroup>
<ItemGroup>
<Content Include="cirno.ico" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SteamAuth\SteamAuth.csproj">
<Project>{5ad0934e-f6c4-4ae5-83af-c788313b2a87}</Project>
<Name>SteamAuth</Name>
</ProjectReference>
<None Include="FodyWeavers.xml" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
@@ -162,18 +181,29 @@
copy "$(TargetDir)config\ASF.json" "$(SolutionDir)out\config"
copy "$(TargetDir)config\example.json" "$(SolutionDir)out\config"
copy "$(TargetDir)config\minimal.json" "$(SolutionDir)out\config"
"$(SolutionDir)tools\ILRepack\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
del "$(SolutionDir)out\ASF.exe.config"
"$(SolutionDir)tools\ILRepack\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF-Service.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
del "$(SolutionDir)out\ASF-Service.exe.config"
copy "$(TargetDir)$(TargetName).exe" "$(SolutionDir)out\ASF.exe"
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
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.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
rm "$(SolutionDir)out/ASF.exe.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"
rm "$(SolutionDir)out/ASF-Service.exe.config"
cp "$(SolutionDir)out/ASF-Service.exe" "$(SolutionDir)out/ASF.exe"
</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">

View File

@@ -47,6 +47,9 @@ namespace ArchiSteamFarm {
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
private readonly WebBrowser WebBrowser;
internal bool Ready { get; private set; }
private ulong SteamID;
private DateTime LastSessionRefreshCheck = DateTime.MinValue;
internal static void Init() {
@@ -61,18 +64,12 @@ namespace ArchiSteamFarm {
}
int index = hashName.IndexOf('-');
if (index < 1) {
Logging.LogNullError(nameof(index));
if (index <= 0) {
return 0;
}
uint appID;
if (uint.TryParse(hashName.Substring(0, index), out appID)) {
return appID;
}
Logging.LogNullError(nameof(appID));
return 0;
return uint.TryParse(hashName.Substring(0, index), out appID) ? appID : 0;
}
private static Steam.Item.EType GetItemType(string name) {
@@ -107,6 +104,53 @@ namespace ArchiSteamFarm {
}
}
private static bool ParseItems(Dictionary<ulong, Tuple<uint, Steam.Item.EType>> descriptions, List<KeyValue> input, HashSet<Steam.Item> output) {
if ((descriptions == null) || (input == null) || (input.Count == 0) || (output == null)) {
Logging.LogNullError(nameof(descriptions) + " || " + nameof(input) + " || " + nameof(output));
return false;
}
foreach (KeyValue item in input) {
uint appID = (uint) item["appid"].AsUnsignedLong();
if (appID == 0) {
Logging.LogNullError(nameof(appID));
return false;
}
ulong contextID = item["contextid"].AsUnsignedLong();
if (contextID == 0) {
Logging.LogNullError(nameof(contextID));
return false;
}
ulong classID = item["classid"].AsUnsignedLong();
if (classID == 0) {
Logging.LogNullError(nameof(classID));
return false;
}
uint amount = (uint) item["amount"].AsUnsignedLong();
if (amount == 0) {
Logging.LogNullError(nameof(amount));
return false;
}
uint realAppID = 0;
Steam.Item.EType type = Steam.Item.EType.Unknown;
Tuple<uint, Steam.Item.EType> description;
if (descriptions.TryGetValue(classID, out description)) {
realAppID = description.Item1;
type = description.Item2;
}
Steam.Item steamItem = new Steam.Item(appID, contextID, classID, amount, realAppID, type);
output.Add(steamItem);
}
return true;
}
internal ArchiWebHandler(Bot bot) {
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
@@ -117,25 +161,24 @@ namespace ArchiSteamFarm {
WebBrowser = new WebBrowser(bot.BotName);
}
internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
if ((steamClient == null) || string.IsNullOrEmpty(webAPIUserNonce)) {
Logging.LogNullError(nameof(steamClient) + " || " + nameof(webAPIUserNonce), Bot.BotName);
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);
return false;
}
ulong steamID = steamClient.SteamID;
if (steamID == 0) {
return false;
}
SteamID = steamID;
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
// Generate an AES session key
byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32);
byte[] sessionKey = SteamKit2.CryptoHelper.GenerateRandomBlock(32);
// RSA encrypt it with the public key for the universe we're on
byte[] cryptedSessionKey;
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(steamClient.ConnectedUniverse))) {
using (RSACrypto rsa = new RSACrypto(KeyDictionary.GetPublicKey(universe))) {
cryptedSessionKey = rsa.Encrypt(sessionKey);
}
@@ -144,7 +187,7 @@ namespace ArchiSteamFarm {
Array.Copy(Encoding.ASCII.GetBytes(webAPIUserNonce), loginKey, webAPIUserNonce.Length);
// AES encrypt the loginkey with our session key
byte[] cryptedLoginKey = CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
byte[] cryptedLoginKey = SteamKit2.CryptoHelper.SymmetricEncrypt(loginKey, sessionKey);
// Do the magic
Logging.LogGenericInfo("Logging in to ISteamUserAuth...", Bot.BotName);
@@ -182,10 +225,12 @@ namespace ArchiSteamFarm {
string steamLoginSecure = authResult["tokensecure"].Value;
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunityHost));
if (!UnlockParentalAccount(parentalPin).Result) {
// Unlock Steam Parental if needed
if (!await UnlockParentalAccount(parentalPin).ConfigureAwait(false)) {
return false;
}
Ready = true;
LastSessionRefreshCheck = DateTime.Now;
return true;
}
@@ -239,6 +284,89 @@ namespace ArchiSteamFarm {
return await WebBrowser.UrlPostRetry(request, data).ConfigureAwait(false);
}
internal async Task<HtmlDocument> GetConfirmations(string deviceID, string confirmationHash, uint time) {
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0)) {
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time), Bot.BotName);
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
string request = SteamCommunityURL + "/mobileconf/conf?l=english&p=" + deviceID + "&a=" + SteamID + "&k=" + WebUtility.UrlEncode(confirmationHash) + "&t=" + time + "&m=android&tag=conf";
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(string deviceID, string confirmationHash, uint time, uint confirmationID) {
if (string.IsNullOrEmpty(deviceID) || string.IsNullOrEmpty(confirmationHash) || (time == 0) || (confirmationID == 0)) {
Logging.LogNullError(nameof(deviceID) + " || " + nameof(confirmationHash) + " || " + nameof(time) + " || " + nameof(confirmationID), Bot.BotName);
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
string request = SteamCommunityURL + "/mobileconf/details/" + confirmationID + "?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);
return null;
}
response.ConfirmationID = confirmationID;
return response;
}
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 false;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
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;
string json = await WebBrowser.UrlGetToContentRetry(request).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;
}
internal async Task<Dictionary<uint, string>> GetOwnedGames() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
@@ -261,19 +389,19 @@ namespace ArchiSteamFarm {
XmlNode appNode = xmlNode.SelectSingleNode("appID");
if (appNode == null) {
Logging.LogNullError(nameof(appNode), Bot.BotName);
continue;
return null;
}
uint appID;
if (!uint.TryParse(appNode.InnerText, out appID)) {
Logging.LogNullError(nameof(appID), Bot.BotName);
continue;
return null;
}
XmlNode nameNode = xmlNode.SelectSingleNode("name");
if (nameNode == null) {
Logging.LogNullError(nameof(nameNode), Bot.BotName);
continue;
return null;
}
result[appID] = nameNode.InnerText;
@@ -284,17 +412,15 @@ namespace ArchiSteamFarm {
internal Dictionary<uint, string> GetOwnedGames(ulong steamID) {
if ((steamID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
// TODO: Correct this when Mono 4.4+ will be a latest stable one | https://bugzilla.xamarin.com/show_bug.cgi?id=39455
Logging.LogNullError("steamID || SteamApiKey", Bot.BotName);
//Logging.LogNullError(nameof(steamID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
Logging.LogNullError(nameof(steamID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
return null;
}
KeyValue response = null;
using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
iPlayerService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
using (dynamic iPlayerService = WebAPI.GetInterface("IPlayerService", Bot.BotConfig.SteamApiKey)) {
iPlayerService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
try {
response = iPlayerService.GetOwnedGames(
steamid: steamID,
@@ -308,7 +434,7 @@ namespace ArchiSteamFarm {
}
if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
}
@@ -316,8 +442,8 @@ namespace ArchiSteamFarm {
foreach (KeyValue game in response["games"].Children) {
uint appID = (uint) game["appid"].AsUnsignedLong();
if (appID == 0) {
Logging.LogNullError(nameof(appID));
continue;
Logging.LogNullError(nameof(appID), Bot.BotName);
return null;
}
result[appID] = game["name"].Value;
@@ -326,19 +452,96 @@ namespace ArchiSteamFarm {
return result;
}
internal HashSet<Steam.TradeOffer> GetTradeOffers() {
internal uint GetServerTime() {
KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
using (dynamic iTwoFactorService = WebAPI.GetInterface("ITwoFactorService")) {
iTwoFactorService.Timeout = Timeout;
try {
response = iTwoFactorService.QueryTime(
method: WebRequestMethods.Http.Post,
secure: !Program.GlobalConfig.ForceHttp
);
} catch (Exception e) {
Logging.LogGenericException(e, Bot.BotName);
}
}
}
if (response != null) {
return (uint) response["server_time"].AsUnsignedLong();
}
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return 0;
}
internal async Task<byte?> GetTradeHoldDuration(ulong tradeID) {
if (tradeID == 0) {
Logging.LogNullError(nameof(tradeID), Bot.BotName);
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
string request = SteamCommunityURL + "/tradeoffer/" + tradeID + "?l=english";
HtmlDocument htmlDocument = await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
if (htmlDocument == null) {
return null;
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//div[@class='pagecontent']/script");
if (htmlNode == null) { // Trade can be no longer valid
return null;
}
string text = htmlNode.InnerText;
if (string.IsNullOrEmpty(text)) {
Logging.LogNullError(nameof(text), Bot.BotName);
return null;
}
int index = text.IndexOf("g_daysTheirEscrow = ", StringComparison.Ordinal);
if (index < 0) {
Logging.LogNullError(nameof(index), Bot.BotName);
return null;
}
index += 20;
text = text.Substring(index);
index = text.IndexOf(';');
if (index < 0) {
Logging.LogNullError(nameof(index), Bot.BotName);
return null;
}
text = text.Substring(0, index);
byte holdDuration;
if (byte.TryParse(text, out holdDuration)) {
return holdDuration;
}
Logging.LogNullError(nameof(holdDuration), Bot.BotName);
return null;
}
internal HashSet<Steam.TradeOffer> GetActiveTradeOffers() {
if (string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
// TODO: Correct this when Mono 4.4+ will be a latest stable one | https://bugzilla.xamarin.com/show_bug.cgi?id=39455
Logging.LogNullError("SteamApiKey", Bot.BotName);
//Logging.LogNullError(nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
Logging.LogNullError(nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
return null;
}
KeyValue response = null;
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
try {
response = iEconService.GetTradeOffers(
get_received_offers: 1,
@@ -353,7 +556,7 @@ namespace ArchiSteamFarm {
}
if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return null;
}
@@ -362,7 +565,7 @@ namespace ArchiSteamFarm {
ulong classID = description["classid"].AsUnsignedLong();
if (classID == 0) {
Logging.LogNullError(nameof(classID), Bot.BotName);
continue;
return null;
}
if (descriptions.ContainsKey(classID)) {
@@ -370,13 +573,18 @@ namespace ArchiSteamFarm {
}
uint appID = 0;
Steam.Item.EType type = Steam.Item.EType.Unknown;
string hashName = description["market_hash_name"].Value;
if (!string.IsNullOrEmpty(hashName)) {
appID = GetAppIDFromMarketHashName(hashName);
}
if (appID == 0) {
appID = (uint) description["appid"].AsUnsignedLong();
}
Steam.Item.EType type = Steam.Item.EType.Unknown;
string descriptionType = description["type"].Value;
if (!string.IsNullOrEmpty(descriptionType)) {
type = GetItemType(descriptionType);
@@ -387,48 +595,44 @@ namespace ArchiSteamFarm {
HashSet<Steam.TradeOffer> result = new HashSet<Steam.TradeOffer>();
foreach (KeyValue trade in response["trade_offers_received"].Children) {
Steam.TradeOffer tradeOffer = new Steam.TradeOffer {
TradeOfferID = trade["tradeofferid"].AsUnsignedLong(),
OtherSteamID3 = (uint) trade["accountid_other"].AsUnsignedLong(),
State = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>()
};
foreach (KeyValue item in trade["items_to_give"].Children) {
Steam.Item steamItem = new Steam.Item {
AppID = (uint) item["appid"].AsUnsignedLong(),
ContextID = item["contextid"].AsUnsignedLong(),
AssetID = item["assetid"].AsUnsignedLong(),
ClassID = item["classid"].AsUnsignedLong(),
InstanceID = item["instanceid"].AsUnsignedLong(),
Amount = (uint) item["amount"].AsUnsignedLong()
};
Tuple<uint, Steam.Item.EType> description;
if (descriptions.TryGetValue(steamItem.ClassID, out description)) {
steamItem.RealAppID = description.Item1;
steamItem.Type = description.Item2;
}
tradeOffer.ItemsToGive.Add(steamItem);
Steam.TradeOffer.ETradeOfferState state = trade["trade_offer_state"].AsEnum<Steam.TradeOffer.ETradeOfferState>();
if (state == Steam.TradeOffer.ETradeOfferState.Unknown) {
Logging.LogNullError(nameof(state));
return null;
}
foreach (KeyValue item in trade["items_to_receive"].Children) {
Steam.Item steamItem = new Steam.Item {
AppID = (uint) item["appid"].AsUnsignedLong(),
ContextID = item["contextid"].AsUnsignedLong(),
AssetID = item["assetid"].AsUnsignedLong(),
ClassID = item["classid"].AsUnsignedLong(),
InstanceID = item["instanceid"].AsUnsignedLong(),
Amount = (uint) item["amount"].AsUnsignedLong()
};
if (state != Steam.TradeOffer.ETradeOfferState.Active) {
continue;
}
Tuple<uint, Steam.Item.EType> description;
if (descriptions.TryGetValue(steamItem.ClassID, out description)) {
steamItem.RealAppID = description.Item1;
steamItem.Type = description.Item2;
ulong tradeOfferID = trade["tradeofferid"].AsUnsignedLong();
if (tradeOfferID == 0) {
Logging.LogNullError(nameof(tradeOfferID));
return null;
}
uint otherSteamID3 = (uint) trade["accountid_other"].AsUnsignedLong();
if (otherSteamID3 == 0) {
Logging.LogNullError(nameof(otherSteamID3));
return null;
}
Steam.TradeOffer tradeOffer = new Steam.TradeOffer(tradeOfferID, otherSteamID3, state);
List<KeyValue> itemsToGive = trade["items_to_give"].Children;
if (itemsToGive.Count > 0) {
if (!ParseItems(descriptions, itemsToGive, tradeOffer.ItemsToGive)) {
Logging.LogGenericError("Parsing " + nameof(itemsToGive) + " failed!", Bot.BotName);
return null;
}
}
tradeOffer.ItemsToReceive.Add(steamItem);
List<KeyValue> itemsToReceive = trade["items_to_receive"].Children;
if (itemsToReceive.Count > 0) {
if (!ParseItems(descriptions, itemsToReceive, tradeOffer.ItemsToReceive)) {
Logging.LogGenericError("Parsing " + nameof(itemsToReceive) + " failed!", Bot.BotName);
return null;
}
}
result.Add(tradeOffer);
@@ -466,17 +670,15 @@ namespace ArchiSteamFarm {
internal bool DeclineTradeOffer(ulong tradeID) {
if ((tradeID == 0) || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
// TODO: Correct this when Mono 4.4+ will be a latest stable one | https://bugzilla.xamarin.com/show_bug.cgi?id=39455
Logging.LogNullError("tradeID || SteamApiKey", Bot.BotName);
//Logging.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
Logging.LogNullError(nameof(tradeID) + " || " + nameof(Bot.BotConfig.SteamApiKey), Bot.BotName);
return false;
}
KeyValue response = null;
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) {
using (dynamic iEconService = WebAPI.GetInterface("IEconService", Bot.BotConfig.SteamApiKey)) {
iEconService.Timeout = Timeout;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
try {
response = iEconService.DeclineTradeOffer(
tradeofferid: tradeID.ToString(),
@@ -489,24 +691,24 @@ namespace ArchiSteamFarm {
}
}
if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
if (response != null) {
return true;
}
return true;
Logging.LogGenericWarning("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
internal async Task<HashSet<Steam.Item>> GetMyTradableInventory() {
internal async Task<HashSet<Steam.Item>> GetMyInventory(bool tradable) {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
ushort nextPage = 0;
uint currentPage = 0;
while (true) {
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=1&start=" + nextPage;
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamContextID + "?trading=" + (tradable ? "1" : "0") + "&start=" + currentPage;
JObject jObject = await WebBrowser.UrlGetToJObjectRetry(request).ConfigureAwait(false);
if (jObject == null) {
@@ -523,13 +725,13 @@ namespace ArchiSteamFarm {
string classIDString = description["classid"].ToString();
if (string.IsNullOrEmpty(classIDString)) {
Logging.LogNullError(nameof(classIDString), Bot.BotName);
continue;
return null;
}
ulong classID;
if (!ulong.TryParse(classIDString, out classID) || (classID == 0)) {
Logging.LogNullError(nameof(classID), Bot.BotName);
continue;
return null;
}
if (descriptionMap.ContainsKey(classID)) {
@@ -537,13 +739,27 @@ namespace ArchiSteamFarm {
}
uint appID = 0;
Steam.Item.EType type = Steam.Item.EType.Unknown;
string hashName = description["market_hash_name"].ToString();
if (!string.IsNullOrEmpty(hashName)) {
appID = GetAppIDFromMarketHashName(hashName);
}
if (appID == 0) {
string appIDString = description["appid"].ToString();
if (string.IsNullOrEmpty(appIDString)) {
Logging.LogNullError(nameof(appIDString), Bot.BotName);
return null;
}
if (!uint.TryParse(appIDString, out appID)) {
Logging.LogNullError(nameof(appID), Bot.BotName);
return null;
}
}
Steam.Item.EType type = Steam.Item.EType.Unknown;
string descriptionType = description["type"].ToString();
if (!string.IsNullOrEmpty(descriptionType)) {
type = GetItemType(descriptionType);
@@ -559,19 +775,18 @@ namespace ArchiSteamFarm {
}
foreach (JToken item in items) {
Steam.Item steamItem;
try {
steamItem = JsonConvert.DeserializeObject<Steam.Item>(item.ToString());
steamItem = item.ToObject<Steam.Item>();
} catch (JsonException e) {
Logging.LogGenericException(e, Bot.BotName);
continue;
return null;
}
if (steamItem == null) {
Logging.LogNullError(nameof(steamItem), Bot.BotName);
continue;
return null;
}
Tuple<uint, Steam.Item.EType> description;
@@ -588,12 +803,13 @@ namespace ArchiSteamFarm {
break; // OK, last page
}
if (ushort.TryParse(jObject["more_start"].ToString(), out nextPage)) {
continue;
uint nextPage;
if (!uint.TryParse(jObject["more_start"].ToString(), out nextPage) || (nextPage <= currentPage)) {
Logging.LogNullError(nameof(nextPage), Bot.BotName);
return null;
}
Logging.LogNullError(nameof(nextPage), Bot.BotName);
break;
currentPage = nextPage;
}
return result;
@@ -630,13 +846,7 @@ namespace ArchiSteamFarm {
itemID = 0;
}
singleTrade.ItemsToGive.Assets.Add(new Steam.Item {
AppID = Steam.Item.SteamAppID,
ContextID = Steam.Item.SteamContextID,
Amount = item.Amount,
AssetID = item.AssetID
});
singleTrade.ItemsToGive.Assets.Add(new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamContextID, item.AssetID, item.Amount));
itemID++;
}
@@ -668,8 +878,7 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/my/badges?p=" + page;
string request = SteamCommunityURL + "/my/badges?l=english&p=" + page;
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
@@ -683,8 +892,7 @@ namespace ArchiSteamFarm {
return null;
}
string request = SteamCommunityURL + "/my/gamecards/" + appID;
string request = SteamCommunityURL + "/my/gamecards/" + appID + "?l=english";
return await WebBrowser.UrlGetToHtmlDocumentRetry(request).ConfigureAwait(false);
}
@@ -694,7 +902,6 @@ namespace ArchiSteamFarm {
}
string request = SteamCommunityURL + "/my/inventory";
return await WebBrowser.UrlHeadRetry(request).ConfigureAwait(false);
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,11 +25,13 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
namespace ArchiSteamFarm {
// ReSharper disable once ClassCannotBeInstantiated
// ReSharper disable once ClassNeverInstantiated.Global
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class BotConfig {
[JsonProperty(Required = Required.DisallowNull)]
internal bool Enabled { get; private set; } = false;
@@ -43,6 +45,10 @@ 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";
@@ -82,9 +88,6 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal bool DistributeKeys { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool UseAsfAsMobileAuthenticator { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ShutdownOnFarmingFinished { get; private set; } = false;
@@ -126,6 +129,21 @@ namespace ArchiSteamFarm {
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
botConfig.SteamPassword = CryptoHelper.Decrypt(botConfig.PasswordFormat, botConfig.SteamPassword);
}
// User might not know what he's doing
// Ensure that he can't screw core ASF variables
if (botConfig.GamesPlayedWhileIdle.Count <= CardsFarmer.MaxGamesPlayedConcurrently) {
return botConfig;
}
Logging.LogGenericWarning("Playing more than " + CardsFarmer.MaxGamesPlayedConcurrently + " games concurrently is not possible, only first " + CardsFarmer.MaxGamesPlayedConcurrently + " entries from GamesPlayedWhileIdle will be used");
botConfig.GamesPlayedWhileIdle = new HashSet<uint>(botConfig.GamesPlayedWhileIdle.Take(CardsFarmer.MaxGamesPlayedConcurrently));
return botConfig;
}

View File

@@ -23,13 +23,16 @@
*/
using Newtonsoft.Json;
using SteamAuth;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class BotDatabase {
[JsonProperty]
private string _LoginKey;
internal string LoginKey {
get {
return _LoginKey;
@@ -44,7 +47,28 @@ namespace ArchiSteamFarm {
}
}
internal SteamGuardAccount SteamGuardAccount {
[JsonProperty]
private MobileAuthenticator _MobileAuthenticator;
internal MobileAuthenticator MobileAuthenticator {
get {
return _MobileAuthenticator;
}
set {
if (_MobileAuthenticator == value) {
return;
}
_MobileAuthenticator = value;
Save();
}
}
// TODO: Converter code will be removed soon
[JsonProperty]
private ObsoleteSteamGuardAccount _SteamGuardAccount;
internal ObsoleteSteamGuardAccount SteamGuardAccount {
get {
return _SteamGuardAccount;
}
@@ -58,12 +82,6 @@ namespace ArchiSteamFarm {
}
}
[JsonProperty]
private string _LoginKey;
[JsonProperty]
private SteamGuardAccount _SteamGuardAccount;
private string FilePath;
internal static BotDatabase Load(string filePath) {
@@ -109,11 +127,22 @@ namespace ArchiSteamFarm {
private BotDatabase() { }
internal void Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
Logging.LogNullError(nameof(json));
return;
}
lock (FilePath) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
} catch (Exception e) {
Logging.LogGenericException(e);
for (byte i = 0; i < 5; i++) {
try {
File.WriteAllText(FilePath, json);
break;
} catch (Exception e) {
Logging.LogGenericException(e);
}
Thread.Sleep(1000);
}
}
}

View File

@@ -31,10 +31,16 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class CardsFarmer {
internal const byte MaxGamesPlayedConcurrently = 32; // This is limit introduced by Steam Network
[JsonProperty]
internal readonly ConcurrentDictionary<uint, float> GamesToFarm = new ConcurrentDictionary<uint, float>();
[JsonProperty]
internal readonly ConcurrentHashSet<uint> CurrentGamesFarming = new ConcurrentHashSet<uint>();
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
@@ -42,9 +48,10 @@ namespace ArchiSteamFarm {
private readonly Bot Bot;
private readonly Timer Timer;
[JsonProperty]
internal bool ManualMode { get; private set; }
private bool NowFarming;
private bool KeepFarming, NowFarming;
internal CardsFarmer(Bot bot) {
if (bot == null) {
@@ -55,7 +62,7 @@ namespace ArchiSteamFarm {
if ((Timer == null) && (Program.GlobalConfig.IdleFarmingPeriod > 0)) {
Timer = new Timer(
async e => await CheckGamesForFarming().ConfigureAwait(false),
e => CheckGamesForFarming(),
null,
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod), // Delay
TimeSpan.FromHours(Program.GlobalConfig.IdleFarmingPeriod) // Period
@@ -102,12 +109,12 @@ namespace ArchiSteamFarm {
// 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!");
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;
}
NowFarming = true;
KeepFarming = NowFarming = true;
FarmingSemaphore.Release(); // From this point we allow other calls to shut us down
do {
@@ -170,26 +177,46 @@ namespace ArchiSteamFarm {
}
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 Utilities.SleepAsync(1000).ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
}
if (NowFarming) {
Logging.LogGenericWarning("Timed out!", Bot.BotName);
}
FarmResetEvent.Reset();
Logging.LogGenericInfo("Farming stopped!", Bot.BotName);
Bot.OnFarmingStopped();
FarmingSemaphore.Release();
}
internal async Task RestartFarming() {
await StopFarming().ConfigureAwait(false);
await StartFarming().ConfigureAwait(false);
internal void OnDisconnected() => StopFarming().Forget();
internal void OnNewItemsNotification() {
if (!NowFarming) {
return;
}
FarmResetEvent.Set();
}
internal async Task OnNewGameAdded() {
if (!NowFarming) {
// If we're not farming yet, obviously it's worth it to make a check
StartFarming().Forget();
return;
}
if (Bot.BotConfig.CardDropsRestricted && (GamesToFarm.Count > 0) && (GamesToFarm.Values.Min() < 2)) {
// If we have Complex algorithm and some games to boost, it's also worth to make a check
// That's because we would check for new games after our current round anyway
await StopFarming().ConfigureAwait(false);
StartFarming().Forget();
}
}
private static HashSet<uint> GetGamesToFarmSolo(ConcurrentDictionary<uint, float> gamesToFarm) {
@@ -218,22 +245,25 @@ namespace ArchiSteamFarm {
}
byte maxPages = 1;
HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
if ((htmlNodeCollection != null) && (htmlNodeCollection.Count > 0)) {
HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1];
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("(//a[@class='pagelink'])[last()]");
if (htmlNode != null) {
string lastPage = htmlNode.InnerText;
if (!string.IsNullOrEmpty(lastPage)) {
if (!byte.TryParse(lastPage, out maxPages)) {
maxPages = 1; // Should never happen
}
if (string.IsNullOrEmpty(lastPage)) {
Logging.LogNullError(nameof(lastPage), Bot.BotName);
return false;
}
if (!byte.TryParse(lastPage, out maxPages) || (maxPages == 0)) {
Logging.LogNullError(nameof(maxPages), Bot.BotName);
return false;
}
}
GamesToFarm.Clear();
CheckPage(htmlDocument);
if (maxPages <= 1) {
if (maxPages == 1) {
return GamesToFarm.Count > 0;
}
@@ -269,19 +299,19 @@ namespace ArchiSteamFarm {
string steamLink = farmingNode.GetAttributeValue("href", null);
if (string.IsNullOrEmpty(steamLink)) {
Logging.LogNullError(nameof(steamLink), Bot.BotName);
continue;
return;
}
int index = steamLink.LastIndexOf('/');
if (index < 0) {
Logging.LogNullError(nameof(index), Bot.BotName);
continue;
return;
}
index++;
if (steamLink.Length <= index) {
Logging.LogNullError(nameof(steamLink.Length), Bot.BotName);
continue;
return;
}
steamLink = steamLink.Substring(index);
@@ -289,7 +319,7 @@ namespace ArchiSteamFarm {
uint appID;
if (!uint.TryParse(steamLink, out appID) || (appID == 0)) {
Logging.LogNullError(nameof(appID), Bot.BotName);
continue;
return;
}
if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
@@ -299,13 +329,13 @@ namespace ArchiSteamFarm {
HtmlNode timeNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
if (timeNode == null) {
Logging.LogNullError(nameof(timeNode), Bot.BotName);
continue;
return;
}
string hoursString = timeNode.InnerText;
if (string.IsNullOrEmpty(hoursString)) {
Logging.LogNullError(nameof(hoursString), Bot.BotName);
continue;
return;
}
float hours = 0;
@@ -314,7 +344,7 @@ namespace ArchiSteamFarm {
if (match.Success) {
if (!float.TryParse(match.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours)) {
Logging.LogNullError(nameof(hours), Bot.BotName);
continue;
return;
}
}
@@ -336,12 +366,12 @@ namespace ArchiSteamFarm {
CheckPage(htmlDocument);
}
private async Task CheckGamesForFarming() {
private void CheckGamesForFarming() {
if (NowFarming || ManualMode || !Bot.SteamClient.IsConnected) {
return;
}
await StartFarming().ConfigureAwait(false);
StartFarming().Forget();
}
private async Task<bool?> ShouldFarm(uint appID) {
@@ -356,12 +386,29 @@ namespace ArchiSteamFarm {
}
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
if (htmlNode != null) {
return !htmlNode.InnerText.Contains("No card drops");
if (htmlNode == null) {
Logging.LogNullError(nameof(htmlNode), Bot.BotName);
return null;
}
Logging.LogNullError(nameof(htmlNode), Bot.BotName);
return null;
string progress = htmlNode.InnerText;
if (string.IsNullOrEmpty(progress)) {
Logging.LogNullError(nameof(progress), Bot.BotName);
return null;
}
byte cardsRemaining = 0;
Match match = Regex.Match(progress, @"\d+");
if (match.Success) {
if (!byte.TryParse(match.Value, out cardsRemaining)) {
Logging.LogNullError(nameof(cardsRemaining), Bot.BotName);
return null;
}
}
Logging.LogGenericInfo("Status for " + appID + ": " + cardsRemaining + " cards remaining", Bot.BotName);
return cardsRemaining > 0;
}
private bool FarmMultiple() {
@@ -375,6 +422,10 @@ namespace ArchiSteamFarm {
if (game.Value > maxHour) {
maxHour = game.Value;
}
if (CurrentGamesFarming.Count >= MaxGamesPlayedConcurrently) {
break;
}
}
if (maxHour >= 2) {
@@ -423,22 +474,28 @@ namespace ArchiSteamFarm {
}
Bot.ArchiHandler.PlayGames(appID);
DateTime endFarmingDate = DateTime.Now.AddHours(Program.GlobalConfig.MaxFarmingTime);
bool success = true;
bool? keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
for (ushort farmingTime = 0; (farmingTime <= 60 * Program.GlobalConfig.MaxFarmingTime) && keepFarming.GetValueOrDefault(true); farmingTime += Program.GlobalConfig.FarmingDelay) {
while (keepFarming.GetValueOrDefault(true) && (DateTime.Now < endFarmingDate)) {
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
DateTime startFarmingPeriod = DateTime.Now;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false;
break;
FarmResetEvent.Reset();
success = KeepFarming;
}
// Don't forget to update our GamesToFarm hours
float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
GamesToFarm[appID] += timePlayed;
GamesToFarm[appID] += (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
if (!success) {
break;
}
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
}
Logging.LogGenericInfo("Stopped farming: " + appID, Bot.BotName);
@@ -451,27 +508,29 @@ namespace ArchiSteamFarm {
return false;
}
if (maxHour >= 2) {
return true;
}
Bot.ArchiHandler.PlayGames(appIDs);
bool success = true;
while (maxHour < 2) {
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
DateTime startFarmingPeriod = DateTime.Now;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false;
break;
FarmResetEvent.Reset();
success = KeepFarming;
}
// Don't forget to update our GamesToFarm hours
float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
float timePlayed = (float) DateTime.Now.Subtract(startFarmingPeriod).TotalHours;
foreach (uint appID in appIDs) {
GamesToFarm[appID] += timePlayed;
}
if (!success) {
break;
}
maxHour += timePlayed;
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
}
Logging.LogGenericInfo("Stopped farming: " + string.Join(", ", appIDs), Bot.BotName);

View File

@@ -36,14 +36,14 @@ namespace ArchiSteamFarm {
private readonly IEnumerator<T> Enumerator;
private readonly ReaderWriterLockSlim Lock;
internal ConcurrentEnumerator(ICollection<T> collection, ReaderWriterLockSlim @lock) {
if ((collection == null) || (@lock == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(@lock));
internal ConcurrentEnumerator(ICollection<T> collection, ReaderWriterLockSlim rwLock) {
if ((collection == null) || (rwLock == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(rwLock));
}
@lock.EnterReadLock();
rwLock.EnterReadLock();
Lock = @lock;
Lock = rwLock;
Enumerator = collection.GetEnumerator();
}

View File

@@ -31,7 +31,7 @@ using System.Threading;
namespace ArchiSteamFarm {
internal sealed class ConcurrentHashSet<T> : ICollection<T>, IDisposable {
private readonly HashSet<T> HashSet = new HashSet<T>();
private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
public bool IsReadOnly => false;
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock);

View File

@@ -0,0 +1,166 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.Security.Cryptography;
using System.Text;
namespace ArchiSteamFarm {
internal static class CryptoHelper {
internal enum ECryptoMethod : byte {
PlainText,
AES,
ProtectedDataForCurrentUser
}
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes("ArchiSteamFarm");
internal static void SetEncryptionKey(string key) {
if (string.IsNullOrEmpty(key)) {
Logging.LogNullError(nameof(key));
return;
}
EncryptionKey = Encoding.UTF8.GetBytes(key);
}
internal static string Encrypt(ECryptoMethod cryptoMethod, string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
Logging.LogNullError(nameof(decrypted));
return null;
}
switch (cryptoMethod) {
case ECryptoMethod.PlainText:
return decrypted;
case ECryptoMethod.AES:
return EncryptAES(decrypted);
case ECryptoMethod.ProtectedDataForCurrentUser:
return EncryptProtectedDataForCurrentUser(decrypted);
default:
return null;
}
}
internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
Logging.LogNullError(nameof(encrypted));
return null;
}
switch (cryptoMethod) {
case ECryptoMethod.PlainText:
return encrypted;
case ECryptoMethod.AES:
return DecryptAES(encrypted);
case ECryptoMethod.ProtectedDataForCurrentUser:
return DecryptProtectedDataForCurrentUser(encrypted);
default:
return null;
}
}
private static string EncryptAES(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
Logging.LogNullError(nameof(decrypted));
return null;
}
try {
byte[] key;
using (SHA256Managed sha256 = new SHA256Managed()) {
key = sha256.ComputeHash(EncryptionKey);
}
byte[] encryptedData = Encoding.UTF8.GetBytes(decrypted);
encryptedData = SteamKit2.CryptoHelper.SymmetricEncrypt(encryptedData, key);
return Convert.ToBase64String(encryptedData);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
}
private static string DecryptAES(string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
Logging.LogNullError(nameof(encrypted));
return null;
}
try {
byte[] key;
using (SHA256Managed sha256 = new SHA256Managed()) {
key = sha256.ComputeHash(EncryptionKey);
}
byte[] decryptedData = Convert.FromBase64String(encrypted);
decryptedData = SteamKit2.CryptoHelper.SymmetricDecrypt(decryptedData, key);
return Encoding.UTF8.GetString(decryptedData);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
}
private static string EncryptProtectedDataForCurrentUser(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
Logging.LogNullError(nameof(decrypted));
return null;
}
try {
byte[] encryptedData = ProtectedData.Protect(
Encoding.UTF8.GetBytes(decrypted),
EncryptionKey, // This is used as salt only
DataProtectionScope.CurrentUser
);
return Convert.ToBase64String(encryptedData);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
}
private static string DecryptProtectedDataForCurrentUser(string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
Logging.LogNullError(nameof(encrypted));
return null;
}
try {
byte[] decryptedData = ProtectedData.Unprotect(
Convert.FromBase64String(encrypted),
EncryptionKey, // This is used as salt only
DataProtectionScope.CurrentUser
);
return Encoding.UTF8.GetString(decryptedData);
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
}
}
}
}

View File

@@ -51,6 +51,11 @@ namespace ArchiSteamFarm {
}
public void WriteLine(string category, string msg) {
if (string.IsNullOrEmpty(category) && string.IsNullOrEmpty(msg)) {
Logging.LogNullError(nameof(category) + " && " + nameof(msg));
return;
}
lock (FilePath) {
try {
File.AppendAllText(FilePath, category + " | " + msg + Environment.NewLine);

View File

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

View File

@@ -30,7 +30,8 @@ using System.IO;
using System.Net.Sockets;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated"), SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class GlobalConfig {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum EUpdateChannel : byte {
@@ -42,12 +43,12 @@ namespace ArchiSteamFarm {
internal const byte DefaultHttpTimeout = 60;
private const byte DefaultMaxFarmingTime = 10;
private const byte DefaultFarmingDelay = 5;
private const byte DefaultFarmingDelay = 15;
private const ushort DefaultWCFPort = 1242;
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
// This is hardcoded blacklist which should not be possible to change
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280 };
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
[JsonProperty(Required = Required.DisallowNull)]
internal bool Debug { get; private set; } = false;
@@ -80,11 +81,17 @@ namespace ArchiSteamFarm {
internal byte FarmingDelay { get; private set; } = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
internal byte LoginLimiterDelay { get; private set; } = 7;
internal byte LoginLimiterDelay { get; private set; } = 10;
[JsonProperty(Required = Required.DisallowNull)]
internal byte InventoryLimiterDelay { get; private set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte GiftsLimiterDelay { get; private set; } = 1;
[JsonProperty(Required = Required.DisallowNull)]
internal byte MaxTradeHoldDuration { get; private set; } = 15;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ForceHttp { get; private set; } = false;
@@ -158,6 +165,11 @@ namespace ArchiSteamFarm {
globalConfig.FarmingDelay = DefaultFarmingDelay;
}
if ((globalConfig.FarmingDelay > 5) && Runtime.RequiresWorkaroundForMonoBug41701()) {
Logging.LogGenericWarning("Your Mono runtime is affected by bug 41701, FarmingDelay of " + globalConfig.FarmingDelay + " is not possible - value of 5 will be used instead");
globalConfig.FarmingDelay = 5;
}
if (globalConfig.HttpTimeout == 0) {
Logging.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout + ". Value of " + DefaultHttpTimeout + " will be used instead");
globalConfig.HttpTimeout = DefaultHttpTimeout;

View File

@@ -26,6 +26,7 @@ using Newtonsoft.Json;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class GlobalDatabase {
@@ -91,11 +92,22 @@ namespace ArchiSteamFarm {
private GlobalDatabase() { }
private void Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
Logging.LogNullError(nameof(json));
return;
}
lock (FilePath) {
try {
File.WriteAllText(FilePath, JsonConvert.SerializeObject(this));
} catch (Exception e) {
Logging.LogGenericException(e);
for (byte i = 0; i < 5; i++) {
try {
File.WriteAllText(FilePath, json);
break;
} catch (Exception e) {
Logging.LogGenericException(e);
}
Thread.Sleep(1000);
}
}
}

View File

@@ -28,7 +28,8 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm.JSON {
internal static class GitHub {
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global"), SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ReleaseResponse {
internal sealed class Asset {
[JsonProperty(PropertyName = "name", Required = Required.Always)]

View File

@@ -22,15 +22,19 @@
*/
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
using HtmlAgilityPack;
using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm.JSON {
internal static class Steam {
internal sealed class Item { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
// Deserialized from JSON (SteamCommunity) and constructed from code
internal const ushort SteamAppID = 753;
internal const byte SteamContextID = 6;
@@ -48,9 +52,10 @@ namespace ArchiSteamFarm.JSON {
TradingCard
}
internal uint AppID { get; set; }
internal uint AppID { get; private set; }
[JsonProperty(PropertyName = "appid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
[JsonProperty(PropertyName = "appid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string AppIDString {
get {
return AppID.ToString();
@@ -58,21 +63,24 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
uint result;
if (!uint.TryParse(value, out result)) {
uint appID;
if (!uint.TryParse(value, out appID) || (appID == 0)) {
Logging.LogNullError(nameof(appID));
return;
}
AppID = result;
AppID = appID;
}
}
internal ulong ContextID { get; set; }
internal ulong ContextID { get; private set; }
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ContextIDString {
get {
return ContextID.ToString();
@@ -80,19 +88,21 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
ulong contextID;
if (!ulong.TryParse(value, out contextID) || (contextID == 0)) {
Logging.LogNullError(nameof(contextID));
return;
}
ContextID = result;
ContextID = contextID;
}
}
internal ulong AssetID { get; set; }
internal ulong AssetID { get; private set; }
[JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)]
private string AssetIDString {
@@ -102,27 +112,31 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
ulong assetID;
if (!ulong.TryParse(value, out assetID) || (assetID == 0)) {
Logging.LogNullError(nameof(assetID));
return;
}
AssetID = result;
AssetID = assetID;
}
}
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ID {
get { return AssetIDString; }
set { AssetIDString = value; }
}
internal ulong ClassID { get; set; }
internal ulong ClassID { get; private set; }
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string ClassIDString {
get {
return ClassID.ToString();
@@ -130,43 +144,23 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
ulong classID;
if (!ulong.TryParse(value, out classID) || (classID == 0)) {
return;
}
ClassID = result;
ClassID = classID;
}
}
internal ulong InstanceID { get; set; }
internal uint Amount { get; private set; }
[JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull), SuppressMessage("ReSharper", "UnusedMember.Local")]
private string InstanceIDString {
get {
return InstanceID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
return;
}
InstanceID = result;
}
}
internal uint Amount { get; set; }
[JsonProperty(PropertyName = "amount", Required = Required.Always), SuppressMessage("ReSharper", "UnusedMember.Local")]
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private string AmountString {
get {
return Amount.ToString();
@@ -174,23 +168,59 @@ namespace ArchiSteamFarm.JSON {
set {
if (string.IsNullOrEmpty(value)) {
Logging.LogNullError(nameof(value));
return;
}
uint result;
if (!uint.TryParse(value, out result)) {
uint amount;
if (!uint.TryParse(value, out amount) || (amount == 0)) {
Logging.LogNullError(nameof(amount));
return;
}
Amount = result;
Amount = amount;
}
}
internal uint RealAppID { get; set; }
internal EType Type { get; set; }
// This constructor is used for constructing items in trades being sent
internal Item(uint appID, ulong contextID, ulong assetID, uint amount) : this(appID, contextID, amount) {
if (assetID == 0) {
throw new ArgumentNullException(nameof(assetID));
}
AssetID = assetID;
}
// This constructor is used for constructing items in trades being received
internal Item(uint appID, ulong contextID, ulong classID, uint amount, uint realAppID, EType type) : this(appID, contextID, amount) {
if (classID == 0) {
throw new ArgumentNullException(nameof(classID));
}
ClassID = classID;
RealAppID = realAppID;
Type = type;
}
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private Item() { }
private Item(uint appID, ulong contextID, uint amount) {
if ((appID == 0) || (contextID == 0) || (amount == 0)) {
throw new ArgumentNullException(nameof(appID) + " || " + nameof(contextID) + " || " + nameof(amount));
}
AppID = appID;
ContextID = contextID;
Amount = amount;
}
}
internal sealed class TradeOffer { // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_TradeOffer
// Constructed from code
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum ETradeOfferState : byte {
Unknown,
@@ -207,44 +237,41 @@ namespace ArchiSteamFarm.JSON {
OnHold
}
internal ulong TradeOfferID { get; set; }
internal readonly ulong TradeOfferID;
internal readonly ETradeOfferState State;
internal readonly HashSet<Item> ItemsToGive = new HashSet<Item>();
internal readonly HashSet<Item> ItemsToReceive = new HashSet<Item>();
[JsonProperty(PropertyName = "tradeofferid", Required = Required.Always), SuppressMessage("ReSharper", "UnusedMember.Local")]
private string TradeOfferIDString {
private readonly uint OtherSteamID3;
private ulong _OtherSteamID64;
internal ulong OtherSteamID64 {
get {
return TradeOfferID.ToString();
}
set {
if (string.IsNullOrEmpty(value)) {
return;
if (_OtherSteamID64 != 0) {
return _OtherSteamID64;
}
ulong result;
if (!ulong.TryParse(value, out result)) {
return;
if (OtherSteamID3 == 0) {
Logging.LogNullError(nameof(OtherSteamID3));
return 0;
}
TradeOfferID = result;
_OtherSteamID64 = new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
return _OtherSteamID64;
}
}
[JsonProperty(PropertyName = "accountid_other", Required = Required.Always)]
internal uint OtherSteamID3 { private get; set; }
internal TradeOffer(ulong tradeOfferID, uint otherSteamID3, ETradeOfferState state) {
if ((tradeOfferID == 0) || (otherSteamID3 == 0) || (state == ETradeOfferState.Unknown)) {
throw new ArgumentNullException(nameof(tradeOfferID) + " || " + nameof(otherSteamID3) + " || " + nameof(state));
}
[JsonProperty(PropertyName = "trade_offer_state", Required = Required.Always)]
internal ETradeOfferState State { get; set; }
TradeOfferID = tradeOfferID;
OtherSteamID3 = otherSteamID3;
State = state;
}
[JsonProperty(PropertyName = "items_to_give", Required = Required.Always)]
internal HashSet<Item> ItemsToGive { get; } = new HashSet<Item>();
[JsonProperty(PropertyName = "items_to_receive", Required = Required.Always)]
internal HashSet<Item> ItemsToReceive { get; } = new HashSet<Item>();
// Extra
internal ulong OtherSteamID64 => OtherSteamID3 == 0 ? 0 : new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
internal bool IsSteamCardsOnlyTradeForUs() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamContextID) && ((item.Type == Item.EType.FoilTradingCard) || (item.Type == Item.EType.TradingCard)));
internal bool IsSteamCardsOnlyTradeForUs() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamContextID) && (item.Type == Item.EType.TradingCard));
internal bool IsPotentiallyDupesTradeForUs() {
Dictionary<uint, Dictionary<Item.EType, uint>> itemsToGivePerGame = new Dictionary<uint, Dictionary<Item.EType, uint>>();
@@ -304,22 +331,203 @@ namespace ArchiSteamFarm.JSON {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class TradeOfferRequest {
// Constructed from code
internal sealed class ItemList {
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal HashSet<Item> Assets { get; } = new HashSet<Item>();
internal readonly HashSet<Item> Assets = new HashSet<Item>();
}
[JsonProperty(PropertyName = "newversion", Required = Required.Always)]
internal bool NewVersion { get; } = true;
[JsonProperty(PropertyName = "version", Required = Required.Always)]
internal byte Version { get; } = 2;
[JsonProperty(PropertyName = "me", Required = Required.Always)]
internal ItemList ItemsToGive { get; } = new ItemList();
internal readonly ItemList ItemsToGive = new ItemList();
[JsonProperty(PropertyName = "them", Required = Required.Always)]
internal ItemList ItemsToReceive { get; } = new ItemList();
internal readonly ItemList ItemsToReceive = new ItemList();
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationResponse {
// Deserialized from JSON
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal bool Success { get; private set; }
private ConfirmationResponse() { }
}
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")]
internal sealed class ConfirmationDetails {
// Deserialized from JSON
internal enum EType : byte {
Unknown,
Trade,
Market,
Other
}
private uint _ConfirmationID;
internal uint ConfirmationID {
get { return _ConfirmationID; }
set {
if (value == 0) {
Logging.LogNullError(nameof(value));
return;
}
_ConfirmationID = value;
}
}
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal bool Success { get; private set; }
private EType _Type;
private EType Type {
get {
if (_Type != EType.Unknown) {
return _Type;
}
if (HtmlDocument == null) {
return EType.Unknown;
}
HtmlNode testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_listing_prices']");
if (testNode != null) {
_Type = EType.Market;
return _Type;
}
testNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='mobileconf_trade_area']");
if (testNode != null) {
_Type = EType.Trade;
return _Type;
}
_Type = EType.Other;
return _Type;
}
}
private ulong _TradeOfferID;
internal ulong TradeOfferID {
get {
if (_TradeOfferID != 0) {
return _TradeOfferID;
}
if ((Type != EType.Trade) || (HtmlDocument == null)) {
return 0;
}
HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//div[@class='tradeoffer']");
if (htmlNode == null) {
Logging.LogNullError(nameof(htmlNode));
return 0;
}
string id = htmlNode.GetAttributeValue("id", null);
if (string.IsNullOrEmpty(id)) {
Logging.LogNullError(nameof(id));
return 0;
}
int index = id.IndexOf('_');
if (index < 0) {
Logging.LogNullError(nameof(index));
return 0;
}
index++;
if (id.Length <= index) {
Logging.LogNullError(nameof(id.Length));
return 0;
}
id = id.Substring(index);
if (ulong.TryParse(id, out _TradeOfferID) && (_TradeOfferID != 0)) {
return _TradeOfferID;
}
Logging.LogNullError(nameof(_TradeOfferID));
return 0;
}
}
private ulong _OtherSteamID64;
internal ulong OtherSteamID64 {
get {
if (_OtherSteamID64 != 0) {
return _OtherSteamID64;
}
if ((Type != EType.Trade) || (OtherSteamID3 == 0)) {
return 0;
}
_OtherSteamID64 = new SteamID(OtherSteamID3, EUniverse.Public, EAccountType.Individual);
return _OtherSteamID64;
}
}
#pragma warning disable 649
[JsonProperty(PropertyName = "html")]
private string HTML;
#pragma warning restore 649
private uint _OtherSteamID3;
private uint OtherSteamID3 {
get {
if (_OtherSteamID3 != 0) {
return _OtherSteamID3;
}
if ((Type != EType.Trade) || (HtmlDocument == null)) {
return 0;
}
HtmlNode htmlNode = HtmlDocument.DocumentNode.SelectSingleNode("//a/@data-miniprofile");
if (htmlNode == null) {
Logging.LogNullError(nameof(htmlNode));
return 0;
}
string miniProfile = htmlNode.GetAttributeValue("data-miniprofile", null);
if (string.IsNullOrEmpty(miniProfile)) {
Logging.LogNullError(nameof(miniProfile));
return 0;
}
if (uint.TryParse(miniProfile, out _OtherSteamID3) && (_OtherSteamID3 != 0)) {
return _OtherSteamID3;
}
Logging.LogNullError(nameof(_OtherSteamID3));
return 0;
}
}
private HtmlDocument _HtmlDocument;
private HtmlDocument HtmlDocument {
get {
if (_HtmlDocument != null) {
return _HtmlDocument;
}
if (string.IsNullOrEmpty(HTML)) {
return null;
}
_HtmlDocument = new HtmlDocument();
_HtmlDocument.LoadHtml(WebUtility.HtmlDecode(HTML));
return _HtmlDocument;
}
}
private ConfirmationDetails() { }
}
}
}

View File

@@ -23,148 +23,165 @@
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using NLog;
using NLog.Config;
using NLog.Targets;
namespace ArchiSteamFarm {
internal static class Logging {
private static readonly object FileLock = new object();
private const string Layout = @"${date:format=yyyy-MM-dd HH\:mm\:ss}|${level:uppercase=true}|${message}${onexception:inner= ${exception:format=toString,Data}}";
private static bool LogToFile;
private static readonly HashSet<LoggingRule> ConsoleLoggingRules = new HashSet<LoggingRule>();
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
internal static void Init() {
LogToFile = Program.GlobalConfig.LogToFile;
private static bool IsUsingCustomConfiguration;
if (!LogToFile) {
internal static void InitCoreLoggers() {
if (LogManager.Configuration != null) {
// User provided custom NLog config, or we have it set already, so don't override it
IsUsingCustomConfiguration = true;
InitConsoleLoggers();
return;
}
lock (FileLock) {
if (!LogToFile) {
return;
}
LoggingConfiguration config = new LoggingConfiguration();
try {
File.Delete(Program.LogFile);
} catch (Exception e) {
LogToFile = false;
LogGenericException(e);
}
}
ColoredConsoleTarget consoleTarget = new ColoredConsoleTarget("Console") {
Layout = Layout
};
config.AddTarget(consoleTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, consoleTarget));
LogManager.Configuration = config;
InitConsoleLoggers();
}
internal static void LogGenericWTF(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
internal static void InitEnhancedLoggers() {
if (IsUsingCustomConfiguration) {
return;
}
if (Program.GlobalConfig.LogToFile) {
FileTarget fileTarget = new FileTarget("File") {
DeleteOldFileOnStartup = true,
FileName = Program.LogFile,
Layout = Layout
};
LogManager.Configuration.AddTarget(fileTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget));
}
if (Program.IsRunningAsService) {
EventLogTarget eventLogTarget = new EventLogTarget("EventLog") {
Layout = Layout
};
LogManager.Configuration.AddTarget(eventLogTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, eventLogTarget));
}
LogGenericInfo("Logging module initialized!");
}
internal static void OnUserInputStart() {
if (ConsoleLoggingRules.Count == 0) {
return;
}
foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) {
LogManager.Configuration.LoggingRules.Remove(consoleLoggingRule);
}
LogManager.ReconfigExistingLoggers();
}
internal static void OnUserInputEnd() {
if (ConsoleLoggingRules.Count == 0) {
return;
}
foreach (LoggingRule consoleLoggingRule in ConsoleLoggingRules) {
LogManager.Configuration.LoggingRules.Add(consoleLoggingRule);
}
LogManager.ReconfigExistingLoggers();
}
internal static void LogGenericError(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
}
Log("[!!] WTF: " + previousMethodName + "() <" + botName + "> " + message + ", WTF?");
Logger.Error($"{botName}|{previousMethodName}() {message}");
}
internal static void LogGenericError(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
internal static void LogGenericException(Exception exception, string botName = Program.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 = Program.ASF, [CallerMemberName] string previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception), botName);
return;
}
Logger.Fatal(exception, $"{botName}|{previousMethodName}()");
}
internal static void LogGenericWarning(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
}
Log("[!!] ERROR: " + previousMethodName + "() <" + botName + "> " + message);
Logger.Warn($"{botName}|{previousMethodName}() {message}");
}
internal static void LogGenericException(Exception exception, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
while (true) {
if (exception == null) {
LogNullError(nameof(exception), botName);
return;
}
Log("[!] EXCEPTION: " + previousMethodName + "() <" + botName + "> " + exception.Message);
Log("[!] StackTrace:" + Environment.NewLine + exception.StackTrace);
if (exception.InnerException != null) {
exception = exception.InnerException;
continue;
}
break;
}
}
internal static void LogGenericWarning(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
internal static void LogGenericInfo(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
}
Log("[!] WARNING: " + previousMethodName + "() <" + botName + "> " + message);
}
internal static void LogGenericInfo(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
}
Log("[*] INFO: " + previousMethodName + "() <" + botName + "> " + message);
Logger.Info($"{botName}|{previousMethodName}() {message}");
}
[SuppressMessage("ReSharper", "ExplicitCallerInfoArgument")]
internal static void LogNullError(string nullObjectName, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
while (true) {
if (string.IsNullOrEmpty(nullObjectName)) {
nullObjectName = nameof(nullObjectName);
continue;
}
LogGenericError(nullObjectName + " is null!", botName, previousMethodName);
break;
internal static void LogNullError(string nullObjectName, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(nullObjectName)) {
return;
}
LogGenericError(nullObjectName + " is null!", botName, previousMethodName);
}
[Conditional("DEBUG"), SuppressMessage("ReSharper", "UnusedMember.Global")]
internal static void LogGenericDebug(string message, string botName = "Main", [CallerMemberName] string previousMethodName = null) {
[Conditional("DEBUG")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal static void LogGenericDebug(string message, string botName = Program.ASF, [CallerMemberName] string previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message), botName);
return;
}
Log("[#] DEBUG: " + previousMethodName + "() <" + botName + "> " + message);
Logger.Debug($"{botName}|{previousMethodName}() {message}");
}
private static void Log(string message) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
}
string loggedMessage = DateTime.Now + " " + message + Environment.NewLine;
// Write on console only when not awaiting response from user
if (!Program.ConsoleIsBusy) {
try {
Console.Write(loggedMessage);
} catch {
// Ignored
}
}
if (!LogToFile) {
return;
}
lock (FileLock) {
if (!LogToFile) {
return;
}
try {
File.AppendAllText(Program.LogFile, loggedMessage);
} catch (Exception e) {
LogToFile = false;
LogGenericException(e);
}
private static void InitConsoleLoggers() {
foreach (LoggingRule loggingRule in from loggingRule in LogManager.Configuration.LoggingRules from target in loggingRule.Targets where target is ColoredConsoleTarget || target is ConsoleTarget select loggingRule) {
ConsoleLoggingRules.Add(loggingRule);
}
}
}

View File

@@ -0,0 +1,318 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.JSON;
using HtmlAgilityPack;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class MobileAuthenticator {
internal sealed class Confirmation {
internal readonly uint ID;
internal readonly ulong Key;
internal readonly Steam.ConfirmationDetails.EType Type;
internal Confirmation(uint id, ulong key, Steam.ConfirmationDetails.EType type) {
if ((id == 0) || (key == 0) || (type == Steam.ConfirmationDetails.EType.Unknown)) {
throw new ArgumentNullException(nameof(id) + " || " + nameof(key) + " || " + nameof(type));
}
ID = id;
Key = key;
Type = type;
}
}
private static readonly byte[] TokenCharacters = { 50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89 };
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
private static short SteamTimeDifference;
internal bool HasDeviceID => !string.IsNullOrEmpty(DeviceID);
[JsonProperty(PropertyName = "shared_secret", Required = Required.DisallowNull)]
private string SharedSecret;
[JsonProperty(PropertyName = "identity_secret", Required = Required.DisallowNull)]
private string IdentitySecret;
[JsonProperty(PropertyName = "device_id")]
private string DeviceID;
private Bot Bot;
internal static MobileAuthenticator LoadFromSteamGuardAccount(ObsoleteSteamGuardAccount sga) {
if (sga != null) {
return new MobileAuthenticator {
SharedSecret = sga.SharedSecret,
IdentitySecret = sga.IdentitySecret,
DeviceID = sga.DeviceID
};
}
Logging.LogNullError(nameof(sga));
return null;
}
private MobileAuthenticator() {
}
internal void Init(Bot bot) {
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
}
Bot = bot;
}
internal void CorrectDeviceID(string deviceID) {
if (string.IsNullOrEmpty(deviceID)) {
Logging.LogNullError(nameof(deviceID), Bot.BotName);
return;
}
DeviceID = deviceID;
}
internal async Task<bool> HandleConfirmation(Confirmation confirmation, bool accept) {
if (confirmation == null) {
Logging.LogNullError(nameof(confirmation), Bot.BotName);
return false;
}
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)) {
return await Bot.ArchiWebHandler.HandleConfirmation(DeviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
}
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
return false;
}
internal async Task<Steam.ConfirmationDetails> GetConfirmationDetails(Confirmation confirmation) {
if (confirmation == null) {
Logging.LogNullError(nameof(confirmation), Bot.BotName);
return null;
}
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
return null;
}
string confirmationHash = GenerateConfirmationKey(time, "conf");
if (string.IsNullOrEmpty(confirmationHash)) {
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
return null;
}
Steam.ConfirmationDetails response = await Bot.ArchiWebHandler.GetConfirmationDetails(DeviceID, confirmationHash, time, confirmation.ID).ConfigureAwait(false);
if ((response == null) || !response.Success) {
return null;
}
return response;
}
internal async Task<string> GenerateToken() {
uint time = await GetSteamTime().ConfigureAwait(false);
if (time != 0) {
return GenerateTokenForTime(time);
}
Logging.LogNullError(nameof(time), Bot.BotName);
return null;
}
internal async Task<HashSet<Confirmation>> GetConfirmations() {
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
return null;
}
string confirmationHash = GenerateConfirmationKey(time, "conf");
if (string.IsNullOrEmpty(confirmationHash)) {
Logging.LogNullError(nameof(confirmationHash), Bot.BotName);
return null;
}
HtmlDocument htmlDocument = await Bot.ArchiWebHandler.GetConfirmations(DeviceID, confirmationHash, time).ConfigureAwait(false);
if (htmlDocument == null) {
return null;
}
HtmlNodeCollection confirmationNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='mobileconf_list_entry']");
if (confirmationNodes == null) {
return null;
}
HashSet<Confirmation> result = new HashSet<Confirmation>();
foreach (HtmlNode confirmationNode in confirmationNodes) {
string idString = confirmationNode.GetAttributeValue("data-confid", null);
if (string.IsNullOrEmpty(idString)) {
Logging.LogNullError(nameof(idString), Bot.BotName);
return null;
}
uint id;
if (!uint.TryParse(idString, out id) || (id == 0)) {
Logging.LogNullError(nameof(id), Bot.BotName);
return null;
}
string keyString = confirmationNode.GetAttributeValue("data-key", null);
if (string.IsNullOrEmpty(keyString)) {
Logging.LogNullError(nameof(keyString), Bot.BotName);
return null;
}
ulong key;
if (!ulong.TryParse(keyString, out key) || (key == 0)) {
Logging.LogNullError(nameof(key), Bot.BotName);
return null;
}
HtmlNode descriptionNode = confirmationNode.SelectSingleNode(".//div[@class='mobileconf_list_entry_description']/div");
if (descriptionNode == null) {
Logging.LogNullError(nameof(descriptionNode), Bot.BotName);
return null;
}
Steam.ConfirmationDetails.EType type;
string description = descriptionNode.InnerText;
if (description.Equals("Sell - Market Listing")) {
type = Steam.ConfirmationDetails.EType.Market;
} else if (description.StartsWith("Trade with ", StringComparison.Ordinal)) {
type = Steam.ConfirmationDetails.EType.Trade;
} else {
type = Steam.ConfirmationDetails.EType.Other;
}
result.Add(new Confirmation(id, key, type));
}
return result;
}
internal async Task<uint> GetSteamTime() {
if (SteamTimeDifference != 0) {
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference);
}
await TimeSemaphore.WaitAsync().ConfigureAwait(false);
if (SteamTimeDifference == 0) {
uint serverTime = Bot.ArchiWebHandler.GetServerTime();
if (serverTime != 0) {
SteamTimeDifference = (short) (serverTime - Utilities.GetUnixTime());
}
}
TimeSemaphore.Release();
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference);
}
private string GenerateTokenForTime(long time) {
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
return null;
}
byte[] sharedSecretArray = Convert.FromBase64String(SharedSecret);
byte[] timeArray = new byte[8];
time /= 30L;
for (int i = 8; i > 0; i--) {
timeArray[i - 1] = (byte) time;
time >>= 8;
}
byte[] hashedData;
using (HMACSHA1 hmacGenerator = new HMACSHA1(sharedSecretArray, true)) {
hashedData = hmacGenerator.ComputeHash(timeArray);
}
byte b = (byte) (hashedData[19] & 0xF);
int codePoint = ((hashedData[b] & 0x7F) << 24) | ((hashedData[b + 1] & 0xFF) << 16) | ((hashedData[b + 2] & 0xFF) << 8) | (hashedData[b + 3] & 0xFF);
byte[] codeArray = new byte[5];
for (int i = 0; i < 5; ++i) {
codeArray[i] = TokenCharacters[codePoint % TokenCharacters.Length];
codePoint /= TokenCharacters.Length;
}
return Encoding.UTF8.GetString(codeArray);
}
private string GenerateConfirmationKey(uint time, string tag = null) {
if (time == 0) {
Logging.LogNullError(nameof(time), Bot.BotName);
return null;
}
byte[] b64Secret = Convert.FromBase64String(IdentitySecret);
int bufferSize = 8;
if (string.IsNullOrEmpty(tag) == false) {
bufferSize += Math.Min(32, tag.Length);
}
byte[] buffer = new byte[bufferSize];
byte[] timeArray = BitConverter.GetBytes((long) time);
if (BitConverter.IsLittleEndian) {
Array.Reverse(timeArray);
}
Array.Copy(timeArray, buffer, 8);
if (string.IsNullOrEmpty(tag) == false) {
Array.Copy(Encoding.UTF8.GetBytes(tag), 0, buffer, 8, bufferSize - 8);
}
byte[] hash;
using (HMACSHA1 hmac = new HMACSHA1(b64Secret, true)) {
hash = hmac.ComputeHash(buffer);
}
return Convert.ToBase64String(hash, Base64FormattingOptions.None);
}
}
}

View File

@@ -0,0 +1,49 @@
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
// TODO: This will be completely removed soon
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "InconsistentNaming")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
public class ObsoleteSteamGuardAccount {
[JsonProperty("shared_secret")]
public string SharedSecret { get; set; }
[JsonProperty("serial_number")]
public string SerialNumber { get; set; }
[JsonProperty("revocation_code")]
public string RevocationCode { get; set; }
[JsonProperty("uri")]
public string URI { get; set; }
[JsonProperty("server_time")]
public long ServerTime { get; set; }
[JsonProperty("account_name")]
public string AccountName { get; set; }
[JsonProperty("token_gid")]
public string TokenGID { get; set; }
[JsonProperty("identity_secret")]
public string IdentitySecret { get; set; }
[JsonProperty("secret_1")]
public string Secret1 { get; set; }
[JsonProperty("status")]
public int Status { get; set; }
[JsonProperty("device_id")]
public string DeviceID { get; set; }
[JsonProperty("fully_enrolled")]
public bool FullyEnrolled { get; set; }
}
}

View File

@@ -30,6 +30,7 @@ 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;
@@ -58,13 +59,12 @@ namespace ArchiSteamFarm {
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";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
private const string ASF = "ASF";
private const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
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";
@@ -77,9 +77,9 @@ namespace ArchiSteamFarm {
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
private static readonly WCF WCF = new WCF();
internal static bool IsRunningAsService { get; private set; }
internal static GlobalConfig GlobalConfig { get; private set; }
internal static GlobalDatabase GlobalDatabase { get; private set; }
internal static bool ConsoleIsBusy { get; private set; }
private static Timer AutoUpdatesTimer;
private static EMode Mode = EMode.Normal;
@@ -91,7 +91,7 @@ namespace ArchiSteamFarm {
// 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 Utilities.SleepAsync(1000).ConfigureAwait(false);
await Task.Delay(1000).ConfigureAwait(false);
try {
File.Delete(oldExeFile);
@@ -172,7 +172,7 @@ namespace ArchiSteamFarm {
if (!updateOverride && !GlobalConfig.AutoUpdates) {
Logging.LogGenericInfo("New version is available!");
Logging.LogGenericInfo("Consider updating yourself!");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
await Task.Delay(5000).ConfigureAwait(false);
return;
}
@@ -199,6 +199,9 @@ namespace ArchiSteamFarm {
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;
@@ -247,17 +250,17 @@ namespace ArchiSteamFarm {
if (GlobalConfig.AutoRestart) {
Logging.LogGenericInfo("Restarting...");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
await Task.Delay(5000).ConfigureAwait(false);
Restart();
} else {
Logging.LogGenericInfo("Exiting...");
await Utilities.SleepAsync(5000).ConfigureAwait(false);
await Task.Delay(5000).ConfigureAwait(false);
Exit();
}
}
internal static void Exit(int exitCode = 0) {
WCF.StopServer();
Shutdown();
Environment.Exit(exitCode);
}
@@ -271,19 +274,19 @@ namespace ArchiSteamFarm {
Exit();
}
internal static string GetUserInput(EUserInputType userInputType, string botName = "Main", string extraInformation = null) {
internal static string GetUserInput(EUserInputType userInputType, string botName = ASF, string extraInformation = null) {
if (userInputType == EUserInputType.Unknown) {
return null;
}
if (GlobalConfig.Headless) {
if (GlobalConfig.Headless || !Runtime.IsUserInteractive) {
Logging.LogGenericWarning("Received a request for user input, but process is running in headless mode!");
return null;
}
string result;
lock (ConsoleLock) {
ConsoleIsBusy = true;
Logging.OnUserInputStart();
switch (userInputType) {
case EUserInputType.DeviceID:
Console.Write("<" + botName + "> Please enter your Device ID (including \"android:\"): ");
@@ -327,13 +330,17 @@ namespace ArchiSteamFarm {
Console.Clear(); // For security purposes
}
ConsoleIsBusy = false;
Logging.OnUserInputEnd();
}
return !string.IsNullOrEmpty(result) ? result.Trim() : null;
}
internal static void OnBotShutdown() {
if (ShutdownResetEvent.IsSet) {
return;
}
if (Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
return;
}
@@ -347,6 +354,19 @@ namespace ArchiSteamFarm {
ShutdownResetEvent.Set();
}
private static void Shutdown() {
if (ShutdownResetEvent.IsSet) {
return;
}
ShutdownResetEvent.Set();
WCF.StopServer();
foreach (Bot bot in Bot.Bots.Values) {
bot.Stop();
}
}
private static void InitServices() {
GlobalConfig = GlobalConfig.Load(Path.Combine(ConfigDirectory, GlobalConfigFile));
if (GlobalConfig == null) {
@@ -366,7 +386,7 @@ namespace ArchiSteamFarm {
WebBrowser.Init();
WCF.Init();
WebBrowser = new WebBrowser("Main");
WebBrowser = new WebBrowser(ASF);
}
private static void ParseArgs(IEnumerable<string> args) {
@@ -388,13 +408,18 @@ namespace ArchiSteamFarm {
break;
default:
if (arg.StartsWith("--", StringComparison.Ordinal)) {
Logging.LogGenericWarning("Unrecognized parameter: " + arg);
continue;
if (arg.StartsWith("--cryptkey=", StringComparison.Ordinal) && (arg.Length > 11)) {
CryptoHelper.SetEncryptionKey(arg.Substring(11));
} else {
Logging.LogGenericWarning("Unrecognized parameter: " + arg);
}
break;
}
if (Mode != EMode.Client) {
Logging.LogGenericWarning("Ignoring command because --client wasn't specified: " + arg);
continue;
break;
}
Logging.LogGenericInfo("Command sent: " + arg);
@@ -417,7 +442,7 @@ namespace ArchiSteamFarm {
return;
}
Logging.LogGenericException((Exception) args.ExceptionObject);
Logging.LogFatalException((Exception) args.ExceptionObject);
}
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs args) {
@@ -426,14 +451,16 @@ namespace ArchiSteamFarm {
return;
}
Logging.LogGenericException(args.Exception);
Logging.LogFatalException(args.Exception);
}
private static void Init(IEnumerable<string> args) {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler;
Logging.InitCoreLoggers();
Logging.LogGenericInfo("ASF V" + Version);
Directory.SetCurrentDirectory(ExecutableDirectory);
InitServices();
@@ -477,7 +504,7 @@ namespace ArchiSteamFarm {
}
// From now on it's server mode
Logging.Init();
Logging.InitEnhancedLoggers();
if (!Directory.Exists(ConfigDirectory)) {
Logging.LogGenericError("Config directory doesn't exist!");
@@ -517,13 +544,37 @@ namespace ArchiSteamFarm {
}
private static void Main(string[] args) {
Init(args);
if (Runtime.IsUserInteractive) {
// App
Init(args);
// Wait for signal to shutdown
ShutdownResetEvent.Wait();
// Wait for signal to shutdown
ShutdownResetEvent.Wait();
// We got a signal to shutdown
Exit();
// We got a signal to shutdown
Exit();
} else {
// Service
IsRunningAsService = true;
using (Service service = new Service()) {
ServiceBase.Run(service);
}
}
}
private sealed class Service : ServiceBase {
internal Service() {
ServiceName = SharedInfo.ServiceName;
}
protected override void OnStart(string[] args) => Task.Run(() => {
Init(args);
ShutdownResetEvent.Wait();
Stop();
});
protected override void OnStop() => Shutdown();
}
}
}

View File

@@ -1,5 +1,6 @@
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
@@ -9,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ArchiSteamFarm")]
[assembly: AssemblyCopyright("Copyright © ArchiSteamFarm 2015-2016")]
[assembly: AssemblyCopyright(SharedInfo.Copyright)]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -31,5 +32,5 @@ using System.Runtime.InteropServices;
// 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("2.0.5.2")]
[assembly: AssemblyFileVersion("2.0.5.2")]
[assembly: AssemblyVersion(SharedInfo.Version)]
[assembly: AssemblyFileVersion(SharedInfo.Version)]

108
ArchiSteamFarm/Runtime.cs Normal file
View File

@@ -0,0 +1,108 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.Reflection;
namespace ArchiSteamFarm {
internal static class Runtime {
private static readonly Type MonoRuntime = Type.GetType("Mono.Runtime");
private static bool IsRunningOnMono => MonoRuntime != null;
private static bool? _IsUserInteractive;
internal static bool IsUserInteractive {
get {
if (_IsUserInteractive.HasValue) {
return _IsUserInteractive.Value;
}
if (Environment.UserInteractive) {
_IsUserInteractive = true;
return true;
}
// If it's non-Mono, we can trust the result
if (!IsRunningOnMono) {
_IsUserInteractive = false;
return false;
}
// In Mono, Environment.UserInteractive is always false
// There is really no reliable way for now, so assume always being interactive
// Maybe in future I find out some awful hack or workaround that could be at least semi-reliable
_IsUserInteractive = true;
return true;
}
}
internal static bool RequiresWorkaroundForMonoBug41701() {
// Mono only, https://bugzilla.xamarin.com/show_bug.cgi?id=41701
if (!IsRunningOnMono) {
return false;
}
Version monoVersion = GetMonoVersion();
if (monoVersion == null) {
return false;
}
return monoVersion >= new Version(4, 4);
}
private static Version GetMonoVersion() {
if (MonoRuntime == null) {
Logging.LogNullError(nameof(MonoRuntime));
return null;
}
MethodInfo displayName = MonoRuntime.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayName == null) {
Logging.LogNullError(nameof(displayName));
return null;
}
string versionString = (string) displayName.Invoke(null, null);
if (string.IsNullOrEmpty(versionString)) {
Logging.LogNullError(nameof(versionString));
return null;
}
int index = versionString.IndexOf(' ');
if (index <= 0) {
Logging.LogNullError(nameof(index));
return null;
}
versionString = versionString.Substring(0, index);
Version version;
if (Version.TryParse(versionString, out version)) {
return version;
}
Logging.LogNullError(nameof(version));
return null;
}
}
}

View File

@@ -0,0 +1,34 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2016 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
namespace ArchiSteamFarm {
internal static class SharedInfo {
internal const string Version = "2.1.2.0";
internal const string Copyright = "Copyright © ArchiSteamFarm 2015-2016";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
internal const string ServiceName = "ArchiSteamFarm";
internal const string ServiceDescription = "ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.";
}
}

View File

@@ -22,9 +22,9 @@
*/
using SteamAuth;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -32,12 +32,23 @@ using ArchiSteamFarm.JSON;
namespace ArchiSteamFarm {
internal sealed class Trading {
private enum ParseTradeResult : byte {
[SuppressMessage("ReSharper", "UnusedMember.Local")]
Unknown,
Error,
AcceptedWithItemLose,
AcceptedWithoutItemLose,
RejectedTemporarily,
RejectedPermanently
}
internal const byte MaxItemsPerTrade = 150; // This is due to limit on POST size in WebBrowser
internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1);
private readonly Bot Bot;
private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>();
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1);
private byte ParsingTasks;
@@ -45,7 +56,7 @@ namespace ArchiSteamFarm {
internal static async Task LimitInventoryRequestsAsync() {
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
Task.Run(async () => {
await Utilities.SleepAsync(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
await Task.Delay(Program.GlobalConfig.InventoryLimiterDelay * 1000).ConfigureAwait(false);
InventorySemaphore.Release();
}).Forget();
}
@@ -58,6 +69,8 @@ namespace ArchiSteamFarm {
Bot = bot;
}
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim();
internal async Task CheckTrades() {
lock (TradesSemaphore) {
if (ParsingTasks >= 2) {
@@ -82,80 +95,115 @@ namespace ArchiSteamFarm {
return;
}
HashSet<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetTradeOffers();
HashSet<Steam.TradeOffer> tradeOffers = Bot.ArchiWebHandler.GetActiveTradeOffers();
if ((tradeOffers == null) || (tradeOffers.Count == 0)) {
return;
}
tradeOffers.RemoveWhere(tradeoffer => tradeoffer.State != Steam.TradeOffer.ETradeOfferState.Active);
tradeOffers.TrimExcess();
if (tradeOffers.Count == 0) {
return;
if (tradeOffers.RemoveWhere(tradeoffer => IgnoredTrades.Contains(tradeoffer.TradeOfferID)) > 0) {
tradeOffers.TrimExcess();
if (tradeOffers.Count == 0) {
return;
}
}
await tradeOffers.ForEachAsync(ParseTrade).ConfigureAwait(false);
await Bot.AcceptConfirmations(true, Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
ParseTradeResult[] results = await Task.WhenAll(tradeOffers.Select(ParseTrade)).ConfigureAwait(false);
if (results.Any(result => result == ParseTradeResult.AcceptedWithItemLose)) {
await Task.Delay(1000).ConfigureAwait(false); // Sometimes we can be too fast for Steam servers to generate confirmations, wait a short moment
HashSet<ulong> tradeIDs = new HashSet<ulong>(tradeOffers.Select(tradeOffer => tradeOffer.TradeOfferID));
await Bot.AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, 0, tradeIDs).ConfigureAwait(false);
}
}
private async Task ParseTrade(Steam.TradeOffer tradeOffer) {
private async Task<ParseTradeResult> ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
return;
return ParseTradeResult.Error;
}
if (tradeOffer.State != Steam.TradeOffer.ETradeOfferState.Active) {
return;
return ParseTradeResult.Error;
}
if (await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false)) {
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false);
} else if (Bot.BotConfig.IsBotAccount) {
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID);
} else {
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
ParseTradeResult result = await ShouldAcceptTrade(tradeOffer).ConfigureAwait(false);
switch (result) {
case ParseTradeResult.AcceptedWithItemLose:
case ParseTradeResult.AcceptedWithoutItemLose:
Logging.LogGenericInfo("Accepting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return await Bot.ArchiWebHandler.AcceptTradeOffer(tradeOffer.TradeOfferID).ConfigureAwait(false) ? result : ParseTradeResult.Error;
case ParseTradeResult.RejectedPermanently:
case ParseTradeResult.RejectedTemporarily:
if (result == ParseTradeResult.RejectedPermanently) {
if (Bot.BotConfig.IsBotAccount) {
Logging.LogGenericInfo("Rejecting trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return Bot.ArchiWebHandler.DeclineTradeOffer(tradeOffer.TradeOfferID) ? result : ParseTradeResult.Error;
}
IgnoredTrades.Add(tradeOffer.TradeOfferID);
}
Logging.LogGenericInfo("Ignoring trade: " + tradeOffer.TradeOfferID, Bot.BotName);
return result;
default:
return result;
}
}
private async Task<bool> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
private async Task<ParseTradeResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Logging.LogNullError(nameof(tradeOffer), Bot.BotName);
return false;
return ParseTradeResult.Error;
}
// Always accept trades when we're not losing anything
if (tradeOffer.ItemsToGive.Count == 0) {
// Unless it's steam fuckup and we're dealing with broken trade
return tradeOffer.ItemsToReceive.Count > 0;
return tradeOffer.ItemsToReceive.Count > 0 ? ParseTradeResult.AcceptedWithoutItemLose : ParseTradeResult.RejectedTemporarily;
}
// Always accept trades from SteamMasterID
if ((tradeOffer.OtherSteamID64 != 0) && (tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID)) {
return true;
return ParseTradeResult.AcceptedWithItemLose;
}
// If we don't have SteamTradeMatcher enabled, this is the end for us
if (!Bot.BotConfig.SteamTradeMatcher) {
return false;
return ParseTradeResult.RejectedPermanently;
}
// Decline trade if we're giving more count-wise
if (tradeOffer.ItemsToGive.Count > tradeOffer.ItemsToReceive.Count) {
return false;
return ParseTradeResult.RejectedPermanently;
}
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade
if (!tradeOffer.IsSteamCardsOnlyTradeForUs() || !tradeOffer.IsPotentiallyDupesTradeForUs()) {
return false;
return ParseTradeResult.RejectedPermanently;
}
// At this point we're sure that STM trade is valid
// Fetch trade hold duration
byte? holdDuration = await Bot.ArchiWebHandler.GetTradeHoldDuration(tradeOffer.TradeOfferID).ConfigureAwait(false);
if (!holdDuration.HasValue) {
// If we can't get trade hold duration, reject trade temporarily
return ParseTradeResult.RejectedTemporarily;
}
// If user has a trade hold, we add extra logic
if (holdDuration.Value > 0) {
// If trade hold duration exceeds our max, or user asks for cards with short lifespan, reject the trade
if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.GlobalBlacklist.Contains(item.RealAppID))) {
return ParseTradeResult.RejectedPermanently;
}
}
// Now check if it's worth for us to do the trade
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyTradableInventory().ConfigureAwait(false);
await LimitInventoryRequestsAsync().ConfigureAwait(false);
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMyInventory(false).ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) {
return true; // OK, assume that this trade is valid, we can't check our EQ
return ParseTradeResult.AcceptedWithItemLose; // OK, assume that this trade is valid, we can't check our EQ
}
// Get appIDs we're interested in
@@ -167,7 +215,7 @@ namespace ArchiSteamFarm {
// If for some reason Valve is talking crap and we can't find mentioned items, assume OK
if (inventory.Count == 0) {
return true;
return ParseTradeResult.AcceptedWithItemLose;
}
// Now let's create a map which maps items to their amount in our EQ
@@ -183,14 +231,16 @@ namespace ArchiSteamFarm {
// Calculate our value of items to give
List<uint> amountsToGive = new List<uint>(tradeOffer.ItemsToGive.Count);
Dictionary<ulong, uint> amountMapToGive = new Dictionary<ulong, uint>(amountMap);
foreach (ulong key in tradeOffer.ItemsToGive.Select(item => item.ClassID)) {
uint amount;
if (!amountMap.TryGetValue(key, out amount)) {
if (!amountMapToGive.TryGetValue(key, out amount)) {
amountsToGive.Add(0);
continue;
}
amountsToGive.Add(amount);
amountMapToGive[key] = amount - 1; // We're giving one, so we have one less
}
// Sort it ascending
@@ -198,24 +248,27 @@ namespace ArchiSteamFarm {
// Calculate our value of items to receive
List<uint> amountsToReceive = new List<uint>(tradeOffer.ItemsToReceive.Count);
Dictionary<ulong, uint> amountMapToReceive = new Dictionary<ulong, uint>(amountMap);
foreach (ulong key in tradeOffer.ItemsToReceive.Select(item => item.ClassID)) {
uint amount;
if (!amountMap.TryGetValue(key, out amount)) {
if (!amountMapToReceive.TryGetValue(key, out amount)) {
amountsToReceive.Add(0);
continue;
}
amountsToReceive.Add(amount);
amountMapToReceive[key] = amount + 1; // We're getting one, so we have one more
}
// Sort it ascending
amountsToReceive.Sort();
// Check actual difference
// We sum only values at proper indexes of giving, because user might be overpaying
int difference = amountsToGive.Select((t, i) => (int) (t - amountsToReceive[i])).Sum();
// Trade is worth for us if the difference is greater than 0
return difference > 0;
return difference > 0 ? ParseTradeResult.AcceptedWithItemLose : ParseTradeResult.RejectedTemporarily;
}
}
}

View File

@@ -23,7 +23,6 @@
*/
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net;
@@ -34,15 +33,6 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
internal static void Forget(this Task task) { }
internal static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
if (action != null) {
return Task.WhenAll(sequence.Select(action));
}
Logging.LogNullError(nameof(action));
return Task.FromResult(true);
}
internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
if (string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) {
Logging.LogNullError(nameof(url) + " || " + nameof(name));
@@ -62,13 +52,6 @@ namespace ArchiSteamFarm {
return cookies.Count == 0 ? null : (from Cookie cookie in cookies where cookie.Name.Equals(name) select cookie.Value).FirstOrDefault();
}
internal static Task SleepAsync(int miliseconds) {
if (miliseconds >= 0) {
return Task.Delay(miliseconds);
}
Logging.LogNullError(nameof(miliseconds));
return Task.FromResult(true);
}
internal static uint GetUnixTime() => (uint) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
}
}

View File

@@ -80,11 +80,14 @@ namespace ArchiSteamFarm {
// Most web services expect that UserAgent is set, so we declare it globally
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
// We should always operate in English language, declare it globally
HttpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.8,en-GB;q=0.6");
}
internal async Task<bool> UrlHeadRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return false;
}
@@ -97,13 +100,13 @@ namespace ArchiSteamFarm {
return true;
}
Logging.LogGenericWTF("Request failed even after " + MaxRetries + " tries", Identifier);
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return false;
}
internal async Task<Uri> UrlHeadToUriRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -116,13 +119,13 @@ namespace ArchiSteamFarm {
return result;
}
Logging.LogGenericWTF("Request failed even after " + MaxRetries + " tries", Identifier);
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return null;
}
internal async Task<byte[]> UrlGetToBytesRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -135,13 +138,13 @@ namespace ArchiSteamFarm {
return result;
}
Logging.LogGenericWTF("Request failed even after " + MaxRetries + " tries", Identifier);
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return null;
}
internal async Task<string> UrlGetToContentRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -154,13 +157,13 @@ namespace ArchiSteamFarm {
return result;
}
Logging.LogGenericWTF("Request failed even after " + MaxRetries + " tries", Identifier);
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));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -173,13 +176,13 @@ namespace ArchiSteamFarm {
return result;
}
Logging.LogGenericWTF("Request failed even after " + MaxRetries + " tries", Identifier);
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return null;
}
internal async Task<JObject> UrlGetToJObjectRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -192,13 +195,13 @@ namespace ArchiSteamFarm {
return result;
}
Logging.LogGenericWTF("Request failed even after " + MaxRetries + " tries", Identifier);
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return null;
}
internal async Task<XmlDocument> UrlGetToXMLRetry(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -211,13 +214,13 @@ namespace ArchiSteamFarm {
return result;
}
Logging.LogGenericWTF("Request failed even after " + MaxRetries + " tries", Identifier);
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return null;
}
internal async Task<bool> UrlPostRetry(string request, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return false;
}
@@ -230,13 +233,13 @@ namespace ArchiSteamFarm {
return true;
}
Logging.LogGenericWTF("Request failed even after " + MaxRetries + " tries", Identifier);
Logging.LogGenericWarning("Request failed even after " + MaxRetries + " tries", Identifier);
return false;
}
private async Task<byte[]> UrlGetToBytes(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -251,7 +254,7 @@ namespace ArchiSteamFarm {
private async Task<string> UrlGetToContent(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -266,7 +269,7 @@ namespace ArchiSteamFarm {
private async Task<HtmlDocument> UrlGetToHtmlDocument(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -282,7 +285,7 @@ namespace ArchiSteamFarm {
private async Task<JObject> UrlGetToJObject(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -308,13 +311,13 @@ namespace ArchiSteamFarm {
return await UrlRequest(request, HttpMethod.Get, null, referer).ConfigureAwait(false);
}
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
private async Task<XmlDocument> UrlGetToXML(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -337,7 +340,7 @@ namespace ArchiSteamFarm {
private async Task<bool> UrlHead(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return false;
}
@@ -351,13 +354,13 @@ namespace ArchiSteamFarm {
return await UrlRequest(request, HttpMethod.Head, null, referer).ConfigureAwait(false);
}
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
private async Task<Uri> UrlHeadToUri(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
@@ -368,7 +371,7 @@ namespace ArchiSteamFarm {
private async Task<bool> UrlPost(string request, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return false;
}
@@ -382,13 +385,13 @@ namespace ArchiSteamFarm {
return await UrlRequest(request, HttpMethod.Post, data, referer).ConfigureAwait(false);
}
Logging.LogNullError(nameof(request));
Logging.LogNullError(nameof(request), Identifier);
return null;
}
private async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request) || (httpMethod == null)) {
Logging.LogNullError(nameof(request) + " || " + nameof(httpMethod));
Logging.LogNullError(nameof(request) + " || " + nameof(httpMethod), Identifier);
return null;
}
@@ -413,7 +416,12 @@ namespace ArchiSteamFarm {
try {
responseMessage = await HttpClient.SendAsync(requestMessage).ConfigureAwait(false);
} catch { // Request failed, we don't need to know the exact reason, swallow exception
} catch (Exception e) {
// This exception is really common, don't bother with it unless debug mode is enabled
if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) {
Logging.LogGenericException(e, Identifier);
}
return null;
}
}

View File

@@ -8,9 +8,11 @@
"SteamOwnerID": 0,
"MaxFarmingTime": 10,
"IdleFarmingPeriod": 3,
"FarmingDelay": 5,
"LoginLimiterDelay": 7,
"FarmingDelay": 15,
"LoginLimiterDelay": 10,
"InventoryLimiterDelay": 3,
"GiftsLimiterDelay": 1,
"MaxTradeHoldDuration": 15,
"ForceHttp": false,
"HttpTimeout": 60,
"WCFHostname": "localhost",
@@ -23,6 +25,7 @@
303700,
335590,
368020,
425280
425280,
480730
]
}

View File

@@ -3,6 +3,7 @@
"StartOnLaunch": true,
"SteamLogin": null,
"SteamPassword": null,
"PasswordFormat": 0,
"SteamParentalPIN": "0",
"SteamApiKey": null,
"SteamMasterID": 0,
@@ -16,7 +17,6 @@
"SteamTradeMatcher": false,
"ForwardKeysToOtherBots": false,
"DistributeKeys": false,
"UseAsfAsMobileAuthenticator": false,
"ShutdownOnFarmingFinished": false,
"SendOnFarmingFinished": false,
"SteamTradeToken": null,

View File

@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="HtmlAgilityPack" version="1.4.9" targetFramework="net45" />
<package id="Newtonsoft.Json" version="9.0.1-beta1" targetFramework="net451" />
<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.4" targetFramework="net461" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
<package id="NLog" version="4.4.0-beta13" targetFramework="net461" />
<package id="protobuf-net" version="2.0.0.668" targetFramework="net45" />
<package id="SteamKit2" version="1.7.0" targetFramework="net452" />
</packages>

30
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,30 @@
# Contributing
Before making an issue or pull request, you should carefully read **[ASF wiki](https://github.com/JustArchi/ArchiSteamFarm/wiki)** first.
## 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.
---
### 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?
---
### Suggestions
ASF has rather strict scope - farming Steam cards from Steam games, which means that anything going greatly out of the scope will not be accepted, even if it's considered useful. A good example of that is Steam discovery queue, that provides extra cards during Steam sales - this is out of the scope of ASF as a program, ASF focuses on one task and is doing it efficiently, if you want to create your own bot that does exactly what you want - pay somebody for creating it.
If your suggestion doesn't go out of the scope of ASF, then explain to us in the issue why you consider it useful, why do you think that adding it to ASF is beneficial for **all users**, not yourself. Why we should spend our time coding it, convince us. If suggestion indeed makes sense, or can be considered practical, most likely we won't have anything against that, but **you** should be the one pointing out advantages, not us.
---
## Pull requests
In general any pull request is welcome and should be accepted, unless there is a strong reason against it. A strong reason includes e.g. a feature going potentially out of the scope of ASF. If you're improving existing codebase, rewriting code to be more efficient, clean, better commented - there is absolutely no reason to reject it. If you want to add missing feature, and you're not sure if it should be included in ASF, it won't hurt to ask before spending your own time.
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.

View File

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

View File

@@ -30,8 +30,17 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace ConfigGenerator {
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global"), SuppressMessage("ReSharper", "CollectionNeverQueried.Global"), SuppressMessage("ReSharper", "MemberCanBePrivate.Global"), SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
[SuppressMessage("ReSharper", "CollectionNeverQueried.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class BotConfig : ASFConfig {
internal enum ECryptoMethod : byte {
PlainText,
AES,
ProtectedDataForCurrentUser
}
[JsonProperty(Required = Required.DisallowNull)]
public bool Enabled { get; set; } = false;
@@ -41,9 +50,13 @@ namespace ConfigGenerator {
[JsonProperty]
public string SteamLogin { get; set; } = null;
[JsonProperty, PasswordPropertyText(true)]
[JsonProperty]
[PasswordPropertyText(true)]
public string SteamPassword { get; set; } = null;
[JsonProperty(Required = Required.DisallowNull)]
public ECryptoMethod PasswordFormat { get; set; } = ECryptoMethod.PlainText;
[JsonProperty]
public string SteamParentalPIN { get; set; } = "0";
@@ -83,9 +96,6 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool DistributeKeys { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool UseAsfAsMobileAuthenticator { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool ShutdownOnFarmingFinished { get; set; } = false;

View File

@@ -9,9 +9,12 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ConfigGenerator</RootNamespace>
<AssemblyName>ConfigGenerator</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@@ -39,7 +42,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1-beta1\lib\net45\Newtonsoft.Json.dll</HintPath>
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@@ -48,6 +51,9 @@
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\ArchiSteamFarm\SharedInfo.cs">
<Link>SharedInfo.cs</Link>
</Compile>
<Compile Include="ASFConfig.cs" />
<Compile Include="BotConfig.cs" />
<Compile Include="DialogBox.cs" />
@@ -101,18 +107,27 @@
</ItemGroup>
<ItemGroup>
<Content Include="cirno.ico" />
<None Include="FodyWeavers.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
"$(SolutionDir)tools\ILRepack\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF-ConfigGenerator.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
del "$(SolutionDir)out\ASF-ConfigGenerator.exe.config"
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"
rm "$(SolutionDir)out/ASF-ConfigGenerator.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">

View File

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

View File

@@ -30,9 +30,11 @@ using System.IO;
using System.Net.Sockets;
namespace ConfigGenerator {
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global"), SuppressMessage("ReSharper", "CollectionNeverQueried.Global"), SuppressMessage("ReSharper", "MemberCanBePrivate.Global"), SuppressMessage("ReSharper", "UnusedMember.Global")]
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
[SuppressMessage("ReSharper", "CollectionNeverQueried.Global")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal sealed class GlobalConfig : ASFConfig {
[SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum EUpdateChannel : byte {
Unknown,
Stable,
@@ -40,13 +42,13 @@ namespace ConfigGenerator {
}
private const byte DefaultMaxFarmingTime = 10;
private const byte DefaultFarmingDelay = 5;
private const byte DefaultFarmingDelay = 15;
private const byte DefaultHttpTimeout = 60;
private const ushort DefaultWCFPort = 1242;
private const ProtocolType DefaultSteamProtocol = ProtocolType.Tcp;
// 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 };
private static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730 };
[JsonProperty(Required = Required.DisallowNull)]
public bool Debug { get; set; } = false;
@@ -79,11 +81,17 @@ namespace ConfigGenerator {
public byte FarmingDelay { get; set; } = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
public byte LoginLimiterDelay { get; set; } = 7;
public byte LoginLimiterDelay { get; set; } = 10;
[JsonProperty(Required = Required.DisallowNull)]
public byte InventoryLimiterDelay { get; set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
public byte GiftsLimiterDelay { get; set; } = 1;
[JsonProperty(Required = Required.DisallowNull)]
public byte MaxTradeHoldDuration { get; set; } = 15;
[JsonProperty(Required = Required.DisallowNull)]
public bool ForceHttp { get; set; } = false;

View File

@@ -29,6 +29,7 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using ArchiSteamFarm;
namespace ConfigGenerator {
internal sealed partial class MainForm : Form {
@@ -154,6 +155,19 @@ namespace ConfigGenerator {
// Get rid of any potential whitespaces in bot name
input = Regex.Replace(input, @"\s+", "");
if (string.IsNullOrEmpty(input)) {
Logging.LogGenericErrorWithoutStacktrace("Your bot name is empty!");
return;
}
switch (input) {
case Program.ASF:
case "example":
case "minimal":
Logging.LogGenericErrorWithoutStacktrace("This name is reserved!");
return;
}
if (ASFConfig.ASFConfigs.Select(config => Path.GetFileNameWithoutExtension(config.FilePath)).Any(fileNameWithoutExtension => (fileNameWithoutExtension == null) || fileNameWithoutExtension.Equals(input))) {
Logging.LogGenericErrorWithoutStacktrace("Bot with such name exists already!");
return;
@@ -196,7 +210,7 @@ namespace ConfigGenerator {
args.Cancel = true;
Tutorial.OnAction(Tutorial.EPhase.Help);
Process.Start("https://github.com/JustArchi/ArchiSteamFarm/wiki/Configuration");
Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/wiki/Configuration");
Tutorial.OnAction(Tutorial.EPhase.HelpFinished);
}
}

View File

@@ -23,10 +23,12 @@
*/
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
using ArchiSteamFarm;
namespace ConfigGenerator {
internal static class Program {
@@ -35,8 +37,10 @@ namespace ConfigGenerator {
internal const string GlobalConfigFile = ASF + ".json";
private const string ASFDirectory = "ArchiSteamFarm";
private const string ASFExecutableFile = ASF + ".exe";
private static readonly string ExecutableDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
private static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
/// <summary>
/// The main entry point for the application.
@@ -75,11 +79,30 @@ namespace ConfigGenerator {
}
}
if (Directory.Exists(ConfigDirectory)) {
if (!Directory.Exists(ConfigDirectory)) {
Logging.LogGenericErrorWithoutStacktrace("Config directory could not be found!");
Environment.Exit(1);
}
if (!File.Exists(ASFExecutableFile)) {
return;
}
Logging.LogGenericErrorWithoutStacktrace("Config directory could not be found!");
FileVersionInfo asfVersionInfo = FileVersionInfo.GetVersionInfo(ASFExecutableFile);
Version asfVersion = new Version(asfVersionInfo.ProductVersion);
if (Version == asfVersion) {
return;
}
Logging.LogGenericErrorWithoutStacktrace(
"Version of ASF and ConfigGenerator doesn't match!" + Environment.NewLine +
"ASF version: " + asfVersion + " | ConfigGenerator version: " + Version + Environment.NewLine +
Environment.NewLine +
"Please use ConfigGenerator from the same ASF release, I'll redirect you to appropriate ASF release..."
);
Process.Start("https://github.com/" + SharedInfo.GithubRepo + "/releases/tag/" + asfVersion);
Environment.Exit(1);
}

View File

@@ -1,5 +1,6 @@
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
@@ -9,7 +10,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ConfigGenerator")]
[assembly: AssemblyCopyright("Copyright © ArchiSteamFarm 2015-2016")]
[assembly: AssemblyCopyright(SharedInfo.Copyright)]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -31,5 +32,5 @@ using System.Runtime.InteropServices;
// 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("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyVersion(SharedInfo.Version)]
[assembly: AssemblyFileVersion(SharedInfo.Version)]

View File

@@ -9,18 +9,18 @@
//------------------------------------------------------------------------------
namespace ConfigGenerator.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;
}
}
}
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="9.0.1-beta1" targetFramework="net451" />
<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="Newtonsoft.Json" version="9.0.1" targetFramework="net461" />
</packages>

View File

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

View File

@@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GUI</RootNamespace>
<AssemblyName>GUI</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>

View File

@@ -1,11 +1,14 @@
ArchiSteamFarm
===================
[![Build Status (Windows)](https://img.shields.io/appveyor/ci/JustArchi/ArchiSteamFarm.svg?label=Windows)](https://ci.appveyor.com/project/JustArchi/ArchiSteamFarm)
[![Build Status (Mono)](https://img.shields.io/travis/JustArchi/ArchiSteamFarm.svg?label=Mono)](https://travis-ci.org/JustArchi/ArchiSteamFarm)
[![GitHub Release](https://img.shields.io/github/release/JustArchi/ArchiSteamFarm.svg?label=Latest)](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)
[![Github All Releases](https://img.shields.io/github/downloads/JustArchi/ArchiSteamFarm/total.svg?label=Downloads)](https://github.com/JustArchi/ArchiSteamFarm/releases)
[![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)
[![Gitter](https://img.shields.io/gitter/room/JustArchi/ArchiSteamFarm.svg?label=Chat&maxAge=60)](https://gitter.im/JustArchi/ArchiSteamFarm)
[![Build Status (Windows)](https://img.shields.io/appveyor/ci/JustArchi/ArchiSteamFarm.svg?label=Windows&maxAge=60)](https://ci.appveyor.com/project/JustArchi/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)
[![Paypal Donate](https://img.shields.io/badge/PayPal-donate-yellow.svg)](https://www.paypal.me/JustArchi/1usd)
[![Steam Donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
---

View File

@@ -1,12 +0,0 @@
namespace SteamAuth
{
public static class APIEndpoints
{
public const string STEAMAPI_BASE = "https://api.steampowered.com";
public const string COMMUNITY_BASE = "https://steamcommunity.com";
public const string MOBILEAUTH_BASE = STEAMAPI_BASE + "/IMobileAuthService/%s/v0001";
public static string MOBILEAUTH_GETWGTOKEN = MOBILEAUTH_BASE.Replace("%s", "GetWGToken");
public const string TWO_FACTOR_BASE = STEAMAPI_BASE + "/ITwoFactorService/%s/v0001";
public static string TWO_FACTOR_TIME_QUERY = TWO_FACTOR_BASE.Replace("%s", "QueryTime");
}
}

View File

@@ -1,292 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace SteamAuth
{
/// <summary>
/// Handles the linking process for a new mobile authenticator.
/// </summary>
public class AuthenticatorLinker
{
/// <summary>
/// Set to register a new phone number when linking. If a phone number is not set on the account, this must be set. If a phone number is set on the account, this must be null.
/// </summary>
public string PhoneNumber = null;
/// <summary>
/// Randomly-generated device ID. Should only be generated once per linker.
/// </summary>
public string DeviceID { get; private set; }
/// <summary>
/// After the initial link step, if successful, this will be the SteamGuard data for the account. PLEASE save this somewhere after generating it; it's vital data.
/// </summary>
public SteamGuardAccount LinkedAccount { get; private set; }
/// <summary>
/// True if the authenticator has been fully finalized.
/// </summary>
public bool Finalized = false;
private SessionData _session;
private CookieContainer _cookies;
public AuthenticatorLinker(SessionData session)
{
this._session = session;
this.DeviceID = GenerateDeviceID();
this._cookies = new CookieContainer();
session.AddCookies(_cookies);
}
public LinkResult AddAuthenticator()
{
bool hasPhone = _hasPhoneAttached();
if (hasPhone && PhoneNumber != null)
return LinkResult.MustRemovePhoneNumber;
if (!hasPhone && PhoneNumber == null)
return LinkResult.MustProvidePhoneNumber;
if (!hasPhone)
{
if (!_addPhoneNumber())
{
return LinkResult.GeneralFailure;
}
}
var postData = new NameValueCollection();
postData.Add("access_token", _session.OAuthToken);
postData.Add("steamid", _session.SteamID.ToString());
postData.Add("authenticator_type", "1");
postData.Add("device_identifier", this.DeviceID);
postData.Add("sms_phone_id", "1");
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/AddAuthenticator/v0001", "POST", postData);
if (response == null) return LinkResult.GeneralFailure;
var addAuthenticatorResponse = JsonConvert.DeserializeObject<AddAuthenticatorResponse>(response);
if (addAuthenticatorResponse == null || addAuthenticatorResponse.Response == null)
{
return LinkResult.GeneralFailure;
}
if (addAuthenticatorResponse.Response.Status == 29)
{
return LinkResult.AuthenticatorPresent;
}
if (addAuthenticatorResponse.Response.Status != 1)
{
return LinkResult.GeneralFailure;
}
this.LinkedAccount = addAuthenticatorResponse.Response;
LinkedAccount.Session = this._session;
LinkedAccount.DeviceID = this.DeviceID;
return LinkResult.AwaitingFinalization;
}
public FinalizeResult FinalizeAddAuthenticator(string smsCode)
{
//The act of checking the SMS code is necessary for Steam to finalize adding the phone number to the account.
//Of course, we only want to check it if we're adding a phone number in the first place...
if (!String.IsNullOrEmpty(this.PhoneNumber) && !this._checkSMSCode(smsCode))
{
return FinalizeResult.BadSMSCode;
}
var postData = new NameValueCollection();
postData.Add("steamid", _session.SteamID.ToString());
postData.Add("access_token", _session.OAuthToken);
postData.Add("activation_code", smsCode);
int tries = 0;
while (tries <= 30)
{
postData.Set("authenticator_code", LinkedAccount.GenerateSteamGuardCode());
postData.Set("authenticator_time", TimeAligner.GetSteamTime().ToString());
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/FinalizeAddAuthenticator/v0001", "POST", postData);
if (response == null) return FinalizeResult.GeneralFailure;
var finalizeResponse = JsonConvert.DeserializeObject<FinalizeAuthenticatorResponse>(response);
if (finalizeResponse == null || finalizeResponse.Response == null)
{
return FinalizeResult.GeneralFailure;
}
if (finalizeResponse.Response.Status == 89)
{
return FinalizeResult.BadSMSCode;
}
if (finalizeResponse.Response.Status == 88)
{
if (tries >= 30)
{
return FinalizeResult.UnableToGenerateCorrectCodes;
}
}
if (!finalizeResponse.Response.Success)
{
return FinalizeResult.GeneralFailure;
}
if (finalizeResponse.Response.WantMore)
{
tries++;
continue;
}
this.LinkedAccount.FullyEnrolled = true;
return FinalizeResult.Success;
}
return FinalizeResult.GeneralFailure;
}
private bool _checkSMSCode(string smsCode)
{
var postData = new NameValueCollection();
postData.Add("op", "check_sms_code");
postData.Add("arg", smsCode);
postData.Add("sessionid", _session.SessionID);
string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
if (response == null) return false;
var addPhoneNumberResponse = JsonConvert.DeserializeObject<AddPhoneResponse>(response);
return addPhoneNumberResponse.Success;
}
private bool _addPhoneNumber()
{
var postData = new NameValueCollection();
postData.Add("op", "add_phone_number");
postData.Add("arg", PhoneNumber);
postData.Add("sessionid", _session.SessionID);
string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
if (response == null) return false;
var addPhoneNumberResponse = JsonConvert.DeserializeObject<AddPhoneResponse>(response);
return addPhoneNumberResponse.Success;
}
private bool _hasPhoneAttached()
{
var postData = new NameValueCollection();
postData.Add("op", "has_phone");
postData.Add("arg", "null");
postData.Add("sessionid", _session.SessionID);
string response = SteamWeb.Request(APIEndpoints.COMMUNITY_BASE + "/steamguard/phoneajax", "POST", postData, _cookies);
if (response == null) return false;
var hasPhoneResponse = JsonConvert.DeserializeObject<HasPhoneResponse>(response);
return hasPhoneResponse.HasPhone;
}
public enum LinkResult
{
MustProvidePhoneNumber, //No phone number on the account
MustRemovePhoneNumber, //A phone number is already on the account
AwaitingFinalization, //Must provide an SMS code
GeneralFailure, //General failure (really now!)
AuthenticatorPresent
}
public enum FinalizeResult
{
BadSMSCode,
UnableToGenerateCorrectCodes,
Success,
GeneralFailure
}
private class AddAuthenticatorResponse
{
[JsonProperty("response")]
public SteamGuardAccount Response { get; set; }
}
private class FinalizeAuthenticatorResponse
{
[JsonProperty("response")]
public FinalizeAuthenticatorInternalResponse Response { get; set; }
internal class FinalizeAuthenticatorInternalResponse
{
[JsonProperty("status")]
public int Status { get; set; }
[JsonProperty("server_time")]
public long ServerTime { get; set; }
[JsonProperty("want_more")]
public bool WantMore { get; set; }
[JsonProperty("success")]
public bool Success { get; set; }
}
}
private class HasPhoneResponse
{
[JsonProperty("has_phone")]
public bool HasPhone { get; set; }
}
private class AddPhoneResponse
{
[JsonProperty("success")]
public bool Success { get; set; }
}
public static string GenerateDeviceID()
{
using (var sha1 = new SHA1Managed())
{
RNGCryptoServiceProvider secureRandom = new RNGCryptoServiceProvider();
byte[] randomBytes = new byte[8];
secureRandom.GetBytes(randomBytes);
byte[] hashedBytes = sha1.ComputeHash(randomBytes);
string random32 = BitConverter.ToString(hashedBytes).Replace("-", "").Substring(0, 32).ToLower();
return "android:" + SplitOnRatios(random32, new[] { 8, 4, 4, 4, 12 }, "-");
}
}
private static string SplitOnRatios(string str, int[] ratios, string intermediate)
{
string result = "";
int pos = 0;
for (int index = 0; index < ratios.Length; index++)
{
result += str.Substring(pos, ratios[index]);
pos = ratios[index];
if (index < ratios.Length - 1)
result += intermediate;
}
return result;
}
}
}

View File

@@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SteamAuth
{
public class Confirmation
{
public string ID;
public string Key;
public string Description;
public ConfirmationType ConfType
{
get
{
if (String.IsNullOrEmpty(Description)) return ConfirmationType.Unknown;
if (Description.StartsWith("Confirm ")) return ConfirmationType.GenericConfirmation;
if (Description.StartsWith("Trade with ")) return ConfirmationType.Trade;
if (Description.StartsWith("Sell -")) return ConfirmationType.MarketSellTransaction;
return ConfirmationType.Unknown;
}
}
public enum ConfirmationType
{
GenericConfirmation,
Trade,
MarketSellTransaction,
Unknown
}
}
}

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Joshua Coffey
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 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("SteamAuth")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SteamAuth")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[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("5ad0934e-f6c4-4ae5-83af-c788313b2a87")]
// 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("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -1,40 +0,0 @@
using System.Net;
namespace SteamAuth
{
public class SessionData
{
public string SessionID { get; set; }
public string SteamLogin { get; set; }
public string SteamLoginSecure { get; set; }
public string WebCookie { get; set; }
public string OAuthToken { get; set; }
public ulong SteamID { get; set; }
public void AddCookies(CookieContainer cookies)
{
cookies.Add(new Cookie("mobileClientVersion", "0 (2.1.3)", "/", ".steamcommunity.com"));
cookies.Add(new Cookie("mobileClient", "android", "/", ".steamcommunity.com"));
cookies.Add(new Cookie("steamid", SteamID.ToString(), "/", ".steamcommunity.com"));
cookies.Add(new Cookie("steamLogin", SteamLogin, "/", ".steamcommunity.com")
{
HttpOnly = true
});
cookies.Add(new Cookie("steamLoginSecure", SteamLoginSecure, "/", ".steamcommunity.com")
{
HttpOnly = true,
Secure = true
});
cookies.Add(new Cookie("Steam_Language", "english", "/", ".steamcommunity.com"));
cookies.Add(new Cookie("dob", "", "/", ".steamcommunity.com"));
cookies.Add(new Cookie("sessionid", this.SessionID, "/", ".steamcommunity.com"));
}
}
}

View File

@@ -1,71 +0,0 @@
<?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>{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SteamAuth</RootNamespace>
<AssemblyName>SteamAuth</AssemblyName>
<TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>
</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1-beta1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="APIEndpoints.cs" />
<Compile Include="AuthenticatorLinker.cs" />
<Compile Include="Confirmation.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SessionData.cs" />
<Compile Include="SteamGuardAccount.cs" />
<Compile Include="SteamWeb.cs" />
<Compile Include="TimeAligner.cs" />
<Compile Include="UserLogin.cs" />
<Compile Include="Util.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.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>

View File

@@ -1,28 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.23107.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamAuth", "SteamAuth.csproj", "{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestBed", "..\TestBed\TestBed.csproj", "{8A732227-C090-4011-9F0A-51180CFE6271}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5AD0934E-F6C4-4AE5-83AF-C788313B2A87}.Release|Any CPU.Build.0 = Release|Any CPU
{8A732227-C090-4011-9F0A-51180CFE6271}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A732227-C090-4011-9F0A-51180CFE6271}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A732227-C090-4011-9F0A-51180CFE6271}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A732227-C090-4011-9F0A-51180CFE6271}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@@ -1,456 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace SteamAuth
{
public class SteamGuardAccount
{
[JsonProperty("shared_secret")]
public string SharedSecret { get; set; }
[JsonProperty("serial_number")]
public string SerialNumber { get; set; }
[JsonProperty("revocation_code")]
public string RevocationCode { get; set; }
[JsonProperty("uri")]
public string URI { get; set; }
[JsonProperty("server_time")]
public long ServerTime { get; set; }
[JsonProperty("account_name")]
public string AccountName { get; set; }
[JsonProperty("token_gid")]
public string TokenGID { get; set; }
[JsonProperty("identity_secret")]
public string IdentitySecret { get; set; }
[JsonProperty("secret_1")]
public string Secret1 { get; set; }
[JsonProperty("status")]
public int Status { get; set; }
[JsonProperty("device_id")]
public string DeviceID { get; set; }
/// <summary>
/// Set to true if the authenticator has actually been applied to the account.
/// </summary>
[JsonProperty("fully_enrolled")]
public bool FullyEnrolled { get; set; }
public SessionData Session { get; set; }
private static byte[] steamGuardCodeTranslations = new byte[] { 50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89 };
public bool DeactivateAuthenticator(int scheme = 2)
{
var postData = new NameValueCollection();
postData.Add("steamid", this.Session.SteamID.ToString());
postData.Add("steamguard_scheme", scheme.ToString());
postData.Add("revocation_code", this.RevocationCode);
postData.Add("access_token", this.Session.OAuthToken);
try
{
string response = SteamWeb.MobileLoginRequest(APIEndpoints.STEAMAPI_BASE + "/ITwoFactorService/RemoveAuthenticator/v0001", "POST", postData);
var removeResponse = JsonConvert.DeserializeObject<RemoveAuthenticatorResponse>(response);
if (removeResponse == null || removeResponse.Response == null || !removeResponse.Response.Success) return false;
return true;
}
catch (Exception)
{
return false;
}
}
public string GenerateSteamGuardCode()
{
return GenerateSteamGuardCodeForTime(TimeAligner.GetSteamTime());
}
public string GenerateSteamGuardCodeForTime(long time)
{
if (this.SharedSecret == null || this.SharedSecret.Length == 0)
{
return "";
}
byte[] sharedSecretArray = Convert.FromBase64String(this.SharedSecret);
byte[] timeArray = new byte[8];
time /= 30L;
for (int i = 8; i > 0; i--)
{
timeArray[i - 1] = (byte)time;
time >>= 8;
}
HMACSHA1 hmacGenerator = new HMACSHA1();
hmacGenerator.Key = sharedSecretArray;
byte[] hashedData = hmacGenerator.ComputeHash(timeArray);
byte[] codeArray = new byte[5];
try
{
byte b = (byte)(hashedData[19] & 0xF);
int codePoint = (hashedData[b] & 0x7F) << 24 | (hashedData[b + 1] & 0xFF) << 16 | (hashedData[b + 2] & 0xFF) << 8 | (hashedData[b + 3] & 0xFF);
for (int i = 0; i < 5; ++i)
{
codeArray[i] = steamGuardCodeTranslations[codePoint % steamGuardCodeTranslations.Length];
codePoint /= steamGuardCodeTranslations.Length;
}
}
catch (Exception)
{
return null; //Change later, catch-alls are bad!
}
return Encoding.UTF8.GetString(codeArray);
}
public Confirmation[] FetchConfirmations()
{
string url = this.GenerateConfirmationURL();
CookieContainer cookies = new CookieContainer();
this.Session.AddCookies(cookies);
string response = SteamWeb.Request(url, "GET", null, cookies);
/*So you're going to see this abomination and you're going to be upset.
It's understandable. But the thing is, regex for HTML -- while awful -- makes this way faster than parsing a DOM, plus we don't need another library.
And because the data is always in the same place and same format... It's not as if we're trying to naturally understand HTML here. Just extract strings.
I'm sorry. */
Regex confIDRegex = new Regex("data-confid=\"(\\d+)\"");
Regex confKeyRegex = new Regex("data-key=\"(\\d+)\"");
Regex confDescRegex = new Regex("<div>((Confirm|Trade with|Sell -) .+)</div>");
if (response == null || !(confIDRegex.IsMatch(response) && confKeyRegex.IsMatch(response) && confDescRegex.IsMatch(response)))
{
if (response == null || !response.Contains("<div>Nothing to confirm</div>"))
{
throw new WGTokenInvalidException();
}
return new Confirmation[0];
}
MatchCollection confIDs = confIDRegex.Matches(response);
MatchCollection confKeys = confKeyRegex.Matches(response);
MatchCollection confDescs = confDescRegex.Matches(response);
List<Confirmation> ret = new List<Confirmation>();
for (int i = 0; i < confIDs.Count; i++)
{
string confID = confIDs[i].Groups[1].Value;
string confKey = confKeys[i].Groups[1].Value;
string confDesc = confDescs[i].Groups[1].Value;
Confirmation conf = new Confirmation()
{
Description = confDesc,
ID = confID,
Key = confKey
};
ret.Add(conf);
}
return ret.ToArray();
}
public async Task<Confirmation[]> FetchConfirmationsAsync()
{
string url = this.GenerateConfirmationURL();
CookieContainer cookies = new CookieContainer();
this.Session.AddCookies(cookies);
string response = await SteamWeb.RequestAsync(url, "GET", null, cookies);
/*So you're going to see this abomination and you're going to be upset.
It's understandable. But the thing is, regex for HTML -- while awful -- makes this way faster than parsing a DOM, plus we don't need another library.
And because the data is always in the same place and same format... It's not as if we're trying to naturally understand HTML here. Just extract strings.
I'm sorry. */
Regex confIDRegex = new Regex("data-confid=\"(\\d+)\"");
Regex confKeyRegex = new Regex("data-key=\"(\\d+)\"");
Regex confDescRegex = new Regex("<div>((Confirm|Trade with|Sell -) .+)</div>");
if (response == null || !(confIDRegex.IsMatch(response) && confKeyRegex.IsMatch(response) && confDescRegex.IsMatch(response)))
{
if (response == null || !response.Contains("<div>Nothing to confirm</div>"))
{
throw new WGTokenInvalidException();
}
return new Confirmation[0];
}
MatchCollection confIDs = confIDRegex.Matches(response);
MatchCollection confKeys = confKeyRegex.Matches(response);
MatchCollection confDescs = confDescRegex.Matches(response);
List<Confirmation> ret = new List<Confirmation>();
for (int i = 0; i < confIDs.Count; i++)
{
string confID = confIDs[i].Groups[1].Value;
string confKey = confKeys[i].Groups[1].Value;
string confDesc = confDescs[i].Groups[1].Value;
Confirmation conf = new Confirmation()
{
Description = confDesc,
ID = confID,
Key = confKey
};
ret.Add(conf);
}
return ret.ToArray();
}
public long GetConfirmationTradeOfferID(Confirmation conf)
{
var confDetails = _getConfirmationDetails(conf);
if (confDetails == null || !confDetails.Success) return -1;
Regex tradeOfferIDRegex = new Regex("<div class=\"tradeoffer\" id=\"tradeofferid_(\\d+)\" >");
if(!tradeOfferIDRegex.IsMatch(confDetails.HTML)) return -1;
return long.Parse(tradeOfferIDRegex.Match(confDetails.HTML).Groups[1].Value);
}
public bool AcceptConfirmation(Confirmation conf)
{
return _sendConfirmationAjax(conf, "allow");
}
public bool DenyConfirmation(Confirmation conf)
{
return _sendConfirmationAjax(conf, "cancel");
}
/// <summary>
/// Refreshes the Steam session. Necessary to perform confirmations if your session has expired or changed.
/// </summary>
/// <returns></returns>
public bool RefreshSession()
{
string url = APIEndpoints.MOBILEAUTH_GETWGTOKEN;
NameValueCollection postData = new NameValueCollection();
postData.Add("access_token", this.Session.OAuthToken);
string response = SteamWeb.Request(url, "POST", postData);
if (response == null) return false;
try
{
var refreshResponse = JsonConvert.DeserializeObject<RefreshSessionDataResponse>(response);
if (refreshResponse == null || refreshResponse.Response == null || String.IsNullOrEmpty(refreshResponse.Response.Token))
return false;
string token = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.Token;
string tokenSecure = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.TokenSecure;
this.Session.SteamLogin = token;
this.Session.SteamLoginSecure = tokenSecure;
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Refreshes the Steam session. Necessary to perform confirmations if your session has expired or changed.
/// </summary>
/// <returns></returns>
public async Task<bool> RefreshSessionAsync()
{
string url = APIEndpoints.MOBILEAUTH_GETWGTOKEN;
NameValueCollection postData = new NameValueCollection();
postData.Add("access_token", this.Session.OAuthToken);
string response = await SteamWeb.RequestAsync(url, "POST", postData);
if (response == null) return false;
try
{
var refreshResponse = JsonConvert.DeserializeObject<RefreshSessionDataResponse>(response);
if (refreshResponse == null || refreshResponse.Response == null || String.IsNullOrEmpty(refreshResponse.Response.Token))
return false;
string token = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.Token;
string tokenSecure = this.Session.SteamID + "%7C%7C" + refreshResponse.Response.TokenSecure;
this.Session.SteamLogin = token;
this.Session.SteamLoginSecure = tokenSecure;
return true;
}
catch (Exception)
{
return false;
}
}
private ConfirmationDetailsResponse _getConfirmationDetails(Confirmation conf)
{
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/details/" + conf.ID + "?";
string queryString = GenerateConfirmationQueryParams("details");
url += queryString;
CookieContainer cookies = new CookieContainer();
this.Session.AddCookies(cookies);
string referer = GenerateConfirmationURL();
string response = SteamWeb.Request(url, "GET", null, cookies, null);
if (String.IsNullOrEmpty(response)) return null;
var confResponse = JsonConvert.DeserializeObject<ConfirmationDetailsResponse>(response);
if (confResponse == null) return null;
return confResponse;
}
private bool _sendConfirmationAjax(Confirmation conf, string op)
{
string url = APIEndpoints.COMMUNITY_BASE + "/mobileconf/ajaxop";
string queryString = "?op=" + op + "&";
queryString += GenerateConfirmationQueryParams(op);
queryString += "&cid=" + conf.ID + "&ck=" + conf.Key;
url += queryString;
CookieContainer cookies = new CookieContainer();
this.Session.AddCookies(cookies);
string referer = GenerateConfirmationURL();
string response = SteamWeb.Request(url, "GET", null, cookies, null);
if (response == null) return false;
SendConfirmationResponse confResponse = JsonConvert.DeserializeObject<SendConfirmationResponse>(response);
return confResponse.Success;
}
public string GenerateConfirmationURL(string tag = "conf")
{
string endpoint = APIEndpoints.COMMUNITY_BASE + "/mobileconf/conf?";
string queryString = GenerateConfirmationQueryParams(tag);
return endpoint + queryString;
}
public string GenerateConfirmationQueryParams(string tag)
{
if (String.IsNullOrEmpty(DeviceID))
throw new ArgumentException("Device ID is not present");
long time = TimeAligner.GetSteamTime();
return "p=" + this.DeviceID + "&a=" + this.Session.SteamID.ToString() + "&k=" + _generateConfirmationHashForTime(time, tag) + "&t=" + time + "&m=android&tag=" + tag;
}
private string _generateConfirmationHashForTime(long time, string tag)
{
byte[] decode = Convert.FromBase64String(this.IdentitySecret);
int n2 = 8;
if (tag != null)
{
if (tag.Length > 32)
{
n2 = 8 + 32;
}
else
{
n2 = 8 + tag.Length;
}
}
byte[] array = new byte[n2];
int n3 = 8;
while (true)
{
int n4 = n3 - 1;
if (n3 <= 0)
{
break;
}
array[n4] = (byte)time;
time >>= 8;
n3 = n4;
}
if (tag != null)
{
Array.Copy(Encoding.UTF8.GetBytes(tag), 0, array, 8, n2 - 8);
}
try
{
HMACSHA1 hmacGenerator = new HMACSHA1();
hmacGenerator.Key = decode;
byte[] hashedData = hmacGenerator.ComputeHash(array);
string encodedData = Convert.ToBase64String(hashedData, Base64FormattingOptions.None);
string hash = WebUtility.UrlEncode(encodedData);
return hash;
}
catch (Exception)
{
return null; //Fix soon: catch-all is BAD!
}
}
//TODO: Determine how to detect an invalid session.
public class WGTokenInvalidException : Exception
{
}
private class RefreshSessionDataResponse
{
[JsonProperty("response")]
public RefreshSessionDataInternalResponse Response { get; set; }
internal class RefreshSessionDataInternalResponse
{
[JsonProperty("token")]
public string Token { get; set; }
[JsonProperty("token_secure")]
public string TokenSecure { get; set; }
}
}
private class RemoveAuthenticatorResponse
{
[JsonProperty("response")]
public RemoveAuthenticatorInternalResponse Response { get; set; }
internal class RemoveAuthenticatorInternalResponse
{
[JsonProperty("success")]
public bool Success { get; set; }
}
}
private class SendConfirmationResponse
{
[JsonProperty("success")]
public bool Success { get; set; }
}
private class ConfirmationDetailsResponse
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("html")]
public string HTML { get; set; }
}
}
}

View File

@@ -1,137 +0,0 @@
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace SteamAuth
{
public class SteamWeb
{
/// <summary>
/// Perform a mobile login request
/// </summary>
/// <param name="url">API url</param>
/// <param name="method">GET or POST</param>
/// <param name="data">Name-data pairs</param>
/// <param name="cookies">current cookie container</param>
/// <returns>response body</returns>
public static string MobileLoginRequest(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null)
{
return Request(url, method, data, cookies, headers, APIEndpoints.COMMUNITY_BASE + "/mobilelogin?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client");
}
public static string Request(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null, string referer = APIEndpoints.COMMUNITY_BASE)
{
string query = (data == null ? string.Empty : string.Join("&", Array.ConvertAll(data.AllKeys, key => String.Format("{0}={1}", WebUtility.UrlEncode(key), WebUtility.UrlEncode(data[key])))));
if (method == "GET")
{
url += (url.Contains("?") ? "&" : "?") + query;
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Accept = "text/javascript, text/html, application/xml, text/xml, */*";
request.UserAgent = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30";
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
request.Referer = referer;
if (headers != null)
{
request.Headers.Add(headers);
}
if (cookies != null)
{
request.CookieContainer = cookies;
}
if (method == "POST")
{
request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
request.ContentLength = query.Length;
StreamWriter requestStream = new StreamWriter(request.GetRequestStream());
requestStream.Write(query);
requestStream.Close();
}
try
{
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
if (response.StatusCode != HttpStatusCode.OK)
{
return null;
}
using (StreamReader responseStream = new StreamReader(response.GetResponseStream()))
{
string responseData = responseStream.ReadToEnd();
return responseData;
}
}
}
catch (WebException)
{
return null;
}
}
public static async Task<string> RequestAsync(string url, string method, NameValueCollection data = null, CookieContainer cookies = null, NameValueCollection headers = null, string referer = APIEndpoints.COMMUNITY_BASE)
{
string query = (data == null ? string.Empty : string.Join("&", Array.ConvertAll(data.AllKeys, key => String.Format("{0}={1}", WebUtility.UrlEncode(key), WebUtility.UrlEncode(data[key])))));
if (method == "GET")
{
url += (url.Contains("?") ? "&" : "?") + query;
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = method;
request.Accept = "text/javascript, text/html, application/xml, text/xml, */*";
request.UserAgent = "Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Google Nexus 4 - 4.1.1 - API 16 - 768x1280 Build/JRO03S) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30";
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
request.Referer = referer;
if (headers != null)
{
request.Headers.Add(headers);
}
if (cookies != null)
{
request.CookieContainer = cookies;
}
if (method == "POST")
{
request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
request.ContentLength = query.Length;
StreamWriter requestStream = new StreamWriter(request.GetRequestStream());
requestStream.Write(query);
requestStream.Close();
}
try
{
HttpWebResponse response = (HttpWebResponse) await request.GetResponseAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
return null;
}
using (StreamReader responseStream = new StreamReader(response.GetResponseStream()))
{
string responseData = responseStream.ReadToEnd();
return responseData;
}
}
catch (WebException)
{
return null;
}
}
}
}

View File

@@ -1,84 +0,0 @@
using System;
using System.Threading.Tasks;
using System.Net;
using Newtonsoft.Json;
namespace SteamAuth
{
/// <summary>
/// Class to help align system time with the Steam server time. Not super advanced; probably not taking some things into account that it should.
/// Necessary to generate up-to-date codes. In general, this will have an error of less than a second, assuming Steam is operational.
/// </summary>
public class TimeAligner
{
private static bool _aligned = false;
private static int _timeDifference = 0;
public static long GetSteamTime()
{
if (!TimeAligner._aligned)
{
TimeAligner.AlignTime();
}
return Util.GetSystemUnixTime() + _timeDifference;
}
public static async Task<long> GetSteamTimeAsync()
{
if (!TimeAligner._aligned)
{
await TimeAligner.AlignTimeAsync();
}
return Util.GetSystemUnixTime() + _timeDifference;
}
public static void AlignTime()
{
long currentTime = Util.GetSystemUnixTime();
using (WebClient client = new WebClient())
{
try
{
string response = client.UploadString(APIEndpoints.TWO_FACTOR_TIME_QUERY, "steamid=0");
TimeQuery query = JsonConvert.DeserializeObject<TimeQuery>(response);
TimeAligner._timeDifference = (int)(query.Response.ServerTime - currentTime);
TimeAligner._aligned = true;
}
catch (WebException)
{
return;
}
}
}
public static async Task AlignTimeAsync()
{
long currentTime = Util.GetSystemUnixTime();
WebClient client = new WebClient();
try
{
string response = await client.UploadStringTaskAsync(new Uri(APIEndpoints.TWO_FACTOR_TIME_QUERY), "steamid=0");
TimeQuery query = JsonConvert.DeserializeObject<TimeQuery>(response);
TimeAligner._timeDifference = (int)(query.Response.ServerTime - currentTime);
TimeAligner._aligned = true;
}
catch (WebException)
{
return;
}
}
internal class TimeQuery
{
[JsonProperty("response")]
internal TimeQueryResponse Response { get; set; }
internal class TimeQueryResponse
{
[JsonProperty("server_time")]
public long ServerTime { get; set; }
}
}
}
}

View File

@@ -1,252 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Specialized;
using System.Net;
using System.Security.Cryptography;
using System.Text;
namespace SteamAuth
{
/// <summary>
/// Handles logging the user into the mobile Steam website. Necessary to generate OAuth token and session cookies.
/// </summary>
public class UserLogin
{
public string Username;
public string Password;
public ulong SteamID;
public bool RequiresCaptcha;
public string CaptchaGID = null;
public string CaptchaText = null;
public bool RequiresEmail;
public string EmailDomain = null;
public string EmailCode = null;
public bool Requires2FA;
public string TwoFactorCode = null;
public SessionData Session = null;
public bool LoggedIn = false;
private CookieContainer _cookies = new CookieContainer();
public UserLogin(string username, string password)
{
this.Username = username;
this.Password = password;
}
public LoginResult DoLogin()
{
var postData = new NameValueCollection();
var cookies = _cookies;
string response = null;
if (cookies.Count == 0)
{
//Generate a SessionID
cookies.Add(new Cookie("mobileClientVersion", "0 (2.1.3)", "/", ".steamcommunity.com"));
cookies.Add(new Cookie("mobileClient", "android", "/", ".steamcommunity.com"));
cookies.Add(new Cookie("Steam_Language", "english", "/", ".steamcommunity.com"));
NameValueCollection headers = new NameValueCollection();
headers.Add("X-Requested-With", "com.valvesoftware.android.steam.community");
SteamWeb.MobileLoginRequest("https://steamcommunity.com/login?oauth_client_id=DE45CD61&oauth_scope=read_profile%20write_profile%20read_client%20write_client", "GET", null, cookies, headers);
}
postData.Add("username", this.Username);
response = SteamWeb.MobileLoginRequest(APIEndpoints.COMMUNITY_BASE + "/login/getrsakey", "POST", postData, cookies);
if (response == null || response.Contains("<BODY>\nAn error occurred while processing your request.")) return LoginResult.GeneralFailure;
var rsaResponse = JsonConvert.DeserializeObject<RSAResponse>(response);
if (!rsaResponse.Success)
{
return LoginResult.BadRSA;
}
RNGCryptoServiceProvider secureRandom = new RNGCryptoServiceProvider();
byte[] encryptedPasswordBytes;
using (var rsaEncryptor = new RSACryptoServiceProvider())
{
var passwordBytes = Encoding.ASCII.GetBytes(this.Password);
var rsaParameters = rsaEncryptor.ExportParameters(false);
rsaParameters.Exponent = Util.HexStringToByteArray(rsaResponse.Exponent);
rsaParameters.Modulus = Util.HexStringToByteArray(rsaResponse.Modulus);
rsaEncryptor.ImportParameters(rsaParameters);
encryptedPasswordBytes = rsaEncryptor.Encrypt(passwordBytes, false);
}
string encryptedPassword = Convert.ToBase64String(encryptedPasswordBytes);
postData.Clear();
postData.Add("username", this.Username);
postData.Add("password", encryptedPassword);
postData.Add("twofactorcode", this.TwoFactorCode ?? "");
postData.Add("captchagid", this.RequiresCaptcha ? this.CaptchaGID : "-1");
postData.Add("captcha_text", this.RequiresCaptcha ? this.CaptchaText : "");
postData.Add("emailsteamid", (this.Requires2FA || this.RequiresEmail) ? this.SteamID.ToString() : "");
postData.Add("emailauth", this.RequiresEmail ? this.EmailCode : "");
postData.Add("rsatimestamp", rsaResponse.Timestamp);
postData.Add("remember_login", "false");
postData.Add("oauth_client_id", "DE45CD61");
postData.Add("oauth_scope", "read_profile write_profile read_client write_client");
postData.Add("loginfriendlyname", "#login_emailauth_friendlyname_mobile");
postData.Add("donotcache", Util.GetSystemUnixTime().ToString());
response = SteamWeb.MobileLoginRequest(APIEndpoints.COMMUNITY_BASE + "/login/dologin", "POST", postData, cookies);
if (response == null) return LoginResult.GeneralFailure;
var loginResponse = JsonConvert.DeserializeObject<LoginResponse>(response);
if (loginResponse.Message != null && loginResponse.Message.Contains("Incorrect login"))
{
return LoginResult.BadCredentials;
}
if (loginResponse.CaptchaNeeded)
{
this.RequiresCaptcha = true;
this.CaptchaGID = loginResponse.CaptchaGID;
return LoginResult.NeedCaptcha;
}
if (loginResponse.EmailAuthNeeded)
{
this.RequiresEmail = true;
this.SteamID = loginResponse.EmailSteamID;
return LoginResult.NeedEmail;
}
if (loginResponse.TwoFactorNeeded && !loginResponse.Success)
{
this.Requires2FA = true;
return LoginResult.Need2FA;
}
if (loginResponse.Message != null && loginResponse.Message.Contains("too many login failures"))
{
return LoginResult.TooManyFailedLogins;
}
if (loginResponse.OAuthData == null || loginResponse.OAuthData.OAuthToken == null || loginResponse.OAuthData.OAuthToken.Length == 0)
{
return LoginResult.GeneralFailure;
}
if (!loginResponse.LoginComplete)
{
return LoginResult.BadCredentials;
}
else
{
var readableCookies = cookies.GetCookies(new Uri("https://steamcommunity.com"));
var oAuthData = loginResponse.OAuthData;
SessionData session = new SessionData();
session.OAuthToken = oAuthData.OAuthToken;
session.SteamID = oAuthData.SteamID;
session.SteamLogin = session.SteamID + "%7C%7C" + oAuthData.SteamLogin;
session.SteamLoginSecure = session.SteamID + "%7C%7C" + oAuthData.SteamLoginSecure;
session.WebCookie = oAuthData.Webcookie;
session.SessionID = readableCookies["sessionid"].Value;
this.Session = session;
this.LoggedIn = true;
return LoginResult.LoginOkay;
}
}
private class LoginResponse
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("login_complete")]
public bool LoginComplete { get; set; }
[JsonProperty("oauth")]
public string OAuthDataString { get; set; }
public OAuth OAuthData
{
get
{
return OAuthDataString != null ? JsonConvert.DeserializeObject<OAuth>(OAuthDataString) : null;
}
}
[JsonProperty("captcha_needed")]
public bool CaptchaNeeded { get; set; }
[JsonProperty("captcha_gid")]
public string CaptchaGID { get; set; }
[JsonProperty("emailsteamid")]
public ulong EmailSteamID { get; set; }
[JsonProperty("emailauth_needed")]
public bool EmailAuthNeeded { get; set; }
[JsonProperty("requires_twofactor")]
public bool TwoFactorNeeded { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
internal class OAuth
{
[JsonProperty("steamid")]
public ulong SteamID { get; set; }
[JsonProperty("oauth_token")]
public string OAuthToken { get; set; }
[JsonProperty("wgtoken")]
public string SteamLogin { get; set; }
[JsonProperty("wgtoken_secure")]
public string SteamLoginSecure { get; set; }
[JsonProperty("webcookie")]
public string Webcookie { get; set; }
}
}
private class RSAResponse
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("publickey_exp")]
public string Exponent { get; set; }
[JsonProperty("publickey_mod")]
public string Modulus { get; set; }
[JsonProperty("timestamp")]
public string Timestamp { get; set; }
[JsonProperty("steamid")]
public ulong SteamID { get; set; }
}
}
public enum LoginResult
{
LoginOkay,
GeneralFailure,
BadRSA,
BadCredentials,
NeedCaptcha,
Need2FA,
NeedEmail,
TooManyFailedLogins,
}
}

View File

@@ -1,24 +0,0 @@
using System;
using System.Net;
namespace SteamAuth
{
public class Util
{
public static long GetSystemUnixTime()
{
return (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
}
public static byte[] HexStringToByteArray(string hex)
{
int hexLen = hex.Length;
byte[] ret = new byte[hexLen / 2];
for (int i = 0; i < hexLen; i += 2)
{
ret[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return ret;
}
}
}

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="9.0.1-beta1" targetFramework="net451" />
</packages>

View File

@@ -6,4 +6,11 @@ clone_depth: 10
build:
project: ArchiSteamFarm.sln
parallel: true
verbosity: minimal
verbosity: minimal
notifications:
- provider: Webhook
url: https://webhooks.gitter.im/e/6cc89e76555ee263cc11
method: POST
on_build_success: true
on_build_failure: true
on_build_status_changed: true

27
docs/CodeStyle.vssettings Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target
AfterTargets="AfterBuild"
Name="FodyPropertyChangedVerification">
<Error
Condition="$(FodyExecutedWeavers) == '*Undefined*'"
Text="Could not find `FodyWeaversExecuted` MSBuild property. It is possible you need to update Fody." />
<Error
Condition="!$(FodyExecutedWeavers.Contains('Costura;'))"
Text="Costura expected to be executed. You may also need to manually add '&lt;Costura /&gt;' into your FodyWeavers.xml. eg &lt;Weavers&gt;&lt;Costura/&gt;&lt;/Weavers&gt;. See https://github.com/Fody/Fody/wiki/SampleUsage" />
</Target>
</Project>

View File

@@ -0,0 +1,94 @@
param($installPath, $toolsPath, $package, $project)
function RemoveForceProjectLevelHack($project)
{
Write-Host "RemoveForceProjectLevelHack"
Foreach ($item in $project.ProjectItems)
{
if ($item.Name -eq "Fody_ToBeDeleted.txt")
{
$item.Delete()
}
}
}
function FlushVariables()
{
Write-Host "Flushing environment variables"
$env:FodyLastProjectPath = ""
$env:FodyLastWeaverName = ""
$env:FodyLastXmlContents = ""
}
function Update-FodyConfig($addinName, $project)
{
Write-Host "Update-FodyConfig"
$fodyWeaversPath = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($project.FullName), "FodyWeavers.xml")
$FodyLastProjectPath = $env:FodyLastProjectPath
$FodyLastWeaverName = $env:FodyLastWeaverName
$FodyLastXmlContents = $env:FodyLastXmlContents
if (
($FodyLastProjectPath -eq $project.FullName) -and
($FodyLastWeaverName -eq $addinName))
{
Write-Host "Upgrade detected. Restoring content for $addinName"
[System.IO.File]::WriteAllText($fodyWeaversPath, $FodyLastXmlContents)
FlushVariables
return
}
FlushVariables
$xml = [xml](get-content $fodyWeaversPath)
$weavers = $xml["Weavers"]
$node = $weavers.SelectSingleNode($addinName)
if (-not $node)
{
Write-Host "Appending node"
$newNode = $xml.CreateElement($addinName)
$weavers.AppendChild($newNode)
}
$xml.Save($fodyWeaversPath)
}
function Fix-ReferencesCopyLocal($package, $project)
{
Write-Host "Fix-ReferencesCopyLocal $($package.Id)"
$asms = $package.AssemblyReferences | %{$_.Name}
foreach ($reference in $project.Object.References)
{
if ($asms -contains $reference.Name + ".dll")
{
if($reference.CopyLocal -eq $true)
{
$reference.CopyLocal = $false;
}
}
}
}
function UnlockWeaversXml($project)
{
$fodyWeaversProjectItem = $project.ProjectItems.Item("FodyWeavers.xml");
if ($fodyWeaversProjectItem)
{
$fodyWeaversProjectItem.Open("{7651A701-06E5-11D1-8EBD-00A0C90F26EA}")
$fodyWeaversProjectItem.Save()
$fodyWeaversProjectItem.Document.Close()
}
}
UnlockWeaversXml($project)
RemoveForceProjectLevelHack $project
Update-FodyConfig $package.Id.Replace(".Fody", "") $project
Fix-ReferencesCopyLocal $package $project

View File

@@ -0,0 +1,47 @@
param($installPath, $toolsPath, $package, $project)
function Update-FodyConfig($addinName, $project)
{
$fodyWeaversPath = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($project.FullName), "FodyWeavers.xml")
if (!(Test-Path ($fodyWeaversPath)))
{
return
}
Write-Host "Caching variables for possible update"
$env:FodyLastProjectPath = $project.FullName
$env:FodyLastWeaverName = $addinName
$env:FodyLastXmlContents = [IO.File]::ReadAllText($fodyWeaversPath)
$xml = [xml](get-content $fodyWeaversPath)
$weavers = $xml["Weavers"]
$node = $weavers.SelectSingleNode($addinName)
if ($node)
{
Write-Host "Removing node from FodyWeavers.xml"
$weavers.RemoveChild($node)
}
$xml.Save($fodyWeaversPath)
}
function UnlockWeaversXml($project)
{
$fodyWeaversProjectItem = $project.ProjectItems.Item("FodyWeavers.xml");
if ($fodyWeaversProjectItem)
{
$fodyWeaversProjectItem.Open("{7651A701-06E5-11D1-8EBD-00A0C90F26EA}")
$fodyWeaversProjectItem.Save()
$fodyWeaversProjectItem.Document.Close()
}
}
UnlockWeaversXml($project)
Update-FodyConfig $package.Id.Replace(".Fody", "") $project

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
param($installPath, $toolsPath, $package, $project)
$item = $project.ProjectItems | where-object {$_.Name -eq "FodyWeavers.xml"}
$item.Properties.Item("BuildAction").Value = [int]0

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Choose>
<When Condition="$(NCrunchOriginalSolutionDir) != '' And $(NCrunchOriginalSolutionDir) != '*Undefined*'">
<PropertyGroup>
<FodySolutionDir>$(NCrunchOriginalSolutionDir)</FodySolutionDir>
</PropertyGroup>
</When>
<When Condition="$(SolutionDir) != '' And $(SolutionDir) != '*Undefined*'">
<PropertyGroup>
<FodySolutionDir>$(SolutionDir)</FodySolutionDir>
</PropertyGroup>
</When>
<When Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">
<PropertyGroup>
<FodySolutionDir>$(MSBuildProjectDirectory)..\..\..\</FodySolutionDir>
</PropertyGroup>
</When>
</Choose>
<Choose>
<When Condition="$(KeyOriginatorFile) != '' And $(KeyOriginatorFile) != '*Undefined*'">
<PropertyGroup>
<FodyKeyFilePath>$(KeyOriginatorFile)</FodyKeyFilePath>
</PropertyGroup>
</When>
<When Condition="$(AssemblyOriginatorKeyFile) != '' And $(AssemblyOriginatorKeyFile) != '*Undefined*'">
<PropertyGroup>
<FodyKeyFilePath>$(AssemblyOriginatorKeyFile)</FodyKeyFilePath>
</PropertyGroup>
</When>
<Otherwise >
<PropertyGroup>
<FodyKeyFilePath></FodyKeyFilePath>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup>
<ProjectWeaverXml>$(ProjectDir)FodyWeavers.xml</ProjectWeaverXml>
<IntermediateDir>$(ProjectDir)$(IntermediateOutputPath)</IntermediateDir>
<FodySignAssembly Condition="$(FodySignAssembly) == '' Or $(FodySignAssembly) == '*Undefined*'">$(SignAssembly)</FodySignAssembly>
<FodyPath Condition="$(FodyPath) == '' Or $(FodyPath) == '*Undefined*'">$(MSBuildThisFileDirectory)..\..\</FodyPath>
</PropertyGroup>
<UsingTask
TaskName="Fody.WeavingTask"
AssemblyFile="$(FodyPath)\Fody.dll" />
<Target
AfterTargets="AfterCompile"
Condition="Exists(@(IntermediateAssembly))"
Name="FodyTarget"
DependsOnTargets="$(FodyDependsOnTargets)"
Inputs="@(IntermediateAssembly->'%(FullPath)');$(FodyKeyFilePath);$(ProjectWeaverXml)"
Outputs="$(TargetPath)">
<Fody.WeavingTask
AssemblyPath="@(IntermediateAssembly)"
IntermediateDir="$(IntermediateDir)"
KeyFilePath="$(FodyKeyFilePath)"
NuGetPackageRoot="$(NuGetPackageRoot)"
ProjectDirectory="$(ProjectDir)"
SolutionDir="$(FodySolutionDir)"
References="@(ReferencePath)"
SignAssembly="$(FodySignAssembly)"
ReferenceCopyLocalPaths="@(ReferenceCopyLocalPaths)"
DefineConstants="$(DefineConstants)"
>
<Output
TaskParameter="ExecutedWeavers"
PropertyName="FodyExecutedWeavers" />
</Fody.WeavingTask>
</Target>
<UsingTask
TaskName="Fody.VerifyTask"
AssemblyFile="$(FodyPath)\Fody.dll" />
<Target Condition="'$(NCrunch)' != '1' And Exists($(TargetPath))"
AfterTargets="AfterBuild"
Name="FodyVerifyTarget"
DependsOnTargets="$(FodyVerifyDependsOnTargets)"
Inputs="@(IntermediateAssembly->'%(FullPath)');$(FodyKeyFilePath);$(ProjectWeaverXml)"
Outputs="$(TargetPath)">
<Fody.VerifyTask
ProjectDirectory="$(ProjectDir)"
TargetPath="$(TargetPath)"
SolutionDir="$(FodySolutionDir)"
DefineConstants="$(DefineConstants)"
/>
</Target>
<!--Support for ncrunch-->
<ItemGroup Condition="'$(NCrunch)' == '1'">
<None Include="$(FodyPath)\*.*" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Choose>
<When Condition="$(NCrunchOriginalSolutionDir) != '' And $(NCrunchOriginalSolutionDir) != '*Undefined*'">
<PropertyGroup>
<FodySolutionDir>$(NCrunchOriginalSolutionDir)</FodySolutionDir>
</PropertyGroup>
</When>
<When Condition="$(SolutionDir) != '' And $(SolutionDir) != '*Undefined*'">
<PropertyGroup>
<FodySolutionDir>$(SolutionDir)</FodySolutionDir>
</PropertyGroup>
</When>
<When Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">
<PropertyGroup>
<FodySolutionDir>$(MSBuildProjectDirectory)..\..\..\</FodySolutionDir>
</PropertyGroup>
</When>
</Choose>
<Choose>
<When Condition="$(KeyOriginatorFile) != '' And $(KeyOriginatorFile) != '*Undefined*'">
<PropertyGroup>
<FodyKeyFilePath>$(KeyOriginatorFile)</FodyKeyFilePath>
</PropertyGroup>
</When>
<When Condition="$(AssemblyOriginatorKeyFile) != '' And $(AssemblyOriginatorKeyFile) != '*Undefined*'">
<PropertyGroup>
<FodyKeyFilePath>$(AssemblyOriginatorKeyFile)</FodyKeyFilePath>
</PropertyGroup>
</When>
<Otherwise >
<PropertyGroup>
<FodyKeyFilePath></FodyKeyFilePath>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup>
<ProjectWeaverXml>$(ProjectDir)FodyWeavers.xml</ProjectWeaverXml>
<IntermediateDir>$(ProjectDir)$(IntermediateOutputPath)</IntermediateDir>
<FodySignAssembly Condition="$(FodySignAssembly) == '' Or $(FodySignAssembly) == '*Undefined*'">$(SignAssembly)</FodySignAssembly>
<FodyPath Condition="$(FodyPath) == '' Or $(FodyPath) == '*Undefined*'">$(MSBuildThisFileDirectory)..\..\</FodyPath>
</PropertyGroup>
<UsingTask
TaskName="Fody.WeavingTask"
AssemblyFile="$(FodyPath)\Fody.dll" />
<Target
AfterTargets="AfterCompile"
Condition="Exists(@(IntermediateAssembly))"
Name="FodyTarget"
DependsOnTargets="$(FodyDependsOnTargets)"
Inputs="@(IntermediateAssembly->'%(FullPath)');$(FodyKeyFilePath);$(ProjectWeaverXml)"
Outputs="$(TargetPath)">
<Fody.WeavingTask
AssemblyPath="@(IntermediateAssembly)"
IntermediateDir="$(IntermediateDir)"
KeyFilePath="$(FodyKeyFilePath)"
NuGetPackageRoot="$(NuGetPackageRoot)"
ProjectDirectory="$(ProjectDir)"
SolutionDir="$(FodySolutionDir)"
References="@(ReferencePath)"
SignAssembly="$(FodySignAssembly)"
ReferenceCopyLocalPaths="@(ReferenceCopyLocalPaths)"
DefineConstants="$(DefineConstants)"
>
<Output
TaskParameter="ExecutedWeavers"
PropertyName="FodyExecutedWeavers" />
</Fody.WeavingTask>
</Target>
<UsingTask
TaskName="Fody.VerifyTask"
AssemblyFile="$(FodyPath)\Fody.dll" />
<Target Condition="'$(NCrunch)' != '1' And Exists($(TargetPath))"
AfterTargets="AfterBuild"
Name="FodyVerifyTarget"
DependsOnTargets="$(FodyVerifyDependsOnTargets)"
Inputs="@(IntermediateAssembly->'%(FullPath)');$(FodyKeyFilePath);$(ProjectWeaverXml)"
Outputs="$(TargetPath)">
<Fody.VerifyTask
ProjectDirectory="$(ProjectDir)"
TargetPath="$(TargetPath)"
SolutionDir="$(FodySolutionDir)"
DefineConstants="$(DefineConstants)"
/>
</Target>
<!--Support for ncrunch-->
<ItemGroup Condition="'$(NCrunch)' == '1'">
<None Include="$(FodyPath)\*.*" />
</ItemGroup>
</Project>

View File

Before

Width:  |  Height:  |  Size: 699 B

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -369,11 +369,12 @@
<param name="node">The node to duplicate. May not be <c>null</c>.</param>
<param name="deep">true to recursively clone the subtree under the specified node, false to clone only the node itself.</param>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.DescendantNodes">
<member name="M:HtmlAgilityPack.HtmlNode.DescendantNodes(System.Int32)">
<summary>
Gets all Descendant nodes for this node and each of child nodes
</summary>
<returns></returns>
<param name="level">The depth level of the node to parse in the html tree</param>
<returns>the current element as an HtmlNode</returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.DescendantNodesAndSelf">
<summary>
@@ -381,7 +382,7 @@
</summary>
<returns></returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.Descendants">
<member name="M:HtmlAgilityPack.HtmlNode.Descendants(System.Int32)">
<summary>
Gets all Descendant nodes in enumerated list
</summary>
@@ -520,11 +521,12 @@
<param name="value">The value for the attribute.</param>
<returns>The corresponding attribute instance.</returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteContentTo(System.IO.TextWriter)">
<member name="M:HtmlAgilityPack.HtmlNode.WriteContentTo(System.IO.TextWriter,System.Int32)">
<summary>
Saves all the children of the node to the specified TextWriter.
</summary>
<param name="outText">The TextWriter to which you want to save.</param>
<param name="level">Identifies the level we are in starting at root with 0</param>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteContentTo">
<summary>
@@ -532,11 +534,12 @@
</summary>
<returns>The saved string.</returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteTo(System.IO.TextWriter)">
<member name="M:HtmlAgilityPack.HtmlNode.WriteTo(System.IO.TextWriter,System.Int32)">
<summary>
Saves the current node to the specified TextWriter.
</summary>
<param name="outText">The TextWriter to which you want to save.</param>
<param name="level">identifies the level we are in starting at root with 0</param>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteTo(System.Xml.XmlWriter)">
<summary>
@@ -766,6 +769,11 @@
</summary>
<returns>An XPathNavigator object. The XPathNavigator is positioned on the root of the document.</returns>
</member>
<member name="F:HtmlAgilityPack.HtmlDocument._maxDepthLevel">
<summary>
Defines the max level we would go deep into the html document
</summary>
</member>
<member name="F:HtmlAgilityPack.HtmlDocument.OptionAddDebuggingAttributes">
<summary>
Adds Debugging attributes to node. Default is false.
@@ -1033,6 +1041,12 @@
</summary>
<param name="writer">The XmlWriter to which you want to save.</param>
</member>
<member name="P:HtmlAgilityPack.HtmlDocument.MaxDepthLevel">
<summary>
Defines the max level we would go deep into the html document. If this depth level is exceeded, and exception is
thrown.
</summary>
</member>
<member name="P:HtmlAgilityPack.HtmlDocument.CheckSum">
<summary>
Gets the document CRC32 checksum if OptionComputeChecksum was set to true before parsing, 0 otherwise.

Binary file not shown.

Binary file not shown.

View File

@@ -465,11 +465,12 @@
<param name="node">The node to duplicate. May not be <c>null</c>.</param>
<param name="deep">true to recursively clone the subtree under the specified node, false to clone only the node itself.</param>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.DescendantNodes">
<member name="M:HtmlAgilityPack.HtmlNode.DescendantNodes(System.Int32)">
<summary>
Gets all Descendant nodes for this node and each of child nodes
</summary>
<returns></returns>
<param name="level">The depth level of the node to parse in the html tree</param>
<returns>the current element as an HtmlNode</returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.DescendantNodesAndSelf">
<summary>
@@ -477,7 +478,7 @@
</summary>
<returns></returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.Descendants">
<member name="M:HtmlAgilityPack.HtmlNode.Descendants(System.Int32)">
<summary>
Gets all Descendant nodes in enumerated list
</summary>
@@ -616,11 +617,12 @@
<param name="value">The value for the attribute.</param>
<returns>The corresponding attribute instance.</returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteContentTo(System.IO.TextWriter)">
<member name="M:HtmlAgilityPack.HtmlNode.WriteContentTo(System.IO.TextWriter,System.Int32)">
<summary>
Saves all the children of the node to the specified TextWriter.
</summary>
<param name="outText">The TextWriter to which you want to save.</param>
<param name="level">Identifies the level we are in starting at root with 0</param>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteContentTo">
<summary>
@@ -628,11 +630,12 @@
</summary>
<returns>The saved string.</returns>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteTo(System.IO.TextWriter)">
<member name="M:HtmlAgilityPack.HtmlNode.WriteTo(System.IO.TextWriter,System.Int32)">
<summary>
Saves the current node to the specified TextWriter.
</summary>
<param name="outText">The TextWriter to which you want to save.</param>
<param name="level">identifies the level we are in starting at root with 0</param>
</member>
<member name="M:HtmlAgilityPack.HtmlNode.WriteTo(System.Xml.XmlWriter)">
<summary>
@@ -786,6 +789,11 @@
Represents a complete HTML document.
</summary>
</member>
<member name="F:HtmlAgilityPack.HtmlDocument._maxDepthLevel">
<summary>
Defines the max level we would go deep into the html document
</summary>
</member>
<member name="F:HtmlAgilityPack.HtmlDocument.OptionAddDebuggingAttributes">
<summary>
Adds Debugging attributes to node. Default is false.
@@ -1053,6 +1061,12 @@
</summary>
<param name="writer">The XmlWriter to which you want to save.</param>
</member>
<member name="P:HtmlAgilityPack.HtmlDocument.MaxDepthLevel">
<summary>
Defines the max level we would go deep into the html document. If this depth level is exceeded, and exception is
thrown.
</summary>
</member>
<member name="P:HtmlAgilityPack.HtmlDocument.CheckSum">
<summary>
Gets the document CRC32 checksum if OptionComputeChecksum was set to true before parsing, 0 otherwise.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More