Compare commits

...

83 Commits

Author SHA1 Message Date
JustArchi
54ad58a22d Misc 2016-04-12 17:09:57 +02:00
JustArchi
790e6baf46 ASF-specific WebBrowser enhancements, closes #192 2016-04-12 16:58:45 +02:00
JustArchi
c8fb715558 Fix for new code 2016-04-12 16:37:49 +02:00
JustArchi
2ab5e6013d Misc 2016-04-12 16:36:09 +02:00
JustArchi
3e0c34e62c Misc 2016-04-12 07:44:59 +02:00
JustArchi
65b31e5537 Bring in latest ArchiBoT WebBrowser
Highlights include: Support for automatic decompression via GZip/Deflate, more aggressive disposal of response messages, rewrite of cookie handling from dictionary to cookiecontainer, debug log of also timed out messages and more.
We sacrifice the performance and scalability of single HttpClient doing the work for being less error prone on eventual steam fuckups (CookieContainer can adapt to always changing structure)
2016-04-12 07:40:02 +02:00
JustArchi
62a6e38e47 Misc 2016-04-10 18:25:22 +02:00
JustArchi
0b50a45336 Checking update does not depend on old binary removal error 2016-04-09 22:15:04 +02:00
JustArchi
8d1d162b02 Misc 2016-04-09 22:11:11 +02:00
JustArchi
dbe13a1965 Bump 2016-04-09 00:06:39 +02:00
JustArchi
fc13633f5e Misc 2016-04-08 17:33:00 +02:00
JustArchi
d0cc10f3c6 Always provide 2FA code when ASF 2FA is enabled 2016-04-08 17:12:08 +02:00
JustArchi
567931a4cc Misc 2016-04-08 04:30:51 +02:00
JustArchi
396dc17ab2 Bump 2016-04-08 04:22:41 +02:00
JustArchi
288cc29338 Be consistent 2016-04-08 03:58:46 +02:00
JustArchi
844ca7da94 Enhance !owns command
Now doesn't only support multiple games for querying, but also a mixed combination of appIDs and strings
2016-04-08 03:57:03 +02:00
JustArchi
b14b9f87c7 Handle really long messages properly 2016-04-07 03:14:49 +02:00
JustArchi
8aa086cc27 Fix rare crash 2016-04-07 01:45:08 +02:00
JustArchi
cf00989d84 Misc 2016-04-07 01:39:02 +02:00
JustArchi
8f2f85282c Bump 2016-04-06 17:05:49 +02:00
JustArchi
fa12ffd9d0 Add headlness mode 2016-04-06 16:37:45 +02:00
JustArchi
ecb27adedd Bump 2016-04-06 15:37:55 +02:00
JustArchi
6e9be09944 Misc 2016-04-06 15:30:03 +02:00
JustArchi
6a79a89a10 Allow offline bot to handle keys redeeming as well 2016-04-06 15:28:28 +02:00
JustArchi
d67be4f092 Fix ResponseRedeem() and make it possible to redeem multiple keys 2016-04-06 15:25:52 +02:00
JustArchi
a8e1039e32 Add !2fano 2016-04-05 03:41:59 +02:00
JustArchi
2ad9d9e197 Bump 2016-04-03 17:00:40 +02:00
JustArchi
fd6e2c72d7 Major cleanup & code review of ResponseRedeem() 2016-04-02 19:23:09 +02:00
JustArchi
1eed0f7647 Misc 2016-04-02 13:41:53 +02:00
JustArchi
c018c08260 Misc WCF enhancements 2016-04-02 13:41:08 +02:00
JustArchi
72fa98cb89 Improve exit/restart 2016-04-02 13:08:43 +02:00
JustArchi
71215d695e Bump 2016-04-02 12:33:41 +02:00
JustArchi
75b785d4b6 Misc 2016-04-01 20:19:17 +02:00
JustArchi
12e32692cb Code review 2016-04-01 20:18:21 +02:00
JustArchi
6158a9268d Misc 2016-04-01 17:14:03 +02:00
JustArchi
a48e53585a Pretty sure those are no longer needed 2016-03-30 13:10:00 +02:00
JustArchi
33383633ea Fix ultra rare crash 2016-03-30 01:41:53 +02:00
JustArchi
3f7359c608 Misc 2016-03-29 23:32:12 +02:00
JustArchi
d8b59c6889 Add Linux scripts 2016-03-29 22:58:18 +02:00
JustArchi
0f71b788cb Use /my/ steamcommunity trick 2016-03-29 14:48:31 +02:00
JustArchi
d012255a21 EXPERIMENTAL: Steam session improvements
1. Make sure that every call to steamcommunity has active session
2. Move whole userspace logic for session handling to ArchiWebHandler (and Bot)
3. Implement session caching and TTL so we won't send IsLoggedIn() on each ArchiWebHandler call
4. Instead of restarting whole steam account, just refresh the session via ArchiWebHandler instead
2016-03-29 14:33:05 +02:00
JustArchi
8fc39a44cd Misc 2016-03-28 15:34:59 +02:00
JustArchi
c6fe424fcc Refuse to handle https requests when ForceHttp is true 2016-03-28 15:34:10 +02:00
JustArchi
12488dafd3 Misc 2016-03-28 13:11:02 +02:00
JustArchi
3b53491567 Bump 2016-03-28 00:18:07 +02:00
JustArchi
e337bd3856 Derp 2016-03-27 23:38:49 +02:00
JustArchi
cc47c0a764 Add new property to config generator 2016-03-27 23:27:02 +02:00
JustArchi
22e4c0cd40 Misc 2016-03-27 23:18:01 +02:00
JustArchi
20b47e1787 Correct config property 2016-03-27 23:09:17 +02:00
JustArchi
941da0658d Misc 2016-03-27 23:07:37 +02:00
JustArchi
8048d4a7aa Add a feature of accepting steam gifts, closes #18 2016-03-27 23:07:00 +02:00
JustArchi
2554794daa Code review 2016-03-27 15:08:43 +02:00
JustArchi
4fcee90b99 Code review 2016-03-26 22:51:19 +01:00
JustArchi
761d73eb90 Remove old converter, closes #153
Decided to remove it faster
2016-03-26 22:23:35 +01:00
JustArchi
c163e5e2f3 Bump 2016-03-26 22:13:24 +01:00
JustArchi
8fd41cc587 Fix broken bot databases 2016-03-26 22:12:55 +01:00
JustArchi
f195563ba5 Misc 2016-03-26 19:39:46 +01:00
JustArchi
33df54365a Add !help + code review 2016-03-26 02:47:25 +01:00
JustArchi
4a6ae3064a Put NetHook2 in it's own directory + add small readme 2016-03-25 12:42:34 +01:00
JustArchi
84857e060b Add !pause + misc 2016-03-24 14:18:07 +01:00
JustArchi
3311f2a703 Make number of hours human-readable 2016-03-24 08:14:26 +01:00
JustArchi
5666ebf891 Bump 2016-03-23 19:04:27 +01:00
JustArchi
0ded4f3b80 Bump 2016-03-23 18:53:51 +01:00
JustArchi
4c9354308c Improve UI a little 2016-03-23 18:38:19 +01:00
JustArchi
0bcf2f35b7 Misc 2016-03-23 13:59:54 +01:00
JustArchi
b93d5187d6 Misc 2016-03-23 13:48:59 +01:00
JustArchi
1426922854 Misc 2016-03-23 13:42:16 +01:00
JustArchi
d8925f9409 Sync ConfigGenerator 2016-03-23 13:39:56 +01:00
JustArchi
4302029bee Be consistent 2016-03-23 13:31:11 +01:00
JustArchi
01a7800a44 Final touch on travis + readme 2016-03-23 13:26:03 +01:00
JustArchi
ab3cf5b8d0 Add travis build status 2016-03-23 13:13:04 +01:00
JustArchi
2ab9741fa8 Add travis integration 2016-03-23 13:09:17 +01:00
JustArchi
f1c3339764 Misc 2016-03-23 12:18:16 +01:00
JustArchi
da01d4eab3 Use self-compiled ILRepack 2016-03-23 12:09:26 +01:00
JustArchi
cd9bd5f0dd Misc 2016-03-22 20:20:04 +01:00
JustArchi
2602ac3623 Improve Start/Stop mechanisms 2016-03-22 20:18:47 +01:00
JustArchi
2fe8db712e Enable enhanced logging when Debug is enabled, #176 2016-03-22 18:45:30 +01:00
JustArchi
687b20bcb5 Misc 2016-03-22 17:33:08 +01:00
JustArchi
23d9dcffe7 Enable AccountPlayingDelay of 0, WCFHostname of null and add code for fixing fuckups 2016-03-22 17:26:39 +01:00
JustArchi
42623ffb5a Misc 2016-03-22 16:49:17 +01:00
JustArchi
214003edbd Misc 2016-03-22 16:42:53 +01:00
JustArchi
c21333b6e9 Further code optimizations 2016-03-22 16:41:17 +01:00
JustArchi
630f1c008c Bump 2016-03-22 16:09:33 +01:00
45 changed files with 1200 additions and 966 deletions

10
.travis.yml Normal file
View File

@@ -0,0 +1,10 @@
sudo: false
language: csharp
solution: ArchiSteamFarm.sln
git:
depth: 10
mono:
- weekly
- latest

View File

