Compare commits

...

56 Commits

Author SHA1 Message Date
JustArchi
a062e98f2d crowdin-cli 2.0.18 2017-08-14 18:00:35 +02:00
JustArchi
8b8671c679 Use TCP by default
We'll play it safe, websocket/udp is not fully tested yet.
2017-08-14 17:58:30 +02:00
JustArchi
5e4f6a7926 Revert "Test"
This reverts commit 57b2c8f1cf.
2017-08-14 06:24:27 +02:00
JustArchi
57b2c8f1cf Test 2017-08-14 06:17:57 +02:00
JustArchi
02e7f2144f Misc 2017-08-14 05:32:22 +02:00
JustArchi
c13fd10bd8 Implement BackgroundGCPeriod
Tough decision to make, but I promised going for full performance, so there is that.
2017-08-14 05:31:01 +02:00
JustArchi
7fe0b4499b Misc 2017-08-12 20:17:00 +02:00
JustArchi
94160bdc42 Packages update 2017-08-12 01:22:52 +02:00
JustArchi
c01c0f7cd1 General cleanup of #609 2017-08-12 01:16:44 +02:00
Łukasz Domeradzki
65b06ae3b9 Merge pull request #609 from KlappPc/master
Transfer command (easy version, flexible loot)
2017-08-12 01:02:41 +02:00
Florian Lang
5d443f3ed2 simplified 2017-08-12 00:45:05 +02:00
Florian Lang
6a6c903d7d tradetoken 2017-08-12 00:31:51 +02:00
Florian Lang
52f5ef2a39 misc 2017-08-12 00:19:08 +02:00
Florian Lang
b92a4ea505 switched to tab as intend 2017-08-11 22:51:27 +02:00
Florian Lang
8ae9db3a34 transfer command easy version 2017-08-11 22:36:03 +02:00
JustArchi
388c72052c Misc optimization
I had to miss this one.
2017-08-10 00:05:00 +02:00
JustArchi
e965899395 Misc 2017-08-09 23:08:46 +02:00
JustArchi
5605e04f0a Properly deal with #607, #586 2017-08-09 18:58:51 +02:00
JustArchi
953103719c Misc 2017-08-09 00:18:38 +02:00
JustArchi
ea53fd0a87 Packages update 2017-08-09 00:12:50 +02:00
JustArchi
f6770ea1c9 Misc 2017-08-09 00:12:24 +02:00
JustArchi
561b2a3566 Bump 2017-08-07 17:42:39 +02:00
JustArchi
d24e6d0302 Packages update 2017-08-07 16:42:03 +02:00
JustArchi
98491a4562 Misc 2017-08-07 16:41:24 +02:00
JustArchi
08d7b9deb0 Misc 2017-08-06 22:09:19 +02:00
JustArchi
b904b1062f Misc 2017-08-05 23:08:40 +02:00
JustArchi
ad187c0d88 Closes #607 2017-08-05 22:35:03 +02:00
JustArchi
6a035f4832 Standarize IPC response, #586 2017-08-05 17:14:31 +02:00
JustArchi
021e8d2ad9 Don't move NLog.config during update, #586 2017-08-05 15:13:06 +02:00
JustArchi
61609574d0 Bump 2017-08-05 12:55:08 +02:00
JustArchi
7b7f1518a6 Translations update 2017-08-04 19:41:51 +02:00
JustArchi
6aa5a633e4 async/dispose code review 2017-08-04 19:26:37 +02:00
JustArchi
6f80ee9faa Misc 2017-08-04 18:27:30 +02:00
JustArchi
99e8df318c Free the main thread
Now possible with C# 7.1
2017-08-04 18:22:49 +02:00
JustArchi
2ea334c62e Improve farming reset logic #586 2017-08-03 12:14:47 +02:00
JustArchi
27d0b7427a Packages update 2017-08-02 23:09:13 +02:00
JustArchi
62c20c331e Bump 2017-08-02 20:53:44 +02:00
JustArchi
51d59f0f66 Fix Windows builds 2017-08-02 20:47:53 +02:00
JustArchi
ab90c9dc68 Bump 2017-08-02 19:59:49 +02:00
JustArchi
f7c8b871b3 Translations update 2017-08-02 19:47:23 +02:00
JustArchi
678b32f318 Remove debug, issue fixed 2017-08-02 19:44:55 +02:00
JustArchi
62221fd6b8 Test fix 2017-08-02 19:36:43 +02:00
JustArchi
f932be1395 Keep original dotnet permissions
Dotnet uses rwx-rw-rw
2017-08-02 19:00:27 +02:00
JustArchi
d8fd1035c3 Handle chmod +x for updates #586 2017-08-02 15:03:22 +02:00
JustArchi
09e8a52811 Packages update + config defaults 2017-08-02 14:32:01 +02:00
JustArchi
0b1032199b Misc 2017-08-01 20:50:41 +02:00
JustArchi
3e5dfb3174 Bump 2017-08-01 19:14:51 +02:00
JustArchi
4ada8595bd Translations update 2017-08-01 19:14:20 +02:00
JustArchi
bc64c43748 Misc 2017-08-01 19:04:55 +02:00
JustArchi
f4f7935d4c Add a workaround for invalid scheme redirections
https://github.com/JustArchi/ArchiSteamFarm/issues/586#issuecomment-319422864
2017-08-01 18:59:10 +02:00
JustArchi
aacc0a1720 Bump 2017-08-01 13:23:57 +02:00
JustArchi
e5ee909b96 Remove debug leftovers 2017-08-01 13:23:19 +02:00
JustArchi
f3f444d0bd Test fix #605 2017-08-01 13:00:54 +02:00
JustArchi
43d18b6d49 Move debug to appropriate place 2017-08-01 12:44:22 +02:00
JustArchi
f24be67c8c Bunch of stuff for #605 2017-08-01 12:41:57 +02:00
JustArchi
6ce4f2941b Bump 2017-07-31 17:51:04 +02:00
27 changed files with 652 additions and 357 deletions

View File

@@ -24,9 +24,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170727-01" />
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0-beta" />
<PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170810-02" />
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0-beta3" />
<PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta3" />
</ItemGroup>
<ItemGroup>

View File

