Compare commits

...

19 Commits

Author SHA1 Message Date
JustArchi
1beb2e01d5 Translations update 2017-07-23 12:06:16 +02:00
JustArchi
a57dc7387c Fix FarmingOrder + add ordering by redeem datetimes
Since we already have logic for that, such farming order is cool too.
2017-07-23 11:55:44 +02:00
JustArchi
0289f65072 Correct caching of apps that weren't released yet 2017-07-23 10:29:57 +02:00
JustArchi
7bb3b38ea4 Closes #522 2017-07-23 10:27:20 +02:00
JustArchi
54d1b8211b Closes #564 2017-07-23 05:35:46 +02:00
JustArchi
f97ffc76b5 Closes #510 2017-07-23 05:06:11 +02:00
JustArchi
39891b835d Closes #509 2017-07-23 04:12:00 +02:00
JustArchi
c3d6a6103e Closes #537 2017-07-23 03:43:20 +02:00
JustArchi
32e005bde3 Closes #542 2017-07-23 03:15:35 +02:00
JustArchi
2db65d324a Closes #545 2017-07-23 02:11:16 +02:00
JustArchi
65c2b81d0a Closes #580 2017-07-23 01:34:02 +02:00
JustArchi
a216eb48af Packages update 2017-07-23 01:19:30 +02:00
JustArchi
6f1e230b81 Translations update 2017-07-21 14:35:01 +02:00
JustArchi
15ec01f447 Finish leftovers from previous commit 2017-07-21 14:25:25 +02:00
JustArchi
f564cc5ebb Update ASF base code to SK 2.0 alpha5
There is a lot of changes in this one, I like how we got rid of SteamProtocol and got SteamProtocols instead.
2017-07-21 02:13:03 +02:00
JustArchi
be349d0557 Closes #598
We can actually move asking for token into OnConnected() method, nicely putting it into login limiter instead of being executed on every connection attempt. This way we'll avoid executing this function on obvius network issues.
2017-07-21 01:24:59 +02:00
JustArchi
93bc8373f8 crowdin-cli 2.0.17 2017-07-15 22:32:36 +02:00
JustArchi
4b11f61ea1 Translations update 2017-07-15 22:31:43 +02:00
JustArchi
060db81f38 Bump 2017-07-15 22:30:24 +02:00
59 changed files with 1591 additions and 807 deletions

View File

@@ -219,8 +219,8 @@ namespace ArchiSteamFarm {
return; return;
} }
// Before attempting to connect, initialize our list of CMs // Before attempting to connect, initialize our configuration
await Bot.InitializeCMs(Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider).ConfigureAwait(false); await Bot.InitializeSteamConfiguration(Program.GlobalConfig.SteamProtocols, Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider).ConfigureAwait(false);
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && IsValidBotName(botName)).OrderBy(botName => botName)) { foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && IsValidBotName(botName)).OrderBy(botName => botName)) {
Bot.RegisterBot(botName); Bot.RegisterBot(botName);

View File

@@ -96,60 +96,6 @@ namespace ArchiSteamFarm {
Client.Send(request); Client.Send(request);
} }
// TODO: Remove me once https://github.com/SteamRE/SteamKit/issues/305 is fixed
internal void LogOnWithoutMachineID(SteamUser.LogOnDetails details) {
if (details == null) {
throw new ArgumentNullException(nameof(details));
}
if (string.IsNullOrEmpty(details.Username) || (string.IsNullOrEmpty(details.Password) && string.IsNullOrEmpty(details.LoginKey))) {
throw new ArgumentException("LogOn requires a username and password to be set in 'details'.");
}
if (!string.IsNullOrEmpty(details.LoginKey) && !details.ShouldRememberPassword) {
// Prevent consumers from screwing this up.
// If should_remember_password is false, the login_key is ignored server-side.
// The inverse is not applicable (you can log in with should_remember_password and no login_key).
throw new ArgumentException("ShouldRememberPassword is required to be set to true in order to use LoginKey.");
}
ClientMsgProtobuf<CMsgClientLogon> logon = new ClientMsgProtobuf<CMsgClientLogon>(EMsg.ClientLogon);
SteamID steamId = new SteamID(details.AccountID, details.AccountInstance, Client.ConnectedUniverse, EAccountType.Individual);
if (details.LoginID.HasValue) {
logon.Body.obfustucated_private_ip = details.LoginID.Value;
}
logon.ProtoHeader.client_sessionid = 0;
logon.ProtoHeader.steamid = steamId.ConvertToUInt64();
logon.Body.account_name = details.Username;
logon.Body.password = details.Password;
logon.Body.should_remember_password = details.ShouldRememberPassword;
logon.Body.protocol_version = MsgClientLogon.CurrentProtocol;
logon.Body.client_os_type = (uint) details.ClientOSType;
logon.Body.client_language = details.ClientLanguage;
logon.Body.cell_id = details.CellID;
logon.Body.steam2_ticket_request = details.RequestSteam2Ticket;
logon.Body.client_package_version = 1771;
logon.Body.supports_rate_limit_response = true;
// steam guard
logon.Body.auth_code = details.AuthCode;
logon.Body.two_factor_code = details.TwoFactorCode;
logon.Body.login_key = details.LoginKey;
logon.Body.sha_sentryfile = details.SentryFileHash;
logon.Body.eresult_sentryfile = (int) (details.SentryFileHash != null ? EResult.OK : EResult.FileNotFound);
Client.Send(logon);
}
internal void PlayGames(IEnumerable<uint> gameIDs, string gameName = null) { internal void PlayGames(IEnumerable<uint> gameIDs, string gameName = null) {
if (gameIDs == null) { if (gameIDs == null) {
ArchiLogger.LogNullError(nameof(gameIDs)); ArchiLogger.LogNullError(nameof(gameIDs));

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.5</AssemblyVersion> <AssemblyVersion>3.0.0.6</AssemblyVersion>
<FileVersion>3.0.0.5</FileVersion> <FileVersion>3.0.0.6</FileVersion>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<ErrorReport>none</ErrorReport> <ErrorReport>none</ErrorReport>
<ApplicationIcon>ASF.ico</ApplicationIcon> <ApplicationIcon>ASF.ico</ApplicationIcon>
@@ -26,12 +26,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta2" /> <PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta3" />
<PackageReference Include="Humanizer" Version="2.2.0" /> <PackageReference Include="Humanizer" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Nito.AsyncEx.Coordination" Version="5.0.0-pre-02" />
<PackageReference Include="NLog" Version="5.0.0-beta09" /> <PackageReference Include="NLog" Version="5.0.0-beta09" />
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha4" /> <PackageReference Include="SteamKit2" Version="2.0.0-Alpha5" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0-preview2-25405-01" /> <PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0-preview2-25405-01" />
</ItemGroup> </ItemGroup>

View File

@@ -1088,6 +1088,33 @@ namespace ArchiSteamFarm {
return true; return true;
} }
internal async Task<bool> UnpackBooster(uint appID, ulong itemID) {
if ((appID == 0) || (itemID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(itemID));
return false;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Bot.ArchiLogger.LogNullError(nameof(sessionID));
return false;
}
string request = SteamCommunityURL + "/profiles/" + SteamID + "/ajaxunpackbooster";
Dictionary<string, string> data = new Dictionary<string, string>(3) {
{ "sessionid", sessionID },
{ "appid", appID.ToString() },
{ "communityitemid", itemID.ToString() }
};
Steam.GenericResponse response = await WebBrowser.UrlPostToJsonResultRetry<Steam.GenericResponse>(request, data).ConfigureAwait(false);
return response?.Result == EResult.OK;
}
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

File diff suppressed because it is too large Load Diff

View File

@@ -41,6 +41,11 @@ namespace ArchiSteamFarm {
internal readonly bool AcceptGifts; internal readonly bool AcceptGifts;
#pragma warning restore 649 #pragma warning restore 649
#pragma warning disable 649
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AutoDiscoveryQueue;
#pragma warning restore 649
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool CardDropsRestricted = true; internal readonly bool CardDropsRestricted = true;
@@ -80,6 +85,11 @@ namespace ArchiSteamFarm {
internal readonly bool HandleOfflineMessages; internal readonly bool HandleOfflineMessages;
#pragma warning restore 649 #pragma warning restore 649
#pragma warning disable 649
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool IdleRefundableGames = true;
#pragma warning restore 649
#pragma warning disable 649 #pragma warning disable 649
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool IsBotAccount; internal readonly bool IsBotAccount;
@@ -217,7 +227,11 @@ namespace ArchiSteamFarm {
HoursDescending, HoursDescending,
NamesAscending, NamesAscending,
NamesDescending, NamesDescending,
Random Random,
BadgeLevelsAscending,
BadgeLevelsDescending,
RedeemDateTimesAscending,
RedeemDateTimesDescending
} }
internal enum EPermission : byte { internal enum EPermission : byte {

View File

@@ -35,6 +35,9 @@ namespace ArchiSteamFarm {
private readonly object FileLock = new object(); private readonly object FileLock = new object();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<uint> IdlingPriorityAppIDs = new ConcurrentHashSet<uint>();
internal string LoginKey { internal string LoginKey {
get => _LoginKey; get => _LoginKey;
@@ -94,15 +97,49 @@ namespace ArchiSteamFarm {
} }
} }
internal IEnumerable<ulong> GetBlacklistedFromTradesSteamIDs() => BlacklistedFromTradesSteamIDs; internal void AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
internal bool IsBlacklistedFromTrades(ulong steamID) { ASF.ArchiLogger.LogNullError(nameof(appIDs));
if (steamID != 0) { return;
return BlacklistedFromTradesSteamIDs.Contains(steamID);
} }
ASF.ArchiLogger.LogNullError(nameof(steamID)); if (IdlingPriorityAppIDs.AddRange(appIDs)) {
return false; Save();
}
}
internal void CorrectMobileAuthenticatorDeviceID(string deviceID) {
if (string.IsNullOrEmpty(deviceID) || (MobileAuthenticator == null)) {
ASF.ArchiLogger.LogNullError(nameof(deviceID) + " || " + nameof(MobileAuthenticator));
return;
}
if (MobileAuthenticator.CorrectDeviceID(deviceID)) {
Save();
}
}
internal IEnumerable<ulong> GetBlacklistedFromTradesSteamIDs() => BlacklistedFromTradesSteamIDs;
internal IEnumerable<uint> GetIdlingPriorityAppIDs() => IdlingPriorityAppIDs;
internal bool IsBlacklistedFromTrades(ulong steamID) {
if (steamID == 0) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
return false;
}
bool result = BlacklistedFromTradesSteamIDs.Contains(steamID);
return result;
}
internal bool IsPriorityIdling(uint appID) {
if (appID == 0) {
ASF.ArchiLogger.LogNullError(nameof(appID));
return false;
}
bool result = IdlingPriorityAppIDs.Contains(appID);
return result;
} }
internal static BotDatabase Load(string filePath) { internal static BotDatabase Load(string filePath) {
@@ -144,7 +181,18 @@ namespace ArchiSteamFarm {
} }
} }
internal void Save() { internal void RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
}
if (IdlingPriorityAppIDs.RemoveRange(appIDs)) {
Save();
}
}
private void Save() {
string json = JsonConvert.SerializeObject(this); string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) { if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json)); ASF.ArchiLogger.LogNullError(nameof(json));