@@ -32,6 +32,16 @@ using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal sealed class ArchiHandler : ClientMsgHandler {
private readonly Bot Bot;
internal ArchiHandler(Bot bot) {
if (bot == null) {
return;
}
Bot = bot;
}
/*
____ _ _ _ _
/ ___| __ _ | || || |__ __ _ ___ | | __ ___
@@ -257,10 +267,11 @@ namespace ArchiSteamFarm {
request.Body.key = key;
Client.Send(request);
try {
return await new AsyncJob<PurchaseResponseCallback>(Client, request.SourceJobID);
} catch (Exception e) {
Logging.LogGenericException(e);
Logging.LogGenericException(e, Bot.BotName);
return null;
}
}

View File

@@ -90,10 +90,7 @@
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.ServiceModel" />
<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>
@@ -160,19 +157,19 @@
</PropertyGroup>
<PropertyGroup>
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir "$(SolutionDir)out" "$(SolutionDir)out\config"
mkdir "$(SolutionDir)out\config"
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.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
"$(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"
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir -p "$(SolutionDir)out" "$(SolutionDir)out/config"
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 -O=all "$(SolutionDir)tools/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
mono -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"
</PostBuildEvent>
</PropertyGroup>

View File

@@ -32,19 +32,22 @@ using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class ArchiWebHandler {
private const string SteamCommunity = "steamcommunity.com";
private const byte MinSessionTTL = 15; // Assume session is valid for at least that amount of seconds
private static string SteamCommunityURL = "https://" + SteamCommunity;
private static int Timeout = 30 * 1000;
private static int Timeout = GlobalConfig.DefaultHttpTimeout * 1000;
private readonly Bot Bot;
private readonly Dictionary<string, string> Cookie = new Dictionary<string, string>(4);
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
private readonly WebBrowser WebBrowser;
private ulong SteamID;
private DateTime LastSessionRefreshCheck = DateTime.MinValue;
internal static void Init() {
Timeout = Program.GlobalConfig.HttpTimeout * 1000;
@@ -57,16 +60,18 @@ namespace ArchiSteamFarm {
}
Bot = bot;
WebBrowser = new WebBrowser(bot.BotName);
}
internal async Task<bool> Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
internal bool Init(SteamClient steamClient, string webAPIUserNonce, string parentalPin) {
if (steamClient == null || steamClient.SteamID == null || string.IsNullOrEmpty(webAPIUserNonce)) {
return false;
}
SteamID = steamClient.SteamID;
ulong steamID = steamClient.SteamID;
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(SteamID.ToString()));
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
// Generate an AES session key
byte[] sessionKey = CryptoHelper.GenerateRandomBlock(32);
@@ -93,7 +98,7 @@ namespace ArchiSteamFarm {
try {
authResult = iSteamUserAuth.AuthenticateUser(
steamid: SteamID,
steamid: steamID,
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedSessionKey, 0, cryptedSessionKey.Length)),
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(cryptedLoginKey, 0, cryptedLoginKey.Length)),
method: WebRequestMethods.Http.Post,
@@ -111,28 +116,26 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Success!", Bot.BotName);
string steamLogin = authResult["token"].AsString();
string steamLoginSecure = authResult["tokensecure"].AsString();
WebBrowser.CookieContainer.Add(new Cookie("sessionid", sessionID, "/", "." + SteamCommunity));
Cookie["sessionid"] = sessionID;
Cookie["steamLogin"] = steamLogin;
Cookie["steamLoginSecure"] = steamLoginSecure;
string steamLogin = authResult["token"].Value;
WebBrowser.CookieContainer.Add(new Cookie("steamLogin", steamLogin, "/", "." + SteamCommunity));
// The below is used for display purposes only
Cookie["webTradeEligibility"] = "{\"allowed\":0,\"reason\":0,\"allowed_at_time\":0,\"steamguard_required_days\":0,\"sales_this_year\":0,\"max_sales_per_year\":0,\"forms_requested\":0}";
string steamLoginSecure = authResult["tokensecure"].Value;
WebBrowser.CookieContainer.Add(new Cookie("steamLoginSecure", steamLoginSecure, "/", "." + SteamCommunity));
await UnlockParentalAccount(parentalPin).ConfigureAwait(false);
if (!UnlockParentalAccount(parentalPin).Result) {
return false;
}
LastSessionRefreshCheck = DateTime.Now;
return true;
}
internal async Task<bool?> IsLoggedIn() {
if (SteamID == 0) {
return false;
}
HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/profile", Cookie).ConfigureAwait(false);
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/profile").ConfigureAwait(false);
}
if (htmlDocument == null) {
@@ -144,27 +147,46 @@ namespace ArchiSteamFarm {
return htmlNode != null;
}
internal async Task<bool> ReconnectIfNeeded() {
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.HasValue && !isLoggedIn.Value) {
Logging.LogGenericInfo("Reconnecting because our sessionID expired!", Bot.BotName);
Bot.RestartIfRunning().Forget();
internal async Task<bool> RefreshSessionIfNeeded() {
DateTime now = DateTime.Now;
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
return true;
}
return false;
await SessionSemaphore.WaitAsync().ConfigureAwait(false);
now = DateTime.Now;
if (now.Subtract(LastSessionRefreshCheck).TotalSeconds < MinSessionTTL) {
SessionSemaphore.Release();
return true;
}
bool result;
bool? isLoggedIn = await IsLoggedIn().ConfigureAwait(false);
if (isLoggedIn.GetValueOrDefault(true)) {
result = true;
now = DateTime.Now;
LastSessionRefreshCheck = now;
} else {
Logging.LogGenericInfo("Refreshing our session!", Bot.BotName);
result = await Bot.RefreshSession().ConfigureAwait(false);
}
SessionSemaphore.Release();
return result;
}
internal async Task<Dictionary<uint, string>> GetOwnedGames() {
if (SteamID == 0) {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
string request = SteamCommunityURL + "/profiles/" + SteamID + "/games/?xml=1";
string request = SteamCommunityURL + "/my/games/?xml=1";
XmlDocument response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlGetToXML(request, Cookie).ConfigureAwait(false);
response = await WebBrowser.UrlGetToXML(request).ConfigureAwait(false);
}
if (response == null) {
@@ -265,8 +287,13 @@ namespace ArchiSteamFarm {
return false;
}
string sessionID;
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false;
}
@@ -277,12 +304,12 @@ namespace ArchiSteamFarm {
{"action", "join"}
};
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie).ConfigureAwait(false);
bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
}
if (response == null) {
if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
@@ -295,8 +322,13 @@ namespace ArchiSteamFarm {
return false;
}
string sessionID;
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false;
}
@@ -309,42 +341,12 @@ namespace ArchiSteamFarm {
{"tradeofferid", tradeID.ToString()}
};
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false);
bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false);
}
if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
return true;
}
internal bool DeclineTradeOffer(ulong tradeID) {
if (tradeID == 0 || string.IsNullOrEmpty(Bot.BotConfig.SteamApiKey)) {
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++) {
try {
response = iEconService.DeclineTradeOffer(
tradeofferid: tradeID.ToString(),
method: WebRequestMethods.Http.Post,
secure: !Program.GlobalConfig.ForceHttp
);
} catch (Exception e) {
Logging.LogGenericException(e, Bot.BotName);
}
}
}
if (response == null) {
if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
@@ -353,9 +355,13 @@ namespace ArchiSteamFarm {
}
internal async Task<List<Steam.Item>> GetMyTradableInventory() {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
JObject jObject = null;
for (byte i = 0; i < WebBrowser.MaxRetries && jObject == null; i++) {
jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/753/6?trading=1", Cookie).ConfigureAwait(false);
jObject = await WebBrowser.UrlGetToJObject(SteamCommunityURL + "/my/inventory/json/753/6?trading=1").ConfigureAwait(false);
}
if (jObject == null) {
@@ -386,8 +392,13 @@ namespace ArchiSteamFarm {
return false;
}
string sessionID;
if (!Cookie.TryGetValue("sessionid", out sessionID)) {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false;
}
@@ -426,12 +437,12 @@ namespace ArchiSteamFarm {
{"trade_offer_create_params", string.IsNullOrEmpty(token) ? "" : $"{{\"trade_offer_access_token\":\"{token}\"}}"}
};
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false);
bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
result = await WebBrowser.UrlPost(request, data, referer).ConfigureAwait(false);
}
if (response == null) {
if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
@@ -441,13 +452,17 @@ namespace ArchiSteamFarm {
}
internal async Task<HtmlDocument> GetBadgePage(byte page) {
if (page == 0 || SteamID == 0) {
if (page == 0) {
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/profiles/" + SteamID + "/badges?l=english&p=" + page, Cookie).ConfigureAwait(false);
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/badges?p=" + page).ConfigureAwait(false);
}
if (htmlDocument == null) {
@@ -459,13 +474,17 @@ namespace ArchiSteamFarm {
}
internal async Task<HtmlDocument> GetGameCardsPage(ulong appID) {
if (appID == 0 || SteamID == 0) {
if (appID == 0) {
return null;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return null;
}
HtmlDocument htmlDocument = null;
for (byte i = 0; i < WebBrowser.MaxRetries && htmlDocument == null; i++) {
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/profiles/" + SteamID + "/gamecards/" + appID + "?l=english", Cookie).ConfigureAwait(false);
htmlDocument = await WebBrowser.UrlGetToHtmlDocument(SteamCommunityURL + "/my/gamecards/" + appID).ConfigureAwait(false);
}
if (htmlDocument == null) {
@@ -477,16 +496,16 @@ namespace ArchiSteamFarm {
}
internal async Task<bool> MarkInventory() {
if (SteamID == 0) {
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlGet(SteamCommunityURL + "/profiles/" + SteamID + "/inventory", Cookie).ConfigureAwait(false);
bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
result = await WebBrowser.UrlGet(SteamCommunityURL + "/my/inventory").ConfigureAwait(false);
}
if (response == null) {
if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
@@ -494,9 +513,42 @@ namespace ArchiSteamFarm {
return true;
}
private async Task UnlockParentalAccount(string parentalPin) {
internal async Task<bool> AcceptGift(ulong gid) {
if (gid == 0) {
return false;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Logging.LogNullError("sessionID");
return false;
}
string request = SteamCommunityURL + "/gifts/" + gid + "/acceptunpack";
Dictionary<string, string> data = new Dictionary<string, string>(1) {
{ "sessionid", sessionID }
};
bool result = false;
for (byte i = 0; i < WebBrowser.MaxRetries && !result; i++) {
result = await WebBrowser.UrlPost(request, data).ConfigureAwait(false);
}
if (!result) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return false;
}
return true;
}
private async Task<bool> UnlockParentalAccount(string parentalPin) {
if (string.IsNullOrEmpty(parentalPin) || parentalPin.Equals("0")) {
return;
return true;
}
Logging.LogGenericInfo("Unlocking parental account...", Bot.BotName);
@@ -509,20 +561,23 @@ namespace ArchiSteamFarm {
HttpResponseMessage response = null;
for (byte i = 0; i < WebBrowser.MaxRetries && response == null; i++) {
response = await WebBrowser.UrlPost(request, data, Cookie, referer).ConfigureAwait(false);
response = await WebBrowser.UrlPostToResponse(request, data, referer).ConfigureAwait(false);
}
if (response == null) {
Logging.LogGenericWTF("Request failed even after " + WebBrowser.MaxRetries + " tries", Bot.BotName);
return;
return false;
}
IEnumerable<string> setCookieValues;
if (!response.Headers.TryGetValues("Set-Cookie", out setCookieValues)) {
response.Dispose();
Logging.LogNullError("setCookieValues", Bot.BotName);
return;
return false;
}
response.Dispose();
foreach (string setCookieValue in setCookieValues) {
if (!setCookieValue.Contains("steamparental=")) {
continue;
@@ -535,12 +590,13 @@ namespace ArchiSteamFarm {
setCookie = setCookie.Substring(0, index);
}
Cookie["steamparental"] = setCookie;
Logging.LogGenericInfo("Success!", Bot.BotName);
return;
WebBrowser.CookieContainer.Add(new Cookie("steamparental", setCookie, "/", "." + SteamCommunity));
return true;
}
Logging.LogGenericWarning("Failed to unlock parental account!", Bot.BotName);
return false;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,6 @@ using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
namespace ArchiSteamFarm {
internal sealed class BotConfig {
@@ -66,6 +65,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal bool HandleOfflineMessages { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AcceptGifts { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool ForwardKeysToOtherBots { get; private set; } = false;
@@ -97,14 +99,14 @@ namespace ArchiSteamFarm {
internal HashSet<uint> GamesPlayedWhileIdle { get; private set; } = new HashSet<uint>() { 0 };
internal static BotConfig Load(string path) {
if (!File.Exists(path)) {
internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) {
return null;
}
BotConfig botConfig;
try {
botConfig = JsonConvert.DeserializeObject<BotConfig>(File.ReadAllText(path));
botConfig = JsonConvert.DeserializeObject<BotConfig>(File.ReadAllText(filePath));
} catch (Exception e) {
Logging.LogGenericException(e);
return null;
@@ -113,146 +115,7 @@ namespace ArchiSteamFarm {
return botConfig;
}
// TODO: This should be removed soon
internal static BotConfig LoadOldFormat(string path) {
if (!File.Exists(path)) {
return null;
}
BotConfig botConfig = new BotConfig();
try {
using (XmlReader reader = XmlReader.Create(path)) {
while (reader.Read()) {
if (reader.NodeType != XmlNodeType.Element) {
continue;
}
string key = reader.Name;
if (string.IsNullOrEmpty(key)) {
continue;
}
string value = reader.GetAttribute("value");
if (string.IsNullOrEmpty(value)) {
continue;
}
switch (key) {
case "Enabled":
botConfig.Enabled = bool.Parse(value);
break;
case "SteamLogin":
botConfig.SteamLogin = value;
break;
case "SteamPassword":
botConfig.SteamPassword = value;
break;
case "SteamApiKey":
botConfig.SteamApiKey = value;
break;
case "SteamTradeToken":
botConfig.SteamTradeToken = value;
break;
case "SteamParentalPIN":
botConfig.SteamParentalPIN = value;
break;
case "SteamMasterID":
botConfig.SteamMasterID = ulong.Parse(value);
break;
case "SteamMasterClanID":
botConfig.SteamMasterClanID = ulong.Parse(value);
break;
case "StartOnLaunch":
botConfig.StartOnLaunch = bool.Parse(value);
break;
case "UseAsfAsMobileAuthenticator":
botConfig.UseAsfAsMobileAuthenticator = bool.Parse(value);
break;
case "CardDropsRestricted":
botConfig.CardDropsRestricted = bool.Parse(value);
break;
case "FarmOffline":
botConfig.FarmOffline = bool.Parse(value);
break;
case "HandleOfflineMessages":
botConfig.HandleOfflineMessages = bool.Parse(value);
break;
case "ForwardKeysToOtherBots":
botConfig.ForwardKeysToOtherBots = bool.Parse(value);
break;
case "DistributeKeys":
botConfig.DistributeKeys = bool.Parse(value);
break;
case "ShutdownOnFarmingFinished":
botConfig.ShutdownOnFarmingFinished = bool.Parse(value);
break;
case "SendOnFarmingFinished":
botConfig.SendOnFarmingFinished = bool.Parse(value);
break;
case "SendTradePeriod":
botConfig.SendTradePeriod = byte.Parse(value);
break;
case "GamesPlayedWhileIdle":
botConfig.GamesPlayedWhileIdle.Clear();
foreach (string appID in value.Split(',')) {
botConfig.GamesPlayedWhileIdle.Add(uint.Parse(appID));
}
break;
case "Statistics":
case "Blacklist":
case "SteamNickname":
break;
default:
Logging.LogGenericWarning("Unrecognized config value: " + key + "=" + value);
break;
}
}
}
} catch (Exception e) {
Logging.LogGenericException(e);
Logging.LogGenericError("Your config for this bot instance is invalid, it won't run!");
return null;
}
// Fixups for new format
if (botConfig.SteamLogin != null && botConfig.SteamLogin.Equals("null")) {
botConfig.SteamLogin = null;
}
if (botConfig.SteamPassword != null && botConfig.SteamPassword.Equals("null")) {
botConfig.SteamPassword = null;
}
if (botConfig.SteamApiKey != null && botConfig.SteamApiKey.Equals("null")) {
botConfig.SteamApiKey = null;
}
if (botConfig.SteamParentalPIN != null && botConfig.SteamParentalPIN.Equals("null")) {
botConfig.SteamParentalPIN = null;
}
if (botConfig.SteamTradeToken != null && botConfig.SteamTradeToken.Equals("null")) {
botConfig.SteamTradeToken = null;
}
return botConfig;
}
// This constructor is used only by deserializer
private BotConfig() { }
// TODO: This should be removed soon
internal bool Convert(string path) {
try {
File.WriteAllText(path, JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented));
} catch (Exception e) {
Logging.LogGenericException(e);
return false;
}
Logging.LogGenericWarning("Your config was converted to new ASF V2.0 format");
return true;
}
}
}

View File

@@ -66,6 +66,10 @@ namespace ArchiSteamFarm {
private string FilePath;
internal static BotDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
return null;
}
if (!File.Exists(filePath)) {
return new BotDatabase(filePath);
}
@@ -78,6 +82,10 @@ namespace ArchiSteamFarm {
return null;
}
if (botDatabase == null) {
return null;
}
botDatabase.FilePath = filePath;
return botDatabase;
}

View File

@@ -42,7 +42,9 @@ namespace ArchiSteamFarm {
private readonly Bot Bot;
private readonly Timer Timer;
private volatile bool ManualMode, NowFarming;
internal bool ManualMode { get; private set; }
private bool NowFarming;
internal CardsFarmer(Bot bot) {
if (bot == null) {
@@ -51,7 +53,7 @@ namespace ArchiSteamFarm {
Bot = bot;
if (Program.GlobalConfig.IdleFarmingPeriod > 0) {
if (Timer == null && Program.GlobalConfig.IdleFarmingPeriod > 0) {
Timer = new Timer(
async e => await CheckGamesForFarming().ConfigureAwait(false),
null,
@@ -61,9 +63,9 @@ namespace ArchiSteamFarm {
}
}
internal async Task<bool> SwitchToManualMode(bool manualMode) {
internal async Task SwitchToManualMode(bool manualMode) {
if (ManualMode == manualMode) {
return false;
return;
}
ManualMode = manualMode;
@@ -75,8 +77,6 @@ namespace ArchiSteamFarm {
Logging.LogGenericInfo("Now running in Automatic Farming mode", Bot.BotName);
StartFarming().Forget();
}
return true;
}
internal async Task StartFarming() {
@@ -103,58 +103,51 @@ namespace ArchiSteamFarm {
bool farmedSomething = false;
// Now the algorithm used for farming depends on whether account is restricted or not
if (Bot.BotConfig.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm
Logging.LogGenericInfo("Chosen farming algorithm: Complex", Bot.BotName);
while (GamesToFarm.Count > 0) {
HashSet<uint> gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm);
if (gamesToFarmSolo.Count > 0) {
while (gamesToFarmSolo.Count > 0) {
uint appID = gamesToFarmSolo.First();
if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true;
Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName);
gamesToFarmSolo.Remove(appID);
gamesToFarmSolo.TrimExcess();
do {
// Now the algorithm used for farming depends on whether account is restricted or not
if (Bot.BotConfig.CardDropsRestricted) { // If we have restricted card drops, we use complex algorithm
Logging.LogGenericInfo("Chosen farming algorithm: Complex", Bot.BotName);
while (GamesToFarm.Count > 0) {
HashSet<uint> gamesToFarmSolo = GetGamesToFarmSolo(GamesToFarm);
if (gamesToFarmSolo.Count > 0) {
while (gamesToFarmSolo.Count > 0) {
uint appID = gamesToFarmSolo.First();
if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true;
gamesToFarmSolo.Remove(appID);
gamesToFarmSolo.TrimExcess();
} else {
NowFarming = false;
return;
}
}
} else {
if (FarmMultiple()) {
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName);
} else {
NowFarming = false;
return;
}
}
} else {
if (FarmMultiple(GamesToFarm)) {
Logging.LogGenericInfo("Done farming: " + string.Join(", ", GamesToFarm.Keys), Bot.BotName);
}
} else { // If we have unrestricted card drops, we use simple algorithm
Logging.LogGenericInfo("Chosen farming algorithm: Simple", Bot.BotName);
while (GamesToFarm.Count > 0) {
uint appID = GamesToFarm.Keys.FirstOrDefault();
if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true;
} else {
NowFarming = false;
return;
}
}
}
} else { // If we have unrestricted card drops, we use simple algorithm
Logging.LogGenericInfo("Chosen farming algorithm: Simple", Bot.BotName);
while (GamesToFarm.Count > 0) {
uint appID = GetAnyGameToFarm(GamesToFarm);
if (await FarmSolo(appID).ConfigureAwait(false)) {
farmedSomething = true;
Logging.LogGenericInfo("Done farming: " + appID, Bot.BotName);
} else {
NowFarming = false;
return;
}
}
}
} while (await IsAnythingToFarm().ConfigureAwait(false));
CurrentGamesFarming.Clear();
CurrentGamesFarming.TrimExcess();
NowFarming = false;
// We finished our queue for now, make sure that everything is indeed farmed before proceeding further
// Some games could be added in the meantime
if (await IsAnythingToFarm().ConfigureAwait(false)) {
StartFarming().Forget();
return;
}
Logging.LogGenericInfo("Farming finished!", Bot.BotName);
await Bot.OnFarmingFinished(farmedSomething).ConfigureAwait(false);
}
@@ -208,23 +201,7 @@ namespace ArchiSteamFarm {
return result;
}
private static uint GetAnyGameToFarm(ConcurrentDictionary<uint, float> gamesToFarm) {
if (gamesToFarm == null) {
return 0;
}
return gamesToFarm.Keys.FirstOrDefault();
}
private async Task<bool> IsAnythingToFarm() {
if (NowFarming) {
return true;
}
if (await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false)) {
return false;
}
Logging.LogGenericInfo("Checking badges...", Bot.BotName);
// Find the number of badge pages
@@ -239,32 +216,29 @@ namespace ArchiSteamFarm {
HtmlNodeCollection htmlNodeCollection = htmlDocument.DocumentNode.SelectNodes("//a[@class='pagelink']");
if (htmlNodeCollection != null && htmlNodeCollection.Count > 0) {
HtmlNode htmlNode = htmlNodeCollection[htmlNodeCollection.Count - 1];
if (!byte.TryParse(htmlNode.InnerText, out maxPages)) {
maxPages = 1; // Should never happen
string lastPage = htmlNode.InnerText;
if (!string.IsNullOrEmpty(lastPage)) {
if (!byte.TryParse(lastPage, out maxPages)) {
maxPages = 1; // Should never happen
}
}
}
GamesToFarm.Clear();
// Find APPIDs we need to farm
Logging.LogGenericInfo("Checking other pages...", Bot.BotName);
CheckPage(htmlDocument);
List<Task> tasks = new List<Task>(maxPages - 1);
for (byte page = 1; page <= maxPages; page++) {
if (page == 1) {
CheckPage(htmlDocument); // Because we fetched page number 1 already
} else {
byte currentPage = page; // We need a copy of variable being passed when in for loops
if (maxPages > 1) {
Logging.LogGenericInfo("Checking other pages...", Bot.BotName);
List<Task> tasks = new List<Task>(maxPages - 1);
for (byte page = 2; page <= maxPages; page++) {
byte currentPage = page; // We need a copy of variable being passed when in for loops, as loop will proceed before task is launched
tasks.Add(CheckPage(currentPage));
}
}
await Task.WhenAll(tasks).ConfigureAwait(false);
if (GamesToFarm.Count == 0) {
return false;
await Task.WhenAll(tasks).ConfigureAwait(false);
}
return true;
return GamesToFarm.Count > 0;
}
private void CheckPage(HtmlDocument htmlDocument) {
@@ -289,7 +263,26 @@ namespace ArchiSteamFarm {
continue;
}
uint appID = (uint) Utilities.OnlyNumbers(steamLink);
int index = steamLink.LastIndexOf('/');
if (index < 0) {
Logging.LogNullError("index", Bot.BotName);
continue;
}
index++;
if (steamLink.Length <= index) {
Logging.LogNullError("length", Bot.BotName);
continue;
}
steamLink = steamLink.Substring(index);
uint appID;
if (!uint.TryParse(steamLink, out appID)) {
Logging.LogNullError("appID", Bot.BotName);
continue;
}
if (appID == 0) {
Logging.LogNullError("appID", Bot.BotName);
continue;
@@ -302,22 +295,20 @@ namespace ArchiSteamFarm {
HtmlNode timeNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
if (timeNode == null) {
Logging.LogNullError("timeNode", Bot.BotName);
return;
continue;
}
string hoursString = timeNode.InnerText;
if (string.IsNullOrEmpty(hoursString)) {
Logging.LogNullError("hoursString", Bot.BotName);
return;
continue;
}
hoursString = Regex.Match(hoursString, @"[0-9\.,]+").Value;
float hours = 0;
float hours;
if (string.IsNullOrEmpty(hoursString)) {
hours = 0;
} else {
hours = float.Parse(hoursString, CultureInfo.InvariantCulture);
Match match = Regex.Match(hoursString, @"[0-9\.,]+");
if (match.Success) {
float.TryParse(match.Value, NumberStyles.Number, CultureInfo.InvariantCulture, out hours);
}
GamesToFarm[appID] = hours;
@@ -357,31 +348,33 @@ namespace ArchiSteamFarm {
HtmlNode htmlNode = htmlDocument.DocumentNode.SelectSingleNode("//span[@class='progress_info_bold']");
if (htmlNode == null) {
await Bot.ArchiWebHandler.ReconnectIfNeeded().ConfigureAwait(false);
return null;
}
return !htmlNode.InnerText.Contains("No card drops");
}
private bool FarmMultiple(ConcurrentDictionary<uint, float> appIDs) {
if (appIDs.Count == 0) {
private bool FarmMultiple() {
if (GamesToFarm.Count == 0) {
return true;
}
float maxHour = 0;
foreach (float hour in appIDs.Values) {
if (hour > maxHour) {
maxHour = hour;
foreach (KeyValuePair<uint, float> game in GamesToFarm) {
CurrentGamesFarming.Add(game.Key);
if (game.Value > maxHour) {
maxHour = game.Value;
}
}
foreach (uint appID in appIDs.Keys) {
CurrentGamesFarming.Add(appID);
if (maxHour >= 2) {
CurrentGamesFarming.Clear();
CurrentGamesFarming.TrimExcess();
return true;
}
Logging.LogGenericInfo("Now farming: " + string.Join(", ", appIDs.Keys), Bot.BotName);
if (FarmHours(maxHour, appIDs.Keys)) {
Logging.LogGenericInfo("Now farming: " + string.Join(", ", CurrentGamesFarming), Bot.BotName);
if (FarmHours(maxHour, CurrentGamesFarming)) {
CurrentGamesFarming.Clear();
CurrentGamesFarming.TrimExcess();
return true;
@@ -403,8 +396,11 @@ namespace ArchiSteamFarm {
if (await Farm(appID).ConfigureAwait(false)) {
CurrentGamesFarming.Clear();
CurrentGamesFarming.TrimExcess();
float ignored;
GamesToFarm.TryRemove(appID, out ignored);
float hours;
if (GamesToFarm.TryRemove(appID, out hours)) {
TimeSpan timeSpan = TimeSpan.FromHours(hours);
Logging.LogGenericInfo("Done farming: " + appID + " after " + timeSpan.ToString(@"hh\:mm") + " hours of playtime!", Bot.BotName);
}
return true;
} else {
CurrentGamesFarming.Clear();
@@ -428,6 +424,11 @@ namespace ArchiSteamFarm {
success = false;
break;
}
// Don't forget to update our GamesToFarm hours
float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
GamesToFarm[appID] += timePlayed;
keepFarming = await ShouldFarm(appID).ConfigureAwait(false);
Logging.LogGenericInfo("Still farming: " + appID, Bot.BotName);
}
@@ -437,7 +438,7 @@ namespace ArchiSteamFarm {
return success;
}
private bool FarmHours(float maxHour, ICollection<uint> appIDs) {
private bool FarmHours(float maxHour, HashSet<uint> appIDs) {
if (maxHour < 0 || appIDs == null || appIDs.Count == 0) {
return false;
}
@@ -450,7 +451,6 @@ namespace ArchiSteamFarm {
bool success = true;
while (maxHour < 2) {
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
if (FarmResetEvent.WaitOne(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
success = false;
break;
@@ -458,15 +458,12 @@ namespace ArchiSteamFarm {
// Don't forget to update our GamesToFarm hours
float timePlayed = Program.GlobalConfig.FarmingDelay / 60.0F;
foreach (KeyValuePair<uint, float> gameToFarm in GamesToFarm) {
if (!appIDs.Contains(gameToFarm.Key)) {
continue;
}
GamesToFarm[gameToFarm.Key] = gameToFarm.Value + timePlayed;
foreach (uint appID in appIDs) {
GamesToFarm[appID] += timePlayed;
}
maxHour += timePlayed;
Logging.LogGenericInfo("Still farming: " + string.Join(", ", appIDs), Bot.BotName);
}
Bot.ResetGamesPlayed();

View File

@@ -36,12 +36,22 @@ namespace ArchiSteamFarm {
Experimental
}
internal const byte DefaultHttpTimeout = 60;
private const byte DefaultMaxFarmingTime = 10;
private const byte DefaultFarmingDelay = 5;
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 };
[JsonProperty(Required = Required.DisallowNull)]
internal bool Debug { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool Headless { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal bool AutoUpdates { get; private set; } = true;
@@ -49,19 +59,19 @@ namespace ArchiSteamFarm {
internal EUpdateChannel UpdateChannel { get; private set; } = EUpdateChannel.Stable;
[JsonProperty(Required = Required.DisallowNull)]
internal ProtocolType SteamProtocol { get; private set; } = ProtocolType.Tcp;
internal ProtocolType SteamProtocol { get; private set; } = DefaultSteamProtocol;
[JsonProperty(Required = Required.DisallowNull)]
internal ulong SteamOwnerID { get; private set; } = 0;
[JsonProperty(Required = Required.DisallowNull)]
internal byte MaxFarmingTime { get; private set; } = 10;
internal byte MaxFarmingTime { get; private set; } = DefaultMaxFarmingTime;
[JsonProperty(Required = Required.DisallowNull)]
internal byte IdleFarmingPeriod { get; private set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
internal byte FarmingDelay { get; private set; } = 5;
internal byte FarmingDelay { get; private set; } = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
internal byte AccountPlayingDelay { get; private set; } = 5;
@@ -76,13 +86,13 @@ namespace ArchiSteamFarm {
internal bool ForceHttp { get; private set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
internal byte HttpTimeout { get; private set; } = 60;
internal byte HttpTimeout { get; private set; } = DefaultHttpTimeout;
[JsonProperty]
internal string WCFHostname { get; set; } = "localhost";
[JsonProperty(Required = Required.DisallowNull)]
internal string WCFHostname { get; private set; } = "localhost";
[JsonProperty(Required = Required.DisallowNull)]
internal ushort WCFPort { get; private set; } = 1242;
internal ushort WCFPort { get; private set; } = DefaultWCFPort;
[JsonProperty(Required = Required.DisallowNull)]
internal bool LogToFile { get; private set; } = true;
@@ -111,18 +121,44 @@ namespace ArchiSteamFarm {
return null;
}
if (globalConfig == null) {
return null;
}
// SK2 supports only TCP and UDP steam protocols
// Make sure that user can't screw this up
// Ensure that user can't screw this up
switch (globalConfig.SteamProtocol) {
case ProtocolType.Tcp:
case ProtocolType.Udp:
break;
default:
Logging.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol + ", default TCP protocol will be used instead");
globalConfig.SteamProtocol = ProtocolType.Tcp;
Logging.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol + ". Value of " + DefaultSteamProtocol + " will be used instead");
globalConfig.SteamProtocol = DefaultSteamProtocol;
break;
}
// User might not know what he's doing
// Ensure that he can't screw core ASF variables
if (globalConfig.MaxFarmingTime == 0) {
Logging.LogGenericWarning("Configured MaxFarmingTime is invalid: " + globalConfig.MaxFarmingTime + ". Value of " + DefaultMaxFarmingTime + " will be used instead");
globalConfig.MaxFarmingTime = DefaultMaxFarmingTime;
}
if (globalConfig.FarmingDelay == 0) {
Logging.LogGenericWarning("Configured FarmingDelay is invalid: " + globalConfig.FarmingDelay + ". Value of " + DefaultFarmingDelay + " will be used instead");
globalConfig.FarmingDelay = DefaultFarmingDelay;
}
if (globalConfig.HttpTimeout == 0) {
Logging.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout + ". Value of " + DefaultHttpTimeout + " will be used instead");
globalConfig.HttpTimeout = DefaultHttpTimeout;
}
if (globalConfig.WCFPort == 0) {
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort + ". Value of " + DefaultWCFPort + " will be used instead");
globalConfig.WCFPort = DefaultWCFPort;
}
return globalConfig;
}

View File

@@ -45,7 +45,7 @@ namespace ArchiSteamFarm {
}
[JsonProperty(Required = Required.DisallowNull)]
private uint _CellID = 0;
private uint _CellID;
internal static GlobalDatabase Load() {
if (!File.Exists(FilePath)) {

View File

@@ -31,7 +31,7 @@ namespace ArchiSteamFarm {
internal static class Logging {
private static readonly object FileLock = new object();
private static bool LogToFile = false;
private static bool LogToFile;
internal static void Init() {
LogToFile = Program.GlobalConfig.LogToFile;

View File

@@ -45,6 +45,7 @@ namespace ArchiSteamFarm {
SteamParentalPIN,
RevocationCode,
TwoFactorAuthentication,
WCFHostname
}
internal enum EMode : byte {
@@ -58,10 +59,11 @@ namespace ArchiSteamFarm {
internal const string ConfigDirectory = "config";
internal const string DebugDirectory = "debug";
internal const string LogFile = "log.txt";
internal const string GithubRepo = "JustArchi/ArchiSteamFarm";
internal const string GlobalConfigFile = ASF + ".json";
internal const string GlobalDatabaseFile = ASF + ".db";
private const string GithubReleaseURL = "https://api.github.com/repos/JustArchi/ArchiSteamFarm/releases"; // GitHub API is HTTPS only
private const string GithubReleaseURL = "https://api.github.com/repos/" + GithubRepo + "/releases"; // GitHub API is HTTPS only
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
internal static readonly Version Version = Assembly.GetName().Version;
@@ -80,17 +82,21 @@ namespace ArchiSteamFarm {
private static Timer AutoUpdatesTimer;
private static EMode Mode = EMode.Normal;
private static WebBrowser WebBrowser;
internal static async Task CheckForUpdate() {
string oldExeFile = ExecutableFile + ".old";
// We booted successfully so we can now remove old exe file
if (File.Exists(oldExeFile)) {
// It's entirely possible that old process is still running, allow at least a second before trying to remove the file
await Utilities.SleepAsync(1000).ConfigureAwait(false);
try {
File.Delete(oldExeFile);
} catch (Exception e) {
Logging.LogGenericException(e);
return;
Logging.LogGenericError("Could not remove old ASF binary, please remove " + oldExeFile + " manually in order for update function to work!");
}
}
@@ -168,6 +174,11 @@ namespace ArchiSteamFarm {
return;
}
if (File.Exists(oldExeFile)) {
Logging.LogGenericWarning("Refusing to proceed with auto update as old " + oldExeFile + " binary could not be removed, please remove it manually");
return;
}
// Auto update logic starts here
if (releaseResponse.Assets == null) {
Logging.LogGenericWarning("Could not proceed with update because that version doesn't include assets!");
@@ -245,13 +256,19 @@ namespace ArchiSteamFarm {
Restart();
}
internal static void Exit(int exitCode = 0) {
WCF.StopServer();
Environment.Exit(exitCode);
}
internal static void Restart() {
try {
Process.Start(ExecutableFile, string.Join(" ", Environment.GetCommandLineArgs().Skip(1)));
Environment.Exit(0);
} catch (Exception e) {
Logging.LogGenericException(e);
}
Exit();
}
internal static async Task LimitSteamRequestsAsync() {
@@ -262,46 +279,53 @@ namespace ArchiSteamFarm {
}).Forget();
}
internal static string GetUserInput(string botLogin, EUserInputType userInputType, string extraInformation = null) {
internal static string GetUserInput(EUserInputType userInputType, string botName = null, string extraInformation = null) {
if (userInputType == EUserInputType.Unknown) {
return null;
}
if (GlobalConfig.Headless) {
Logging.LogGenericWarning("Received a request for user input, but process is running in headless mode!");
return null;
}
string result;
lock (ConsoleLock) {
ConsoleIsBusy = true;
switch (userInputType) {
case EUserInputType.DeviceID:
Console.Write("<" + botLogin + "> Please enter your Device ID (including \"android:\"): ");
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter your Device ID (including \"android:\"): ");
break;
case EUserInputType.Login:
Console.Write("<" + botLogin + "> Please enter your login: ");
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter your login: ");
break;
case EUserInputType.Password:
Console.Write("<" + botLogin + "> Please enter your password: ");
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter your password: ");
break;
case EUserInputType.PhoneNumber:
Console.Write("<" + botLogin + "> Please enter your full phone number (e.g. +1234567890): ");
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter your full phone number (e.g. +1234567890): ");
break;
case EUserInputType.SMS:
Console.Write("<" + botLogin + "> Please enter SMS code sent on your mobile: ");
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter SMS code sent on your mobile: ");
break;
case EUserInputType.SteamGuard:
Console.Write("<" + botLogin + "> Please enter the auth code sent to your email: ");
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter the auth code sent to your email: ");
break;
case EUserInputType.SteamParentalPIN:
Console.Write("<" + botLogin + "> Please enter steam parental PIN: ");
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter steam parental PIN: ");
break;
case EUserInputType.RevocationCode:
Console.WriteLine("<" + botLogin + "> PLEASE WRITE DOWN YOUR REVOCATION CODE: " + extraInformation);
Console.WriteLine("<" + botLogin + "> THIS IS THE ONLY WAY TO NOT GET LOCKED OUT OF YOUR ACCOUNT!");
Console.Write("<" + botLogin + "> Hit enter once ready...");
Console.WriteLine((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "PLEASE WRITE DOWN YOUR REVOCATION CODE: " + extraInformation);
Console.Write("Hit enter once ready...");
break;
case EUserInputType.TwoFactorAuthentication:
Console.Write("<" + botLogin + "> Please enter your 2 factor auth code from your authenticator app: ");
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter your 2 factor auth code from your authenticator app: ");
break;
case EUserInputType.WCFHostname:
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter your WCF hostname: ");
break;
default:
Console.Write("<" + botLogin + "> Please enter not documented yet value of \"" + userInputType + "\": ");
Console.Write((string.IsNullOrEmpty(botName) ? "" : "<" + botName + "> ") + "Please enter not documented yet value of \"" + userInputType + "\": ");
break;
}
result = Console.ReadLine();
@@ -333,19 +357,21 @@ namespace ArchiSteamFarm {
if (GlobalConfig == null) {
Logging.LogGenericError("Global config could not be loaded, please make sure that ASF.json exists and is valid!");
Thread.Sleep(5000);
Environment.Exit(1);
Exit(1);
}
GlobalDatabase = GlobalDatabase.Load();
if (GlobalDatabase == null) {
Logging.LogGenericError("Global database could not be loaded!");
Thread.Sleep(5000);
Environment.Exit(1);
Exit(1);
}
ArchiWebHandler.Init();
WebBrowser.Init();
WCF.Init();
WebBrowser = new WebBrowser("Main");
}
private static void ParseArgs(string[] args) {
@@ -441,7 +467,7 @@ namespace ArchiSteamFarm {
// If we ran ASF as a client, we're done by now
if (Mode == EMode.Client) {
Environment.Exit(0);
Exit();
}
// From now on it's server mode
@@ -450,7 +476,7 @@ namespace ArchiSteamFarm {
if (!Directory.Exists(ConfigDirectory)) {
Logging.LogGenericError("Config directory doesn't exist!");
Thread.Sleep(5000);
Environment.Exit(1);
Exit(1);
}
CheckForUpdate().Wait();
@@ -477,19 +503,6 @@ namespace ArchiSteamFarm {
}
}
// CONVERSION START
foreach (var configFile in Directory.EnumerateFiles(ConfigDirectory, "*.xml")) {
string botName = Path.GetFileNameWithoutExtension(configFile);
Logging.LogGenericWarning("Found legacy " + botName + ".xml config file, it will now be converted to new ASF V2.0 format!");
Bot bot = new Bot(botName);
if (bot.BotConfig != null && bot.BotConfig.Enabled) {
isRunning = true;
} else {
Logging.LogGenericInfo("Not starting this instance because it's disabled in config file", botName);
}
}
// CONVERSION END
// Check if we got any bots running
if (!isRunning) {
OnBotShutdown();
@@ -503,7 +516,7 @@ namespace ArchiSteamFarm {
ShutdownResetEvent.WaitOne();
// We got a signal to shutdown
Environment.Exit(0);
Exit();
}
}
}

View File

@@ -32,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.1.7")]
[assembly: AssemblyFileVersion("2.0.1.7")]
[assembly: AssemblyVersion("2.0.2.8")]
[assembly: AssemblyFileVersion("2.0.2.8")]

View File

@@ -35,9 +35,9 @@ namespace ArchiSteamFarm {
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1);
private readonly Bot Bot;
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1);
private volatile byte ParsingTasks = 0;
private byte ParsingTasks;
internal static async Task LimitInventoryRequestsAsync() {
await InventorySemaphore.WaitAsync().ConfigureAwait(false);
@@ -55,18 +55,27 @@ namespace ArchiSteamFarm {
Bot = bot;
}
internal async void CheckTrades() {
if (ParsingTasks >= 2) {
internal async Task CheckTrades() {
bool shouldRun = false;
lock (TradesSemaphore) {
if (ParsingTasks < 2) {
ParsingTasks++;
shouldRun = true;
}
}
if (!shouldRun) {
return;
}
ParsingTasks++;
await TradesSemaphore.WaitAsync().ConfigureAwait(false);
await Semaphore.WaitAsync().ConfigureAwait(false);
await ParseActiveTrades().ConfigureAwait(false);
Semaphore.Release();
lock (TradesSemaphore) {
ParsingTasks--;
}
ParsingTasks--;
TradesSemaphore.Release();
}
private async Task ParseActiveTrades() {
@@ -75,19 +84,12 @@ namespace ArchiSteamFarm {
return;
}
List<Task> tasks = new List<Task>();
foreach (Steam.TradeOffer tradeOffer in tradeOffers) {
if (tradeOffer.trade_offer_state == Steam.TradeOffer.ETradeOfferState.Active) {
tasks.Add(Task.Run(async () => await ParseTrade(tradeOffer).ConfigureAwait(false)));
}
}
await Task.WhenAll(tasks).ConfigureAwait(false);
await Bot.AcceptConfirmations(Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
await tradeOffers.ForEachAsync(ParseTrade).ConfigureAwait(false);
await Bot.AcceptConfirmations(true, Confirmation.ConfirmationType.Trade).ConfigureAwait(false);
}
private async Task ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
if (tradeOffer == null || tradeOffer.trade_offer_state != Steam.TradeOffer.ETradeOfferState.Active) {
return;
}
@@ -96,12 +98,33 @@ namespace ArchiSteamFarm {
return;
}
if (tradeOffer.items_to_give.Count == 0 || tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) {
if (ShouldAcceptTrade(tradeOffer)) {
Logging.LogGenericInfo("Accepting trade: " + tradeID, Bot.BotName);
await Bot.ArchiWebHandler.AcceptTradeOffer(tradeID).ConfigureAwait(false);
} else {
Logging.LogGenericInfo("Ignoring trade: " + tradeID, Bot.BotName);
}
}
private bool ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
return false;
}
// Always accept trades when we're not losing anything
if (tradeOffer.items_to_give.Count == 0) {
return true;
}
// Always accept trades from SteamMasterID
if (tradeOffer.OtherSteamID64 != 0 && tradeOffer.OtherSteamID64 == Bot.BotConfig.SteamMasterID) {
return true;
}
// TODO: Add optional SteamTradeMatcher integration here
// If no rule above matched this trade, reject it
return false;
}
}
}

View File

@@ -22,41 +22,47 @@
*/
using System.Text.RegularExpressions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace ArchiSteamFarm {
internal static class Utilities {
internal static void Forget(this Task task) { }
internal static async Task SleepAsync(int miliseconds) {
await Task.Delay(miliseconds).ConfigureAwait(false);
internal static Task ForEachAsync<T>(this IEnumerable<T> sequence, Func<T, Task> action) {
return Task.WhenAll(sequence.Select(action));
}
internal static ulong OnlyNumbers(string inputString) {
if (string.IsNullOrEmpty(inputString)) {
return 0;
}
string resultString = OnlyNumbersString(inputString);
if (string.IsNullOrEmpty(resultString)) {
return 0;
}
ulong result;
if (!ulong.TryParse(resultString, out result)) {
return 0;
}
return result;
}
internal static string OnlyNumbersString(string text) {
if (string.IsNullOrEmpty(text)) {
internal static string GetCookieValue(this CookieContainer cookieContainer, string URL, string name) {
if (string.IsNullOrEmpty(URL) || string.IsNullOrEmpty(name)) {
return null;
}
return Regex.Replace(text, @"[^\d]", "");
CookieCollection cookies = cookieContainer.GetCookies(new Uri(URL));
if (cookies == null || cookies.Count == 0) {
return null;
}
foreach (Cookie cookie in cookies) {
if (!cookie.Name.Equals(name, StringComparison.Ordinal)) {
continue;
}
return cookie.Value;
}
return null;
}
internal static Task SleepAsync(int miliseconds) {
if (miliseconds < 0) {
return Task.FromResult(true);
}
return Task.Delay(miliseconds);
}
internal static uint GetCharCountInString(string s, char c) {

View File

@@ -42,6 +42,13 @@ namespace ArchiSteamFarm {
private Client Client;
internal static void Init() {
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
Program.GlobalConfig.WCFHostname = Program.GetUserInput(Program.EUserInputType.WCFHostname);
if (string.IsNullOrEmpty(Program.GlobalConfig.WCFHostname)) {
return;
}
}
URL = "http://" + Program.GlobalConfig.WCFHostname + ":" + Program.GlobalConfig.WCFPort + "/ASF";
}

View File

@@ -23,37 +23,31 @@
*/
using HtmlAgilityPack;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace ArchiSteamFarm {
internal static class WebBrowser {
internal sealed class WebBrowser {
internal const byte MaxRetries = 5; // Defines maximum number of retries, UrlRequest() does not handle retry by itself (it's app responsibility)
private const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state
private const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
private static readonly string DefaultUserAgent = "ArchiSteamFarm/" + Program.Version;
private static readonly HttpClient HttpClient = new HttpClient(new HttpClientHandler {
UseCookies = false
}) {
Timeout = TimeSpan.FromSeconds(60)
};
internal readonly CookieContainer CookieContainer = new CookieContainer();
private readonly HttpClient HttpClient;
private readonly string Identifier;
internal static void Init() {
HttpClient.Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout);
// Most web services expect that UserAgent is set, so we declare it globally
// Any request can override that on as-needed basis (see: RequestOptions.FakeUserAgent)
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
// Set max connection limit from default of 2 to desired value
ServicePointManager.DefaultConnectionLimit = MaxConnections;
@@ -63,67 +57,114 @@ namespace ArchiSteamFarm {
// Don't use Expect100Continue, we're sure about our POSTs, save some TCP packets
ServicePointManager.Expect100Continue = false;
// Reuse ports if possible
// TODO: Mono doesn't support that feature yet
#if !__MonoCS__
// Reuse ports if possible (since .NET 4.6+)
//ServicePointManager.ReusePort = true;
#endif
}
internal static async Task<HttpResponseMessage> UrlGet(string request, Dictionary<string, string> cookies = null, string referer = null) {
internal WebBrowser(string identifier) {
if (string.IsNullOrEmpty(identifier)) {
return;
}
Identifier = identifier;
HttpClientHandler httpClientHandler = new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
CookieContainer = CookieContainer
};
HttpClient = new HttpClient(httpClientHandler) {
Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.HttpTimeout)
};
// Most web services expect that UserAgent is set, so we declare it globally
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(DefaultUserAgent);
}
internal async Task<bool> UrlGet(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return false;
}
HttpResponseMessage response = await UrlGetToResponse(request, referer).ConfigureAwait(false);
if (response == null) {
return false;
}
response.Dispose();
return true;
}
internal async Task<bool> UrlPost(string request, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return false;
}
HttpResponseMessage response = await UrlPostToResponse(request, data, referer).ConfigureAwait(false);
if (response == null) {
return false;
}
response.Dispose();
return true;
}
internal async Task<HttpResponseMessage> UrlGetToResponse(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
return await UrlRequest(request, HttpMethod.Get, null, cookies, referer).ConfigureAwait(false);
return await UrlRequest(request, HttpMethod.Get, null, referer).ConfigureAwait(false);
}
internal static async Task<HttpResponseMessage> UrlPost(string request, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null) {
internal async Task<HttpResponseMessage> UrlPostToResponse(string request, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
return await UrlRequest(request, HttpMethod.Post, data, cookies, referer).ConfigureAwait(false);
return await UrlRequest(request, HttpMethod.Post, data, referer).ConfigureAwait(false);
}
internal static async Task<string> UrlGetToContent(string request, Dictionary<string, string> cookies = null, string referer = null) {
internal async Task<string> UrlGetToContent(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false);
HttpResponseMessage httpResponse = await UrlGetToResponse(request, referer).ConfigureAwait(false);
if (httpResponse == null) {
return null;
}
if (httpResponse.Content == null) {
return null;
}
string result = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
httpResponse.Dispose();
return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
return result;
}
internal static async Task<Stream> UrlGetToStream(string request, Dictionary<string, string> cookies = null, string referer = null) {
internal async Task<Stream> UrlGetToStream(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
HttpResponseMessage httpResponse = await UrlGet(request, cookies, referer).ConfigureAwait(false);
HttpResponseMessage httpResponse = await UrlGetToResponse(request, referer).ConfigureAwait(false);
if (httpResponse == null) {
return null;
}
if (httpResponse.Content == null) {
return null;
}
Stream result = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
httpResponse.Dispose();
return await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);
return result;
}
internal static async Task<HtmlDocument> UrlGetToHtmlDocument(string request, Dictionary<string, string> cookies = null, string referer = null) {
internal async Task<HtmlDocument> UrlGetToHtmlDocument(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false);
string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) {
return null;
}
@@ -135,12 +176,12 @@ namespace ArchiSteamFarm {
return htmlDocument;
}
internal static async Task<JObject> UrlGetToJObject(string request, Dictionary<string, string> cookies = null, string referer = null) {
internal async Task<JObject> UrlGetToJObject(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false);
string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) {
return null;
}
@@ -149,20 +190,20 @@ namespace ArchiSteamFarm {
try {
jObject = JObject.Parse(content);
} catch (Exception e) {
Logging.LogGenericException(e);
} catch (JsonException e) {
Logging.LogGenericException(e, Identifier);
return null;
}
return jObject;
}
internal static async Task<XmlDocument> UrlGetToXML(string request, Dictionary<string, string> cookies = null, string referer = null) {
internal async Task<XmlDocument> UrlGetToXML(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) {
return null;
}
string content = await UrlGetToContent(request, cookies, referer).ConfigureAwait(false);
string content = await UrlGetToContent(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(content)) {
return null;
}
@@ -172,37 +213,33 @@ namespace ArchiSteamFarm {
try {
xmlDocument.LoadXml(content);
} catch (XmlException e) {
Logging.LogGenericException(e);
Logging.LogGenericException(e, Identifier);
return null;
}
return xmlDocument;
}
private static async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, Dictionary<string, string> cookies = null, string referer = null) {
private async Task<HttpResponseMessage> UrlRequest(string request, HttpMethod httpMethod, Dictionary<string, string> data = null, string referer = null) {
if (string.IsNullOrEmpty(request) || httpMethod == null) {
return null;
}
if (request.StartsWith("https://") && Program.GlobalConfig.ForceHttp) {
return null;
}
HttpResponseMessage responseMessage;
using (HttpRequestMessage requestMessage = new HttpRequestMessage(httpMethod, request)) {
if (data != null && data.Count > 0) {
try {
requestMessage.Content = new FormUrlEncodedContent(data);
} catch (UriFormatException e) {
Logging.LogGenericException(e);
Logging.LogGenericException(e, Identifier);
return null;
}
}
if (cookies != null && cookies.Count > 0) {
StringBuilder cookieHeader = new StringBuilder();
foreach (KeyValuePair<string, string> cookie in cookies) {
cookieHeader.Append(cookie.Key + "=" + cookie.Value + ";");
}
requestMessage.Headers.Add("Cookie", cookieHeader.ToString());
}
if (!string.IsNullOrEmpty(referer)) {
requestMessage.Headers.Referrer = new Uri(referer);
}
@@ -215,6 +252,14 @@ namespace ArchiSteamFarm {
}
if (responseMessage == null || !responseMessage.IsSuccessStatusCode) {
if (Debugging.IsDebugBuild || Program.GlobalConfig.Debug) {
Logging.LogGenericError("Request: " + request + " failed!", Identifier);
if (responseMessage != null) {
Logging.LogGenericError("Status code: " + responseMessage.StatusCode, Identifier);
Logging.LogGenericError("Content: " + Environment.NewLine + await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false), Identifier);
responseMessage.Dispose();
}
}
return null;
}

View File

@@ -1,5 +1,6 @@
{
"Debug": false,
"Headless": false,
"AutoUpdates": true,
"UpdateChannel": 1,
"SteamProtocol": 6,

View File

@@ -11,6 +11,7 @@
"DismissInventoryNotifications": true,
"FarmOffline": false,
"HandleOfflineMessages": false,
"AcceptGifts": false,
"ForwardKeysToOtherBots": false,
"DistributeKeys": false,
"UseAsfAsMobileAuthenticator": false,

View File

@@ -29,7 +29,7 @@ using System.IO;
namespace ConfigGenerator {
internal class ASFConfig {
internal static List<ASFConfig> ASFConfigs = new List<ASFConfig>();
internal static readonly HashSet<ASFConfig> ASFConfigs = new HashSet<ASFConfig>();
internal string FilePath { get; set; }
@@ -37,9 +37,8 @@ namespace ConfigGenerator {
ASFConfigs.Add(this);
}
protected ASFConfig(string filePath) {
protected ASFConfig(string filePath) : this() {
FilePath = filePath;
ASFConfigs.Add(this);
}
internal virtual void Save() {
@@ -55,9 +54,9 @@ namespace ConfigGenerator {
internal virtual void Remove() {
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FilePath) {
foreach (var configFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
try {
File.Delete(configFile);
File.Delete(botFile);
} catch (Exception e) {
Logging.LogGenericException(e);
}
@@ -73,9 +72,9 @@ namespace ConfigGenerator {
string queryPath = Path.GetFileNameWithoutExtension(FilePath);
lock (FilePath) {
foreach (var file in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
foreach (string botFile in Directory.EnumerateFiles(Program.ConfigDirectory, queryPath + ".*")) {
try {
File.Move(file, Path.Combine(Program.ConfigDirectory, botName + Path.GetExtension(file)));
File.Move(botFile, Path.Combine(Program.ConfigDirectory, botName + Path.GetExtension(botFile)));
} catch (Exception e) {
Logging.LogGenericException(e);
}

View File

@@ -65,6 +65,9 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public bool HandleOfflineMessages { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool AcceptGifts { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool ForwardKeysToOtherBots { get; set; } = false;
@@ -95,7 +98,6 @@ namespace ConfigGenerator {
[JsonProperty(Required = Required.DisallowNull)]
public List<uint> GamesPlayedWhileIdle { get; set; } = new List<uint>();
internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
return null;
@@ -113,13 +115,17 @@ namespace ConfigGenerator {
return new BotConfig(filePath);
}
if (botConfig == null) {
return new BotConfig(filePath);
}
botConfig.FilePath = filePath;
return botConfig;
}
// This constructor is used only by deserializer
private BotConfig() : base() { }
private BotConfig() { }
private BotConfig(string filePath) : base(filePath) {
FilePath = filePath;

View File

@@ -44,15 +44,8 @@
</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.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ASFConfig.cs" />
@@ -111,13 +104,11 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent Condition=" '$(OS)' != 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir "$(SolutionDir)out" "$(SolutionDir)out\config"
"$(SolutionDir)tools\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
"$(SolutionDir)tools\ILRepack\ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out\ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
del "$(SolutionDir)out\ASF-GUI.exe.config"
</PostBuildEvent>
<PostBuildEvent Condition=" '$(OS)' == 'Unix' AND '$(ConfigurationName)' == 'Release' ">
mkdir -p "$(SolutionDir)out" "$(SolutionDir)out/config"
mono -O=all "$(SolutionDir)tools/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
mono -O=all "$(SolutionDir)tools/ILRepack/ILRepack.exe" /ndebug /internalize /parallel /targetplatform:v4 /wildcards /out:"$(SolutionDir)out/ASF-GUI.exe" "$(TargetDir)$(TargetName).exe" "$(TargetDir)*.dll"
rm "$(SolutionDir)out/ASF-GUI.exe.config"
</PostBuildEvent>
</PropertyGroup>

View File

@@ -27,12 +27,9 @@ using System.Windows.Forms;
namespace ConfigGenerator {
internal class ConfigPage : TabPage {
internal readonly ASFConfig ASFConfig;
private EnhancedPropertyGrid EnhancedPropertyGrid;
internal ConfigPage(ASFConfig config) : base() {
internal ConfigPage(ASFConfig config) {
if (config == null) {
return;
}
@@ -41,17 +38,12 @@ namespace ConfigGenerator {
RefreshText();
EnhancedPropertyGrid = new EnhancedPropertyGrid(config);
Controls.Add(EnhancedPropertyGrid);
EnhancedPropertyGrid enhancedPropertyGrid = new EnhancedPropertyGrid(config);
Controls.Add(enhancedPropertyGrid);
}
internal void RefreshText() {
Text = Path.GetFileNameWithoutExtension(ASFConfig.FilePath);
}
private void InitializeComponent() {
SuspendLayout();
ResumeLayout(false);
}
}
}

View File

@@ -22,9 +22,6 @@
*/
using System;
using System.IO;
namespace ConfigGenerator {
internal static class Debugging {
#if DEBUG

View File

@@ -26,8 +26,9 @@ using System.Windows.Forms;
namespace ConfigGenerator {
internal sealed class EnhancedPropertyGrid : PropertyGrid {
private ASFConfig ASFConfig;
internal EnhancedPropertyGrid(ASFConfig config) : base() {
private readonly ASFConfig ASFConfig;
internal EnhancedPropertyGrid(ASFConfig config) {
if (config == null) {
return;
}
@@ -65,7 +66,6 @@ namespace ConfigGenerator {
if (globalConfig.SteamOwnerID != 0) {
Tutorial.OnAction(Tutorial.EPhase.GlobalConfigReady);
}
return;
}
}
}

View File

@@ -36,12 +36,21 @@ namespace ConfigGenerator {
Experimental
}
private const byte DefaultMaxFarmingTime = 10;
private const byte DefaultFarmingDelay = 5;
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
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280 };
[JsonProperty(Required = Required.DisallowNull)]
public bool Debug { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool Headless { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public bool AutoUpdates { get; set; } = true;
@@ -49,19 +58,19 @@ namespace ConfigGenerator {
public EUpdateChannel UpdateChannel { get; set; } = EUpdateChannel.Stable;
[JsonProperty(Required = Required.DisallowNull)]
public ProtocolType SteamProtocol { get; set; } = ProtocolType.Tcp;
public ProtocolType SteamProtocol { get; set; } = DefaultSteamProtocol;
[JsonProperty(Required = Required.DisallowNull)]
public ulong SteamOwnerID { get; set; } = 0;
[JsonProperty(Required = Required.DisallowNull)]
public byte MaxFarmingTime { get; set; } = 10;
public byte MaxFarmingTime { get; set; } = DefaultMaxFarmingTime;
[JsonProperty(Required = Required.DisallowNull)]
public byte IdleFarmingPeriod { get; set; } = 3;
[JsonProperty(Required = Required.DisallowNull)]
public byte FarmingDelay { get; set; } = 5;
public byte FarmingDelay { get; set; } = DefaultFarmingDelay;
[JsonProperty(Required = Required.DisallowNull)]
public byte AccountPlayingDelay { get; set; } = 5;
@@ -76,13 +85,13 @@ namespace ConfigGenerator {
public bool ForceHttp { get; set; } = false;
[JsonProperty(Required = Required.DisallowNull)]
public byte HttpTimeout { get; set; } = 60;
public byte HttpTimeout { get; set; } = DefaultHttpTimeout;
[JsonProperty(Required = Required.DisallowNull)]
[JsonProperty]
public string WCFHostname { get; set; } = "localhost";
[JsonProperty(Required = Required.DisallowNull)]
public ushort WCFPort { get; set; } = 1242;
public ushort WCFPort { get; set; } = DefaultWCFPort;
[JsonProperty(Required = Required.DisallowNull)]
public bool LogToFile { get; set; } = true;
@@ -114,25 +123,51 @@ namespace ConfigGenerator {
return new GlobalConfig(filePath);
}
if (globalConfig == null) {
return new GlobalConfig(filePath);
}
globalConfig.FilePath = filePath;
// SK2 supports only TCP and UDP steam protocols
// Make sure that user can't screw this up
// Ensure that user can't screw this up
switch (globalConfig.SteamProtocol) {
case ProtocolType.Tcp:
case ProtocolType.Udp:
break;
default:
Logging.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol + ", default TCP protocol will be used instead");
globalConfig.SteamProtocol = ProtocolType.Tcp;
Logging.LogGenericWarning("Configured SteamProtocol is invalid: " + globalConfig.SteamProtocol + ". Value of " + DefaultSteamProtocol + " will be used instead");
globalConfig.SteamProtocol = DefaultSteamProtocol;
break;
}
// User might not know what he's doing
// Ensure that he can't screw core ASF variables
if (globalConfig.MaxFarmingTime == 0) {
Logging.LogGenericWarning("Configured MaxFarmingTime is invalid: " + globalConfig.MaxFarmingTime + ". Value of " + DefaultMaxFarmingTime + " will be used instead");
globalConfig.MaxFarmingTime = DefaultMaxFarmingTime;
}
if (globalConfig.FarmingDelay == 0) {
Logging.LogGenericWarning("Configured FarmingDelay is invalid: " + globalConfig.FarmingDelay + ". Value of " + DefaultFarmingDelay + " will be used instead");
globalConfig.FarmingDelay = DefaultFarmingDelay;
}
if (globalConfig.HttpTimeout == 0) {
Logging.LogGenericWarning("Configured HttpTimeout is invalid: " + globalConfig.HttpTimeout + ". Value of " + DefaultHttpTimeout + " will be used instead");
globalConfig.HttpTimeout = DefaultHttpTimeout;
}
if (globalConfig.WCFPort == 0) {
Logging.LogGenericWarning("Configured WCFPort is invalid: " + globalConfig.WCFPort + ". Value of " + DefaultWCFPort + " will be used instead");
globalConfig.WCFPort = DefaultWCFPort;
}
return globalConfig;
}
// This constructor is used only by deserializer
private GlobalConfig() : base() { }
private GlobalConfig() { }
private GlobalConfig(string filePath) : base(filePath) {
FilePath = filePath;

View File

@@ -32,28 +32,30 @@
this.MainTab.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.MainTab.Appearance = System.Windows.Forms.TabAppearance.Buttons;
this.MainTab.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.MainTab.HotTrack = true;
this.MainTab.Location = new System.Drawing.Point(16, 13);
this.MainTab.Location = new System.Drawing.Point(14, 14);
this.MainTab.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);
this.MainTab.Multiline = true;
this.MainTab.Name = "MainTab";
this.MainTab.SelectedIndex = 0;
this.MainTab.Size = new System.Drawing.Size(748, 529);
this.MainTab.Size = new System.Drawing.Size(854, 745);
this.MainTab.SizeMode = System.Windows.Forms.TabSizeMode.Fixed;
this.MainTab.TabIndex = 1;
this.MainTab.Selected += new System.Windows.Forms.TabControlEventHandler(this.MainTab_Selected);
this.MainTab.Deselecting += new System.Windows.Forms.TabControlCancelEventHandler(this.MainTab_Deselecting);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F);
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoScroll = true;
this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.ClientSize = new System.Drawing.Size(780, 557);
this.ClientSize = new System.Drawing.Size(882, 774);
this.Controls.Add(this.MainTab);
this.Cursor = System.Windows.Forms.Cursors.Default;
this.DoubleBuffered = true;
this.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238)));
this.HelpButton = true;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4);

View File

@@ -23,6 +23,7 @@
*/
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
@@ -31,20 +32,11 @@ namespace ConfigGenerator {
public partial class MainForm : Form {
private const byte ReservedTabs = 3;
private readonly TabPage NewTab = new TabPage { Text = "+" };
private readonly TabPage RemoveTab = new TabPage { Text = "-" };
private readonly TabPage RenameTab = new TabPage { Text = "~" };
private ConfigPage ASFTab;
private TabPage RemoveTab = new TabPage() {
Text = "-",
};
private TabPage RenameTab = new TabPage() {
Text = "~",
};
private TabPage NewTab = new TabPage() {
Text = "+",
};
private TabPage OldTab;
public MainForm() {
@@ -73,7 +65,7 @@ namespace ConfigGenerator {
Tutorial.Enabled = false;
}
MainTab.TabPages.AddRange(new TabPage[] { RemoveTab, RenameTab, NewTab });
MainTab.TabPages.AddRange(new[] { RemoveTab, RenameTab, NewTab });
Tutorial.OnAction(Tutorial.EPhase.Start);
}
@@ -186,7 +178,7 @@ namespace ConfigGenerator {
Tutorial.OnAction(Tutorial.EPhase.Shown);
}
private void MainForm_HelpButtonClicked(object sender, System.ComponentModel.CancelEventArgs e) {
private void MainForm_HelpButtonClicked(object sender, CancelEventArgs e) {
if (sender == null || e == null) {
return;
}

View File

@@ -36,10 +36,7 @@ namespace ConfigGenerator {
private const string ASFDirectory = "ArchiSteamFarm";
private static readonly Assembly Assembly = Assembly.GetExecutingAssembly();
private static readonly string ExecutableFile = Assembly.Location;
private static readonly string ExecutableName = Path.GetFileName(ExecutableFile);
private static readonly string ExecutableDirectory = Path.GetDirectoryName(ExecutableFile);
private static readonly string ExecutableDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
/// <summary>
/// The main entry point for the application.

View File

@@ -1,7 +1,12 @@
ArchiSteamFarm
===================
[![Build status](https://ci.appveyor.com/api/projects/status/yi0y25nipcb1j1yj?svg=true)](https://ci.appveyor.com/project/JustArchi/archisteamfarm) [![GitHub release](https://img.shields.io/github/release/JustArchi/ArchiSteamFarm.svg)](https://github.com/JustArchi/ArchiSteamFarm/releases/latest) [![Github All Releases](https://img.shields.io/github/downloads/JustArchi/ArchiSteamFarm/total.svg)](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) [![Steam donate](https://img.shields.io/badge/steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
[![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)
[![Steam Donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)
---

66
cc.sh Executable file
View File

@@ -0,0 +1,66 @@
#!/bin/bash
set -eu
BUILD="Release"
CLEAN=0
MONO_ARGS=("--aot" "--llvm" "--server" "-O=all")
XBUILD_ARGS=("/nologo")
BINARIES=("ArchiSteamFarm/bin/Release/ArchiSteamFarm.exe")
SOLUTION="ArchiSteamFarm.sln"
PRINT_USAGE() {
echo "Usage: $0 [--clean] [debug/release]"
exit 1
}
for ARG in "$@"; do
case "$ARG" in
release|Release) BUILD="Release" ;;
debug|Debug) BUILD="Debug" ;;
--clean) CLEAN=1 ;;
*) PRINT_USAGE
esac
done
XBUILD_ARGS+=("/p:Configuration=$BUILD")
cd "$(dirname "$(readlink -f "$0")")"
if [[ -d ".git" ]] && hash git &>/dev/null; then
git pull
fi
if [[ ! -f "$SOLUTION" ]]; then
echo "ERROR: $SOLUTION could not be found!"
exit 1
fi
if hash nuget &>/dev/null; then
nuget restore "$SOLUTION"
fi
if [[ "$CLEAN" -eq 1 ]]; then
rm -rf out
xbuild "${XBUILD_ARGS[@]}" "/t:Clean" "$SOLUTION"
fi
xbuild "${XBUILD_ARGS[@]}" "$SOLUTION"
if [[ ! -f "${BINARIES[0]}" ]]; then
echo "ERROR: ${BINARIES[0]} binary could not be found!"
fi
# If it's release build, use Mono AOT for output binaries
if [[ "$BUILD" = "Release" ]]; then
for BINARY in "${BINARIES[@]}"; do
if [[ ! -f "$BINARY" ]]; then
continue
fi
mono "${MONO_ARGS[@]}" "$BINARY"
done
fi
echo
echo "Compilation finished successfully! :)"

28
run.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
set -eu
BUILD="Release"
MONO_ARGS=("--llvm" "--server" "-O=all")
PRINT_USAGE() {
echo "Usage: $0 [debug/release]"
exit 1
}
for ARG in "$@"; do
case "$ARG" in
release|Release) BUILD="Release" ;;
debug|Debug) BUILD="Debug" ;;
*) PRINT_USAGE
esac
done
BINARY="ArchiSteamFarm/bin/$BUILD/ArchiSteamFarm.exe"
if [[ ! -f "$BINARY" ]]; then
echo "ERROR: $BINARY could not be found!"
exit 1
fi
mono "${MONO_ARGS[@]}" "$BINARY"

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tools/ILRepack/ILRepack.exe Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

17
tools/NetHook2/README.md Normal file
View File

@@ -0,0 +1,17 @@
NetHook2
===================
This tool is used for reverse-engineering of Steam client. It's capable of hooking and recording network traffic sent/received by the client. If you're not trying to implement missing SK2 functionality in ASF, then please do not proceed.
1. Launch Steam client
2. Execute ```hook.bat```
3. Reproduce the functionality you're trying to add
4. Execute ```unhook.bat```
5. Use ```NetHookAnalyzer2.exe``` for analyzing recorded log (which can be found in your Steam directory)
- Source of the ```NetHook2.dll``` can be found **[here](https://github.com/SteamRE/SteamKit/tree/master/Resources/NetHook2)**
- Source of the ```NetHookAnalyzer2.exe``` can be found **[here](https://github.com/SteamRE/SteamKit/tree/master/Resources/NetHookAnalyzer2)**
===================
There is absolutely no guarantee that this will even work for you, not to mention the consequences from hooking the external DLL into steam client. You're on your own.