Compare commits

...

19 Commits

Author SHA1 Message Date
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
JustArchi
ef5398fae8 Translations update 2017-07-31 17:39:40 +02:00
JustArchi
ce600fdf42 Packages update 2017-07-31 17:38:38 +02:00
JustArchi
7a6485775d Misc 2017-07-31 17:35:17 +02:00
JustArchi
d39e7a32d6 Add debug for #586 2017-07-31 17:22:18 +02:00
JustArchi
b4cbe85b36 Closes #605 2017-07-31 16:57:10 +02:00
JustArchi
4d83417c9d Misc 2017-07-24 12:18:40 +02:00
JustArchi
25bfbf4862 Misc optimization
I like how R# spots multiple enumerations.
2017-07-24 11:13:55 +02:00
JustArchi
e539e1bdc7 Fixes #537
Apparently Steam web is too stupid to handle redirections properly.
2017-07-24 10:39:51 +02:00
JustArchi
b0f83fcd6d AppVeyor: Misc 2017-07-24 10:08:51 +02:00
JustArchi
4880a4e60b Bump 2017-07-23 13:42:03 +02:00
28 changed files with 249 additions and 135 deletions

View File

@@ -50,11 +50,10 @@ matrix:
fast_finish: true fast_finish: true
include: include:
# We're building ASF with dotnet on latest versions of Linux and OS X # We're building ASF with dotnet on latest versions of Linux and OS X
# Ref: https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments
- os: linux - os: linux
# Ref: https://docs.travis-ci.com/user/trusty-ci-environment/ # Ref: https://docs.travis-ci.com/user/reference/trusty/
dist: trusty dist: trusty
sudo: false sudo: false
- os: osx - os: osx
# Ref: https://docs.travis-ci.com/user/osx-ci-environment/ # Ref: https://docs.travis-ci.com/user/reference/osx/
osx_image: xcode9 osx_image: xcode9

View File

@@ -24,7 +24,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" /> <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.TestAdapter" Version="1.2.0-beta" />
<PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta" /> <PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta" />
</ItemGroup> </ItemGroup>

View File

@@ -3,8 +3,8 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyVersion>3.0.0.6</AssemblyVersion> <AssemblyVersion>3.0.0.9</AssemblyVersion>
<FileVersion>3.0.0.6</FileVersion> <FileVersion>3.0.0.9</FileVersion>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<ErrorReport>none</ErrorReport> <ErrorReport>none</ErrorReport>
<ApplicationIcon>ASF.ico</ApplicationIcon> <ApplicationIcon>ASF.ico</ApplicationIcon>

View File