@@ -210,6 +210,13 @@ namespace ArchiSteamFarm {
return;
}
if (IsUnixVersion(version)) {
string executable = Path.Combine(targetDirectory, SharedInfo.AssemblyName);
if (File.Exists(executable)) {
OS.UnixSetFileAccessExecutable(executable);
}
}
ArchiLogger.LogGenericInfo(Strings.UpdateFinished);
await RestartOrExit().ConfigureAwait(false);
}
@@ -268,6 +275,22 @@ namespace ArchiSteamFarm {
Bot.RegisterBot(botName);
}
private static bool IsUnixVersion(string version) {
if (string.IsNullOrEmpty(version)) {
ArchiLogger.LogNullError(nameof(version));
return false;
}
switch (version) {
case "linux-arm":
case "linux-x64":
case "osx-x64":
return true;
default:
return false;
}
}
private static bool IsValidBotName(string botName) {
if (string.IsNullOrEmpty(botName)) {
ArchiLogger.LogNullError(nameof(botName));
@@ -459,7 +482,14 @@ namespace ArchiSteamFarm {
// Move top-level runtime in-use files to other directory
// We must do it in order to not crash at later stage - all libraries/executables must keep original names
foreach (string file in Directory.EnumerateFiles(targetDirectory)) {
string target = Path.Combine(backupDirectory, Path.GetFileName(file));
string fileName = Path.GetFileName(file);
switch (fileName) {
// Files that we want to keep in original directory
case "NLog.config":
continue;
}
string target = Path.Combine(backupDirectory, fileName);
File.Move(file, target);
}

View File

@@ -3,8 +3,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyVersion>3.0.0.7</AssemblyVersion>
<FileVersion>3.0.0.7</FileVersion>
<AssemblyVersion>3.0.1.4</AssemblyVersion>
<FileVersion>3.0.1.4</FileVersion>
<LangVersion>latest</LangVersion>
<ErrorReport>none</ErrorReport>
<ApplicationIcon>ASF.ico</ApplicationIcon>
@@ -18,6 +18,8 @@
<RepositoryUrl>https://github.com/JustArchi/ArchiSteamFarm.git</RepositoryUrl>
<PackageIconUrl>https://github.com/JustArchi/ArchiSteamFarm/raw/master/resources/ASF.ico</PackageIconUrl>
<RepositoryType>Git</RepositoryType>
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -26,12 +28,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta3" />
<PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta6" />
<PackageReference Include="Humanizer" Version="2.2.0" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0-beta1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="NLog" Version="5.0.0-beta09" />
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha5" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0-preview2-25405-01" />
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha6" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -58,15 +58,15 @@ namespace ArchiSteamFarm {
private const string SteamStoreURL = "http://" + SteamStoreHost;
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1, 1);
private static int Timeout = GlobalConfig.DefaultConnectionTimeout * 1000; // This must be int type
private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1, 1);
private readonly Bot Bot;
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1, 1);
private readonly WebBrowser WebBrowser;
private string CachedApiKey;
@@ -86,6 +86,7 @@ namespace ArchiSteamFarm {
PublicInventorySemaphore.Dispose();
SessionSemaphore.Dispose();
TradeTokenSemaphore.Dispose();
WebBrowser.Dispose();
}
internal async Task<bool> AcceptTradeOffer(ulong tradeID) {
@@ -482,7 +483,7 @@ namespace ArchiSteamFarm {
}
[SuppressMessage("ReSharper", "FunctionComplexityOverflow")]
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool tradable, HashSet<Steam.Item.EType> wantedTypes, HashSet<uint> wantedRealAppIDs = null) {
internal async Task<HashSet<Steam.Item>> GetMySteamInventory(bool trading, HashSet<Steam.Item.EType> wantedTypes, HashSet<uint> wantedRealAppIDs = null) {
if ((wantedTypes == null) || (wantedTypes.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(wantedTypes));
return null;
@@ -494,7 +495,7 @@ namespace ArchiSteamFarm {
HashSet<Steam.Item> result = new HashSet<Steam.Item>();
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamCommunityContextID + "?l=english&trading=" + (tradable ? "1" : "0") + "&start=";
string request = SteamCommunityURL + "/my/inventory/json/" + Steam.Item.SteamAppID + "/" + Steam.Item.SteamCommunityContextID + "?l=english" + (trading ? "&trading=1" : "") + "&start=";
uint currentPage = 0;
await InventorySemaphore.WaitAsync().ConfigureAwait(false);

View File

@@ -50,12 +50,11 @@ namespace ArchiSteamFarm {
private const ushort MaxSteamMessageLength = 2048;
private const byte MaxTwoFactorCodeFailures = 3;
private const byte MinHeartBeatTTL = GlobalConfig.DefaultConnectionTimeout; // Assume client is responsive for at least that amount of seconds
private const byte PICSCooldownInMiliseconds = 200; // We might need to tune this further
internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1);
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1);
private static readonly SteamConfiguration SteamConfiguration = new SteamConfiguration();
internal readonly ArchiLogger ArchiLogger;
@@ -74,16 +73,16 @@ namespace ArchiSteamFarm {
private readonly BotDatabase BotDatabase;
private readonly string BotName;
private readonly CallbackManager CallbackManager;
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1, 1);
[JsonProperty]
private readonly CardsFarmer CardsFarmer;
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
private readonly Timer HeartBeatTimer;
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1, 1);
private readonly Statistics Statistics;
private readonly SteamApps SteamApps;
private readonly SteamClient SteamClient;
@@ -232,22 +231,24 @@ namespace ArchiSteamFarm {
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
ArchiWebHandler.Dispose();
CallbackSemaphore.Dispose();
CardsFarmer.Dispose();
HeartBeatTimer.Dispose();
InitializationSemaphore.Dispose();
LootingSemaphore.Dispose();
PICSSemaphore.Dispose();
Trading.Dispose();
// Those are objects that might be null and the check should be in-place
ArchiWebHandler?.Dispose();
BotDatabase?.Dispose();
CardsFarmer?.Dispose();
CardsFarmerResumeTimer?.Dispose();
ConnectionFailureTimer?.Dispose();
FamilySharingInactivityTimer?.Dispose();
HeartBeatTimer?.Dispose();
PlayingWasBlockedTimer?.Dispose();
SendItemsTimer?.Dispose();
Statistics?.Dispose();
SteamSaleEvent?.Dispose();
Trading?.Dispose();
}
internal async Task<bool> AcceptConfirmations(bool accept, Steam.ConfirmationDetails.EType acceptedType = Steam.ConfirmationDetails.EType.Unknown, ulong acceptedSteamID = 0, HashSet<ulong> acceptedTradeIDs = null) {
@@ -358,13 +359,10 @@ namespace ArchiSteamFarm {
try {
productInfoResultSet = await SteamApps.PICSGetProductInfo(appID, null, false);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericWarningException(e);
return (0, DateTime.MinValue);
} finally {
Task.Run(async () => {
await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false);
PICSSemaphore.Release();
}).Forget();
PICSSemaphore.Release();
}
// ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619
@@ -468,13 +466,10 @@ namespace ArchiSteamFarm {
try {
productInfoResultSet = await SteamApps.PICSGetProductInfo(Enumerable.Empty<uint>(), packageIDs);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericWarningException(e);
return null;
} finally {
Task.Run(async () => {
await Task.Delay(PICSCooldownInMiliseconds).ConfigureAwait(false);
PICSSemaphore.Release();
}).Forget();
PICSSemaphore.Release();
}
Dictionary<uint, HashSet<uint>> result = new Dictionary<uint, HashSet<uint>>();
@@ -674,7 +669,7 @@ namespace ArchiSteamFarm {
try {
callback = await SteamUser.RequestWebAPIUserNonce();
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericWarningException(e);
await Connect(true).ConfigureAwait(false);
return false;
}
@@ -810,13 +805,13 @@ namespace ArchiSteamFarm {
return await ResponseBlacklistAdd(steamID, args[1], args[2]).ConfigureAwait(false);
}
return ResponseBlacklistAdd(steamID, args[1]);
return await ResponseBlacklistAdd(steamID, args[1]).ConfigureAwait(false);
case "!BLRM":
if (args.Length > 2) {
return await ResponseBlacklistRemove(steamID, args[1], args[2]).ConfigureAwait(false);
}
return ResponseBlacklistRemove(steamID, args[1]);
return await ResponseBlacklistRemove(steamID, args[1]).ConfigureAwait(false);
case "!FARM":
return await ResponseFarm(steamID, args[1]).ConfigureAwait(false);
case "!INPUT":
@@ -832,13 +827,13 @@ namespace ArchiSteamFarm {
return await ResponseIdleQueueAdd(steamID, args[1], args[2]).ConfigureAwait(false);
}
return ResponseIdleQueueAdd(steamID, args[1]);
return await ResponseIdleQueueAdd(steamID, args[1]).ConfigureAwait(false);
case "!IQRM":
if (args.Length > 2) {
return await ResponseIdleQueueRemove(steamID, args[1], args[2]).ConfigureAwait(false);
}
return ResponseIdleQueueRemove(steamID, args[1]);
return await ResponseIdleQueueRemove(steamID, args[1]).ConfigureAwait(false);
case "!LOOT":
return await ResponseLoot(steamID, args[1]).ConfigureAwait(false);
case "!LOOT^":
@@ -903,6 +898,16 @@ namespace ArchiSteamFarm {
return await ResponseStatus(steamID, args[1]).ConfigureAwait(false);
case "!STOP":
return await ResponseStop(steamID, args[1]).ConfigureAwait(false);
case "!TRANSFER":
if (args.Length > 3) {
return await ResponseTransfer(steamID, args[1], args[2], args[3]).ConfigureAwait(false);
}
if (args.Length > 2) {
return await ResponseTransfer(steamID, args[1], args[2]).ConfigureAwait(false);
}
return ResponseUnknown(steamID);
case "!UNPACK":
return await ResponseUnpackBoosters(steamID, args[1]).ConfigureAwait(false);
default:
@@ -1141,7 +1146,7 @@ namespace ArchiSteamFarm {
try {
if (DateTime.UtcNow.Subtract(ArchiHandler.LastPacketReceived).TotalSeconds > MinHeartBeatTTL) {
await SteamApps.PICSGetProductInfo(0, null);
await SteamFriends.RequestProfileInfo(SteamClient.SteamID);
}
HeartBeatFailures = 0;
@@ -1163,7 +1168,7 @@ namespace ArchiSteamFarm {
}
}
private void ImportAuthenticator(string maFilePath) {
private async Task ImportAuthenticator(string maFilePath) {
if (HasMobileAuthenticator || !File.Exists(maFilePath)) {
return;
}
@@ -1171,7 +1176,8 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorConverting);
try {
BotDatabase.MobileAuthenticator = JsonConvert.DeserializeObject<MobileAuthenticator>(File.ReadAllText(maFilePath));
MobileAuthenticator authenticator = JsonConvert.DeserializeObject<MobileAuthenticator>(File.ReadAllText(maFilePath));
await BotDatabase.SetMobileAuthenticator(authenticator).ConfigureAwait(false);
File.Delete(maFilePath);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
@@ -1190,14 +1196,14 @@ namespace ArchiSteamFarm {
if (string.IsNullOrEmpty(DeviceID)) {
string deviceID = Program.GetUserInput(ASF.EUserInputType.DeviceID, BotName);
if (string.IsNullOrEmpty(deviceID)) {
BotDatabase.MobileAuthenticator = null;
await BotDatabase.SetMobileAuthenticator().ConfigureAwait(false);
return;
}
SetUserInput(ASF.EUserInputType.DeviceID, deviceID);
}
BotDatabase.CorrectMobileAuthenticatorDeviceID(DeviceID);
await BotDatabase.CorrectMobileAuthenticatorDeviceID(DeviceID).ConfigureAwait(false);
}
ArchiLogger.LogGenericInfo(Strings.BotAuthenticatorImportFinished);
@@ -1283,7 +1289,7 @@ namespace ArchiSteamFarm {
return;
}
ArchiLogger.LogGenericError(Strings.BotHeartBeatFailed);
ArchiLogger.LogGenericWarning(Strings.BotHeartBeatFailed);
Destroy(true);
RegisterBot(BotName);
}
@@ -1433,7 +1439,7 @@ namespace ArchiSteamFarm {
try {
await SteamFriends.SetPersonaState(EPersonaState.Online);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericWarningException(e);
}
}
@@ -1591,7 +1597,7 @@ namespace ArchiSteamFarm {
goto case EResult.RateLimitExceeded;
}
BotDatabase.LoginKey = null;
await BotDatabase.SetLoginKey().ConfigureAwait(false);
ArchiLogger.LogGenericInfo(Strings.BotRemovedExpiredLoginKey);
break;
case EResult.NoConnection:
@@ -1828,15 +1834,15 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericWarning(Strings.BotAccountLocked);
}
if (callback.CellID != 0) {
Program.GlobalDatabase.CellID = callback.CellID;
if ((callback.CellID != 0) && (callback.CellID != Program.GlobalDatabase.CellID)) {
await Program.GlobalDatabase.SetCellID(callback.CellID).ConfigureAwait(false);
}
if (!HasMobileAuthenticator) {
// Support and convert 2FA files
string maFilePath = Path.Combine(SharedInfo.ConfigDirectory, callback.ClientSteamID.ConvertToUInt64() + ".maFile");
if (File.Exists(maFilePath)) {
ImportAuthenticator(maFilePath);
await ImportAuthenticator(maFilePath).ConfigureAwait(false);
}
}
@@ -1903,7 +1909,7 @@ namespace ArchiSteamFarm {
}
}
private void OnLoginKey(SteamUser.LoginKeyCallback callback) {
private async void OnLoginKey(SteamUser.LoginKeyCallback callback) {
if (string.IsNullOrEmpty(callback?.LoginKey)) {
ArchiLogger.LogNullError(nameof(callback) + " || " + nameof(callback.LoginKey));
return;
@@ -1914,7 +1920,7 @@ namespace ArchiSteamFarm {
loginKey = CryptoHelper.Encrypt(BotConfig.PasswordFormat, loginKey);
}
BotDatabase.LoginKey = loginKey;
await BotDatabase.SetLoginKey(loginKey).ConfigureAwait(false);
SteamUser.AcceptNewLoginKey(callback);
}
@@ -2210,7 +2216,7 @@ namespace ArchiSteamFarm {
try {
callback = await SteamApps.RequestFreeLicense(gameID);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericWarningException(e);
response.Append(FormatBotResponse(string.Format(Strings.BotAddLicense, gameID, EResult.Timeout)));
break;
}
@@ -2313,7 +2319,7 @@ namespace ArchiSteamFarm {
foreach (string flag in flags) {
switch (flag.ToUpperInvariant()) {
case "FD":
redeemFlags |= ERedeemFlags.ForceDistribution;
redeemFlags |= ERedeemFlags.ForceDistributing;
break;
case "FF":
redeemFlags |= ERedeemFlags.ForceForwarding;
@@ -2322,7 +2328,7 @@ namespace ArchiSteamFarm {
redeemFlags |= ERedeemFlags.ForceKeepMissingGames;
break;
case "SD":
redeemFlags |= ERedeemFlags.SkipDistribution;
redeemFlags |= ERedeemFlags.SkipDistributing;
break;
case "SF":
redeemFlags |= ERedeemFlags.SkipForwarding;
@@ -2438,7 +2444,7 @@ namespace ArchiSteamFarm {
return null;
}
private string ResponseBlacklistAdd(ulong steamID, string targetsText) {
private async Task<string> ResponseBlacklistAdd(ulong steamID, string targetsText) {
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
return null;
@@ -2463,7 +2469,7 @@ namespace ArchiSteamFarm {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
}
BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs);
await BotDatabase.AddBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
return FormatBotResponse(Strings.Done);
}
@@ -2478,7 +2484,7 @@ namespace ArchiSteamFarm {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistAdd(steamID, targetsText)));
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseBlacklistAdd(steamID, targetsText));
ICollection<string> results;
switch (Program.GlobalConfig.OptimizationMode) {
@@ -2509,7 +2515,7 @@ namespace ArchiSteamFarm {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseBlacklistRemove(steamID, targetsText)));
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseBlacklistRemove(steamID, targetsText));
ICollection<string> results;
switch (Program.GlobalConfig.OptimizationMode) {
@@ -2529,7 +2535,7 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join("", responses) : null;
}
private string ResponseBlacklistRemove(ulong steamID, string targetsText) {
private async Task<string> ResponseBlacklistRemove(ulong steamID, string targetsText) {
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
return null;
@@ -2554,7 +2560,7 @@ namespace ArchiSteamFarm {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(targetIDs)));
}
BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs);
await BotDatabase.RemoveBlacklistedFromTradesSteamIDs(targetIDs).ConfigureAwait(false);
return FormatBotResponse(Strings.Done);
}
@@ -2678,7 +2684,7 @@ namespace ArchiSteamFarm {
return result;
}
private string ResponseIdleQueueAdd(ulong steamID, string targetsText) {
private async Task<string> ResponseIdleQueueAdd(ulong steamID, string targetsText) {
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
return null;
@@ -2703,7 +2709,7 @@ namespace ArchiSteamFarm {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
}
BotDatabase.AddIdlingPriorityAppIDs(appIDs);
await BotDatabase.AddIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
return FormatBotResponse(Strings.Done);
}
@@ -2718,7 +2724,7 @@ namespace ArchiSteamFarm {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueAdd(steamID, targetsText)));
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleQueueAdd(steamID, targetsText));
ICollection<string> results;
switch (Program.GlobalConfig.OptimizationMode) {
@@ -2749,7 +2755,7 @@ namespace ArchiSteamFarm {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IEnumerable<Task<string>> tasks = bots.Select(bot => Task.Run(() => bot.ResponseIdleQueueRemove(steamID, targetsText)));
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseIdleQueueRemove(steamID, targetsText));
ICollection<string> results;
switch (Program.GlobalConfig.OptimizationMode) {
@@ -2769,7 +2775,7 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join("", responses) : null;
}
private string ResponseIdleQueueRemove(ulong steamID, string targetsText) {
private async Task<string> ResponseIdleQueueRemove(ulong steamID, string targetsText) {
if ((steamID == 0) || string.IsNullOrEmpty(targetsText)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(targetsText));
return null;
@@ -2794,7 +2800,7 @@ namespace ArchiSteamFarm {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(appIDs)));
}
BotDatabase.RemoveIdlingPriorityAppIDs(appIDs);
await BotDatabase.RemoveIdlingPriorityAppIDs(appIDs).ConfigureAwait(false);
return FormatBotResponse(Strings.Done);
}
@@ -3011,7 +3017,7 @@ namespace ArchiSteamFarm {
try {
result = await SteamFriends.SetPersonaName(nickname);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
ArchiLogger.LogGenericWarningException(e);
return FormatBotResponse(Strings.WarningFailed);
}
@@ -3403,7 +3409,7 @@ namespace ArchiSteamFarm {
}
bool forward = !redeemFlags.HasFlag(ERedeemFlags.SkipForwarding) && (redeemFlags.HasFlag(ERedeemFlags.ForceForwarding) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Forwarding));
bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistribution) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistribution) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing));
bool distribute = !redeemFlags.HasFlag(ERedeemFlags.SkipDistributing) && (redeemFlags.HasFlag(ERedeemFlags.ForceDistributing) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.Distributing));
bool keepMissingGames = !redeemFlags.HasFlag(ERedeemFlags.SkipKeepMissingGames) && (redeemFlags.HasFlag(ERedeemFlags.ForceKeepMissingGames) || BotConfig.RedeemingPreferences.HasFlag(BotConfig.ERedeemingPreferences.KeepMissingGames));
string[] keysList = keys.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
@@ -3916,6 +3922,153 @@ namespace ArchiSteamFarm {
return responses.Count > 0 ? string.Join("", responses) : null;
}
private async Task<string> ResponseTransfer(ulong steamID, string mode, string botNameTo) {
if ((steamID == 0) || string.IsNullOrEmpty(botNameTo) || string.IsNullOrEmpty(mode)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(mode) + " || " + nameof(botNameTo));
return null;
}
if (!IsMaster(steamID)) {
return null;
}
if (!IsConnectedAndLoggedOn) {
return FormatBotResponse(Strings.BotNotConnected);
}
if (!LootingAllowed) {
return FormatBotResponse(Strings.BotLootingTemporarilyDisabled);
}
if (!Bots.TryGetValue(botNameTo, out Bot targetBot)) {
return IsOwner(steamID) ? FormatBotResponse(string.Format(Strings.BotNotFound, botNameTo)) : null;
}
if (targetBot.SteamID == 0) {
return FormatBotResponse(Strings.BotNotConnected);
}
if (targetBot.SteamID == SteamID) {
return FormatBotResponse(Strings.BotLootingYourself);
}
HashSet<Steam.Item.EType> transferTypes = new HashSet<Steam.Item.EType>();
string[] modes = mode.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string singleMode in modes) {
switch (singleMode.ToUpper()) {
case "A":
case "ALL":
foreach (Steam.Item.EType type in Enum.GetValues(typeof(Steam.Item.EType))) {
transferTypes.Add(type);
}
break;
case "BG":
case "BACKGROUND":
transferTypes.Add(Steam.Item.EType.ProfileBackground);
break;
case "BO":
case "BOOSTER":
transferTypes.Add(Steam.Item.EType.BoosterPack);
break;
case "C":
case "CARD":
transferTypes.Add(Steam.Item.EType.TradingCard);
break;
case "E":
case "EMOTICON":
transferTypes.Add(Steam.Item.EType.Emoticon);
break;
case "F":
case "FOIL":
transferTypes.Add(Steam.Item.EType.FoilTradingCard);
break;
case "G":
case "GEMS":
transferTypes.Add(Steam.Item.EType.SteamGems);
break;
case "U":
case "UNKNOWN":
transferTypes.Add(Steam.Item.EType.Unknown);
break;
default:
return FormatBotResponse(string.Format(Strings.ErrorIsInvalid, mode));
}
}
if (!LootingSemaphore.Wait(0)) {
return FormatBotResponse(Strings.BotLootingFailed);
}
try {
HashSet<Steam.Item> inventory = await ArchiWebHandler.GetMySteamInventory(true, transferTypes).ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) {
return FormatBotResponse(string.Format(Strings.ErrorIsEmpty, nameof(inventory)));
}
string tradeToken = null;
if (SteamFriends.GetFriendRelationship(targetBot.SteamID) != EFriendRelationship.Friend) {
tradeToken = await targetBot.ArchiWebHandler.GetTradeToken().ConfigureAwait(false);
if (string.IsNullOrEmpty(tradeToken)) {
return FormatBotResponse(Strings.BotLootingFailed);
}
}
if (!await ArchiWebHandler.MarkSentTrades().ConfigureAwait(false)) {
return FormatBotResponse(Strings.BotLootingFailed);
}
if (!await ArchiWebHandler.SendTradeOffer(inventory, targetBot.SteamID, tradeToken).ConfigureAwait(false)) {
return FormatBotResponse(Strings.BotLootingFailed);
}
if (HasMobileAuthenticator) {
// Give Steam network some time to generate confirmations
await Task.Delay(3000).ConfigureAwait(false);
if (!await AcceptConfirmations(true, Steam.ConfirmationDetails.EType.Trade, targetBot.SteamID).ConfigureAwait(false)) {
return FormatBotResponse(Strings.BotLootingFailed);
}
}
} finally {
LootingSemaphore.Release();
}
return FormatBotResponse(Strings.BotLootingSuccess);
}
private static async Task<string> ResponseTransfer(ulong steamID, string botNames, string mode, string botNameTo) {
if ((steamID == 0) || string.IsNullOrEmpty(botNames) || string.IsNullOrEmpty(mode) || string.IsNullOrEmpty(botNameTo)) {
ASF.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(botNames) + " || " + nameof(mode) + " || " + nameof(botNameTo));
return null;
}
HashSet<Bot> bots = GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return IsOwner(steamID) ? FormatStaticResponse(string.Format(Strings.BotNotFound, botNames)) : null;
}
IEnumerable<Task<string>> tasks = bots.Select(bot => bot.ResponseTransfer(steamID, mode, botNameTo));
ICollection<string> results;
switch (Program.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
results = new List<string>(bots.Count);
foreach (Task<string> task in tasks) {
results.Add(await task.ConfigureAwait(false));
}
break;
default:
results = await Task.WhenAll(tasks).ConfigureAwait(false);
break;
}
List<string> responses = new List<string>(results.Where(result => !string.IsNullOrEmpty(result)));
return responses.Count > 0 ? string.Join("", responses) : null;
}
private string ResponseUnknown(ulong steamID) {
if (steamID != 0) {
return IsOperator(steamID) ? FormatBotResponse(Strings.UnknownCommand) : null;
@@ -4111,7 +4264,7 @@ namespace ArchiSteamFarm {
if (!HasMobileAuthenticator) {
string maFilePath = BotPath + ".maFile";
if (File.Exists(maFilePath)) {
ImportAuthenticator(maFilePath);
await ImportAuthenticator(maFilePath).ConfigureAwait(false);
}
}
@@ -4164,8 +4317,8 @@ namespace ArchiSteamFarm {
Validate = 1,
ForceForwarding = 2,
SkipForwarding = 4,
ForceDistribution = 8,
SkipDistribution = 16,
ForceDistributing = 8,
SkipDistributing = 16,
SkipInitial = 32,
ForceKeepMissingGames = 64,
SkipKeepMissingGames = 128

View File

@@ -172,6 +172,11 @@ namespace ArchiSteamFarm {
// This constructor is used only by deserializer
private BotConfig() { }
// Functions below are used for skipping serialization of sensitive fields in API response
public bool ShouldSerializeSteamLogin() => false;
public bool ShouldSerializeSteamParentalPIN() => false;
public bool ShouldSerializeSteamPassword() => false;
internal static BotConfig Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));

View File

@@ -26,49 +26,25 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal sealed class BotDatabase {
internal sealed class BotDatabase : IDisposable {
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<ulong> BlacklistedFromTradesSteamIDs = new ConcurrentHashSet<ulong>();
private readonly object FileLock = new object();
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<uint> IdlingPriorityAppIDs = new ConcurrentHashSet<uint>();
internal string LoginKey {
get => _LoginKey;
[JsonProperty(PropertyName = "_LoginKey")]
internal string LoginKey { get; private set; }
set {
if (_LoginKey == value) {
return;
}
_LoginKey = value;
Save();
}
}
internal MobileAuthenticator MobileAuthenticator {
get => _MobileAuthenticator;
set {
if (_MobileAuthenticator == value) {
return;
}
_MobileAuthenticator = value;
Save();
}
}
[JsonProperty]
private string _LoginKey;
[JsonProperty]
private MobileAuthenticator _MobileAuthenticator;
[JsonProperty(PropertyName = "_MobileAuthenticator")]
internal MobileAuthenticator MobileAuthenticator { get; private set; }
private string FilePath;
@@ -79,43 +55,51 @@ namespace ArchiSteamFarm {
}
FilePath = filePath;
Save();
Save().Wait();
}
// This constructor is used only by deserializer
[SuppressMessage("ReSharper", "UnusedMember.Local")]
private BotDatabase() { }
internal void AddBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
FileSemaphore.Dispose();
// Those are objects that might be null and the check should be in-place
MobileAuthenticator?.Dispose();
}
internal async Task AddBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
if ((steamIDs == null) || (steamIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
return;
}
if (BlacklistedFromTradesSteamIDs.AddRange(steamIDs)) {
Save();
await Save().ConfigureAwait(false);
}
}
internal void AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
internal async Task AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
}
if (IdlingPriorityAppIDs.AddRange(appIDs)) {
Save();
await Save().ConfigureAwait(false);
}
}
internal void CorrectMobileAuthenticatorDeviceID(string deviceID) {
internal async Task CorrectMobileAuthenticatorDeviceID(string deviceID) {
if (string.IsNullOrEmpty(deviceID) || (MobileAuthenticator == null)) {
ASF.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(MobileAuthenticator));
return;
}
if (MobileAuthenticator.CorrectDeviceID(deviceID)) {
Save();
await Save().ConfigureAwait(false);
}
}
@@ -170,49 +154,69 @@ namespace ArchiSteamFarm {
return botDatabase;
}
internal void RemoveBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
internal async Task RemoveBlacklistedFromTradesSteamIDs(HashSet<ulong> steamIDs) {
if ((steamIDs == null) || (steamIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
return;
}
if (BlacklistedFromTradesSteamIDs.RemoveRange(steamIDs)) {
Save();
await Save().ConfigureAwait(false);
}
}
internal void RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
internal async Task RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
}
if (IdlingPriorityAppIDs.RemoveRange(appIDs)) {
Save();
await Save().ConfigureAwait(false);
}
}
private void Save() {
internal async Task SetLoginKey(string value = null) {
if (value == LoginKey) {
return;
}
LoginKey = value;
await Save().ConfigureAwait(false);
}
internal async Task SetMobileAuthenticator(MobileAuthenticator value = null) {
if (value == MobileAuthenticator) {
return;
}
MobileAuthenticator = value;
await Save().ConfigureAwait(false);
}
private async Task Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json));
return;
}
lock (FileLock) {
string newFilePath = FilePath + ".new";
string newFilePath = FilePath + ".new";
try {
File.WriteAllText(newFilePath, json);
await FileSemaphore.WaitAsync().ConfigureAwait(false);
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
try {
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
} finally {
FileSemaphore.Release();
}
}
}