View File

@@ -34,6 +34,7 @@ using System.Threading.Tasks;
using ArchiSteamFarm.Localization; using ArchiSteamFarm.Localization;
using HtmlAgilityPack; using HtmlAgilityPack;
using Newtonsoft.Json; using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class CardsFarmer : IDisposable { internal sealed class CardsFarmer : IDisposable {
@@ -47,7 +48,7 @@ namespace ArchiSteamFarm {
internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>(); internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>();
[JsonProperty] [JsonProperty]
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>(); internal readonly ConcurrentSortedHashSet<Game> GamesToFarm = new ConcurrentSortedHashSet<Game>();
[JsonProperty] [JsonProperty]
internal TimeSpan TimeRemaining => new TimeSpan( internal TimeSpan TimeRemaining => new TimeSpan(
@@ -264,7 +265,7 @@ namespace ArchiSteamFarm {
} }
} }
private async Task CheckGame(uint appID, string name, float hours) { private async Task CheckGame(uint appID, string name, float hours, byte badgeLevel) {
if ((appID == 0) || string.IsNullOrEmpty(name) || (hours < 0)) { if ((appID == 0) || string.IsNullOrEmpty(name) || (hours < 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(name) + " || " + nameof(hours)); Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(name) + " || " + nameof(hours));
return; return;
@@ -280,7 +281,7 @@ namespace ArchiSteamFarm {
return; return;
} }
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining.Value)); GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining.Value, badgeLevel));
} }
private async Task CheckGamesForFarming() { private async Task CheckGamesForFarming() {
@@ -297,7 +298,7 @@ namespace ArchiSteamFarm {
return; return;
} }
HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_title_stats_content']"); HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_row_inner']");
if (htmlNodes == null) { if (htmlNodes == null) {
// No eligible badges whatsoever // No eligible badges whatsoever
return; return;
@@ -306,7 +307,9 @@ namespace ArchiSteamFarm {
HashSet<Task> backgroundTasks = new HashSet<Task>(); HashSet<Task> backgroundTasks = new HashSet<Task>();
foreach (HtmlNode htmlNode in htmlNodes) { foreach (HtmlNode htmlNode in htmlNodes) {
HtmlNode appIDNode = htmlNode.SelectSingleNode(".//div[@class='card_drop_info_dialog']"); HtmlNode statsNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_content']");
HtmlNode appIDNode = statsNode?.SelectSingleNode(".//div[@class='card_drop_info_dialog']");
if (appIDNode == null) { if (appIDNode == null) {
// It's just a badge, nothing more // It's just a badge, nothing more
continue; continue;
@@ -336,10 +339,10 @@ namespace ArchiSteamFarm {
continue; continue;
} }
if (IgnoredAppIDs.TryGetValue(appID, out DateTime lastPICSReport)) { if (IgnoredAppIDs.TryGetValue(appID, out DateTime ignoredUntil)) {
if (lastPICSReport.AddHours(HoursToIgnore) < DateTime.UtcNow) { if (ignoredUntil < DateTime.UtcNow) {
// This game served its time as being ignored // This game served its time as being ignored
IgnoredAppIDs.TryRemove(appID, out lastPICSReport); IgnoredAppIDs.TryRemove(appID, out _);
} else { } else {
// This game is still ignored // This game is still ignored
continue; continue;
@@ -347,7 +350,7 @@ namespace ArchiSteamFarm {
} }
// Cards // Cards
HtmlNode progressNode = htmlNode.SelectSingleNode(".//span[@class='progress_info_bold']"); HtmlNode progressNode = statsNode.SelectSingleNode(".//span[@class='progress_info_bold']");
if (progressNode == null) { if (progressNode == null) {
Bot.ArchiLogger.LogNullError(nameof(progressNode)); Bot.ArchiLogger.LogNullError(nameof(progressNode));
continue; continue;
@@ -380,7 +383,7 @@ namespace ArchiSteamFarm {
} }
// To save us on extra work, check cards earned so far first // To save us on extra work, check cards earned so far first
HtmlNode cardsEarnedNode = htmlNode.SelectSingleNode(".//div[@class='card_drop_info_header']"); HtmlNode cardsEarnedNode = statsNode.SelectSingleNode(".//div[@class='card_drop_info_header']");
if (cardsEarnedNode == null) { if (cardsEarnedNode == null) {
Bot.ArchiLogger.LogNullError(nameof(cardsEarnedNode)); Bot.ArchiLogger.LogNullError(nameof(cardsEarnedNode));
continue; continue;
@@ -419,7 +422,7 @@ namespace ArchiSteamFarm {
} }
// Hours // Hours
HtmlNode timeNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']"); HtmlNode timeNode = statsNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
if (timeNode == null) { if (timeNode == null) {
Bot.ArchiLogger.LogNullError(nameof(timeNode)); Bot.ArchiLogger.LogNullError(nameof(timeNode));
continue; continue;
@@ -443,7 +446,7 @@ namespace ArchiSteamFarm {
} }
// Names // Names
HtmlNode nameNode = htmlNode.SelectSingleNode("(.//div[@class='card_drop_info_body'])[last()]"); HtmlNode nameNode = statsNode.SelectSingleNode("(.//div[@class='card_drop_info_body'])[last()]");
if (nameNode == null) { if (nameNode == null) {
Bot.ArchiLogger.LogNullError(nameof(nameNode)); Bot.ArchiLogger.LogNullError(nameof(nameNode));
continue; continue;
@@ -477,14 +480,44 @@ namespace ArchiSteamFarm {
name = WebUtility.HtmlDecode(name.Substring(nameStartIndex, nameEndIndex - nameStartIndex)); name = WebUtility.HtmlDecode(name.Substring(nameStartIndex, nameEndIndex - nameStartIndex));
// We have two possible cases here // Levels
// Either we have decent info about appID, name, hours and cardsRemaining (cardsRemaining > 0) byte badgeLevel = 0;
// OR we strongly believe that Steam lied to us, in this case we will need to check game invidually (cardsRemaining == 0)
HtmlNode levelNode = htmlNode.SelectSingleNode(".//div[@class='badge_info_description']/div[2]");
if (levelNode != null) {
// There is no levelNode if we didn't craft that badge yet (level 0)
string levelString = levelNode.InnerText;
if (string.IsNullOrEmpty(levelString)) {
Bot.ArchiLogger.LogNullError(nameof(levelString));
continue;
}
int levelIndex = levelString.IndexOf("Level ", StringComparison.OrdinalIgnoreCase);
if (levelIndex < 0) {
Bot.ArchiLogger.LogNullError(nameof(levelIndex));
continue;
}
levelIndex += 6;
if (levelString.Length <= levelIndex) {
Bot.ArchiLogger.LogNullError(nameof(levelIndex));
continue;
}
levelString = levelString.Substring(levelIndex, 1);
if (!byte.TryParse(levelString, out badgeLevel) || (badgeLevel == 0) || (badgeLevel > 5)) {
Bot.ArchiLogger.LogNullError(nameof(badgeLevel));
continue;
}
}
// Done with parsing, we have two possible cases here
// Either we have decent info about appID, name, hours, cardsRemaining (cardsRemaining > 0) and level
// OR we strongly believe that Steam lied to us, in this case we will need to check game invidually (cardsRemaining == 0)
if (cardsRemaining > 0) { if (cardsRemaining > 0) {
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining)); GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining, badgeLevel));
} else { } else {
Task task = CheckGame(appID, name, hours); Task task = CheckGame(appID, name, hours, badgeLevel);
switch (Program.GlobalConfig.OptimizationMode) { switch (Program.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage: case GlobalConfig.EOptimizationMode.MinMemoryUsage:
await task.ConfigureAwait(false); await task.ConfigureAwait(false);
@@ -523,20 +556,37 @@ 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> gamesToFarmSolo = GamesToFarm.Count > 1 ? new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) : new HashSet<Game>(GamesToFarm); HashSet<Game> playableGamesToFarmSolo = new HashSet<Game>();
if (gamesToFarmSolo.Count > 0) { foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) {
while (gamesToFarmSolo.Count > 0) { if (await IsPlayableGame(game).ConfigureAwait(false)) {
Game game = gamesToFarmSolo.First(); playableGamesToFarmSolo.Add(game);
if (await FarmSolo(game).ConfigureAwait(false)) { }
gamesToFarmSolo.Remove(game); }
if (playableGamesToFarmSolo.Count > 0) {
while (playableGamesToFarmSolo.Count > 0) {
Game playableGame = playableGamesToFarmSolo.First();
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
playableGamesToFarmSolo.Remove(playableGame);
} else { } else {
NowFarming = false; NowFarming = false;
return; return;
} }
} }
} else { } else {
if (FarmMultiple(GamesToFarm.OrderByDescending(game => game.HoursPlayed).Take(ArchiHandler.MaxGamesPlayedConcurrently))) { HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", GamesToFarm.Select(game => game.AppID)))); 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 { } else {
NowFarming = false; NowFarming = false;
return; return;
@@ -547,9 +597,20 @@ namespace ArchiSteamFarm {
// 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 game = GamesToFarm.First(); Game playableGame = null;
if (await FarmSolo(game).ConfigureAwait(false)) { foreach (Game game in GamesToFarm) {
continue; if (!await IsPlayableGame(game).ConfigureAwait(false)) {
continue;
}
playableGame = game;
break;
}
if (playableGame != null) {
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
continue;
}
} }
NowFarming = false; NowFarming = false;
@@ -558,7 +619,7 @@ namespace ArchiSteamFarm {
} }
} while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault()); } while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault());
CurrentGamesFarming.ClearAndTrim(); CurrentGamesFarming.Clear();
NowFarming = false; NowFarming = false;
Bot.ArchiLogger.LogGenericInfo(Strings.IdlingFinished); Bot.ArchiLogger.LogGenericInfo(Strings.IdlingFinished);
@@ -573,37 +634,31 @@ namespace ArchiSteamFarm {
bool success = true; bool success = true;
uint appID = await Bot.GetAppIDForIdling(game.AppID).ConfigureAwait(false); if (game.AppID != game.PlayableAppID) {
if (appID != 0) { Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningIdlingGameMismatch, game.AppID, game.GameName, game.PlayableAppID));
if (appID != game.AppID) { }
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningIdlingGameMismatch, game.AppID, game.GameName, appID));
Bot.PlayGame(game.PlayableAppID, Bot.BotConfig.CustomGamePlayedWhileFarming);
DateTime endFarmingDate = DateTime.UtcNow.AddHours(Program.GlobalConfig.MaxFarmingTime);
bool? keepFarming = await ShouldFarm(game).ConfigureAwait(false);
while (keepFarming.GetValueOrDefault(true) && (DateTime.UtcNow < endFarmingDate)) {
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();
success = KeepFarming;
} }
Bot.PlayGame(appID, Bot.BotConfig.CustomGamePlayedWhileFarming); // Don't forget to update our GamesToFarm hours
DateTime endFarmingDate = DateTime.UtcNow.AddHours(Program.GlobalConfig.MaxFarmingTime); game.HoursPlayed += (float) DateTime.UtcNow.Subtract(startFarmingPeriod).TotalHours;
bool? keepFarming = await ShouldFarm(game).ConfigureAwait(false); if (!success) {
while (keepFarming.GetValueOrDefault(true) && (DateTime.UtcNow < endFarmingDate)) { break;
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();
success = KeepFarming;
}
// Don't forget to update our GamesToFarm hours
game.HoursPlayed += (float) DateTime.UtcNow.Subtract(startFarmingPeriod).TotalHours;
if (!success) {
break;
}
keepFarming = await ShouldFarm(game).ConfigureAwait(false);
} }
} else {
IgnoredAppIDs[game.AppID] = DateTime.UtcNow; keepFarming = await ShouldFarm(game).ConfigureAwait(false);
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingGameNotPossible, game.AppID, game.GameName));
} }
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StoppedIdling, game.AppID, game.GameName)); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StoppedIdling, game.AppID, game.GameName));
@@ -667,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 = FarmHours(CurrentGamesFarming);
CurrentGamesFarming.ClearAndTrim(); CurrentGamesFarming.Clear();
return result; return result;
} }
@@ -682,7 +737,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdling, game.AppID, game.GameName)); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdling, game.AppID, game.GameName));
bool result = await Farm(game).ConfigureAwait(false); bool result = await Farm(game).ConfigureAwait(false);
CurrentGamesFarming.ClearAndTrim(); CurrentGamesFarming.Clear();
if (!result) { if (!result) {
return false; return false;
@@ -751,7 +806,7 @@ namespace ArchiSteamFarm {
} }
} }
GamesToFarm.ClearAndTrim(); GamesToFarm.Clear();
List<Task> tasks = new List<Task>(); List<Task> tasks = new List<Task>();
Task mainTask = CheckPage(htmlDocument); Task mainTask = CheckPage(htmlDocument);
@@ -798,6 +853,18 @@ namespace ArchiSteamFarm {
return true; return true;
} }
private async Task<bool> IsPlayableGame(Game game) {
(uint PlayableAppID, DateTime IgnoredUntil) appData = await Bot.GetAppDataForIdling(game.AppID).ConfigureAwait(false);
if (appData.PlayableAppID != 0) {
game.PlayableAppID = appData.PlayableAppID;
return true;
}
IgnoredAppIDs[game.AppID] = appData.IgnoredUntil != DateTime.MaxValue ? appData.IgnoredUntil : DateTime.UtcNow.AddHours(HoursToIgnore);
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingGameNotPossible, game.AppID, game.GameName));
return false;
}
private async Task<bool?> ShouldFarm(Game game) { private async Task<bool?> ShouldFarm(Game game) {
if (game == null) { if (game == null) {
Bot.ArchiLogger.LogNullError(nameof(game)); Bot.ArchiLogger.LogNullError(nameof(game));
@@ -817,50 +884,94 @@ namespace ArchiSteamFarm {
} }
private void SortGamesToFarm() { private void SortGamesToFarm() {
IOrderedEnumerable<Game> gamesToFarm; IOrderedEnumerable<Game> gamesToFarm = GamesToFarm.OrderBy(game => Bot.IsPriorityIdling(game.AppID) ? 1 : 0);
switch (Bot.BotConfig.FarmingOrder) { switch (Bot.BotConfig.FarmingOrder) {
case BotConfig.EFarmingOrder.Unordered: case BotConfig.EFarmingOrder.Unordered:
return; break;
case BotConfig.EFarmingOrder.AppIDsAscending: case BotConfig.EFarmingOrder.AppIDsAscending:
gamesToFarm = GamesToFarm.OrderBy(game => game.AppID); gamesToFarm = gamesToFarm.ThenBy(game => game.AppID);
break; break;
case BotConfig.EFarmingOrder.AppIDsDescending: case BotConfig.EFarmingOrder.AppIDsDescending:
gamesToFarm = GamesToFarm.OrderByDescending(game => game.AppID); gamesToFarm = gamesToFarm.ThenByDescending(game => game.AppID);
break;
case BotConfig.EFarmingOrder.BadgeLevelsAscending:
gamesToFarm = gamesToFarm.ThenBy(game => game.BadgeLevel);
break;
case BotConfig.EFarmingOrder.BadgeLevelsDescending:
gamesToFarm = gamesToFarm.ThenByDescending(game => game.BadgeLevel);
break; break;
case BotConfig.EFarmingOrder.CardDropsAscending: case BotConfig.EFarmingOrder.CardDropsAscending:
gamesToFarm = GamesToFarm.OrderBy(game => game.CardsRemaining); gamesToFarm = gamesToFarm.ThenBy(game => game.CardsRemaining);
break; break;
case BotConfig.EFarmingOrder.CardDropsDescending: case BotConfig.EFarmingOrder.CardDropsDescending:
gamesToFarm = GamesToFarm.OrderByDescending(game => game.CardsRemaining); gamesToFarm = gamesToFarm.ThenByDescending(game => game.CardsRemaining);
break; break;
case BotConfig.EFarmingOrder.HoursAscending: case BotConfig.EFarmingOrder.HoursAscending:
gamesToFarm = GamesToFarm.OrderBy(game => game.HoursPlayed); gamesToFarm = gamesToFarm.ThenBy(game => game.HoursPlayed);
break; break;
case BotConfig.EFarmingOrder.HoursDescending: case BotConfig.EFarmingOrder.HoursDescending:
gamesToFarm = GamesToFarm.OrderByDescending(game => game.HoursPlayed); gamesToFarm = gamesToFarm.ThenByDescending(game => game.HoursPlayed);
break; break;
case BotConfig.EFarmingOrder.NamesAscending: case BotConfig.EFarmingOrder.NamesAscending:
gamesToFarm = GamesToFarm.OrderBy(game => game.GameName); gamesToFarm = gamesToFarm.ThenBy(game => game.GameName);
break; break;
case BotConfig.EFarmingOrder.NamesDescending: case BotConfig.EFarmingOrder.NamesDescending:
gamesToFarm = GamesToFarm.OrderByDescending(game => game.GameName); gamesToFarm = gamesToFarm.ThenByDescending(game => game.GameName);
break; break;
case BotConfig.EFarmingOrder.Random: case BotConfig.EFarmingOrder.Random:
gamesToFarm = GamesToFarm.OrderBy(game => Utilities.RandomNext()); gamesToFarm = gamesToFarm.ThenBy(game => Utilities.RandomNext());
break;
case BotConfig.EFarmingOrder.RedeemDateTimesAscending:
case BotConfig.EFarmingOrder.RedeemDateTimesDescending:
Dictionary<uint, DateTime> redeemDates = new Dictionary<uint, DateTime>(GamesToFarm.Count);
foreach (Game game in gamesToFarm) {
DateTime redeemDate = DateTime.MinValue;
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
foreach (uint packageID in packageIDs) {
if (!Bot.OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) {
continue;
}
if (packageData.TimeCreated > redeemDate) {
redeemDate = packageData.TimeCreated;
}
}
}
redeemDates[game.AppID] = redeemDate;
}
switch (Bot.BotConfig.FarmingOrder) {
case BotConfig.EFarmingOrder.RedeemDateTimesAscending:
gamesToFarm = gamesToFarm.ThenBy(game => redeemDates[game.AppID]);
break;
case BotConfig.EFarmingOrder.RedeemDateTimesDescending:
gamesToFarm = gamesToFarm.ThenByDescending(game => redeemDates[game.AppID]);
break;
default:
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder)));
return;
}
break; break;
default: default:
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder))); Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder)));
return; return;
} }
GamesToFarm.ReplaceWith(gamesToFarm.ToList()); // We must call ToList() here as we can't enumerate during replacing // We must call ToList() here as we can't enumerate during replacing
GamesToFarm.ReplaceWith(gamesToFarm.ToList());
} }
internal sealed class Game { internal sealed class Game {
[JsonProperty] [JsonProperty]
internal readonly uint AppID; internal readonly uint AppID;
internal readonly byte BadgeLevel;
[JsonProperty] [JsonProperty]
internal readonly string GameName; internal readonly string GameName;
@@ -870,9 +981,9 @@ namespace ArchiSteamFarm {
[JsonProperty] [JsonProperty]
internal float HoursPlayed { get; set; } internal float HoursPlayed { get; set; }
//internal string HeaderURL => "https://steamcdn-a.akamaihd.net/steam/apps/" + AppID + "/header.jpg"; internal uint PlayableAppID { get; set; }
internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining) { internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining, byte badgeLevel) {
if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) { if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) {
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining)); throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
} }
@@ -881,14 +992,17 @@ namespace ArchiSteamFarm {
GameName = gameName; GameName = gameName;
HoursPlayed = hoursPlayed; HoursPlayed = hoursPlayed;
CardsRemaining = cardsRemaining; CardsRemaining = cardsRemaining;
BadgeLevel = badgeLevel;
PlayableAppID = appID;
} }
public override bool Equals(object obj) { public override bool Equals(object obj) {
if (obj == null) { if (ReferenceEquals(null, obj)) {
return false; return false;
} }
if (obj == this) { if (ReferenceEquals(this, obj)) {
return true; return true;
} }

View File

@@ -25,27 +25,29 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Nito.AsyncEx; using System.Threading;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> { internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> {
public T Current => Enumerator.Current; public T Current => Enumerator.Current;
private readonly IEnumerator<T> Enumerator; private readonly IEnumerator<T> Enumerator;
private readonly IDisposable Lock; private readonly SemaphoreSlim SemaphoreSlim;
object IEnumerator.Current => Current; object IEnumerator.Current => Current;
internal ConcurrentEnumerator(ICollection<T> collection, AsyncReaderWriterLock rwLock) { internal ConcurrentEnumerator(ICollection<T> collection, SemaphoreSlim semaphoreSlim) {
if ((collection == null) || (rwLock == null)) { if ((collection == null) || (semaphoreSlim == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(rwLock)); throw new ArgumentNullException(nameof(collection) + " || " + nameof(semaphoreSlim));
} }
Lock = rwLock.ReaderLock(); SemaphoreSlim = semaphoreSlim;
SemaphoreSlim.Wait();
Enumerator = collection.GetEnumerator(); Enumerator = collection.GetEnumerator();
} }
public void Dispose() => Lock.Dispose(); public void Dispose() => SemaphoreSlim.Release();
public bool MoveNext() => Enumerator.MoveNext(); public bool MoveNext() => Enumerator.MoveNext();
public void Reset() => Enumerator.Reset(); public void Reset() => Enumerator.Reset();
} }

View File

@@ -23,168 +23,103 @@
*/ */
using System.Collections; using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Nito.AsyncEx;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> { internal sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> {
public int Count { public int Count => BackingCollection.Count;
get {
using (Lock.ReaderLock()) {
return HashSet.Count;
}
}
}
public bool IsReadOnly => false; public bool IsReadOnly => false;
private readonly HashSet<T> HashSet = new HashSet<T>(); private readonly ConcurrentDictionary<T, bool> BackingCollection = new ConcurrentDictionary<T, bool>();
private readonly AsyncReaderWriterLock Lock = new AsyncReaderWriterLock();
public bool Add(T item) { public bool Add(T item) => BackingCollection.TryAdd(item, true);
using (Lock.WriterLock()) { public void Clear() => BackingCollection.Clear();
return HashSet.Add(item); public bool Contains(T item) => BackingCollection.ContainsKey(item);
} public void CopyTo(T[] array, int arrayIndex) => BackingCollection.Keys.CopyTo(array, arrayIndex);
}
public void Clear() {
using (Lock.WriterLock()) {
HashSet.Clear();
}
}
public bool Contains(T item) {
using (Lock.ReaderLock()) {
return HashSet.Contains(item);
}
}
public void CopyTo(T[] array, int arrayIndex) {
using (Lock.ReaderLock()) {
HashSet.CopyTo(array, arrayIndex);
}
}
public void ExceptWith(IEnumerable<T> other) { public void ExceptWith(IEnumerable<T> other) {
using (Lock.WriterLock()) { foreach (T item in other) {
HashSet.ExceptWith(other); Remove(item);
} }
} }
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(HashSet, Lock); public IEnumerator<T> GetEnumerator() => BackingCollection.Keys.GetEnumerator();
public void IntersectWith(IEnumerable<T> other) { public void IntersectWith(IEnumerable<T> other) {
using (Lock.WriterLock()) { ICollection<T> collection = other as ICollection<T> ?? other.ToList();
HashSet.IntersectWith(other); foreach (T item in this.Where(item => !collection.Contains(item))) {
Remove(item);
} }
} }
public bool IsProperSubsetOf(IEnumerable<T> other) { public bool IsProperSubsetOf(IEnumerable<T> other) {
using (Lock.ReaderLock()) { ICollection<T> collection = other as ICollection<T> ?? other.ToList();
return HashSet.IsProperSubsetOf(other); return (collection.Count != Count) && IsSubsetOf(collection);
}
} }
public bool IsProperSupersetOf(IEnumerable<T> other) { public bool IsProperSupersetOf(IEnumerable<T> other) {
using (Lock.ReaderLock()) { ICollection<T> collection = other as ICollection<T> ?? other.ToList();
return HashSet.IsProperSupersetOf(other); return (collection.Count != Count) && IsSupersetOf(collection);
}
} }
public bool IsSubsetOf(IEnumerable<T> other) { public bool IsSubsetOf(IEnumerable<T> other) {
using (Lock.ReaderLock()) { ICollection<T> collection = other as ICollection<T> ?? other.ToList();
return HashSet.IsSubsetOf(other); return this.AsParallel().All(collection.Contains);
}
} }
public bool IsSupersetOf(IEnumerable<T> other) { public bool IsSupersetOf(IEnumerable<T> other) => other.AsParallel().All(Contains);
using (Lock.ReaderLock()) { public bool Overlaps(IEnumerable<T> other) => other.AsParallel().Any(Contains);
return HashSet.IsSupersetOf(other); public bool Remove(T item) => BackingCollection.TryRemove(item, out _);
}
}
public bool Overlaps(IEnumerable<T> other) {
using (Lock.ReaderLock()) {
return HashSet.Overlaps(other);
}
}
public bool Remove(T item) {
using (Lock.WriterLock()) {
return HashSet.Remove(item);
}
}
public bool SetEquals(IEnumerable<T> other) { public bool SetEquals(IEnumerable<T> other) {
using (Lock.ReaderLock()) { ICollection<T> collection = other as ICollection<T> ?? other.ToList();
return HashSet.SetEquals(other); return (collection.Count == Count) && collection.AsParallel().All(Contains);
}
} }
public void SymmetricExceptWith(IEnumerable<T> other) { public void SymmetricExceptWith(IEnumerable<T> other) {
using (Lock.WriterLock()) { ICollection<T> collection = other as ICollection<T> ?? other.ToList();
HashSet.SymmetricExceptWith(other);
HashSet<T> removed = new HashSet<T>();
foreach (T item in collection.Where(Contains)) {
removed.Add(item);
Remove(item);
}
foreach (T item in collection.Where(item => !removed.Contains(item))) {
Add(item);
} }
} }
public void UnionWith(IEnumerable<T> other) { public void UnionWith(IEnumerable<T> other) {
using (Lock.WriterLock()) { foreach (T otherElement in other) {
HashSet.UnionWith(other); Add(otherElement);
} }
} }
void ICollection<T>.Add(T item) => Add(item); void ICollection<T>.Add(T item) => Add(item);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal bool AddRange(IEnumerable<T> items) { // We use Count() and not Any() because we must ensure full loop pass
using (Lock.WriterLock()) { internal bool AddRange(IEnumerable<T> items) => items.Count(Add) > 0;
// We use Count() and not Any() because we must ensure full loop pass
return items.Count(item => HashSet.Add(item)) > 0; // We use Count() and not Any() because we must ensure full loop pass
internal bool RemoveRange(IEnumerable<T> items) => items.Count(Remove) > 0;
internal bool ReplaceIfNeededWith(ICollection<T> other) {
if (SetEquals(other)) {
return false;
} }
ReplaceWith(other);
return true;
} }
internal void ClearAndTrim() { internal void ReplaceWith(IEnumerable<T> other) {
using (Lock.WriterLock()) { BackingCollection.Clear();
HashSet.Clear(); foreach (T item in other) {
HashSet.TrimExcess(); BackingCollection[item] = true;
}
}
internal bool RemoveRange(IEnumerable<T> items) {
using (Lock.WriterLock()) {
// We use Count() and not Any() because we must ensure full loop pass
return items.Count(item => HashSet.Remove(item)) > 0;
}
}
internal bool ReplaceIfNeededWith(ICollection<T> items) {
using (Lock.WriterLock()) {
if (HashSet.SetEquals(items)) {
return false;
}
HashSet.Clear();
foreach (T item in items) {
HashSet.Add(item);
}
HashSet.TrimExcess();
return true;
}
}
internal void ReplaceWith(IEnumerable<T> items) {
using (Lock.WriterLock()) {
HashSet.Clear();
foreach (T item in items) {
HashSet.Add(item);
}
HashSet.TrimExcess();
} }
} }
} }

View File

@@ -0,0 +1,220 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2017 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
namespace ArchiSteamFarm {
internal sealed class ConcurrentSortedHashSet<T> : IDisposable, IReadOnlyCollection<T>, ISet<T> {
public int Count {
get {
SemaphoreSlim.Wait();
try {
return BackingCollection.Count;
} finally {
SemaphoreSlim.Release();
}
}
}
public bool IsReadOnly => false;
private readonly HashSet<T> BackingCollection = new HashSet<T>();
private readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1);
public bool Add(T item) {
SemaphoreSlim.Wait();
try {
return BackingCollection.Add(item);
} finally {
SemaphoreSlim.Release();
}
}
public void Clear() {
SemaphoreSlim.Wait();
try {
BackingCollection.Clear();
} finally {
SemaphoreSlim.Release();
}
}
public bool Contains(T item) {
SemaphoreSlim.Wait();
try {
return BackingCollection.Contains(item);
} finally {
SemaphoreSlim.Release();
}
}
public void CopyTo(T[] array, int arrayIndex) {
SemaphoreSlim.Wait();
try {
BackingCollection.CopyTo(array, arrayIndex);
} finally {
SemaphoreSlim.Release();
}
}
public void Dispose() => SemaphoreSlim.Dispose();
public void ExceptWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
BackingCollection.ExceptWith(other);
} finally {
SemaphoreSlim.Release();
}
}
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, SemaphoreSlim);
public void IntersectWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
BackingCollection.IntersectWith(other);
} finally {
SemaphoreSlim.Release();
}
}
public bool IsProperSubsetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
return BackingCollection.IsProperSubsetOf(other);
} finally {
SemaphoreSlim.Release();
}
}
public bool IsProperSupersetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
return BackingCollection.IsProperSupersetOf(other);
} finally {
SemaphoreSlim.Release();
}
}
public bool IsSubsetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
return BackingCollection.IsSubsetOf(other);
} finally {
SemaphoreSlim.Release();
}
}
public bool IsSupersetOf(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
return BackingCollection.IsSupersetOf(other);
} finally {
SemaphoreSlim.Release();
}
}
public bool Overlaps(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
return BackingCollection.Overlaps(other);
} finally {
SemaphoreSlim.Release();
}
}
public bool Remove(T item) {
SemaphoreSlim.Wait();
try {
return BackingCollection.Remove(item);
} finally {
SemaphoreSlim.Release();
}
}
public bool SetEquals(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
return BackingCollection.SetEquals(other);
} finally {
SemaphoreSlim.Release();
}
}
public void SymmetricExceptWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
BackingCollection.SymmetricExceptWith(other);
} finally {
SemaphoreSlim.Release();
}
}
public void UnionWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
BackingCollection.UnionWith(other);
} finally {
SemaphoreSlim.Release();
}
}
void ICollection<T>.Add(T item) => Add(item);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal void ReplaceWith(IEnumerable<T> other) {
SemaphoreSlim.Wait();
try {
BackingCollection.Clear();
foreach (T item in other) {
BackingCollection.Add(item);
}
} finally {
SemaphoreSlim.Release();
}
}
}
}