@@ -58,15 +58,15 @@ namespace ArchiSteamFarm {
private const string SteamStoreURL = "http://" + SteamStoreHost; 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 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 Bot Bot;
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1, 1);
private readonly WebBrowser WebBrowser; private readonly WebBrowser WebBrowser;
private string CachedApiKey; private string CachedApiKey;
@@ -74,6 +74,7 @@ namespace ArchiSteamFarm {
private string CachedTradeToken; private string CachedTradeToken;
private DateTime LastSessionRefreshCheck = DateTime.MinValue; private DateTime LastSessionRefreshCheck = DateTime.MinValue;
private ulong SteamID; private ulong SteamID;
private string VanityURL;
internal ArchiWebHandler(Bot bot) { internal ArchiWebHandler(Bot bot) {
Bot = bot ?? throw new ArgumentNullException(nameof(bot)); Bot = bot ?? throw new ArgumentNullException(nameof(bot));
@@ -867,12 +868,16 @@ namespace ArchiSteamFarm {
internal static void Init() => Timeout = Program.GlobalConfig.ConnectionTimeout * 1000; internal static void Init() => Timeout = Program.GlobalConfig.ConnectionTimeout * 1000;
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) { internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin, string vanityURL = null) {
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) { if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin)); Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin));
return false; return false;
} }
if (!string.IsNullOrEmpty(vanityURL)) {
VanityURL = vanityURL;
}
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString())); string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
// Generate an AES session key // Generate an AES session key
@@ -1104,7 +1109,7 @@ namespace ArchiSteamFarm {
return false; return false;
} }
string request = SteamCommunityURL + "/profiles/" + SteamID + "/ajaxunpackbooster"; string request = GetAbsoluteProfileURL() + "/ajaxunpackbooster";
Dictionary<string, string> data = new Dictionary<string, string>(3) { Dictionary<string, string> data = new Dictionary<string, string>(3) {
{ "sessionid", sessionID }, { "sessionid", sessionID },
{ "appid", appID.ToString() }, { "appid", appID.ToString() },
@@ -1115,6 +1120,14 @@ namespace ArchiSteamFarm {
return response?.Result == EResult.OK; return response?.Result == EResult.OK;
} }
private string GetAbsoluteProfileURL() {
if (!string.IsNullOrEmpty(VanityURL)) {
return SteamCommunityURL + "/id/" + VanityURL;
}
return SteamCommunityURL + "/profiles/" + SteamID;
}
private async Task<string> GetApiKey() { private async Task<string> GetApiKey() {
if (CachedApiKey != null) { if (CachedApiKey != null) {
// We fetched API key already, and either got valid one, or permanent AccessDenied // We fetched API key already, and either got valid one, or permanent AccessDenied

View File

@@ -53,8 +53,8 @@ namespace ArchiSteamFarm {
internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>(); internal static readonly ConcurrentDictionary<string, Bot> Bots = new ConcurrentDictionary<string, Bot>();
private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1); private static readonly SemaphoreSlim GiftsSemaphore = new SemaphoreSlim(1, 1);
private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1); private static readonly SemaphoreSlim LoginSemaphore = new SemaphoreSlim(1, 1);
private static readonly SteamConfiguration SteamConfiguration = new SteamConfiguration(); private static readonly SteamConfiguration SteamConfiguration = new SteamConfiguration();
internal readonly ArchiLogger ArchiLogger; internal readonly ArchiLogger ArchiLogger;
@@ -73,15 +73,16 @@ namespace ArchiSteamFarm {
private readonly BotDatabase BotDatabase; private readonly BotDatabase BotDatabase;
private readonly string BotName; private readonly string BotName;
private readonly CallbackManager CallbackManager; private readonly CallbackManager CallbackManager;
private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim CallbackSemaphore = new SemaphoreSlim(1, 1);
[JsonProperty] [JsonProperty]
private readonly CardsFarmer CardsFarmer; private readonly CardsFarmer CardsFarmer;
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>(); private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
private readonly Timer HeartBeatTimer; private readonly Timer HeartBeatTimer;
private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim InitializationSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim LootingSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim PICSSemaphore = new SemaphoreSlim(1, 1);
private readonly Statistics Statistics; private readonly Statistics Statistics;
private readonly SteamApps SteamApps; private readonly SteamApps SteamApps;
private readonly SteamClient SteamClient; private readonly SteamClient SteamClient;
@@ -235,6 +236,8 @@ namespace ArchiSteamFarm {
CardsFarmer.Dispose(); CardsFarmer.Dispose();
HeartBeatTimer.Dispose(); HeartBeatTimer.Dispose();
InitializationSemaphore.Dispose(); InitializationSemaphore.Dispose();
LootingSemaphore.Dispose();
PICSSemaphore.Dispose();
Trading.Dispose(); Trading.Dispose();
// Those are objects that might be null and the check should be in-place // Those are objects that might be null and the check should be in-place
@@ -349,11 +352,15 @@ namespace ArchiSteamFarm {
AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet productInfoResultSet; AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet productInfoResultSet;
await PICSSemaphore.WaitAsync().ConfigureAwait(false);
try { try {
productInfoResultSet = await SteamApps.PICSGetProductInfo(appID, null, false); productInfoResultSet = await SteamApps.PICSGetProductInfo(appID, null, false);
} catch (Exception e) { } catch (Exception e) {
ArchiLogger.LogGenericException(e); ArchiLogger.LogGenericException(e);
return (0, DateTime.MinValue); return (0, DateTime.MinValue);
} finally {
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 // ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619
@@ -452,11 +459,15 @@ namespace ArchiSteamFarm {
internal async Task<Dictionary<uint, HashSet<uint>>> GetAppIDsToPackageIDs(IEnumerable<uint> packageIDs) { internal async Task<Dictionary<uint, HashSet<uint>>> GetAppIDsToPackageIDs(IEnumerable<uint> packageIDs) {
AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet productInfoResultSet; AsyncJobMultiple<SteamApps.PICSProductInfoCallback>.ResultSet productInfoResultSet;
await PICSSemaphore.WaitAsync().ConfigureAwait(false);
try { try {
productInfoResultSet = await SteamApps.PICSGetProductInfo(Enumerable.Empty<uint>(), packageIDs); productInfoResultSet = await SteamApps.PICSGetProductInfo(Enumerable.Empty<uint>(), packageIDs);
} catch (Exception e) { } catch (Exception e) {
ArchiLogger.LogGenericException(e); ArchiLogger.LogGenericException(e);
return null; return null;
} finally {
PICSSemaphore.Release();
} }
Dictionary<uint, HashSet<uint>> result = new Dictionary<uint, HashSet<uint>>(); Dictionary<uint, HashSet<uint>> result = new Dictionary<uint, HashSet<uint>>();
@@ -510,6 +521,17 @@ namespace ArchiSteamFarm {
return result; return result;
} }
internal void IdleGame(uint gameID) => IdleGames(gameID.ToEnumerable());
internal void IdleGames(IEnumerable<uint> gameIDs) {
if (gameIDs == null) {
ArchiLogger.LogNullError(nameof(gameIDs));
return;
}
ArchiHandler.PlayGames(gameIDs, BotConfig.CustomGamePlayedWhileFarming);
}
internal static async Task InitializeSteamConfiguration(ProtocolTypes protocolTypes, uint cellID, InMemoryServerListProvider serverListProvider) { internal static async Task InitializeSteamConfiguration(ProtocolTypes protocolTypes, uint cellID, InMemoryServerListProvider serverListProvider) {
if (serverListProvider == null) { if (serverListProvider == null) {
ASF.ArchiLogger.LogNullError(nameof(serverListProvider)); ASF.ArchiLogger.LogNullError(nameof(serverListProvider));
@@ -635,17 +657,6 @@ namespace ArchiSteamFarm {
} }
} }
internal void PlayGame(uint gameID, string gameName = null) => PlayGames(gameID.ToEnumerable(), gameName);
internal void PlayGames(IEnumerable<uint> gameIDs, string gameName = null) {
if (gameIDs == null) {
ArchiLogger.LogNullError(nameof(gameIDs));
return;
}
ArchiHandler.PlayGames(gameIDs, gameName);
}
internal async Task<bool> RefreshSession() { internal async Task<bool> RefreshSession() {
if (!IsConnectedAndLoggedOn) { if (!IsConnectedAndLoggedOn) {
return false; return false;
@@ -1123,7 +1134,7 @@ namespace ArchiSteamFarm {
try { try {
if (DateTime.UtcNow.Subtract(ArchiHandler.LastPacketReceived).TotalSeconds > MinHeartBeatTTL) { if (DateTime.UtcNow.Subtract(ArchiHandler.LastPacketReceived).TotalSeconds > MinHeartBeatTTL) {
await SteamApps.PICSGetProductInfo(0, null); await SteamFriends.RequestProfileInfo(SteamClient.SteamID);
} }
HeartBeatFailures = 0; HeartBeatFailures = 0;
@@ -1810,7 +1821,7 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericWarning(Strings.BotAccountLocked); ArchiLogger.LogGenericWarning(Strings.BotAccountLocked);
} }
if ((callback.CellID != 0) && (Program.GlobalDatabase.CellID != callback.CellID)) { if (callback.CellID != 0) {
Program.GlobalDatabase.CellID = callback.CellID; Program.GlobalDatabase.CellID = callback.CellID;
} }
@@ -1832,7 +1843,7 @@ namespace ArchiSteamFarm {
SetUserInput(ASF.EUserInputType.SteamParentalPIN, steamParentalPIN); SetUserInput(ASF.EUserInputType.SteamParentalPIN, steamParentalPIN);
} }
if (!await ArchiWebHandler.Init(callback.ClientSteamID, SteamClient.Universe, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN).ConfigureAwait(false)) { if (!await ArchiWebHandler.Init(callback.ClientSteamID, SteamClient.Universe, callback.WebAPIUserNonce, BotConfig.SteamParentalPIN, callback.VanityURL).ConfigureAwait(false)) {
if (!await RefreshSession().ConfigureAwait(false)) { if (!await RefreshSession().ConfigureAwait(false)) {
break; break;
} }

View File

@@ -58,9 +58,9 @@ namespace ArchiSteamFarm {
); );
private readonly Bot Bot; private readonly Bot Bot;
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1);
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false); private readonly SemaphoreSlim FarmingResetSemaphore = new SemaphoreSlim(0, 1);
private readonly Timer IdleFarmingTimer; private readonly Timer IdleFarmingTimer;
[JsonProperty] [JsonProperty]
@@ -87,8 +87,8 @@ namespace ArchiSteamFarm {
public void Dispose() { public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception // Those are objects that are always being created if constructor doesn't throw exception
EventSemaphore.Dispose(); EventSemaphore.Dispose();
FarmingSemaphore.Dispose(); FarmingInitializationSemaphore.Dispose();
FarmResetEvent.Dispose(); FarmingResetSemaphore.Dispose();
// Those are objects that might be null and the check should be in-place // Those are objects that might be null and the check should be in-place
IdleFarmingTimer?.Dispose(); IdleFarmingTimer?.Dispose();
@@ -140,7 +140,7 @@ namespace ArchiSteamFarm {
internal async Task OnNewItemsNotification() { internal async Task OnNewItemsNotification() {
if (NowFarming) { if (NowFarming) {
FarmResetEvent.Set(); FarmingResetSemaphore.Release();
return; return;
} }
@@ -188,7 +188,7 @@ namespace ArchiSteamFarm {
return; return;
} }
await FarmingSemaphore.WaitAsync().ConfigureAwait(false); await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try { try {
if (NowFarming || Paused || !Bot.IsPlayingPossible) { if (NowFarming || Paused || !Bot.IsPlayingPossible) {
@@ -231,7 +231,7 @@ namespace ArchiSteamFarm {
KeepFarming = NowFarming = true; KeepFarming = NowFarming = true;
Utilities.StartBackgroundFunction(Farm); Utilities.StartBackgroundFunction(Farm);
} finally { } finally {
FarmingSemaphore.Release(); FarmingInitializationSemaphore.Release();
} }
} }
@@ -240,7 +240,7 @@ namespace ArchiSteamFarm {
return; return;
} }
await FarmingSemaphore.WaitAsync().ConfigureAwait(false); await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try { try {
if (!NowFarming) { if (!NowFarming) {
@@ -248,7 +248,7 @@ namespace ArchiSteamFarm {
} }
KeepFarming = false; KeepFarming = false;
FarmResetEvent.Set(); FarmingResetSemaphore.Release();
for (byte i = 0; (i < 5) && NowFarming; i++) { for (byte i = 0; (i < 5) && NowFarming; i++) {
await Task.Delay(1000).ConfigureAwait(false); await Task.Delay(1000).ConfigureAwait(false);
@@ -261,7 +261,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(Strings.IdlingStopped); Bot.ArchiLogger.LogGenericInfo(Strings.IdlingStopped);
Bot.OnFarmingStopped(); Bot.OnFarmingStopped();
} finally { } finally {
FarmingSemaphore.Release(); FarmingInitializationSemaphore.Release();
} }
} }
@@ -556,67 +556,70 @@ namespace ArchiSteamFarm {
// If we have restricted card drops, we use complex algorithm // If we have restricted card drops, we use complex algorithm
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex")); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex"));
while (GamesToFarm.Count > 0) { while (GamesToFarm.Count > 0) {
HashSet<Game> playableGamesToFarmSolo = new HashSet<Game>(); HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump));
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) {
if (await IsPlayableGame(game).ConfigureAwait(false)) { foreach (Game game in gamesToCheck) {
playableGamesToFarmSolo.Add(game); if (!await IsPlayableGame(game).ConfigureAwait(false)) {
} GamesToFarm.Remove(game);
continue;
}
if (await FarmSolo(game).ConfigureAwait(false)) {
continue;
} }
if (playableGamesToFarmSolo.Count > 0) {
while (playableGamesToFarmSolo.Count > 0) {
Game playableGame = playableGamesToFarmSolo.First();
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
playableGamesToFarmSolo.Remove(playableGame);
} else {
NowFarming = false; NowFarming = false;
return; return;
} }
}
} else { gamesToCheck = new HashSet<Game>(GamesToFarm.OrderByDescending(game => game.HoursPlayed));
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>(); 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)) { foreach (Game game in gamesToCheck) {
playableGamesToFarmMultiple.Add(game); if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
} }
playableGamesToFarmMultiple.Add(game);
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) { if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
break; break;
} }
} }
if (FarmMultiple(playableGamesToFarmMultiple)) { 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)))); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
} else { } else {
NowFarming = false; NowFarming = false;
return; return;
} }
} }
}
} else { } else {
// If we have unrestricted card drops, we use simple algorithm // If we have unrestricted card drops, we use simple algorithm
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple")); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple"));
while (GamesToFarm.Count > 0) { while (GamesToFarm.Count > 0) {
Game playableGame = null; HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm);
foreach (Game game in GamesToFarm) {
foreach (Game game in gamesToCheck) {
if (!await IsPlayableGame(game).ConfigureAwait(false)) { if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue; continue;
} }
playableGame = game; if (await FarmSolo(game).ConfigureAwait(false)) {
break;
}
if (playableGame != null) {
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
continue; continue;
} }
}
NowFarming = false; NowFarming = false;
return; return;
} }
} }
}
} while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault()); } while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault());
CurrentGamesFarming.Clear(); CurrentGamesFarming.Clear();
@@ -638,7 +641,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningIdlingGameMismatch, game.AppID, game.GameName, game.PlayableAppID)); Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningIdlingGameMismatch, game.AppID, game.GameName, game.PlayableAppID));
} }
Bot.PlayGame(game.PlayableAppID, Bot.BotConfig.CustomGamePlayedWhileFarming); Bot.IdleGame(game.PlayableAppID);
DateTime endFarmingDate = DateTime.UtcNow.AddHours(Program.GlobalConfig.MaxFarmingTime); DateTime endFarmingDate = DateTime.UtcNow.AddHours(Program.GlobalConfig.MaxFarmingTime);
bool? keepFarming = await ShouldFarm(game).ConfigureAwait(false); bool? keepFarming = await ShouldFarm(game).ConfigureAwait(false);
@@ -646,8 +649,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName)); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName));
DateTime startFarmingPeriod = DateTime.UtcNow; DateTime startFarmingPeriod = DateTime.UtcNow;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
FarmResetEvent.Reset();
success = KeepFarming; success = KeepFarming;
} }
@@ -665,7 +667,7 @@ namespace ArchiSteamFarm {
return success; return success;
} }
private bool FarmHours(ConcurrentHashSet<Game> games) { private async Task<bool> FarmHours(ConcurrentHashSet<Game> games) {
if ((games == null) || (games.Count == 0)) { if ((games == null) || (games.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(games)); Bot.ArchiLogger.LogNullError(nameof(games));
return false; return false;
@@ -682,15 +684,14 @@ namespace ArchiSteamFarm {
return true; return true;
} }
Bot.PlayGames(games.Select(game => game.AppID), Bot.BotConfig.CustomGamePlayedWhileFarming); Bot.IdleGames(games.Select(game => game.PlayableAppID));
bool success = true; bool success = true;
while (maxHour < 2) { while (maxHour < 2) {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID)))); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID))));
DateTime startFarmingPeriod = DateTime.UtcNow; DateTime startFarmingPeriod = DateTime.UtcNow;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
FarmResetEvent.Reset();
success = KeepFarming; success = KeepFarming;
} }
@@ -711,7 +712,7 @@ namespace ArchiSteamFarm {
return success; return success;
} }
private bool FarmMultiple(IEnumerable<Game> games) { private async Task<bool> FarmMultiple(IEnumerable<Game> games) {
if (games == null) { if (games == null) {
Bot.ArchiLogger.LogNullError(nameof(games)); Bot.ArchiLogger.LogNullError(nameof(games));
return false; return false;
@@ -721,7 +722,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdlingList, string.Join(", ", CurrentGamesFarming.Select(game => game.AppID)))); 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(); CurrentGamesFarming.Clear();
return result; return result;
} }
@@ -926,7 +927,7 @@ namespace ArchiSteamFarm {
case BotConfig.EFarmingOrder.RedeemDateTimesDescending: case BotConfig.EFarmingOrder.RedeemDateTimesDescending:
Dictionary<uint, DateTime> redeemDates = new Dictionary<uint, DateTime>(GamesToFarm.Count); Dictionary<uint, DateTime> redeemDates = new Dictionary<uint, DateTime>(GamesToFarm.Count);
foreach (Game game in gamesToFarm) { foreach (Game game in GamesToFarm) {
DateTime redeemDate = DateTime.MinValue; DateTime redeemDate = DateTime.MinValue;
if (Program.GlobalDatabase.AppIDsToPackageIDs.TryGetValue(game.AppID, out ConcurrentHashSet<uint> packageIDs)) { if (Program.GlobalDatabase.AppIDsToPackageIDs.TryGetValue(game.AppID, out ConcurrentHashSet<uint> packageIDs)) {
// ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619 // ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619

View File

@@ -44,7 +44,7 @@ namespace ArchiSteamFarm {
public bool IsReadOnly => false; public bool IsReadOnly => false;
private readonly HashSet<T> BackingCollection = new HashSet<T>(); private readonly HashSet<T> BackingCollection = new HashSet<T>();
private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1); private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
public bool Add(T item) { public bool Add(T item) {
SemaphoreSlim.Wait(); SemaphoreSlim.Wait();

View File

@@ -102,7 +102,7 @@ namespace ArchiSteamFarm {
internal readonly bool Statistics = true; internal readonly bool Statistics = true;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.All; internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.Tcp;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable; internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;

View File

@@ -44,7 +44,7 @@ namespace ArchiSteamFarm {
private readonly object FileLock = new object(); private readonly object FileLock = new object();
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1);
internal uint CellID { internal uint CellID {
get => _CellID; get => _CellID;

View File

@@ -673,5 +673,8 @@ StackTrace:
<value>Procházení fronty doporučení #{0} dokončeno.</value> <value>Procházení fronty doporučení #{0} dokončeno.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>{0}/{1} botů již vlastní všechny uvedené hry.</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> </root>

View File

@@ -673,5 +673,8 @@ StackTrace:
<value>Fertig mit Löschung der Steam Entdeckungsliste #{0}.</value> <value>Fertig mit Löschung der Steam Entdeckungsliste #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Es gibt {0}/{1}-Bots, die bereits alle geprüften Spiele besitzen.</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> </root>

View File

@@ -673,5 +673,8 @@ StackTrace:
<value>Ολοκληρώθηκε η εκκαθάριση σειράς ανακαλύψεων Steam #{0}.</value> <value>Ολοκληρώθηκε η εκκαθάριση σειράς ανακαλύψεων Steam #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Υπάρχουν {0}/{1} bot που κατέχουν ήδη όλα τα παιχνίδια για τα οποία γίνεται έλεγχος.</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> </root>

View File

@@ -289,7 +289,9 @@
</data> </data>
<data name="IPCReady" xml:space="preserve">
<value>Server IPC siap!</value>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Bot ini sudah berhenti!</value> <value>Bot ini sudah berhenti!</value>

View File

@@ -184,14 +184,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>최신 버전을 확인할 수 없습니다!</value> <value>최신 버전을 확인할 수 없습니다!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>현재 실행 중인 버전과 관련된 자산이 없으므로 업데이트를 진행할 수 없습니다! 해당 버전으로 자동 업데이트가 불가능합니다.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>해당 버전이 아무 내용도 포함되어 있지 않아 업데이트를 진행할 수 없습니다!</value> <value>해당 버전이 아무 내용도 포함되어 있지 않아 업데이트를 진행할 수 없습니다!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>사용자 입력 요청을 받았지만, 프로세스는 Headless 모드로 실행 중입니다.</value> <value>사용자 입력 요청을 받았지만, 프로세스는 Headless 모드로 실행 중입니다.</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>SteamOwnerID가 설정되지 않았기 때문에 요청을 거절합니다!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>종료 중...</value> <value>종료 중...</value>
</data> </data>
@@ -241,7 +246,10 @@ StackTrace:
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>새로운 버전 확인 중...</value> <value>새로운 버전 확인 중...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>새로운 버전을 내려받는 중: {0} ({1} MB).... 기다리는 동안, 이 프로그램이 고맙다면, 기부하는 것을 고려해보세요! :)</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"> <data name="UpdateFinished" xml:space="preserve">
<value>업데이트 작업 완료!</value> <value>업데이트 작업 완료!</value>
</data> </data>
@@ -280,7 +288,10 @@ StackTrace:
<value>등록되지 않은 {0}의 값을 입력하세요: </value> <value>등록되지 않은 {0}의 값을 입력하세요: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>IPC 호스트를 입력하세요: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>{0}의 알 수 없는 값을 받았습니다. 이것을 보고 바랍니다: {1}</value> <value>{0}의 알 수 없는 값을 받았습니다. 이것을 보고 바랍니다: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -289,10 +300,20 @@ StackTrace:
<value>동시에 {0}개 이상의 게임들을 플레이하는 것은 불가능합니다. {1}에 의해 단지 {0}개의 항목만 사용될 것입니다!</value> <value>동시에 {0}개 이상의 게임들을 플레이하는 것은 불가능합니다. {1}에 의해 단지 {0}개의 항목만 사용될 것입니다!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>AddressAccessDeniedException로 인해 IPC 서비스가 시작될 수 없습니다! ASF에서 제공하는 IPC 서비스를 사용하고 싶다면, ASF를 관리자 모드로 실행하거나 적절한 권한을 줘야 합니다!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>{0} IPC 명령에 대한 응답: {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>IPC 서버 준비 완료!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>{0} 에서 IPC 서버 시작...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>이 봇은 이미 중지되어 있습니다!</value> <value>이 봇은 이미 중지되어 있습니다!</value>
</data> </data>
@@ -652,5 +673,8 @@ StackTrace:
<value>스팀 맞춤 대기열 #{0}을 지웠습니다.</value> <value>스팀 맞춤 대기열 #{0}을 지웠습니다.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>게임을 확인 중인 {0}/{1} 개의 봇이 있습니다.</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> </root>

View File

@@ -670,5 +670,8 @@
<value>Baigta peržiūrėti Steam atradimo eilė #{0}.</value> <value>Baigta peržiūrėti Steam atradimo eilė #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Šiuo metu {0}/{1} botai turi visus tikrinamus žaidimus.</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> </root>

View File

@@ -673,5 +673,8 @@ StackTrace:
<value>Steam-ontdekkingswachtrij voltooid #{0}.</value> <value>Steam-ontdekkingswachtrij voltooid #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Er zijn {0}/{1} bots die de gecontroleerde spellen al in bezit hebben.</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> </root>

View File

@@ -673,5 +673,8 @@ StackTrace:
<value>Steam-ontdekkingswachtrij voltooid #{0}.</value> <value>Steam-ontdekkingswachtrij voltooid #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Er zijn {0}/{1} bots die de gecontroleerde spellen al in bezit hebben.</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> </root>

View File

@@ -673,5 +673,8 @@ StackTrace:
<value>Limpeza da lista de descoberta da Steam #{0} concluída.</value> <value>Limpeza da lista de descoberta da Steam #{0} concluída.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Os bots {0}/{1} já possuem todos os jogos que estão sendo verificados.</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> </root>

View File

@@ -674,5 +674,8 @@ inválidas, abortando!</value>
<value>Fila de exploração da Steam feita #{0}.</value> <value>Fila de exploração da Steam feita #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Os bots {0}/{1} já possuem todos os jogos que está sendo verificados.</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> </root>

View File

@@ -673,5 +673,8 @@ StackTrace:
<value>S-a terminat ștergerea cozii pentru lista de descoperiri Steam #{0}.</value> <value>S-a terminat ștergerea cozii pentru lista de descoperiri Steam #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Există {0}/{1} boți care dețin deja toate jocurile verificate.</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> </root>

View File

@@ -673,5 +673,8 @@ Yığın izleme:
<value>Steam keşif kuyruğu temizlenmesi bitti #{0}.</value> <value>Steam keşif kuyruğu temizlenmesi bitti #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>{0}/{1} bot zaten sahip olunan tüm oyunları denetliyor.</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> </root>

View File

@@ -181,14 +181,19 @@
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>无法检查最新版本 </value> <value>无法检查最新版本 </value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>无法继续更新,因为没有与当前正在运行的版本相关的版本! 无法自动更新到该版本。</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>不能进行更新,因为此版本没有任何资源!</value> <value>不能进行更新,因为此版本没有任何资源!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>收到一个用户输入请求,但进程目前正在以无显示模式运行 </value> <value>收到一个用户输入请求,但进程目前正在以无显示模式运行 </value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>拒绝处理该请求,因为未设置 SteamOwnerID </value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>正在退出...</value> <value>正在退出...</value>
</data> </data>
@@ -238,7 +243,10 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>正在检查新版本...</value> <value>正在检查新版本...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>更新中: {0} ({1} MB)...... 在你等待的时候,可以考虑趁现在进行捐赠!:)</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"> <data name="UpdateFinished" xml:space="preserve">
<value>更新完毕</value> <value>更新完毕</value>
</data> </data>
@@ -277,7 +285,10 @@
<value>请输入非正式的值 {0}: </value> <value>请输入非正式的值 {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>请输入你的IPC主机 </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>收到的{0} 为未知值,请报告此值:{1}</value> <value>收到的{0} 为未知值,请报告此值:{1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -286,10 +297,20 @@
<value>目前无法同时挂 {0} 个以上的游戏,只有 {1} 里面的前 {0} 个游戏可用!</value> <value>目前无法同时挂 {0} 个以上的游戏,只有 {1} 里面的前 {0} 个游戏可用!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>由于目标地址访问受拒绝,无法启动 IPC 服务 如果你想要使用ASF提供的 IPC 服务,请用管理员身份运行,或者给予更高的权限。</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>IPC 命令响应︰ {0} 及 {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>IPC 服务已就绪 </value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>在 {0} 上的启动 IPC 服务...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>这个帐号已停止运行!</value> <value>这个帐号已停止运行!</value>
</data> </data>
@@ -649,5 +670,8 @@
<value>已完成Steam探索队列 #{0}。</value> <value>已完成Steam探索队列 #{0}。</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>{0}/{1} 个BOT已经拥有所有正在被检查的游戏。</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> </root>

View File

@@ -42,14 +42,14 @@ namespace ArchiSteamFarm {
private const byte CodeInterval = 30; 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 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; private static int? SteamTimeDifference;
// "ERROR" is being used by SteamDesktopAuthenticator // "ERROR" is being used by SteamDesktopAuthenticator
internal bool HasCorrectDeviceID => !string.IsNullOrEmpty(DeviceID) && !DeviceID.Equals("ERROR"); 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 #pragma warning disable 649
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)] [JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]

View File

@@ -40,7 +40,7 @@ namespace ArchiSteamFarm {
private const string URL = "https://" + SharedInfo.StatisticsServer; private const string URL = "https://" + SharedInfo.StatisticsServer;
private readonly Bot Bot; private readonly Bot Bot;
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
private DateTime LastAnnouncementCheck = DateTime.MinValue; private DateTime LastAnnouncementCheck = DateTime.MinValue;
private DateTime LastHeartBeat = DateTime.MinValue; private DateTime LastHeartBeat = DateTime.MinValue;

View File

@@ -37,7 +37,7 @@ namespace ArchiSteamFarm {
private readonly Bot Bot; private readonly Bot Bot;
private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>(); 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; private bool ParsingScheduled;

View File

@@ -535,7 +535,17 @@ namespace ArchiSteamFarm {
ushort status = (ushort) responseMessage.StatusCode; ushort status = (ushort) responseMessage.StatusCode;
if ((status >= 300) && (status <= 399) && (maxRedirections > 0)) { if ((status >= 300) && (status <= 399) && (maxRedirections > 0)) {
redirectUri = responseMessage.Headers.Location; 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); redirectUri = new Uri(requestUri.GetLeftPart(UriPartial.Authority) + redirectUri);
} }
} else { } else {

View File

@@ -18,6 +18,6 @@
"OptimizationMode": 0, "OptimizationMode": 0,
"Statistics": true, "Statistics": true,
"SteamOwnerID": 0, "SteamOwnerID": 0,
"SteamProtocols": 7, "SteamProtocols": 1,
"UpdateChannel": 1 "UpdateChannel": 1
} }

View File

@@ -42,7 +42,7 @@ deploy:
- provider: GitHub - provider: GitHub
tag: $(appveyor_repo_tag_name) tag: $(appveyor_repo_tag_name)
release: ArchiSteamFarm V$(appveyor_repo_tag_name) release: ArchiSteamFarm V$(appveyor_repo_tag_name)
description: '**NOTICE:** Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features or rewritten implementations. If you don''t consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchi/ArchiSteamFarm/wiki/Release-cycle)** if you''d like to learn more.\n\n---\n\nThis is automated AppVeyor GitHub deployment, human-readable changelog should be available soon. In the meantime please refer to **[GitHub commits](https://github.com/JustArchi/ArchiSteamFarm/commits/$(appveyor_repo_tag_name))**.\n\n---\n\nASF is available for free. If you''re grateful for what we''re doing, please consider donating. Developing ASF requires massive amount of time and knowledge, especially when it comes to Steam (and its problems). Even 1$ is highly appreciated and shows that you care!\n\n [![Patreon support](https://img.shields.io/badge/Patreon-support-yellow.svg)](https://www.patreon.com/JustArchi) [![Paypal.me donate](https://img.shields.io/badge/Paypal.me-donate-yellow.svg)](https://www.paypal.me/JustArchi/1usd) [![Paypal donate](https://img.shields.io/badge/Paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [![Bitcoin donate](https://img.shields.io/badge/Bitcoin-donate-yellow.svg)](https://blockchain.info/payment_request?address=1Archi6M1r5b41Rvn1SY2FfJAzsrEUT7aT) [![Steam donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)' description: '### **NOTICE:** Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features or rewritten implementations. If you don''t consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchi/ArchiSteamFarm/wiki/Release-cycle)** if you''d like to learn more.\n\n---\n\nThis is automated AppVeyor GitHub deployment, human-readable changelog should be available soon. In the meantime please refer to **[GitHub commits](https://github.com/JustArchi/ArchiSteamFarm/commits/$(appveyor_repo_tag_name))**.\n\n---\n\nASF is available for free. If you''re grateful for what we''re doing, please consider donating. Developing ASF requires massive amount of time and knowledge, especially when it comes to Steam (and its problems). Even 1$ is highly appreciated and shows that you care!\n\n [![Patreon support](https://img.shields.io/badge/Patreon-support-yellow.svg)](https://www.patreon.com/JustArchi) [![Paypal.me donate](https://img.shields.io/badge/Paypal.me-donate-yellow.svg)](https://www.paypal.me/JustArchi/1usd) [![Paypal donate](https://img.shields.io/badge/Paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [![Bitcoin donate](https://img.shields.io/badge/Bitcoin-donate-yellow.svg)](https://blockchain.info/payment_request?address=1Archi6M1r5b41Rvn1SY2FfJAzsrEUT7aT) [![Steam donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)'
auth_token: auth_token:
secure: QC5gIDMvSpd43EG6qW8d1E3ZHiVU4aR7pbKQonXstjj/JtAABf5S1IbtoY4OsnOR secure: QC5gIDMvSpd43EG6qW8d1E3ZHiVU4aR7pbKQonXstjj/JtAABf5S1IbtoY4OsnOR
artifact: /.*/ artifact: /.*/
@@ -62,7 +62,7 @@ notifications:
{ {
"avatar_url": "https://www.appveyor.com/assets/img/appveyor-logo-256.png", "avatar_url": "https://www.appveyor.com/assets/img/appveyor-logo-256.png",
"username": "AppVeyor", "username": "AppVeyor",
"content": "[{{projectName}}:{{branch}}] {{commitMessage}} - {{committerName}} ({{commitId}}) | **{{status}}** | {{buildUrl}}" "content": "[{{projectName}}:{{branch}}] {{commitMessage}} - {{committerName}} ({{commitId}}) | {{buildUrl}}/artifacts | **{{status}}**"
} }
on_build_success: true on_build_success: true
on_build_failure: true on_build_failure: true