View File

@@ -58,9 +58,9 @@ namespace ArchiSteamFarm {
);
private readonly Bot Bot;
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1);
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false);
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim FarmingResetSemaphore = new SemaphoreSlim(0, 1);
private readonly Timer IdleFarmingTimer;
[JsonProperty]
@@ -87,8 +87,9 @@ namespace ArchiSteamFarm {
public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception
EventSemaphore.Dispose();
FarmingSemaphore.Dispose();
FarmResetEvent.Dispose();
FarmingInitializationSemaphore.Dispose();
FarmingResetSemaphore.Dispose();
GamesToFarm.Dispose();
// Those are objects that might be null and the check should be in-place
IdleFarmingTimer?.Dispose();
@@ -140,8 +141,19 @@ namespace ArchiSteamFarm {
internal async Task OnNewItemsNotification() {
if (NowFarming) {
FarmResetEvent.Set();
return;
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (NowFarming) {
if (FarmingResetSemaphore.CurrentCount == 0) {
FarmingResetSemaphore.Release();
}
return;
}
} finally {
FarmingInitializationSemaphore.Release();
}
}
// If we're not farming, and we got new items, it's likely to be a booster pack or likewise
@@ -188,7 +200,7 @@ namespace ArchiSteamFarm {
return;
}
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (NowFarming || Paused || !Bot.IsPlayingPossible) {
@@ -231,7 +243,7 @@ namespace ArchiSteamFarm {
KeepFarming = NowFarming = true;
Utilities.StartBackgroundFunction(Farm);
} finally {
FarmingSemaphore.Release();
FarmingInitializationSemaphore.Release();
}
}
@@ -240,7 +252,7 @@ namespace ArchiSteamFarm {
return;
}
await FarmingSemaphore.WaitAsync().ConfigureAwait(false);
await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (!NowFarming) {
@@ -248,7 +260,10 @@ namespace ArchiSteamFarm {
}
KeepFarming = false;
FarmResetEvent.Set();
if (FarmingResetSemaphore.CurrentCount == 0) {
FarmingResetSemaphore.Release();
}
for (byte i = 0; (i < 5) && NowFarming; i++) {
await Task.Delay(1000).ConfigureAwait(false);
@@ -261,7 +276,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(Strings.IdlingStopped);
Bot.OnFarmingStopped();
} finally {
FarmingSemaphore.Release();
FarmingInitializationSemaphore.Release();
}
}
@@ -334,7 +349,7 @@ namespace ArchiSteamFarm {
continue;
}
if (GlobalConfig.GlobalBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
if (GlobalConfig.GamesBlacklist.Contains(appID) || GlobalConfig.SalesBlacklist.Contains(appID) || Program.GlobalConfig.Blacklist.Contains(appID)) {
// We have this appID blacklisted, so skip it
continue;
}
@@ -556,65 +571,68 @@ namespace ArchiSteamFarm {
// If we have restricted card drops, we use complex algorithm
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex"));
while (GamesToFarm.Count > 0) {
HashSet<Game> playableGamesToFarmSolo = new HashSet<Game>();
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) {
if (await IsPlayableGame(game).ConfigureAwait(false)) {
playableGamesToFarmSolo.Add(game);
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump));
foreach (Game game in gamesToCheck) {
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
}
if (await FarmSolo(game).ConfigureAwait(false)) {
continue;
}
NowFarming = false;
return;
}
gamesToCheck = new HashSet<Game>(GamesToFarm.OrderByDescending(game => game.HoursPlayed));
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
foreach (Game game in gamesToCheck) {
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
}
playableGamesToFarmMultiple.Add(game);
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
break;
}
}
if (playableGamesToFarmSolo.Count > 0) {
while (playableGamesToFarmSolo.Count > 0) {
Game playableGame = playableGamesToFarmSolo.First();
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
playableGamesToFarmSolo.Remove(playableGame);
} else {
NowFarming = false;
return;
}
}
if (playableGamesToFarmMultiple.Count == 0) {
break;
}
if (await FarmMultiple(playableGamesToFarmMultiple).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
} else {
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed < HoursToBump).OrderByDescending(game => game.HoursPlayed)) {
if (await IsPlayableGame(game).ConfigureAwait(false)) {
playableGamesToFarmMultiple.Add(game);
}
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
break;
}
}
if (FarmMultiple(playableGamesToFarmMultiple)) {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
} else {
NowFarming = false;
return;
}
NowFarming = false;
return;
}
}
} else {
// If we have unrestricted card drops, we use simple algorithm
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple"));
while (GamesToFarm.Count > 0) {
Game playableGame = null;
foreach (Game game in GamesToFarm) {
HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm);
foreach (Game game in gamesToCheck) {
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
}
playableGame = game;
break;
}
if (playableGame != null) {
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
if (await FarmSolo(game).ConfigureAwait(false)) {
continue;
}
}
NowFarming = false;
return;
NowFarming = false;
return;
}
}
}
} while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault());
@@ -646,8 +664,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName));
DateTime startFarmingPeriod = DateTime.UtcNow;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset();
if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
success = KeepFarming;
}
@@ -665,7 +682,7 @@ namespace ArchiSteamFarm {
return success;
}
private bool FarmHours(ConcurrentHashSet<Game> games) {
private async Task<bool> FarmHours(ConcurrentHashSet<Game> games) {
if ((games == null) || (games.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(games));
return false;
@@ -689,8 +706,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID))));
DateTime startFarmingPeriod = DateTime.UtcNow;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) {
FarmResetEvent.Reset();
if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
success = KeepFarming;
}
@@ -711,7 +727,7 @@ namespace ArchiSteamFarm {
return success;
}
private bool FarmMultiple(IEnumerable<Game> games) {
private async Task<bool> FarmMultiple(IEnumerable<Game> games) {
if (games == null) {
Bot.ArchiLogger.LogNullError(nameof(games));
return false;
@@ -721,7 +737,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdlingList, string.Join(", ", CurrentGamesFarming.Select(game => game.AppID))));
bool result = FarmHours(CurrentGamesFarming);
bool result = await FarmHours(CurrentGamesFarming).ConfigureAwait(false);
CurrentGamesFarming.Clear();
return result;
}