View File

@@ -26,9 +26,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net.Sockets;
using ArchiSteamFarm.Localization; using ArchiSteamFarm.Localization;
using Newtonsoft.Json; using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
@@ -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 ProtocolType SteamProtocol = ProtocolType.Tcp; internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.All;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable; internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;
@@ -152,17 +152,6 @@ namespace ArchiSteamFarm {
return null; return null;
} }
// SK2 supports only TCP and UDP steam protocols
// Ensure that user can't screw this up
switch (globalConfig.SteamProtocol) {
case ProtocolType.Tcp:
case ProtocolType.Udp:
break;
default:
ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(globalConfig.SteamProtocol), globalConfig.SteamProtocol));
return null;
}
// User might not know what he's doing // User might not know what he's doing
// Ensure that he can't screw core ASF variables // Ensure that he can't screw core ASF variables
if (globalConfig.MaxFarmingTime == 0) { if (globalConfig.MaxFarmingTime == 0) {

View File

@@ -23,18 +23,18 @@
*/ */
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class GlobalDatabase : IDisposable { internal sealed class GlobalDatabase : IDisposable {
private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings { [JsonProperty(Required = Required.DisallowNull)]
Converters = new List<JsonConverter>(2) { internal readonly ConcurrentDictionary<uint, ConcurrentHashSet<uint>> AppIDsToPackageIDs = new ConcurrentDictionary<uint, ConcurrentHashSet<uint>>();
new IPAddressConverter(),
new IPEndPointConverter()
}
};
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly Guid Guid = Guid.NewGuid(); internal readonly Guid Guid = Guid.NewGuid();
@@ -44,6 +44,8 @@ namespace ArchiSteamFarm {
private readonly object FileLock = new object(); private readonly object FileLock = new object();
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1);
internal uint CellID { internal uint CellID {
get => _CellID; get => _CellID;
set { set {
@@ -89,7 +91,7 @@ namespace ArchiSteamFarm {
GlobalDatabase globalDatabase; GlobalDatabase globalDatabase;
try { try {
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(filePath), CustomSerializerSettings); globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(filePath));
} catch (Exception e) { } catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e); ASF.ArchiLogger.LogGenericException(e);
return null; return null;
@@ -104,10 +106,46 @@ namespace ArchiSteamFarm {
return globalDatabase; return globalDatabase;
} }
internal async Task RefreshPackageIDs(Bot bot, ICollection<uint> packageIDs) {
if ((bot == null) || (packageIDs == null) || (packageIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(packageIDs));
return;
}
await PackagesRefreshSemaphore.WaitAsync().ConfigureAwait(false);
try {
HashSet<uint> missingPackageIDs = new HashSet<uint>(packageIDs.AsParallel().Where(packageID => AppIDsToPackageIDs.Values.All(packages => !packages.Contains(packageID))));
if (missingPackageIDs.Count == 0) {
return;
}
Dictionary<uint, HashSet<uint>> appIDsToPackageIDs = await bot.GetAppIDsToPackageIDs(missingPackageIDs);
if ((appIDsToPackageIDs == null) || (appIDsToPackageIDs.Count == 0)) {
return;
}
foreach (KeyValuePair<uint, HashSet<uint>> appIDtoPackageID in appIDsToPackageIDs) {
if (!AppIDsToPackageIDs.TryGetValue(appIDtoPackageID.Key, out ConcurrentHashSet<uint> packages)) {
packages = new ConcurrentHashSet<uint>();
AppIDsToPackageIDs[appIDtoPackageID.Key] = packages;
}
foreach (uint package in appIDtoPackageID.Value) {
packages.Add(package);
}
}
Save();
} finally {
PackagesRefreshSemaphore.Release();
}
}
private void OnServerListUpdated(object sender, EventArgs e) => Save(); private void OnServerListUpdated(object sender, EventArgs e) => Save();
private void Save() { private void Save() {
string json = JsonConvert.SerializeObject(this, CustomSerializerSettings); string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) { if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json)); ASF.ArchiLogger.LogNullError(nameof(json));
return; return;

View File

@@ -1,44 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2017 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm {
internal sealed class IPAddressConverter : JsonConverter {
public override bool CanConvert(Type objectType) => objectType == typeof(IPAddress);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JToken token = JToken.Load(reader);
return IPAddress.Parse(token.Value<string>());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
IPAddress ip = (IPAddress) value;
writer.WriteValue(ip.ToString());
}
}
}

View File

@@ -1,51 +0,0 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2017 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm {
internal sealed class IPEndPointConverter : JsonConverter {
public override bool CanConvert(Type objectType) => objectType == typeof(IPEndPoint);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
JObject jo = JObject.Load(reader);
IPAddress address = jo["Address"].ToObject<IPAddress>(serializer);
ushort port = jo["Port"].Value<ushort>();
return new IPEndPoint(address, port);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
IPEndPoint ep = (IPEndPoint) value;
writer.WriteStartObject();
writer.WritePropertyName("Address");
serializer.Serialize(writer, ep.Address);
writer.WritePropertyName("Port");
writer.WriteValue(ep.Port);
writer.WriteEndObject();
}
}
}

View File

@@ -24,7 +24,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using SteamKit2.Discovery; using SteamKit2.Discovery;
@@ -32,19 +32,19 @@ using SteamKit2.Discovery;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class InMemoryServerListProvider : IServerListProvider { internal sealed class InMemoryServerListProvider : IServerListProvider {
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<IPEndPoint> Servers = new ConcurrentHashSet<IPEndPoint>(); private readonly ConcurrentHashSet<ServerRecordEndPoint> ServerRecords = new ConcurrentHashSet<ServerRecordEndPoint>();
public Task<IEnumerable<IPEndPoint>> FetchServerListAsync() => Task.FromResult<IEnumerable<IPEndPoint>>(Servers); public Task<IEnumerable<ServerRecord>> FetchServerListAsync() => Task.FromResult(ServerRecords.Select(server => ServerRecord.CreateServer(server.Host, server.Port, server.ProtocolTypes)));
public Task UpdateServerListAsync(IEnumerable<IPEndPoint> endPoints) { public Task UpdateServerListAsync(IEnumerable<ServerRecord> endpoints) {
if (endPoints == null) { if (endpoints == null) {
ASF.ArchiLogger.LogNullError(nameof(endPoints)); ASF.ArchiLogger.LogNullError(nameof(endpoints));
return Task.CompletedTask; return Task.CompletedTask;
} }
HashSet<IPEndPoint> newServers = new HashSet<IPEndPoint>(endPoints); HashSet<ServerRecordEndPoint> newServerRecords = new HashSet<ServerRecordEndPoint>(endpoints.Select(ep => new ServerRecordEndPoint(ep.GetHost(), (ushort) ep.GetPort(), ep.ProtocolTypes)));
if (!Servers.ReplaceIfNeededWith(newServers)) { if (!ServerRecords.ReplaceIfNeededWith(newServerRecords)) {
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@@ -220,6 +220,18 @@ namespace ArchiSteamFarm.JSON {
private ConfirmationResponse() { } private ConfirmationResponse() { }
} }
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class GenericResponse {
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal readonly EResult Result;
#pragma warning restore 649
// Deserialized from JSON
private GenericResponse() { }
}
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
internal sealed class Item { internal sealed class Item {
internal const ushort SteamAppID = 753; internal const ushort SteamAppID = 753;
@@ -227,13 +239,12 @@ namespace ArchiSteamFarm.JSON {
internal uint Amount { get; private set; } internal uint Amount { get; private set; }
internal uint AppID { get; set; } internal uint AppID { get; set; }
internal ulong AssetID { get; private set; }
internal ulong ClassID { get; private set; } internal ulong ClassID { get; private set; }
internal ulong ContextID { get; set; } internal ulong ContextID { get; set; }
internal uint RealAppID { get; set; } internal uint RealAppID { get; set; }
internal EType Type { get; set; } internal EType Type { get; set; }
private ulong AssetID;
[JsonProperty(PropertyName = "amount", Required = Required.Always)] [JsonProperty(PropertyName = "amount", Required = Required.Always)]
[SuppressMessage("ReSharper", "UnusedMember.Local")] [SuppressMessage("ReSharper", "UnusedMember.Local")]
private string AmountString { private string AmountString {

View File

@@ -492,6 +492,15 @@ namespace ArchiSteamFarm.Localization {
} }
} }
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu There are {0}/{1} bots that already own all of the games being checked..
/// </summary>
internal static string BotOwnsOverview {
get {
return ResourceManager.GetString("BotOwnsOverview", resourceCulture);
}
}
/// <summary> /// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Rate limit exceeded; we will retry after {0} of cooldown.... /// Wyszukuje zlokalizowany ciąg podobny do ciągu Rate limit exceeded; we will retry after {0} of cooldown....
/// </summary> /// </summary>
@@ -654,15 +663,6 @@ namespace ArchiSteamFarm.Localization {
} }
} }
/// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Unable to connect to Steam: {0}.
/// </summary>
internal static string BotUnableToConnect {
get {
return ResourceManager.GetString("BotUnableToConnect", resourceCulture);
}
}
/// <summary> /// <summary>
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Unable to login to Steam: {0}/{1}. /// Wyszukuje zlokalizowany ciąg podobny do ciągu Unable to login to Steam: {0}/{1}.
/// </summary> /// </summary>

View File

@@ -485,10 +485,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>В момента се ползва бот.</value> <value>В момента се ползва бот.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Не може да се свърже със Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Не може да се впише в Steam: {0}/{1}</value> <value>Не може да се впише в Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -551,4 +547,5 @@
</root> </root>

View File

@@ -184,14 +184,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Nelze zkontrolovat nejnovější verzi.</value> <value>Nelze zkontrolovat nejnovější verzi.</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Aktualizaci nelze provést, protože neexistuje žádný asset související s aktuálně spuštěnou verzí. Automatickou aktualizaci této verze nelze provést.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Aktualizace nemohla pokračovat, protože žádaná verze neobsahuje žádné assety.</value> <value>Aktualizace nemohla pokračovat, protože žádaná verze neobsahuje žádné assety.</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Obdržen vstup od uživatele, ale proces běží v automatickém režimu.</value> <value>Obdržen vstup od uživatele, ale proces běží v automatickém režimu.</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Požadavek byl zamítnut, protože není nastaven identifikátor 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>Ukončení...</value> <value>Ukončení...</value>
</data> </data>
@@ -241,7 +246,10 @@ StackTrace:
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Kontrola nové verze...</value> <value>Kontrola nové verze...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Probíhá stahování nové verze: {0} ({1} MB)...Během čekání zvažte podporu tohoto projektu! :)</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>Úspěšně aktualizováno.</value> <value>Úspěšně aktualizováno.</value>
</data> </data>
@@ -280,7 +288,10 @@ StackTrace:
<value>Prosím, zadejte nezdokumentovanou hodnotu {0}: </value> <value>Prosím, zadejte nezdokumentovanou hodnotu {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>Zadejte hostitele 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>Obdržena neznámá konfigurace pro {0}, nahlašte: {1}</value> <value>Obdržena neznámá konfigurace pro {0}, nahlašte: {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>Spušt2ní více než {0} her není aktuálně možné, použito bude pouze prvních {0} položek z {1}.</value> <value>Spušt2ní více než {0} her není aktuálně možné, použito bude pouze prvních {0} položek z {1}.</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>Služba IPC nemohla být spuštěna kvůli AddressAccessDeniedException. Pokud si přejete použít službu IPC, poskytovanou aplikací ASF, zvažte spuštění aplikace ASF jako správce, nebo aplikaci přidělte dostatečná oprávnění.</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Odpovězeno na příkaz IPC: {0} odpověď: {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 server je připravený.</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Spouštění serveru IPC na adrese {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Tento bot byl již zastaven.</value> <value>Tento bot byl již zastaven.</value>
</data> </data>
@@ -544,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot je právě použiván.</value> <value>Bot je právě použiván.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nelze se připojit ke Steamu: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nelze se příhlásit do Steamu: {0}/{1}</value> <value>Nelze se příhlásit do Steamu: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -656,4 +673,5 @@ 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>
</root> </root>

View File

@@ -543,10 +543,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Botten bruges i øjeblikket.</value> <value>Botten bruges i øjeblikket.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Kan ikke forbinde til Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Kan ikke logge ind på Steam: {0}/{1}</value> <value>Kan ikke logge ind på Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -655,4 +651,5 @@ StackTrace:
<value>Færdig med rensning af Steam opdagelses kø #{0}.</value> <value>Færdig med rensning af Steam opdagelses kø #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -564,10 +564,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Der Bot wird zurzeit benutzt.</value> <value>Der Bot wird zurzeit benutzt.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Verbindung zu Steam nicht möglich: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Anmeldung in Steam nicht möglich: {0}/{1}</value> <value>Anmeldung in Steam nicht möglich: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -676,4 +672,5 @@ 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>
</root> </root>

View File

@@ -565,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot wird zurzeit benutzt.</value> <value>Bot wird zurzeit benutzt.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Verbindung zu Steam nicht möglich: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Anmeldung in Steam nicht möglich: {0}/{1}</value> <value>Anmeldung in Steam nicht möglich: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -677,4 +673,5 @@ 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>
</root> </root>

View File

@@ -134,7 +134,10 @@
<value>Η ρυθμισμένη ιδιότητα {0} δεν είναι έγκυρη: {1}</value> <value>Η ρυθμισμένη ιδιότητα {0} δεν είναι έγκυρη: {1}</value>
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment> <comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
</data> </data>
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
<value>Το ASF V{0} αντιμετώπισε κρίσιμο σφάλμα πριν καν ξεκινήσει η κύρια μονάδα καταγραφής σφαλμάτων!</value>
<comment>{0} will be replaced by version number</comment>
</data>
<data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve"> <data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve">
<value>Εξαίρεση: {0}() {1} <value>Εξαίρεση: {0}() {1}
StackTrace: StackTrace:
@@ -166,7 +169,10 @@ StackTrace:
<value>Το {0} είναι null!</value> <value>Το {0} είναι null!</value>
<comment>{0} will be replaced by object's name</comment> <comment>{0} will be replaced by object's name</comment>
</data> </data>
<data name="ErrorParsingObject" xml:space="preserve">
<value>Η ανάλυση του {0} απέτυχε!</value>
<comment>{0} will be replaced by object's name</comment>
</data>
<data name="ErrorRemovingOldBinary" xml:space="preserve"> <data name="ErrorRemovingOldBinary" xml:space="preserve">
<value>Αδυναμία αφαίρεσης του παλιού ASF binary, αφαιρέστε το {0} χειροκίνητα ώστε να λειτουργήσει η λειτουργία ενημέρωσης!</value> <value>Αδυναμία αφαίρεσης του παλιού ASF binary, αφαιρέστε το {0} χειροκίνητα ώστε να λειτουργήσει η λειτουργία ενημέρωσης!</value>
<comment>{0} will be replaced by file's path</comment> <comment>{0} will be replaced by file's path</comment>
@@ -178,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>Λήφθηκε αίτημα για είσοδο από τον χρήστη, αλλά η διεργασία εκτελείται σε σιωπηλή λειτουργία!</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>
@@ -235,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>
@@ -274,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>
@@ -283,10 +300,20 @@ StackTrace:
<value>Η συλλογή περισσότερων από {0} παιχνιδιών ταυτόχρονα δεν είναι δυνατή, μόνο οι πρώτες {0} καταχρήσεις από το {1} θα χρησιμοποιηθούν!</value> <value>Η συλλογή περισσότερων από {0} παιχνιδιών ταυτόχρονα δεν είναι δυνατή, μόνο οι πρώτες {0} καταχρήσεις από το {1} θα χρησιμοποιηθούν!</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 δεν ήταν δυνατό να εκκινηθεί λόγω σφάλματος AddressAccessDeniedException! Εάν θέλετε να χρησιμοποιήσετε την υπηρεσία IPC που παρέχεται από το ASF, δοκιμάστε να εκκινήσετε το ASF ως διαχειριστής ή να παραχωρήσετε τα κατάλληλα δικαιώματα!</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>Έναρξη διακομιστή IPC στο {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Το bot έχει ήδη σταματήσει!</value> <value>Το bot έχει ήδη σταματήσει!</value>
</data> </data>
@@ -447,7 +474,10 @@ StackTrace:
<data name="BotInstanceNotStartingBecauseDisabled" xml:space="preserve"> <data name="BotInstanceNotStartingBecauseDisabled" xml:space="preserve">
<value>Δεν γίνεται εκκίνηση αυτού του bot καθώς έχει απενεργοποιηθεί στο αρχείο διαμόρφωσης!</value> <value>Δεν γίνεται εκκίνηση αυτού του bot καθώς έχει απενεργοποιηθεί στο αρχείο διαμόρφωσης!</value>
</data> </data>
<data name="BotInvalidAuthenticatorDuringLogin" xml:space="preserve">
<value>Λήφθηκε κωδικός σφάλματος «TwoFactorCodeMismatch» {0} συνεχόμενες φορές. Αυτό σχεδόν πάντα υποδεικνύει μη έγκυρα στοιχεία εισόδου στο ASF 2FA. Γίνεται τερματισμός!</value>
<comment>{0} will be replaced by maximum allowed number of failed 2FA attempts</comment>
</data>
<data name="BotLoggedOff" xml:space="preserve"> <data name="BotLoggedOff" xml:space="preserve">
<value>Έγινε αποσύνδεση από το Steam: {0}</value> <value>Έγινε αποσύνδεση από το Steam: {0}</value>
<comment>{0} will be replaced by logging off reason (string)</comment> <comment>{0} will be replaced by logging off reason (string)</comment>
@@ -458,20 +488,36 @@ StackTrace:
<data name="BotLoggingIn" xml:space="preserve"> <data name="BotLoggingIn" xml:space="preserve">
<value>Σύνδεση...</value> <value>Σύνδεση...</value>
</data> </data>
<data name="BotLogonSessionReplaced" xml:space="preserve">
<value>Αυτός ο λογαριασμός φαίνεται να χρησιμοποιείται σε άλλη συνεδρία ASF, το οποίο προκαλεί απρόσμενη συμπεριφορά. Θα γίνει τερματισμός!</value>
</data>
<data name="BotLootingFailed" xml:space="preserve"> <data name="BotLootingFailed" xml:space="preserve">
<value>Η πρόταση ανταλλαγής απέτυχε!</value> <value>Η πρόταση ανταλλαγής απέτυχε!</value>
</data> </data>
<data name="BotLootingMasterNotDefined" xml:space="preserve">
<value>Η ανταλλαγή δεν μπορεί να αποσταλεί καθώς δεν υπάρχει κανένας χρήστης με ορισμένο το δικαίωμα «master»!</value>
</data>
<data name="BotLootingNoLootableTypes" xml:space="preserve">
<value>Δεν έχετε ορίσει τύπους αντικειμένων για μαζική συλλογή!</value>
</data>
<data name="BotLootingNowDisabled" xml:space="preserve">
<value>Η μαζική συλλογή απενεργοποιήθηκε!</value>
</data>
<data name="BotLootingNowEnabled" xml:space="preserve">
<value>Η μαζική συλλογή ενεργοποιήθηκε!</value>
</data>
<data name="BotLootingSuccess" xml:space="preserve"> <data name="BotLootingSuccess" xml:space="preserve">
<value>Η πρόταση ανταλλαγής στάλθηκε επιτυχώς!</value> <value>Η πρόταση ανταλλαγής στάλθηκε επιτυχώς!</value>
</data> </data>
<data name="BotLootingTemporarilyDisabled" xml:space="preserve">
<value>Η μαζική συλλογή είναι προσωρινά απενεργοποιημένη!</value>
</data>
<data name="BotLootingYourself" xml:space="preserve">
<value>Δεν μπορείτε να κάνετε μαζική συλλογή από τον εαυτό σας!</value>
</data>
<data name="BotNoASFAuthenticator" xml:space="preserve">
<value>Αυτό το bot δεν έχει ενεργοποιημένο το ASF 2FA! Μήπως ξεχάσατε να εισάγετε τον επαληθευτή σας ως ASF 2FA;</value>
</data>
<data name="BotNotConnected" xml:space="preserve"> <data name="BotNotConnected" xml:space="preserve">
<value>Αυτό το bot δεν έχει συνδεθεί!</value> <value>Αυτό το bot δεν έχει συνδεθεί!</value>
</data> </data>
@@ -519,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Το bot χρησιμοποιείται αυτή τη στιγμή.</value> <value>Το bot χρησιμοποιείται αυτή τη στιγμή.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Αδυναμία σύνδεσης στο Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Αδυναμία σύνδεσης στο Steam: {0}/{1}</value> <value>Αδυναμία σύνδεσης στο Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -631,4 +673,5 @@ 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>
</root> </root>

View File

@@ -564,10 +564,6 @@ Trazo de pila:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>El bot se está utilizando actualmente.</value> <value>El bot se está utilizando actualmente.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>No se puede conectar a Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>No se puede iniciar sesión en Steam: {0}/{1}</value> <value>No se puede iniciar sesión en Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -676,4 +672,5 @@ Trazo de pila:
<value>Lista de decubrimientos de Steam #{0} completada.</value> <value>Lista de decubrimientos de Steam #{0} completada.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -368,7 +368,6 @@
<data name="ErrorIsEmpty" xml:space="preserve"> <data name="ErrorIsEmpty" xml:space="preserve">
<value>{0} on tyhjä!</value> <value>{0} on tyhjä!</value>
<comment>{0} will be replaced by object's name</comment> <comment>{0} will be replaced by object's name</comment>
@@ -414,4 +413,5 @@
</root> </root>

View File

@@ -550,10 +550,6 @@ StackTrace :
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Le bot est actuellement utilisé.</value> <value>Le bot est actuellement utilisé.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Impossible de se connecter à Steam : {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Impossible de se connecter à Steam : {0}/{1}</value> <value>Impossible de se connecter à Steam : {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -662,4 +658,5 @@ StackTrace :
<value>Fini de consulter la liste de découvertes #{0}.</value> <value>Fini de consulter la liste de découvertes #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -550,10 +550,6 @@ StackTrace :
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Le bot est actuellement utilisé.</value> <value>Le bot est actuellement utilisé.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Impossible de se connecter à Steam : {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Impossible de se connecter à Steam : {0}/{1}</value> <value>Impossible de se connecter à Steam : {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -662,4 +658,5 @@ StackTrace :
<value>Fini de consulter la liste de découvertes #{0}.</value> <value>Fini de consulter la liste de découvertes #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -434,10 +434,6 @@ StackTrace:
<data name="BotUnableToConnect" xml:space="preserve">
<value>לא ניתן להתחבר לסטים: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>לא ניתן להתחבר לסטים: {0}/{1}</value> <value>לא ניתן להתחבר לסטים: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -488,4 +484,5 @@ StackTrace:
</root> </root>

View File

@@ -542,10 +542,6 @@ StackTrace: {2}</value>
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>A bot jelenleg használatban van.</value> <value>A bot jelenleg használatban van.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nem lehet csatlakozni a Steamhez: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nem lehet belépni a Steamre: {0}/{1}</value> <value>Nem lehet belépni a Steamre: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -654,4 +650,5 @@ StackTrace: {2}</value>
<value>{0}-s számú Steam Felfedezési Várólista kitsztítva.</value> <value>{0}-s számú Steam Felfedezési Várólista kitsztítva.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -542,10 +542,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot saat ini sedang digunakan.</value> <value>Bot saat ini sedang digunakan.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Tidak dapat terhubung ke Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Tidak dapat login ke Steam: {0}/{1}</value> <value>Tidak dapat login ke Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -654,4 +650,5 @@
<value>Selesai membersihkan antrian penemuan Steam #{0}.</value> <value>Selesai membersihkan antrian penemuan Steam #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -542,10 +542,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Il bot è attualmente in uso.</value> <value>Il bot è attualmente in uso.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Impossibile connettersi a Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Impossibile effettuare l'accesso a Steam: {0}/{1}</value> <value>Impossibile effettuare l'accesso a Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -654,4 +650,5 @@
<value>Fine coda #{0} dell'elenco scoperte Steam.</value> <value>Fine coda #{0} dell'elenco scoperte Steam.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -541,10 +541,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Botは現在使用されています。</value> <value>Botは現在使用されています。</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Steamに接続できませんでした: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Steamにログインできませんでした: {0}/{1}</value> <value>Steamにログインできませんでした: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -647,4 +643,5 @@
</data> </data>
</root> </root>

View File

@@ -544,10 +544,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>봇 - 현재 사용 중.</value> <value>봇 - 현재 사용 중.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Steam에 연결할 수 없습니다: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Steam에 로그인할 수 없습니다: {0}/{1}</value> <value>Steam에 로그인할 수 없습니다: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -656,4 +652,5 @@ 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>
</root> </root>

View File

@@ -562,10 +562,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Botas šiuo metu yra naudojamas.</value> <value>Botas šiuo metu yra naudojamas.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nepavyko prisijungti prie Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nepavyko prisijungti į Steam: {0}/{1}</value> <value>Nepavyko prisijungti į Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -674,4 +670,5 @@
<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>
</root> </root>

View File

@@ -565,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot is momenteel in gebruik.</value> <value>Bot is momenteel in gebruik.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Kan geen verbinding maken met Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Kan niet inloggen op Steam: {0}/{1}</value> <value>Kan niet inloggen op Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -677,4 +673,5 @@ 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>
</root> </root>

View File

@@ -565,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot is momenteel in gebruik.</value> <value>Bot is momenteel in gebruik.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Kan geen verbinding maken met Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Kan niet inloggen op Steam: {0}/{1}</value> <value>Kan niet inloggen op Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -677,4 +673,5 @@ 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>
</root> </root>

View File

@@ -286,10 +286,6 @@
<data name="BotUnableToConnect" xml:space="preserve">
<value>Kan ikke koble til Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Kan ikke logge inn på Steam: {0}/{1}</value> <value>Kan ikke logge inn på Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -340,4 +336,5 @@
</root> </root>

View File

@@ -565,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot jest aktualnie używany.</value> <value>Bot jest aktualnie używany.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nie można połączyć ze Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nie można zalogować do Steam: {0}/{1}</value> <value>Nie można zalogować do Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -677,4 +673,8 @@ StackTrace:
<value>Ukończono czyszczenie #{0} kolejki odkryć Steam.</value> <value>Ukończono czyszczenie #{0} kolejki odkryć Steam.</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>W tej chwili {0}/{1} botów posiada już wszystkie sprawdzane gry.</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

@@ -565,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot está sendo usado no momento.</value> <value>Bot está sendo usado no momento.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Não foi possível conectar-se ao Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Não foi possível iniciar a sessão no Steam: {0}/{1}</value> <value>Não foi possível iniciar a sessão no Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -677,4 +673,5 @@ 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>
</root> </root>

View File

@@ -566,10 +566,6 @@ inválidas, abortando!</value>
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>O Bot está a ser usado atualmente.</value> <value>O Bot está a ser usado atualmente.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Não foi possível estabelecer ligação à Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Não foi possível efectuar o login para a Steam: {0}/{1}</value> <value>Não foi possível efectuar o login para a Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -678,4 +674,5 @@ 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>
</root> </root>

View File

@@ -565,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot is currently being used.</value> <value>Bot is currently being used.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Unable to connect to Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Unable to login to Steam: {0}/{1}</value> <value>Unable to login to Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -677,4 +673,8 @@ StackTrace:
<value>Done clearing Steam discovery queue #{0}.</value> <value>Done clearing Steam discovery queue #{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>There are {0}/{1} bots that already own all of the games being checked.</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

@@ -184,14 +184,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Nu a fost posibilă verificarea celei mai recente versiuni!</value> <value>Nu a fost posibilă verificarea celei mai recente versiuni!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Nu s-a putut continua cu actualizarea deoarece nu există niciun fișier asemănător versiunii care rulează! Actualizarea automată către acea versiune nu este posibilă.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Nu putem continua cu actualizarea deoarece acea versiune nu conține niciun fișier!</value> <value>Nu putem continua cu actualizarea deoarece acea versiune nu conține niciun fișier!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>S-a primit o cerere de date introduse de utilizator, dar procesul se execută în modul fără cap (serviciu)!</value> <value>S-a primit o cerere de date introduse de utilizator, dar procesul se execută în modul fără cap (serviciu)!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Se refuză gestionarea cererii, deoarece SteamOwnerID nu este setat!</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>Ieșire...</value> <value>Ieșire...</value>
</data> </data>
@@ -241,7 +246,10 @@ StackTrace:
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Se caută versiune nouă...</value> <value>Se caută versiune nouă...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Se descarcă versiunea nouă: {0} ({1} MB)... Cât timp aștepți, ia în cosiderare donarea dacă apreciezi munca depusă! :)</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>Proces de actualizare finalizat!</value> <value>Proces de actualizare finalizat!</value>
</data> </data>
@@ -280,7 +288,10 @@ StackTrace:
<value>Te rog să introduci valoarea nedocumentată a {0}: </value> <value>Te rog să introduci valoarea nedocumentată a {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>Te rugăm să introduci gazda IPC-ului tău: </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>Am primit o valoare necunoscută pentru {0}, te rog să raportezi acest lucru: {1}</value> <value>Am primit o valoare necunoscută pentru {0}, te rog să raportezi acest lucru: {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>Jucarea a mai mult de {0} jocuri concomitent nu este posibil, numai primele {0} intrări de la {1} vor fi folosite!</value> <value>Jucarea a mai mult de {0} jocuri concomitent nu este posibil, numai primele {0} intrări de la {1} vor fi folosite!</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>Serviciul IPC nu a putut fi pornit din cauza AddressAccessDeniedException! Dacă dorești să utilizezi serviciul IPC oferit de ASF, ia în considerare rularea ASF-ului ca administrator sau acordarea de permisiuni adecvate!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Se răspunde la comanda IPC: {0} cu: {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>Serverul IPC este pregătit!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Pornește serverul IPC pe {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Acest bot s-a oprit deja!</value> <value>Acest bot s-a oprit deja!</value>
</data> </data>
@@ -544,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Botul este folosit în prezent.</value> <value>Botul este folosit în prezent.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nu s-a putut realiza conexiunea la Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nu s-a putut autentifica la Steam: {0}/{1}</value> <value>Nu s-a putut autentifica la Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -656,4 +673,5 @@ 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>
</root> </root>

View File

@@ -565,10 +565,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Бот сейчас используется.</value> <value>Бот сейчас используется.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Не удалось подключиться к Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Не удалось войти в Steam: {0}/{1}</value> <value>Не удалось войти в Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -677,4 +673,5 @@
<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>
</root> </root>

View File

@@ -544,10 +544,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot je práve používaný.</value> <value>Bot je práve používaný.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nie je možné pripojiť sa k službe Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nie je možné prihlásenie do služby Steam: {0}/{1}</value> <value>Nie je možné prihlásenie do služby Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -650,4 +646,5 @@ StackTrace:
</data> </data>
</root> </root>

View File

@@ -446,10 +446,6 @@ StackTrace:
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nije moguće povezati se na Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nije moguće prijavljivanje na Steam: {0}{1}</value> <value>Nije moguće prijavljivanje na Steam: {0}{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -510,4 +506,5 @@ StackTrace:
</root> </root>

View File

@@ -544,10 +544,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot används för närvarande.</value> <value>Bot används för närvarande.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Kan inte ansluta till Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Det går inte att logga in på Steam: {0}/{1}</value> <value>Det går inte att logga in på Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -647,4 +643,5 @@ StackTrace:
</root> </root>

View File

@@ -565,10 +565,6 @@ Yığın izleme:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot şu anda kullanılıyor.</value> <value>Bot şu anda kullanılıyor.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Steam'e bağlanılamadı: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Steam'e giriş yapılamıyor: {0}/{1}</value> <value>Steam'e giriş yapılamıyor: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -677,4 +673,5 @@ 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>
</root> </root>

View File

@@ -544,10 +544,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Бот зараз використовується.</value> <value>Бот зараз використовується.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Неможливо з'єднатися зі Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Неможливо увійти до Steam: {0}/{1}</value> <value>Неможливо увійти до Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -650,4 +646,5 @@
</data> </data>
</root> </root>

View File

@@ -565,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot hiện đang được sử dụng.</value> <value>Bot hiện đang được sử dụng.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Không thể kết nối với Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Không thể đăng nhập Steam: {0}/{1}</value> <value>Không thể đăng nhập Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -677,4 +673,5 @@ StackTrace:
<value>Đã xóa hàng đợi khám phá Steam số #{0}.</value> <value>Đã xóa hàng đợi khám phá Steam số #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -541,10 +541,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>当前帐号正在使用。</value> <value>当前帐号正在使用。</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>无法连接到 Steam{0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>无法登录到 Steam{0}/{1}</value> <value>无法登录到 Steam{0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -653,4 +649,5 @@
<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>
</root> </root>

View File

@@ -541,10 +541,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>BOT 目前正被使用。</value> <value>BOT 目前正被使用。</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>無法連線至 Steam{0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>無法登入到 Steam{0}/{1}</value> <value>無法登入到 Steam{0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -653,4 +649,5 @@
<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>
</root> </root>

View File

@@ -70,13 +70,18 @@ namespace ArchiSteamFarm {
public void Dispose() => ConfirmationsSemaphore.Dispose(); public void Dispose() => ConfirmationsSemaphore.Dispose();
internal void CorrectDeviceID(string deviceID) { internal bool CorrectDeviceID(string deviceID) {
if (string.IsNullOrEmpty(deviceID)) { if (string.IsNullOrEmpty(deviceID)) {
Bot.ArchiLogger.LogNullError(nameof(deviceID)); Bot.ArchiLogger.LogNullError(nameof(deviceID));
return; return false;
}
if (!string.IsNullOrEmpty(DeviceID) && DeviceID.Equals(deviceID)) {
return false;
} }
DeviceID = deviceID; DeviceID = deviceID;
return true;
} }
internal async Task<string> GenerateToken() { internal async Task<string> GenerateToken() {

View File

@@ -0,0 +1,77 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
Copyright 2015-2017 Łukasz "JustArchi" Domeradzki
Contact: JustArchi@JustArchi.net
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
using System;
using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm {
internal sealed class ServerRecordEndPoint {
[JsonProperty(Required = Required.Always)]
internal readonly string Host;
[JsonProperty(Required = Required.Always)]
internal readonly ushort Port;
[JsonProperty(Required = Required.Always)]
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));
}
Host = host;
Port = port;
ProtocolTypes = protocolTypes;
}
private ServerRecordEndPoint() { }
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
ServerRecordEndPoint serverRecord = obj as ServerRecordEndPoint;
return (serverRecord != null) && Equals(serverRecord);
}
public override int GetHashCode() => (Host, Port, ProtocolTypes).GetHashCode();
private bool Equals(ServerRecordEndPoint other) => string.Equals(Host, other.Host) && (Port == other.Port) && (ProtocolTypes == other.ProtocolTypes);
}
}

View File

@@ -45,7 +45,7 @@ namespace ArchiSteamFarm {
public void Dispose() => TradesSemaphore.Dispose(); public void Dispose() => TradesSemaphore.Dispose();
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim(); internal void OnDisconnected() => IgnoredTrades.Clear();
internal async Task OnNewTrade() { internal async Task OnNewTrade() {
// We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue // We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue
@@ -71,6 +71,76 @@ namespace ArchiSteamFarm {
} }
} }
private static bool IsTradeNeutralOrBetter(HashSet<Steam.Item> inventory, HashSet<Steam.Item> itemsToGive, HashSet<Steam.Item> itemsToReceive) {
if ((inventory == null) || (inventory.Count == 0) || (itemsToGive == null) || (itemsToGive.Count == 0) || (itemsToReceive == null) || (itemsToReceive.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(itemsToGive) + " || " + nameof(itemsToReceive));
return false;
}
// Now let's create a map which maps items to their amount in our EQ
// This has to be done as we might have multiple items of given ClassID with multiple amounts
Dictionary<ulong, uint> itemAmounts = new Dictionary<ulong, uint>();
foreach (Steam.Item item in inventory) {
if (itemAmounts.TryGetValue(item.ClassID, out uint amount)) {
itemAmounts[item.ClassID] = amount + item.Amount;
} else {
itemAmounts[item.ClassID] = item.Amount;
}
}
// Calculate our value of items to give on per-game basis
Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>> itemAmountToGivePerGame = new Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>>();
Dictionary<ulong, uint> itemAmountsToGive = new Dictionary<ulong, uint>(itemAmounts);
foreach (Steam.Item item in itemsToGive) {
if (!itemAmountToGivePerGame.TryGetValue((item.Type, item.RealAppID), out List<uint> amountsToGive)) {
amountsToGive = new List<uint>();
itemAmountToGivePerGame[(item.Type, item.RealAppID)] = amountsToGive;
}
if (!itemAmountsToGive.TryGetValue(item.ClassID, out uint amount)) {
amountsToGive.Add(0);
continue;
}
amountsToGive.Add(amount);
itemAmountsToGive[item.ClassID] = amount - 1; // We're giving one, so we have one less
}
// Sort all the lists of amounts to give on per-game basis ascending
foreach (List<uint> amountsToGive in itemAmountToGivePerGame.Values) {
amountsToGive.Sort();
}
// Calculate our value of items to receive on per-game basis
Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>> itemAmountToReceivePerGame = new Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>>();
Dictionary<ulong, uint> itemAmountsToReceive = new Dictionary<ulong, uint>(itemAmounts);
foreach (Steam.Item item in itemsToReceive) {
if (!itemAmountToReceivePerGame.TryGetValue((item.Type, item.RealAppID), out List<uint> amountsToReceive)) {
amountsToReceive = new List<uint>();
itemAmountToReceivePerGame[(item.Type, item.RealAppID)] = amountsToReceive;
}
if (!itemAmountsToReceive.TryGetValue(item.ClassID, out uint amount)) {
amountsToReceive.Add(0);
continue;
}
amountsToReceive.Add(amount);
itemAmountsToReceive[item.ClassID] = amount + 1; // We're getting one, so we have one more
}
// Sort all the lists of amounts to receive on per-game basis ascending
foreach (List<uint> amountsToReceive in itemAmountToReceivePerGame.Values) {
amountsToReceive.Sort();
}
// Calculate final neutrality result
// This is quite complex operation of taking minimum difference from all differences on per-game basis
// When calculating per-game difference, we sum only amounts at proper indexes, because user might be overpaying
int difference = itemAmountToGivePerGame.Min(kv => kv.Value.Select((t, i) => (int) (t - itemAmountToReceivePerGame[kv.Key][i])).Sum());
return difference > 0;
}
private async Task ParseActiveTrades() { private async Task ParseActiveTrades() {
HashSet<Steam.TradeOffer> tradeOffers = await Bot.ArchiWebHandler.GetActiveTradeOffers().ConfigureAwait(false); HashSet<Steam.TradeOffer> tradeOffers = await Bot.ArchiWebHandler.GetActiveTradeOffers().ConfigureAwait(false);
if ((tradeOffers == null) || (tradeOffers.Count == 0)) { if ((tradeOffers == null) || (tradeOffers.Count == 0)) {
@@ -264,76 +334,6 @@ namespace ArchiSteamFarm {
return new ParseTradeResult(tradeOffer.TradeOfferID, accept ? ParseTradeResult.EResult.AcceptedWithItemLose : (Bot.BotConfig.IsBotAccount ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily)); return new ParseTradeResult(tradeOffer.TradeOfferID, accept ? ParseTradeResult.EResult.AcceptedWithItemLose : (Bot.BotConfig.IsBotAccount ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily));
} }
private static bool IsTradeNeutralOrBetter(HashSet<Steam.Item> inventory, HashSet<Steam.Item> itemsToGive, HashSet<Steam.Item> itemsToReceive) {
if ((inventory == null) || (inventory.Count == 0) || (itemsToGive == null) || (itemsToGive.Count == 0) || (itemsToReceive == null) || (itemsToReceive.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(itemsToGive) + " || " + nameof(itemsToReceive));
return false;
}
// Now let's create a map which maps items to their amount in our EQ
// This has to be done as we might have multiple items of given ClassID with multiple amounts
Dictionary<ulong, uint> itemAmounts = new Dictionary<ulong, uint>();
foreach (Steam.Item item in inventory) {
if (itemAmounts.TryGetValue(item.ClassID, out uint amount)) {
itemAmounts[item.ClassID] = amount + item.Amount;
} else {
itemAmounts[item.ClassID] = item.Amount;
}
}
// Calculate our value of items to give on per-game basis
Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>> itemAmountToGivePerGame = new Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>>();
Dictionary<ulong, uint> itemAmountsToGive = new Dictionary<ulong, uint>(itemAmounts);
foreach (Steam.Item item in itemsToGive) {
if (!itemAmountToGivePerGame.TryGetValue((item.Type, item.RealAppID), out List<uint> amountsToGive)) {
amountsToGive = new List<uint>();
itemAmountToGivePerGame[(item.Type, item.RealAppID)] = amountsToGive;
}
if (!itemAmountsToGive.TryGetValue(item.ClassID, out uint amount)) {
amountsToGive.Add(0);
continue;
}
amountsToGive.Add(amount);
itemAmountsToGive[item.ClassID] = amount - 1; // We're giving one, so we have one less
}
// Sort all the lists of amounts to give on per-game basis ascending
foreach (List<uint> amountsToGive in itemAmountToGivePerGame.Values) {
amountsToGive.Sort();
}
// Calculate our value of items to receive on per-game basis
Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>> itemAmountToReceivePerGame = new Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>>();
Dictionary<ulong, uint> itemAmountsToReceive = new Dictionary<ulong, uint>(itemAmounts);
foreach (Steam.Item item in itemsToReceive) {
if (!itemAmountToReceivePerGame.TryGetValue((item.Type, item.RealAppID), out List<uint> amountsToReceive)) {
amountsToReceive = new List<uint>();
itemAmountToReceivePerGame[(item.Type, item.RealAppID)] = amountsToReceive;
}
if (!itemAmountsToReceive.TryGetValue(item.ClassID, out uint amount)) {
amountsToReceive.Add(0);
continue;
}
amountsToReceive.Add(amount);
itemAmountsToReceive[item.ClassID] = amount + 1; // We're getting one, so we have one more
}
// Sort all the lists of amounts to receive on per-game basis ascending
foreach (List<uint> amountsToReceive in itemAmountToReceivePerGame.Values) {
amountsToReceive.Sort();
}
// Calculate final neutrality result
// This is quite complex operation of taking minimum difference from all differences on per-game basis
// When calculating per-game difference, we sum only amounts at proper indexes, because user might be overpaying
int difference = itemAmountToGivePerGame.Min(kv => kv.Value.Select((t, i) => (int) (t - itemAmountToReceivePerGame[kv.Key][i])).Sum());
return difference > 0;
}
private sealed class ParseTradeResult { private sealed class ParseTradeResult {
internal readonly EResult Result; internal readonly EResult Result;

View File

@@ -40,16 +40,6 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "UnusedParameter.Global")] [SuppressMessage("ReSharper", "UnusedParameter.Global")]
internal static void Forget(this object obj) { } internal static void Forget(this object obj) { }
internal static string GetArgsAsString(this string[] args, byte argsToSkip = 1) {
if ((args == null) || (args.Length < argsToSkip)) {
ASF.ArchiLogger.LogNullError(nameof(args));
return null;
}
string result = string.Join(" ", args.GetArgs(argsToSkip));
return result;
}
internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) { internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
if ((cookieContainer == null) || string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) { if ((cookieContainer == null) || string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) {
ASF.ArchiLogger.LogNullError(nameof(cookieContainer) + " || " + nameof(url) + " || " + nameof(name)); ASF.ArchiLogger.LogNullError(nameof(cookieContainer) + " || " + nameof(url) + " || " + nameof(name));
@@ -130,22 +120,5 @@ namespace ArchiSteamFarm {
} }
internal static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3); internal static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3);
private static string[] GetArgs(this string[] args, byte argsToSkip = 1) {
if ((args == null) || (args.Length < argsToSkip)) {
ASF.ArchiLogger.LogNullError(nameof(args));
return null;
}
byte argsToCopy = (byte) (args.Length - argsToSkip);
string[] result = new string[argsToCopy];
if (argsToCopy > 0) {
Array.Copy(args, argsToSkip, result, 0, argsToCopy);
}
return result;
}
} }
} }

View File

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

View File

@@ -1,5 +1,6 @@
{ {
"AcceptGifts": false, "AcceptGifts": false,
"AutoDiscoveryQueue": false,
"CardDropsRestricted": true, "CardDropsRestricted": true,
"CustomGamePlayedWhileFarming": null, "CustomGamePlayedWhileFarming": null,
"CustomGamePlayedWhileIdle": null, "CustomGamePlayedWhileIdle": null,
@@ -9,6 +10,7 @@
"FarmOffline": false, "FarmOffline": false,
"GamesPlayedWhileIdle": [], "GamesPlayedWhileIdle": [],
"HandleOfflineMessages": false, "HandleOfflineMessages": false,
"IdleRefundableGames": true,
"IsBotAccount": false, "IsBotAccount": false,
"LootableTypes": [ "LootableTypes": [
1, 1,

Binary file not shown.