View File

@@ -31,12 +31,12 @@ namespace ArchiSteamFarm {
internal sealed class ConcurrentSortedHashSet<T> : IDisposable, IReadOnlyCollection<T>, ISet<T> {
public int Count {
get {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Count;
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
}
@@ -44,159 +44,159 @@ namespace ArchiSteamFarm {
public bool IsReadOnly => false;
private readonly HashSet<T> BackingCollection = new HashSet<T>();
private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1);
private readonly SemaphoreSlim CollectionSemaphore = new SemaphoreSlim(1, 1);
public bool Add(T item) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Add(item);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void Clear() {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.Clear();
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool Contains(T item) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Contains(item);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void CopyTo(T[] array, int arrayIndex) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.CopyTo(array, arrayIndex);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void Dispose() => SemaphoreSlim.Dispose();
public void Dispose() => CollectionSemaphore.Dispose();
public void ExceptWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.ExceptWith(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, SemaphoreSlim);
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, CollectionSemaphore);
public void IntersectWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.IntersectWith(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool IsProperSubsetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.IsProperSubsetOf(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool IsProperSupersetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.IsProperSupersetOf(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool IsSubsetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.IsSubsetOf(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool IsSupersetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.IsSupersetOf(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool Overlaps(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Overlaps(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool Remove(T item) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.Remove(item);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public bool SetEquals(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
return BackingCollection.SetEquals(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void SymmetricExceptWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.SymmetricExceptWith(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
public void UnionWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.UnionWith(other);
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
@@ -204,7 +204,7 @@ namespace ArchiSteamFarm {
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal void ReplaceWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
CollectionSemaphore.Wait();
try {
BackingCollection.Clear();
@@ -213,7 +213,7 @@ namespace ArchiSteamFarm {
BackingCollection.Add(item);
}
} finally {
SemaphoreSlim.Release();
CollectionSemaphore.Release();
}
}
}

View File

@@ -40,8 +40,8 @@ namespace ArchiSteamFarm {
internal const byte DefaultLoginLimiterDelay = 10;
internal const string UlongStringPrefix = "s_";
// This is hardcoded blacklist which should not be possible to change
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 402590, 425280, 480730, 566020, 639900 };
internal static readonly HashSet<uint> GamesBlacklist = new HashSet<uint> { 402590 }; // Games with broken/unobtainable card drops
internal static readonly HashSet<uint> SalesBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730, 566020, 639900 }; // Steam Summer/Winter sales
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AutoRestart = true;
@@ -49,6 +49,9 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AutoUpdates = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly byte BackgroundGCPeriod;
[SuppressMessage("ReSharper", "CollectionNeverUpdated.Global")]
[JsonProperty(Required = Required.DisallowNull)]
internal readonly HashSet<uint> Blacklist = new HashSet<uint>();
@@ -102,7 +105,7 @@ namespace ArchiSteamFarm {
internal readonly bool Statistics = true;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.All;
internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.Tcp;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;

View File

@@ -42,24 +42,11 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
private readonly object FileLock = new object();
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1);
internal uint CellID {
get => _CellID;
set {
if ((value == 0) || (_CellID == value)) {
return;
}
_CellID = value;
Save();
}
}
[JsonProperty(Required = Required.DisallowNull)]
private uint _CellID;
[JsonProperty(PropertyName = "_CellID", Required = Required.DisallowNull)]
internal uint CellID { get; private set; }
private string FilePath;
@@ -70,13 +57,20 @@ namespace ArchiSteamFarm {
}
FilePath = filePath;
Save();
Save().Wait();
}
// This constructor is used only by deserializer
private GlobalDatabase() => ServerListProvider.ServerListUpdated += OnServerListUpdated;
public void Dispose() => ServerListProvider.ServerListUpdated -= OnServerListUpdated;
public void Dispose() {
// Events we registered
ServerListProvider.ServerListUpdated -= OnServerListUpdated;
// Those are objects that are always being created if constructor doesn't throw exception
FileSemaphore.Dispose();
PackagesRefreshSemaphore.Dispose();
}
internal static GlobalDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
@@ -136,35 +130,46 @@ namespace ArchiSteamFarm {
}
}
Save();
await Save().ConfigureAwait(false);
} finally {
PackagesRefreshSemaphore.Release();
}
}
private void OnServerListUpdated(object sender, EventArgs e) => Save();
internal async Task SetCellID(uint value = 0) {
if (value == CellID) {
return;
}
private void Save() {
CellID = value;
await Save().ConfigureAwait(false);
}
private async void OnServerListUpdated(object sender, EventArgs e) => await Save().ConfigureAwait(false);
private async Task Save() {
string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json));
return;
}
lock (FileLock) {
string newFilePath = FilePath + ".new";
string newFilePath = FilePath + ".new";
try {
File.WriteAllText(newFilePath, json);
await FileSemaphore.WaitAsync().ConfigureAwait(false);
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
try {
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
} finally {
FileSemaphore.Release();
}
}
}

View File

@@ -28,18 +28,21 @@ using System.Threading;
namespace ArchiSteamFarm {
internal static class Hacks {
private const byte GarbageCollectorDelay = 1;
private static Timer GarbageCollectionTimer;
private static Timer GarbageCompactionTimer;
internal static void Init() {
internal static void EnableBackgroundGC(byte period) {
if (period == 0) {
ASF.ArchiLogger.LogNullError(nameof(period));
return;
}
if (GarbageCollectionTimer == null) {
GarbageCollectionTimer = new Timer(
e => GC.Collect(),
null,
TimeSpan.FromSeconds(GarbageCollectorDelay), // Delay
TimeSpan.FromSeconds(GarbageCollectorDelay) // Period
TimeSpan.FromSeconds(period), // Delay
TimeSpan.FromSeconds(period) // Period
);
}
@@ -47,8 +50,8 @@ namespace ArchiSteamFarm {
GarbageCompactionTimer = new Timer(
e => GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce,
null,
TimeSpan.FromMinutes(GarbageCollectorDelay), // Delay
TimeSpan.FromMinutes(GarbageCollectorDelay) // Period
TimeSpan.FromMinutes(period), // Delay
TimeSpan.FromMinutes(period) // Period
);
}
}

View File

@@ -168,11 +168,11 @@ namespace ArchiSteamFarm {
}
Encoding encoding = Encoding.UTF8;
response.ContentEncoding = encoding;
response.ContentType = "text/plain; charset=" + encoding.WebName;
string html = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body><p>" + message + "</p></body></html>";
byte[] buffer = encoding.GetBytes(html);
byte[] buffer = encoding.GetBytes(message + Environment.NewLine);
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);

View File

@@ -130,8 +130,7 @@
<comment>{0} will be replaced by content string. Please note that this string should include newline for formatting.</comment>
</data>
<data name="ErrorConfigPropertyInvalid" xml:space="preserve">
<value>ErrorPropertiConfiginvalid
{0} akan diubah dengan nama properti konfigurasi, {1} akan diubah dengan nilai invalid</value>
<value>Konfigurasi properti {0} tidak valid: {1}</value>
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
</data>
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
@@ -182,14 +181,19 @@
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Tidak dapat memeriksa versi terbaru!</value>
</data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Tidak bisa melanjutkan dengan pembaruan karena tidak ada asset yang berkaitan dengan versi yang berjalan sekarang! Pembaruan otomatis ke versi tersebut tidak mungkin.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Tidak bisa melanjutkan update karena tak ada aset yang termasuk dalam versi tersebut!</value>
</data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Menerima permintaan untuk input pengguna, tetapi proses berjalan dalam mode Headless!</value>
</data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Menolak untuk menangani permintaan karena SteamOwnerID tidak diatur!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve">
<value>Menutup...</value>
</data>
@@ -239,9 +243,12 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Sedang mengecek versi terbaru...</value>
</data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Mengunduh versi baru: {0} ({1} MB)... Sambil menunggu, pertimbangkan untuk mengapresiasi seluruh kerja keras dengan mendonasi! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data>
<data name="UpdateFinished" xml:space="preserve">
<value>Proses update selesai!</value>
<value>Proses pembaruan selesai!</value>
</data>
<data name="UpdateNewVersionAvailable" xml:space="preserve">
<value>Versi terbaru ASF tersedia! Pertimbangkan untuk di-update!</value>
@@ -278,19 +285,32 @@
<value>Masukkan suatu nilai yang tidak terdokumentasi {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Masukkan host WCF Anda: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Menerima nilai yang tidak diketahui untuk {0}, laporkan hal ini: {1}</value>
<value>Menerima nilai yang tidak diketahui untuk {0}, mohon laporkan ini: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
</data>
<data name="WarningTooManyGamesToPlay" xml:space="preserve">
<value>Tidak dapat bermain lebih dari {0} game secara bersamaan, hanya {0} entri pertama dari {1} game yang akan digunakan</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Layanan WCF tidak bisa dimulai karena AddressAccessDeniedException! Jika anda ingin menggunakan layanan WCF yang disediakan ASF, jalankan ASF sebagai Administrator, atau berikan izin yang benar!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Jawaban untuk perintah IPC: {0} dengan: {1}</value>
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
</data>
<data name="IPCReady" xml:space="preserve">
<value>Server IPC siap!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Memulai server IPC di {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve">
<value>Bot ini sudah berhenti!</value>
</data>
@@ -317,14 +337,14 @@
<value>Mengecek halaman badge lainnya...</value>
</data>
<data name="ChosenFarmingAlgorithm" xml:space="preserve">
<value>Memilih Algoritma Idling: {0}</value>
<value>Memilih algoritma idling: {0}</value>
<comment>{0} will be replaced by the name of chosen idling algorithm</comment>
</data>
<data name="Done" xml:space="preserve">
<value>Selesai!</value>
</data>
<data name="GamesToIdle" xml:space="preserve">
<value>Kami memiliki total {0} permainan ({1} kartu) meninggalkan ke siaga (~{2} yang tersisa)...</value>
<value>Kita memiliki total {0} permainan ({1} kartu) tersisa untuk idle (~{2} tersisa)...</value>
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="IdlingFinished" xml:space="preserve">
@@ -360,7 +380,7 @@
<comment>{0} will be replaced by list of the games (IDs, numbers), separated by a comma</comment>
</data>
<data name="PlayingNotAvailable" xml:space="preserve">
<value>Bermain sedang tidak tersedia, kami akan mencoba lagi nanti!</value>
<value>Bermain sedang tidak tersedia, kita akan coba lagi nanti!</value>
</data>
<data name="StillIdling" xml:space="preserve">
<value>Masih idling: {0} ({1})</value>
@@ -460,7 +480,7 @@
<comment>{0} will be replaced by logging off reason (string)</comment>
</data>
<data name="BotLoggedOn" xml:space="preserve">
<value>Berhasil Login!</value>
<value>Berhasil login!</value>
</data>
<data name="BotLoggingIn" xml:space="preserve">
<value>Sedang masuk...</value>
@@ -650,5 +670,8 @@
<value>Selesai membersihkan antrian penemuan Steam #{0}.</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Ada {0}/{1} bot yang sudah memiliki semua permainan yang sedang diperiksa.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root>

View File

@@ -218,7 +218,7 @@
<value>无账号正在运行,即将退出...</value>
</data>
<data name="RefreshingOurSession" xml:space="preserve">
<value>刷新会话 </value>
<value>正在刷新会话!</value>
</data>
<data name="RejectingTrade" xml:space="preserve">
<value>拒绝交易︰ {0}</value>
@@ -341,10 +341,10 @@
<comment>{0} will be replaced by the name of chosen idling algorithm</comment>
</data>
<data name="Done" xml:space="preserve">
<value>完成</value>
<value>完成</value>
</data>
<data name="GamesToIdle" xml:space="preserve">
<value>共有{0} 个游戏(共计{1} 张卡) 等待挂卡(~还剩{2})...</value>
<value>共有 {0} 个游戏(共计 {1} 张卡) 等待挂卡(~还剩{2})...</value>
<comment>{0} will be replaced by number of games, {1} will be replaced by number of cards, {2} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="IdlingFinished" xml:space="preserve">
@@ -363,13 +363,13 @@
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to idle</comment>
</data>
<data name="IdlingStopped" xml:space="preserve">
<value>挂卡已停止</value>
<value>已停止挂卡!</value>
</data>
<data name="IgnoredStickyPauseEnabled" xml:space="preserve">
<value>请求已忽略,因为强制暂停已开启</value>
</data>
<data name="NothingToIdle" xml:space="preserve">
<value>该账户已经无卡可挂</value>
<value>该账户已经无卡可挂</value>
</data>
<data name="NowIdling" xml:space="preserve">
<value>正在挂卡︰{0} ({1})</value>

View File

@@ -42,14 +42,14 @@ namespace ArchiSteamFarm {
private const byte CodeInterval = 30;
private static readonly char[] CodeCharacters = { '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y' };
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1);
private static readonly SemaphoreSlim TimeSemaphore = new SemaphoreSlim(1, 1);
private static int? SteamTimeDifference;
// "ERROR" is being used by SteamDesktopAuthenticator
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR");
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim ConfirmationsSemaphore = new SemaphoreSlim(1, 1);
#pragma warning disable 649
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]

View File

@@ -23,8 +23,10 @@
*/
using System;
using System.IO;
using System.Runtime.InteropServices;
using ArchiSteamFarm.Localization;
using Mono.Unix;
namespace ArchiSteamFarm {
internal static class OS {
@@ -44,6 +46,16 @@ namespace ArchiSteamFarm {
}
}
internal static void UnixSetFileAccessExecutable(string path) {
if (!File.Exists(path) || !UnixFileSystemInfo.TryGetFileSystemEntry(path, out UnixFileSystemInfo entry)) {
return;
}
if (!entry.FileAccessPermissions.HasFlag(FileAccessPermissions.UserExecute)) {
entry.FileAccessPermissions = entry.FileAccessPermissions | FileAccessPermissions.UserExecute;
}
}
private static void DisableQuickEditMode() {
// http://stackoverflow.com/questions/30418886/how-and-why-does-quickedit-mode-in-command-prompt-freeze-applications
IntPtr consoleHandle = NativeMethods.GetStdHandle(NativeMethods.StandardInputHandle);

View File

@@ -31,8 +31,6 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Runtime;
using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Localization;
using NLog;
@@ -53,7 +51,11 @@ namespace ArchiSteamFarm {
internal static WebBrowser WebBrowser { get; private set; }
private static readonly object ConsoleLock = new object();
private static readonly ManualResetEventSlim ShutdownResetEvent = new ManualResetEventSlim(false);
// We need to keep this one assigned and not calculated on-demand
private static readonly string ProcessFileName = Process.GetCurrentProcess().MainModule.FileName;
private static readonly TaskCompletionSource<bool> ShutdownResetEvent = new TaskCompletionSource<bool>();
private static bool ShutdownSequenceInitialized;
@@ -82,33 +84,39 @@ namespace ArchiSteamFarm {
switch (userInputType) {
case ASF.EUserInputType.DeviceID:
Console.Write(Bot.FormatBotResponse(Strings.UserInputDeviceID, botName));
result = Console.ReadLine();
break;
case ASF.EUserInputType.IPCHostname:
Console.Write(Bot.FormatBotResponse(Strings.UserInputIPCHost, botName));
result = Console.ReadLine();
break;
case ASF.EUserInputType.Login:
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamLogin, botName));
result = Console.ReadLine();
break;
case ASF.EUserInputType.Password:
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamPassword, botName));
result = Utilities.ReadLineMasked();
break;
case ASF.EUserInputType.SteamGuard:
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamGuard, botName));
result = Console.ReadLine();
break;
case ASF.EUserInputType.SteamParentalPIN:
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteamParentalPIN, botName));
result = Utilities.ReadLineMasked();
break;
case ASF.EUserInputType.TwoFactorAuthentication:
Console.Write(Bot.FormatBotResponse(Strings.UserInputSteam2FA, botName));
result = Console.ReadLine();
break;
default:
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(userInputType), userInputType));
Console.Write(Bot.FormatBotResponse(string.Format(Strings.UserInputUnknown, userInputType), botName));
result = Console.ReadLine();
break;
}
result = Console.ReadLine();
if (!Console.IsOutputRedirected) {
Console.Clear(); // For security purposes
}
@@ -124,13 +132,11 @@ namespace ArchiSteamFarm {
return;
}
string executable = Process.GetCurrentProcess().MainModule.FileName;
string executableName = Path.GetFileNameWithoutExtension(executable);
string executableName = Path.GetFileNameWithoutExtension(ProcessFileName);
IEnumerable<string> arguments = Environment.GetCommandLineArgs().Skip(executableName.Equals(SharedInfo.AssemblyName) ? 1 : 0);
try {
Process.Start(executable, string.Join(" ", arguments));
Process.Start(ProcessFileName, string.Join(" ", arguments));
} catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e);
}
@@ -138,7 +144,7 @@ namespace ArchiSteamFarm {
// Give new process some time to take over the window (if needed)
await Task.Delay(2000).ConfigureAwait(false);
ShutdownResetEvent.Set();
ShutdownResetEvent.TrySetResult(true);
Environment.Exit(0);
}
@@ -155,7 +161,7 @@ namespace ArchiSteamFarm {
}
private static async Task InitASF(string[] args) {
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version);
ASF.ArchiLogger.LogGenericInfo("ASF V" + SharedInfo.Version + " (" + SharedInfo.ModuleVersion + ")");
await InitGlobalConfigAndLanguage().ConfigureAwait(false);
await InitGlobalDatabaseAndServices().ConfigureAwait(false);
@@ -231,8 +237,8 @@ namespace ArchiSteamFarm {
return;
}
if (GCSettings.IsServerGC) {
Hacks.Init();
if (GlobalConfig.BackgroundGCPeriod > 0) {
Hacks.EnableBackgroundGC(GlobalConfig.BackgroundGCPeriod);
}
if (!string.IsNullOrEmpty(GlobalConfig.CurrentCulture)) {
@@ -318,36 +324,35 @@ namespace ArchiSteamFarm {
ShutdownSequenceInitialized = true;
if (Bot.Bots.Count == 0) {
return true;
}
if (Bot.Bots.Count > 0) {
IEnumerable<Task> tasks = Bot.Bots.Values.Select(bot => Task.Run(() => bot.Stop(false)));
IEnumerable<Task> tasks = Bot.Bots.Values.Select(bot => Task.Run(() => bot.Stop(false)));
switch (GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
foreach (Task task in tasks) {
await Task.WhenAny(task, Task.Delay(WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
}
switch (GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
foreach (Task task in tasks) {
await Task.WhenAny(task, Task.Delay(WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
}
break;
default:
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(Bot.Bots.Count * WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
break;
}
break;
default:
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(Bot.Bots.Count * WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
break;
// Extra second for Steam requests to go through
await Task.Delay(1000).ConfigureAwait(false);
}
LogManager.Flush();
return true;
}
private static void Main(string[] args) {
Init(args).Wait();
private static async Task Main(string[] args) {
// Initialize
await Init(args).ConfigureAwait(false);
// Wait for signal to shutdown
ShutdownResetEvent.Wait();
// We got a signal to shutdown
Exit().Wait();
// Wait for shutdown event
await ShutdownResetEvent.Task.ConfigureAwait(false);
}
private static void OnProcessExit(object sender, EventArgs e) => IPC.Stop();
@@ -427,7 +432,7 @@ namespace ArchiSteamFarm {
return;
}
ShutdownResetEvent.Set();
ShutdownResetEvent.TrySetResult(true);
}
}
}

View File

@@ -38,16 +38,8 @@ namespace ArchiSteamFarm {
internal readonly ProtocolTypes ProtocolTypes;
internal ServerRecordEndPoint(string host, ushort port, ProtocolTypes protocolTypes) {
if (string.IsNullOrEmpty(host)) {
throw new ArgumentNullException(nameof(host));
}
if (port == 0) {
throw new ArgumentNullException(nameof(port));
}
if (protocolTypes == 0) {
throw new ArgumentNullException(nameof(protocolTypes));
if (string.IsNullOrEmpty(host) || (port == 0) || (protocolTypes == 0)) {
throw new ArgumentNullException(nameof(host) + " || " + nameof(port) + " || " + nameof(protocolTypes));
}
Host = host;

View File

@@ -42,6 +42,7 @@ namespace ArchiSteamFarm {
internal const string UpdateDirectory = "_old";
internal const string VersionFile = AssemblyName + ".version";
internal static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
internal static Guid ModuleVersion => Assembly.GetEntryAssembly().ManifestModule.ModuleVersionId;
internal static Version Version => Assembly.GetEntryAssembly().GetName().Version;
}
}

View File

@@ -40,7 +40,7 @@ namespace ArchiSteamFarm {
private const string URL = "https://" + SharedInfo.StatisticsServer;
private readonly Bot Bot;
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim RequestsSemaphore = new SemaphoreSlim(1, 1);
private DateTime LastAnnouncementCheck = DateTime.MinValue;
private DateTime LastHeartBeat = DateTime.MinValue;
@@ -49,7 +49,7 @@ namespace ArchiSteamFarm {
internal Statistics(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
public void Dispose() => Semaphore.Dispose();
public void Dispose() => RequestsSemaphore.Dispose();
internal async Task OnHeartBeat() {
// Request persona update if needed
@@ -62,7 +62,7 @@ namespace ArchiSteamFarm {
return;
}
await Semaphore.WaitAsync().ConfigureAwait(false);
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (!ShouldSendHeartBeats || (DateTime.UtcNow < LastHeartBeat.AddMinutes(MinHeartBeatTTL))) {
@@ -80,7 +80,7 @@ namespace ArchiSteamFarm {
LastHeartBeat = DateTime.UtcNow;
}
} finally {
Semaphore.Release();
RequestsSemaphore.Release();
}
}
@@ -114,7 +114,7 @@ namespace ArchiSteamFarm {
}
}
await Semaphore.WaitAsync().ConfigureAwait(false);
await RequestsSemaphore.WaitAsync().ConfigureAwait(false);
try {
if (DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) {
@@ -154,7 +154,7 @@ namespace ArchiSteamFarm {
ShouldSendHeartBeats = true;
}
} finally {
Semaphore.Release();
RequestsSemaphore.Release();
}
}
}

View File

@@ -56,6 +56,8 @@ namespace ArchiSteamFarm {
return;
}
base.Write(logEvent);
if (SteamID == 0) {
return;
}

View File

@@ -37,7 +37,7 @@ namespace ArchiSteamFarm {
private readonly Bot Bot;
private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>();
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1);
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1, 1);
private bool ParsingScheduled;
@@ -301,7 +301,7 @@ namespace ArchiSteamFarm {
// 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))) {
if ((holdDuration.Value > Program.GlobalConfig.MaxTradeHoldDuration) || tradeOffer.ItemsToGive.Any(item => GlobalConfig.SalesBlacklist.Contains(item.RealAppID))) {
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
}
}

View File

@@ -29,6 +29,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Humanizer;
@@ -85,6 +86,31 @@ namespace ArchiSteamFarm {
}
}
internal static string ReadLineMasked(char mask = '*') {
StringBuilder result = new StringBuilder();
ConsoleKeyInfo keyInfo;
while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter) {
if (!char.IsControl(keyInfo.KeyChar)) {
result.Append(keyInfo.KeyChar);
Console.Write(mask);
} else if ((keyInfo.Key == ConsoleKey.Backspace) && (result.Length > 0)) {
result.Remove(result.Length - 1, 1);
if (Console.CursorLeft == 0) {
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
Console.Write(' ');
Console.SetCursorPosition(Console.BufferWidth - 1, Console.CursorTop - 1);
} else {
Console.Write("\b \b");
}
}
}
Console.WriteLine();
return result.ToString();
}
internal static void StartBackgroundAction(Action action, bool longRunning = true) {
if (action == null) {
ASF.ArchiLogger.LogNullError(nameof(action));

View File

@@ -34,7 +34,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm {
internal sealed class WebBrowser {
internal sealed class WebBrowser : IDisposable {
internal const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
private const byte ExtendedTimeoutMultiplier = 10; // Defines multiplier of timeout for WebBrowsers dealing with huge data (ASF update)
@@ -63,6 +63,8 @@ namespace ArchiSteamFarm {
HttpClient.DefaultRequestHeaders.UserAgent.ParseAdd(SharedInfo.AssemblyName + "/" + SharedInfo.Version);
}
public void Dispose() => HttpClient.Dispose();
internal static void Init() {
// Set max connection limit from default of 2 to desired value
ServicePointManager.DefaultConnectionLimit = MaxConnections;
@@ -535,11 +537,19 @@ namespace ArchiSteamFarm {
ushort status = (ushort) responseMessage.StatusCode;
if ((status >= 300) && (status <= 399) && (maxRedirections > 0)) {
redirectUri = responseMessage.Headers.Location;
if (!redirectUri.IsAbsoluteUri) {
if (redirectUri.IsAbsoluteUri) {
switch (redirectUri.Scheme) {
case "http":
case "https":
break;
default:
// Invalid ones such as "steammobile"
return null;
}
} else {
redirectUri = new Uri(requestUri.GetLeftPart(UriPartial.Authority) + redirectUri);
}
ASF.ArchiLogger.LogGenericDebug("Asked for <" + requestUri + ">, got unsafely redirected to <" + responseMessage.Headers.Location + ">, resolved URI to: <" + redirectUri + ">");
} else {
if (!Debugging.IsDebugBuild) {
return null;

View File

@@ -1,6 +1,7 @@
{
"AutoRestart": true,
"AutoUpdates": true,
"BackgroundGCPeriod": 0,
"Blacklist": [],
"ConnectionTimeout": 60,
"CurrentCulture": null,
@@ -18,6 +19,6 @@
"OptimizationMode": 0,
"Statistics": true,
"SteamOwnerID": 0,
"SteamProtocols": 7,
"SteamProtocols": 1,
"UpdateChannel": 1
}

Binary file not shown.