mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-17 06:50:29 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1beb2e01d5 | ||
|
|
a57dc7387c | ||
|
|
0289f65072 | ||
|
|
7bb3b38ea4 | ||
|
|
54d1b8211b | ||
|
|
f97ffc76b5 | ||
|
|
39891b835d | ||
|
|
c3d6a6103e | ||
|
|
32e005bde3 | ||
|
|
2db65d324a | ||
|
|
65c2b81d0a | ||
|
|
a216eb48af | ||
|
|
6f1e230b81 | ||
|
|
15ec01f447 | ||
|
|
f564cc5ebb | ||
|
|
be349d0557 | ||
|
|
93bc8373f8 | ||
|
|
4b11f61ea1 | ||
|
|
060db81f38 |
@@ -219,8 +219,8 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
// Before attempting to connect, initialize our list of CMs
|
||||
await Bot.InitializeCMs(Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider).ConfigureAwait(false);
|
||||
// Before attempting to connect, initialize our configuration
|
||||
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)) {
|
||||
Bot.RegisterBot(botName);
|
||||
|
||||
@@ -96,60 +96,6 @@ namespace ArchiSteamFarm {
|
||||
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) {
|
||||
if (gameIDs == null) {
|
||||
ArchiLogger.LogNullError(nameof(gameIDs));
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<AssemblyVersion>3.0.0.5</AssemblyVersion>
|
||||
<FileVersion>3.0.0.5</FileVersion>
|
||||
<AssemblyVersion>3.0.0.6</AssemblyVersion>
|
||||
<FileVersion>3.0.0.6</FileVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ErrorReport>none</ErrorReport>
|
||||
<ApplicationIcon>ASF.ico</ApplicationIcon>
|
||||
@@ -26,12 +26,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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="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="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" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1088,6 +1088,33 @@ namespace ArchiSteamFarm {
|
||||
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() {
|
||||
if (CachedApiKey != null) {
|
||||
// We fetched API key already, and either got valid one, or permanent AccessDenied
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,6 +41,11 @@ namespace ArchiSteamFarm {
|
||||
internal readonly bool AcceptGifts;
|
||||
#pragma warning restore 649
|
||||
|
||||
#pragma warning disable 649
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool AutoDiscoveryQueue;
|
||||
#pragma warning restore 649
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool CardDropsRestricted = true;
|
||||
|
||||
@@ -80,6 +85,11 @@ namespace ArchiSteamFarm {
|
||||
internal readonly bool HandleOfflineMessages;
|
||||
#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
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly bool IsBotAccount;
|
||||
@@ -217,7 +227,11 @@ namespace ArchiSteamFarm {
|
||||
HoursDescending,
|
||||
NamesAscending,
|
||||
NamesDescending,
|
||||
Random
|
||||
Random,
|
||||
BadgeLevelsAscending,
|
||||
BadgeLevelsDescending,
|
||||
RedeemDateTimesAscending,
|
||||
RedeemDateTimesDescending
|
||||
}
|
||||
|
||||
internal enum EPermission : byte {
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private readonly object FileLock = new object();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentHashSet<uint> IdlingPriorityAppIDs = new ConcurrentHashSet<uint>();
|
||||
|
||||
internal string LoginKey {
|
||||
get => _LoginKey;
|
||||
|
||||
@@ -94,15 +97,49 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
internal IEnumerable<ulong> GetBlacklistedFromTradesSteamIDs() => BlacklistedFromTradesSteamIDs;
|
||||
|
||||
internal bool IsBlacklistedFromTrades(ulong steamID) {
|
||||
if (steamID != 0) {
|
||||
return BlacklistedFromTradesSteamIDs.Contains(steamID);
|
||||
internal void AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
|
||||
if ((appIDs == null) || (appIDs.Count == 0)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(appIDs));
|
||||
return;
|
||||
}
|
||||
|
||||
ASF.ArchiLogger.LogNullError(nameof(steamID));
|
||||
return false;
|
||||
if (IdlingPriorityAppIDs.AddRange(appIDs)) {
|
||||
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) {
|
||||
@@ -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);
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(json));
|
||||
|
||||
@@ -34,6 +34,7 @@ using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using HtmlAgilityPack;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class CardsFarmer : IDisposable {
|
||||
@@ -47,7 +48,7 @@ namespace ArchiSteamFarm {
|
||||
internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>();
|
||||
|
||||
[JsonProperty]
|
||||
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>();
|
||||
internal readonly ConcurrentSortedHashSet<Game> GamesToFarm = new ConcurrentSortedHashSet<Game>();
|
||||
|
||||
[JsonProperty]
|
||||
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)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(name) + " || " + nameof(hours));
|
||||
return;
|
||||
@@ -280,7 +281,7 @@ namespace ArchiSteamFarm {
|
||||
return;
|
||||
}
|
||||
|
||||
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining.Value));
|
||||
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining.Value, badgeLevel));
|
||||
}
|
||||
|
||||
private async Task CheckGamesForFarming() {
|
||||
@@ -297,7 +298,7 @@ namespace ArchiSteamFarm {
|
||||
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) {
|
||||
// No eligible badges whatsoever
|
||||
return;
|
||||
@@ -306,7 +307,9 @@ namespace ArchiSteamFarm {
|
||||
HashSet<Task> backgroundTasks = new HashSet<Task>();
|
||||
|
||||
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) {
|
||||
// It's just a badge, nothing more
|
||||
continue;
|
||||
@@ -336,10 +339,10 @@ namespace ArchiSteamFarm {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IgnoredAppIDs.TryGetValue(appID, out DateTime lastPICSReport)) {
|
||||
if (lastPICSReport.AddHours(HoursToIgnore) < DateTime.UtcNow) {
|
||||
if (IgnoredAppIDs.TryGetValue(appID, out DateTime ignoredUntil)) {
|
||||
if (ignoredUntil < DateTime.UtcNow) {
|
||||
// This game served its time as being ignored
|
||||
IgnoredAppIDs.TryRemove(appID, out lastPICSReport);
|
||||
IgnoredAppIDs.TryRemove(appID, out _);
|
||||
} else {
|
||||
// This game is still ignored
|
||||
continue;
|
||||
@@ -347,7 +350,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Cards
|
||||
HtmlNode progressNode = htmlNode.SelectSingleNode(".//span[@class='progress_info_bold']");
|
||||
HtmlNode progressNode = statsNode.SelectSingleNode(".//span[@class='progress_info_bold']");
|
||||
if (progressNode == null) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(progressNode));
|
||||
continue;
|
||||
@@ -380,7 +383,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(cardsEarnedNode));
|
||||
continue;
|
||||
@@ -419,7 +422,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// Hours
|
||||
HtmlNode timeNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
|
||||
HtmlNode timeNode = statsNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
|
||||
if (timeNode == null) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(timeNode));
|
||||
continue;
|
||||
@@ -443,7 +446,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(nameNode));
|
||||
continue;
|
||||
@@ -477,14 +480,44 @@ namespace ArchiSteamFarm {
|
||||
|
||||
name = WebUtility.HtmlDecode(name.Substring(nameStartIndex, nameEndIndex - nameStartIndex));
|
||||
|
||||
// We have two possible cases here
|
||||
// Either we have decent info about appID, name, hours and cardsRemaining (cardsRemaining > 0)
|
||||
// OR we strongly believe that Steam lied to us, in this case we will need to check game invidually (cardsRemaining == 0)
|
||||
// Levels
|
||||
byte badgeLevel = 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) {
|
||||
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining));
|
||||
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining, badgeLevel));
|
||||
} else {
|
||||
Task task = CheckGame(appID, name, hours);
|
||||
Task task = CheckGame(appID, name, hours, badgeLevel);
|
||||
switch (Program.GlobalConfig.OptimizationMode) {
|
||||
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
|
||||
await task.ConfigureAwait(false);
|
||||
@@ -523,20 +556,37 @@ namespace ArchiSteamFarm {
|
||||
// If we have restricted card drops, we use complex algorithm
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex"));
|
||||
while (GamesToFarm.Count > 0) {
|
||||
HashSet<Game> gamesToFarmSolo = GamesToFarm.Count > 1 ? new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) : new HashSet<Game>(GamesToFarm);
|
||||
if (gamesToFarmSolo.Count > 0) {
|
||||
while (gamesToFarmSolo.Count > 0) {
|
||||
Game game = gamesToFarmSolo.First();
|
||||
if (await FarmSolo(game).ConfigureAwait(false)) {
|
||||
gamesToFarmSolo.Remove(game);
|
||||
HashSet<Game> playableGamesToFarmSolo = new HashSet<Game>();
|
||||
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) {
|
||||
if (await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
playableGamesToFarmSolo.Add(game);
|
||||
}
|
||||
}
|
||||
|
||||
if (playableGamesToFarmSolo.Count > 0) {
|
||||
while (playableGamesToFarmSolo.Count > 0) {
|
||||
Game playableGame = playableGamesToFarmSolo.First();
|
||||
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
|
||||
playableGamesToFarmSolo.Remove(playableGame);
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (FarmMultiple(GamesToFarm.OrderByDescending(game => game.HoursPlayed).Take(ArchiHandler.MaxGamesPlayedConcurrently))) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", GamesToFarm.Select(game => game.AppID))));
|
||||
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
|
||||
foreach (Game game in GamesToFarm.Where(game => game.HoursPlayed < HoursToBump).OrderByDescending(game => game.HoursPlayed)) {
|
||||
if (await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
playableGamesToFarmMultiple.Add(game);
|
||||
}
|
||||
|
||||
if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (FarmMultiple(playableGamesToFarmMultiple)) {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
|
||||
} else {
|
||||
NowFarming = false;
|
||||
return;
|
||||
@@ -547,9 +597,20 @@ namespace ArchiSteamFarm {
|
||||
// If we have unrestricted card drops, we use simple algorithm
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple"));
|
||||
while (GamesToFarm.Count > 0) {
|
||||
Game game = GamesToFarm.First();
|
||||
if (await FarmSolo(game).ConfigureAwait(false)) {
|
||||
continue;
|
||||
Game playableGame = null;
|
||||
foreach (Game game in GamesToFarm) {
|
||||
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
playableGame = game;
|
||||
break;
|
||||
}
|
||||
|
||||
if (playableGame != null) {
|
||||
if (await FarmSolo(playableGame).ConfigureAwait(false)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
NowFarming = false;
|
||||
@@ -558,7 +619,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
} while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault());
|
||||
|
||||
CurrentGamesFarming.ClearAndTrim();
|
||||
CurrentGamesFarming.Clear();
|
||||
NowFarming = false;
|
||||
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.IdlingFinished);
|
||||
@@ -573,37 +634,31 @@ namespace ArchiSteamFarm {
|
||||
|
||||
bool success = true;
|
||||
|
||||
uint appID = await Bot.GetAppIDForIdling(game.AppID).ConfigureAwait(false);
|
||||
if (appID != 0) {
|
||||
if (appID != game.AppID) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningIdlingGameMismatch, game.AppID, game.GameName, appID));
|
||||
if (game.AppID != game.PlayableAppID) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningIdlingGameMismatch, game.AppID, game.GameName, game.PlayableAppID));
|
||||
}
|
||||
|
||||
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);
|
||||
DateTime endFarmingDate = DateTime.UtcNow.AddHours(Program.GlobalConfig.MaxFarmingTime);
|
||||
// Don't forget to update our GamesToFarm hours
|
||||
game.HoursPlayed += (float) DateTime.UtcNow.Subtract(startFarmingPeriod).TotalHours;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
if (!success) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
IgnoredAppIDs[game.AppID] = DateTime.UtcNow;
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingGameNotPossible, game.AppID, game.GameName));
|
||||
|
||||
keepFarming = await ShouldFarm(game).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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))));
|
||||
|
||||
bool result = FarmHours(CurrentGamesFarming);
|
||||
CurrentGamesFarming.ClearAndTrim();
|
||||
CurrentGamesFarming.Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -682,7 +737,7 @@ namespace ArchiSteamFarm {
|
||||
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdling, game.AppID, game.GameName));
|
||||
|
||||
bool result = await Farm(game).ConfigureAwait(false);
|
||||
CurrentGamesFarming.ClearAndTrim();
|
||||
CurrentGamesFarming.Clear();
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
@@ -751,7 +806,7 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
}
|
||||
|
||||
GamesToFarm.ClearAndTrim();
|
||||
GamesToFarm.Clear();
|
||||
|
||||
List<Task> tasks = new List<Task>();
|
||||
Task mainTask = CheckPage(htmlDocument);
|
||||
@@ -798,6 +853,18 @@ namespace ArchiSteamFarm {
|
||||
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) {
|
||||
if (game == null) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(game));
|
||||
@@ -817,50 +884,94 @@ namespace ArchiSteamFarm {
|
||||
}
|
||||
|
||||
private void SortGamesToFarm() {
|
||||
IOrderedEnumerable<Game> gamesToFarm;
|
||||
IOrderedEnumerable<Game> gamesToFarm = GamesToFarm.OrderBy(game => Bot.IsPriorityIdling(game.AppID) ? 1 : 0);
|
||||
|
||||
switch (Bot.BotConfig.FarmingOrder) {
|
||||
case BotConfig.EFarmingOrder.Unordered:
|
||||
return;
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.AppIDsAscending:
|
||||
gamesToFarm = GamesToFarm.OrderBy(game => game.AppID);
|
||||
gamesToFarm = gamesToFarm.ThenBy(game => game.AppID);
|
||||
break;
|
||||
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;
|
||||
case BotConfig.EFarmingOrder.CardDropsAscending:
|
||||
gamesToFarm = GamesToFarm.OrderBy(game => game.CardsRemaining);
|
||||
gamesToFarm = gamesToFarm.ThenBy(game => game.CardsRemaining);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.CardDropsDescending:
|
||||
gamesToFarm = GamesToFarm.OrderByDescending(game => game.CardsRemaining);
|
||||
gamesToFarm = gamesToFarm.ThenByDescending(game => game.CardsRemaining);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.HoursAscending:
|
||||
gamesToFarm = GamesToFarm.OrderBy(game => game.HoursPlayed);
|
||||
gamesToFarm = gamesToFarm.ThenBy(game => game.HoursPlayed);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.HoursDescending:
|
||||
gamesToFarm = GamesToFarm.OrderByDescending(game => game.HoursPlayed);
|
||||
gamesToFarm = gamesToFarm.ThenByDescending(game => game.HoursPlayed);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.NamesAscending:
|
||||
gamesToFarm = GamesToFarm.OrderBy(game => game.GameName);
|
||||
gamesToFarm = gamesToFarm.ThenBy(game => game.GameName);
|
||||
break;
|
||||
case BotConfig.EFarmingOrder.NamesDescending:
|
||||
gamesToFarm = GamesToFarm.OrderByDescending(game => game.GameName);
|
||||
gamesToFarm = gamesToFarm.ThenByDescending(game => game.GameName);
|
||||
break;
|
||||
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;
|
||||
default:
|
||||
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder)));
|
||||
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 {
|
||||
[JsonProperty]
|
||||
internal readonly uint AppID;
|
||||
|
||||
internal readonly byte BadgeLevel;
|
||||
|
||||
[JsonProperty]
|
||||
internal readonly string GameName;
|
||||
|
||||
@@ -870,9 +981,9 @@ namespace ArchiSteamFarm {
|
||||
[JsonProperty]
|
||||
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)) {
|
||||
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
|
||||
}
|
||||
@@ -881,14 +992,17 @@ namespace ArchiSteamFarm {
|
||||
GameName = gameName;
|
||||
HoursPlayed = hoursPlayed;
|
||||
CardsRemaining = cardsRemaining;
|
||||
BadgeLevel = badgeLevel;
|
||||
|
||||
PlayableAppID = appID;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
if (obj == null) {
|
||||
if (ReferenceEquals(null, obj)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj == this) {
|
||||
if (ReferenceEquals(this, obj)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,27 +25,29 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Nito.AsyncEx;
|
||||
using System.Threading;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> {
|
||||
public T Current => Enumerator.Current;
|
||||
|
||||
private readonly IEnumerator<T> Enumerator;
|
||||
private readonly IDisposable Lock;
|
||||
private readonly SemaphoreSlim SemaphoreSlim;
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
internal ConcurrentEnumerator(ICollection<T> collection, AsyncReaderWriterLock rwLock) {
|
||||
if ((collection == null) || (rwLock == null)) {
|
||||
throw new ArgumentNullException(nameof(collection) + " || " + nameof(rwLock));
|
||||
internal ConcurrentEnumerator(ICollection<T> collection, SemaphoreSlim semaphoreSlim) {
|
||||
if ((collection == null) || (semaphoreSlim == null)) {
|
||||
throw new ArgumentNullException(nameof(collection) + " || " + nameof(semaphoreSlim));
|
||||
}
|
||||
|
||||
Lock = rwLock.ReaderLock();
|
||||
SemaphoreSlim = semaphoreSlim;
|
||||
SemaphoreSlim.Wait();
|
||||
|
||||
Enumerator = collection.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Dispose() => Lock.Dispose();
|
||||
public void Dispose() => SemaphoreSlim.Release();
|
||||
public bool MoveNext() => Enumerator.MoveNext();
|
||||
public void Reset() => Enumerator.Reset();
|
||||
}
|
||||
|
||||
@@ -23,168 +23,103 @@
|
||||
*/
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Nito.AsyncEx;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> {
|
||||
public int Count {
|
||||
get {
|
||||
using (Lock.ReaderLock()) {
|
||||
return HashSet.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => BackingCollection.Count;
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
private readonly HashSet<T> HashSet = new HashSet<T>();
|
||||
private readonly AsyncReaderWriterLock Lock = new AsyncReaderWriterLock();
|
||||
private readonly ConcurrentDictionary<T, bool> BackingCollection = new ConcurrentDictionary<T, bool>();
|
||||
|
||||
public bool Add(T item) {
|
||||
using (Lock.WriterLock()) {
|
||||
return HashSet.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
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 bool Add(T item) => BackingCollection.TryAdd(item, true);
|
||||
public void Clear() => BackingCollection.Clear();
|
||||
public bool Contains(T item) => BackingCollection.ContainsKey(item);
|
||||
public void CopyTo(T[] array, int arrayIndex) => BackingCollection.Keys.CopyTo(array, arrayIndex);
|
||||
|
||||
public void ExceptWith(IEnumerable<T> other) {
|
||||
using (Lock.WriterLock()) {
|
||||
HashSet.ExceptWith(other);
|
||||
foreach (T item in 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) {
|
||||
using (Lock.WriterLock()) {
|
||||
HashSet.IntersectWith(other);
|
||||
ICollection<T> collection = other as ICollection<T> ?? other.ToList();
|
||||
foreach (T item in this.Where(item => !collection.Contains(item))) {
|
||||
Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other) {
|
||||
using (Lock.ReaderLock()) {
|
||||
return HashSet.IsProperSubsetOf(other);
|
||||
}
|
||||
ICollection<T> collection = other as ICollection<T> ?? other.ToList();
|
||||
return (collection.Count != Count) && IsSubsetOf(collection);
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other) {
|
||||
using (Lock.ReaderLock()) {
|
||||
return HashSet.IsProperSupersetOf(other);
|
||||
}
|
||||
ICollection<T> collection = other as ICollection<T> ?? other.ToList();
|
||||
return (collection.Count != Count) && IsSupersetOf(collection);
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other) {
|
||||
using (Lock.ReaderLock()) {
|
||||
return HashSet.IsSubsetOf(other);
|
||||
}
|
||||
ICollection<T> collection = other as ICollection<T> ?? other.ToList();
|
||||
return this.AsParallel().All(collection.Contains);
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other) {
|
||||
using (Lock.ReaderLock()) {
|
||||
return HashSet.IsSupersetOf(other);
|
||||
}
|
||||
}
|
||||
|
||||
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 IsSupersetOf(IEnumerable<T> other) => other.AsParallel().All(Contains);
|
||||
public bool Overlaps(IEnumerable<T> other) => other.AsParallel().Any(Contains);
|
||||
public bool Remove(T item) => BackingCollection.TryRemove(item, out _);
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other) {
|
||||
using (Lock.ReaderLock()) {
|
||||
return HashSet.SetEquals(other);
|
||||
}
|
||||
ICollection<T> collection = other as ICollection<T> ?? other.ToList();
|
||||
return (collection.Count == Count) && collection.AsParallel().All(Contains);
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<T> other) {
|
||||
using (Lock.WriterLock()) {
|
||||
HashSet.SymmetricExceptWith(other);
|
||||
ICollection<T> collection = other as ICollection<T> ?? other.ToList();
|
||||
|
||||
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) {
|
||||
using (Lock.WriterLock()) {
|
||||
HashSet.UnionWith(other);
|
||||
foreach (T otherElement in other) {
|
||||
Add(otherElement);
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item) => Add(item);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
internal bool AddRange(IEnumerable<T> items) {
|
||||
using (Lock.WriterLock()) {
|
||||
// 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 AddRange(IEnumerable<T> items) => items.Count(Add) > 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() {
|
||||
using (Lock.WriterLock()) {
|
||||
HashSet.Clear();
|
||||
HashSet.TrimExcess();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
internal void ReplaceWith(IEnumerable<T> other) {
|
||||
BackingCollection.Clear();
|
||||
foreach (T item in other) {
|
||||
BackingCollection[item] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
220
ArchiSteamFarm/ConcurrentSortedHashSet.cs
Normal file
220
ArchiSteamFarm/ConcurrentSortedHashSet.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,9 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
@@ -102,7 +102,7 @@ namespace ArchiSteamFarm {
|
||||
internal readonly bool Statistics = true;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly ProtocolType SteamProtocol = ProtocolType.Tcp;
|
||||
internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.All;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;
|
||||
@@ -152,17 +152,6 @@ namespace ArchiSteamFarm {
|
||||
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
|
||||
// Ensure that he can't screw core ASF variables
|
||||
if (globalConfig.MaxFarmingTime == 0) {
|
||||
|
||||
@@ -23,18 +23,18 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class GlobalDatabase : IDisposable {
|
||||
private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings {
|
||||
Converters = new List<JsonConverter>(2) {
|
||||
new IPAddressConverter(),
|
||||
new IPEndPointConverter()
|
||||
}
|
||||
};
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly ConcurrentDictionary<uint, ConcurrentHashSet<uint>> AppIDsToPackageIDs = new ConcurrentDictionary<uint, ConcurrentHashSet<uint>>();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly Guid Guid = Guid.NewGuid();
|
||||
@@ -44,6 +44,8 @@ namespace ArchiSteamFarm {
|
||||
|
||||
private readonly object FileLock = new object();
|
||||
|
||||
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1);
|
||||
|
||||
internal uint CellID {
|
||||
get => _CellID;
|
||||
set {
|
||||
@@ -89,7 +91,7 @@ namespace ArchiSteamFarm {
|
||||
GlobalDatabase globalDatabase;
|
||||
|
||||
try {
|
||||
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(filePath), CustomSerializerSettings);
|
||||
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(filePath));
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
return null;
|
||||
@@ -104,10 +106,46 @@ namespace ArchiSteamFarm {
|
||||
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 Save() {
|
||||
string json = JsonConvert.SerializeObject(this, CustomSerializerSettings);
|
||||
string json = JsonConvert.SerializeObject(this);
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(json));
|
||||
return;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2.Discovery;
|
||||
@@ -32,19 +32,19 @@ using SteamKit2.Discovery;
|
||||
namespace ArchiSteamFarm {
|
||||
internal sealed class InMemoryServerListProvider : IServerListProvider {
|
||||
[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) {
|
||||
if (endPoints == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(endPoints));
|
||||
public Task UpdateServerListAsync(IEnumerable<ServerRecord> endpoints) {
|
||||
if (endpoints == null) {
|
||||
ASF.ArchiLogger.LogNullError(nameof(endpoints));
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,6 +220,18 @@ namespace ArchiSteamFarm.JSON {
|
||||
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
|
||||
internal sealed class Item {
|
||||
internal const ushort SteamAppID = 753;
|
||||
@@ -227,13 +239,12 @@ namespace ArchiSteamFarm.JSON {
|
||||
|
||||
internal uint Amount { get; private set; }
|
||||
internal uint AppID { get; set; }
|
||||
internal ulong AssetID { get; private set; }
|
||||
internal ulong ClassID { get; private set; }
|
||||
internal ulong ContextID { get; set; }
|
||||
internal uint RealAppID { get; set; }
|
||||
internal EType Type { get; set; }
|
||||
|
||||
private ulong AssetID;
|
||||
|
||||
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Local")]
|
||||
private string AmountString {
|
||||
|
||||
18
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
18
ArchiSteamFarm/Localization/Strings.Designer.cs
generated
@@ -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>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Rate limit exceeded; we will retry after {0} of cooldown....
|
||||
/// </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>
|
||||
/// Wyszukuje zlokalizowany ciąg podobny do ciągu Unable to login to Steam: {0}/{1}.
|
||||
/// </summary>
|
||||
|
||||
@@ -485,10 +485,6 @@
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>В момента се ползва бот.</value>
|
||||
</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">
|
||||
<value>Не може да се впише в Steam: {0}/{1}</value>
|
||||
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
|
||||
@@ -551,4 +547,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -184,14 +184,19 @@ StackTrace:
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Nelze zkontrolovat nejnovější verzi.</value>
|
||||
</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">
|
||||
<value>Aktualizace nemohla pokračovat, protože žádaná verze neobsahuje žádné assety.</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Obdržen vstup od uživatele, ale proces běží v automatickém režimu.</value>
|
||||
</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">
|
||||
<value>Ukončení...</value>
|
||||
</data>
|
||||
@@ -241,7 +246,10 @@ StackTrace:
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Kontrola nové verze...</value>
|
||||
</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">
|
||||
<value>Úspěšně aktualizováno.</value>
|
||||
</data>
|
||||
@@ -280,7 +288,10 @@ StackTrace:
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
@@ -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>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>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">
|
||||
<value>Tento bot byl již zastaven.</value>
|
||||
</data>
|
||||
@@ -544,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot je právě použiván.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -656,4 +673,5 @@ StackTrace:
|
||||
<value>Procházení fronty doporučení #{0} dokončeno.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -543,10 +543,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Botten bruges i øjeblikket.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -655,4 +651,5 @@ StackTrace:
|
||||
<value>Færdig med rensning af Steam opdagelses kø #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -564,10 +564,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Der Bot wird zurzeit benutzt.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -676,4 +672,5 @@ StackTrace:
|
||||
<value>Fertig mit Löschung der Steam Entdeckungsliste #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -565,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot wird zurzeit benutzt.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -677,4 +673,5 @@ StackTrace:
|
||||
<value>Fertig mit Löschung der Steam Entdeckungsliste #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -134,7 +134,10 @@
|
||||
<value>Η ρυθμισμένη ιδιότητα {0} δεν είναι έγκυρη: {1}</value>
|
||||
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
|
||||
</data>
|
||||
|
||||
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
|
||||
<value>Το ASF V{0} αντιμετώπισε κρίσιμο σφάλμα πριν καν ξεκινήσει η κύρια μονάδα καταγραφής σφαλμάτων!</value>
|
||||
<comment>{0} will be replaced by version number</comment>
|
||||
</data>
|
||||
<data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve">
|
||||
<value>Εξαίρεση: {0}() {1}
|
||||
StackTrace:
|
||||
@@ -166,7 +169,10 @@ StackTrace:
|
||||
<value>Το {0} είναι null!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
</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">
|
||||
<value>Αδυναμία αφαίρεσης του παλιού ASF binary, αφαιρέστε το {0} χειροκίνητα ώστε να λειτουργήσει η λειτουργία ενημέρωσης!</value>
|
||||
<comment>{0} will be replaced by file's path</comment>
|
||||
@@ -178,14 +184,19 @@ StackTrace:
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Αδυναμία ελέγχου για την τελευταία έκδοση!</value>
|
||||
</data>
|
||||
|
||||
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
|
||||
<value>Αδυναμία συνέχειας με την ενημέρωση καθώς δεν υπάρχουν τα απαραίτητα αρχεία που σχετίζονται με την τρέχουσα έκδοση. Η αυτόματη ενημέρωση σε αυτή την έκδοση δεν είναι δυνατή.</value>
|
||||
</data>
|
||||
<data name="ErrorUpdateNoAssets" xml:space="preserve">
|
||||
<value>Αδυναμία συνέχειας με την ενημέρωση γιατί η συγκεκριμένη έκδοση δεν περιέχει καθόλου αρχεία!</value>
|
||||
</data>
|
||||
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
|
||||
<value>Λήφθηκε αίτημα για είσοδο από τον χρήστη, αλλά η διεργασία εκτελείται σε σιωπηλή λειτουργία!</value>
|
||||
</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">
|
||||
<value>Έξοδος...</value>
|
||||
</data>
|
||||
@@ -235,7 +246,10 @@ StackTrace:
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Έλεγχος για νέα έκδοση...</value>
|
||||
</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">
|
||||
<value>Η διαδικασία ενημέρωσης ολοκληρώθηκε!</value>
|
||||
</data>
|
||||
@@ -274,7 +288,10 @@ StackTrace:
|
||||
<value>Εισάγετε τη μη τεκμηριωμένη τιμή {0}: </value>
|
||||
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
|
||||
<data name="UserInputIPCHost" xml:space="preserve">
|
||||
<value>Παρακαλούμε εισάγετε τον διακομιστή IPC σας: </value>
|
||||
<comment>Please note that this translation should end with space</comment>
|
||||
</data>
|
||||
<data name="WarningUnknownValuePleaseReport" xml:space="preserve">
|
||||
<value>Λήφθηκε άγνωστη τιμή για το {0}, παρακαλούμε αναφέρετέ το: {1}</value>
|
||||
<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>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>Η υπηρεσία 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">
|
||||
<value>Το bot έχει ήδη σταματήσει!</value>
|
||||
</data>
|
||||
@@ -447,7 +474,10 @@ StackTrace:
|
||||
<data name="BotInstanceNotStartingBecauseDisabled" xml:space="preserve">
|
||||
<value>Δεν γίνεται εκκίνηση αυτού του bot καθώς έχει απενεργοποιηθεί στο αρχείο διαμόρφωσης!</value>
|
||||
</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">
|
||||
<value>Έγινε αποσύνδεση από το Steam: {0}</value>
|
||||
<comment>{0} will be replaced by logging off reason (string)</comment>
|
||||
@@ -458,20 +488,36 @@ StackTrace:
|
||||
<data name="BotLoggingIn" xml:space="preserve">
|
||||
<value>Σύνδεση...</value>
|
||||
</data>
|
||||
|
||||
<data name="BotLogonSessionReplaced" xml:space="preserve">
|
||||
<value>Αυτός ο λογαριασμός φαίνεται να χρησιμοποιείται σε άλλη συνεδρία ASF, το οποίο προκαλεί απρόσμενη συμπεριφορά. Θα γίνει τερματισμός!</value>
|
||||
</data>
|
||||
<data name="BotLootingFailed" xml:space="preserve">
|
||||
<value>Η πρόταση ανταλλαγής απέτυχε!</value>
|
||||
</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">
|
||||
<value>Η πρόταση ανταλλαγής στάλθηκε επιτυχώς!</value>
|
||||
</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">
|
||||
<value>Αυτό το bot δεν έχει συνδεθεί!</value>
|
||||
</data>
|
||||
@@ -519,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Το bot χρησιμοποιείται αυτή τη στιγμή.</value>
|
||||
</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">
|
||||
<value>Αδυναμία σύνδεσης στο Steam: {0}/{1}</value>
|
||||
<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>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -564,10 +564,6 @@ Trazo de pila:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>El bot se está utilizando actualmente.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -676,4 +672,5 @@ Trazo de pila:
|
||||
<value>Lista de decubrimientos de Steam #{0} completada.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -368,7 +368,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="ErrorIsEmpty" xml:space="preserve">
|
||||
<value>{0} on tyhjä!</value>
|
||||
<comment>{0} will be replaced by object's name</comment>
|
||||
@@ -414,4 +413,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -550,10 +550,6 @@ StackTrace :
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Le bot est actuellement utilisé.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -662,4 +658,5 @@ StackTrace :
|
||||
<value>Fini de consulter la liste de découvertes #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -550,10 +550,6 @@ StackTrace :
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Le bot est actuellement utilisé.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -662,4 +658,5 @@ StackTrace :
|
||||
<value>Fini de consulter la liste de découvertes #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -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">
|
||||
<value>לא ניתן להתחבר לסטים: {0}/{1}</value>
|
||||
<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>
|
||||
|
||||
@@ -542,10 +542,6 @@ StackTrace: {2}</value>
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>A bot jelenleg használatban van.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -654,4 +650,5 @@ StackTrace: {2}</value>
|
||||
<value>{0}-s számú Steam Felfedezési Várólista kitsztítva.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -542,10 +542,6 @@
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot saat ini sedang digunakan.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -654,4 +650,5 @@
|
||||
<value>Selesai membersihkan antrian penemuan Steam #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -542,10 +542,6 @@
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Il bot è attualmente in uso.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -654,4 +650,5 @@
|
||||
<value>Fine coda #{0} dell'elenco scoperte Steam.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -541,10 +541,6 @@
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Botは現在使用されています。</value>
|
||||
</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">
|
||||
<value>Steamにログインできませんでした: {0}/{1}</value>
|
||||
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
|
||||
@@ -647,4 +643,5 @@
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -544,10 +544,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>봇 - 현재 사용 중.</value>
|
||||
</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">
|
||||
<value>Steam에 로그인할 수 없습니다: {0}/{1}</value>
|
||||
<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>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -562,10 +562,6 @@
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Botas šiuo metu yra naudojamas.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -674,4 +670,5 @@
|
||||
<value>Baigta peržiūrėti Steam atradimo eilė #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -565,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot is momenteel in gebruik.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -677,4 +673,5 @@ StackTrace:
|
||||
<value>Steam-ontdekkingswachtrij voltooid #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -565,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot is momenteel in gebruik.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -677,4 +673,5 @@ StackTrace:
|
||||
<value>Steam-ontdekkingswachtrij voltooid #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -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">
|
||||
<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>
|
||||
@@ -340,4 +336,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -565,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot jest aktualnie używany.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -677,4 +673,8 @@ StackTrace:
|
||||
<value>Ukończono czyszczenie #{0} kolejki odkryć Steam.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</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>
|
||||
|
||||
@@ -565,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot está sendo usado no momento.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -677,4 +673,5 @@ StackTrace:
|
||||
<value>Limpeza da lista de descoberta da Steam #{0} concluída.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -566,10 +566,6 @@ inválidas, abortando!</value>
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>O Bot está a ser usado atualmente.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -678,4 +674,5 @@ inválidas, abortando!</value>
|
||||
<value>Fila de exploração da Steam feita #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -565,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot is currently being used.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -677,4 +673,8 @@ StackTrace:
|
||||
<value>Done clearing Steam discovery queue #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</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>
|
||||
@@ -184,14 +184,19 @@ StackTrace:
|
||||
<data name="ErrorUpdateCheckFailed" xml:space="preserve">
|
||||
<value>Nu a fost posibilă verificarea celei mai recente versiuni!</value>
|
||||
</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">
|
||||
<value>Nu putem continua cu actualizarea deoarece acea versiune nu conține niciun fișier!</value>
|
||||
</data>
|
||||
<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>
|
||||
</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">
|
||||
<value>Ieșire...</value>
|
||||
</data>
|
||||
@@ -241,7 +246,10 @@ StackTrace:
|
||||
<data name="UpdateCheckingNewVersion" xml:space="preserve">
|
||||
<value>Se caută versiune nouă...</value>
|
||||
</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">
|
||||
<value>Proces de actualizare finalizat!</value>
|
||||
</data>
|
||||
@@ -280,7 +288,10 @@ StackTrace:
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
@@ -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>
|
||||
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
|
||||
<value>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">
|
||||
<value>Acest bot s-a oprit deja!</value>
|
||||
</data>
|
||||
@@ -544,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Botul este folosit în prezent.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -656,4 +673,5 @@ StackTrace:
|
||||
<value>S-a terminat ștergerea cozii pentru lista de descoperiri Steam #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -565,10 +565,6 @@
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Бот сейчас используется.</value>
|
||||
</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">
|
||||
<value>Не удалось войти в Steam: {0}/{1}</value>
|
||||
<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>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -544,10 +544,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot je práve používaný.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -650,4 +646,5 @@ StackTrace:
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -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">
|
||||
<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>
|
||||
@@ -510,4 +506,5 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -544,10 +544,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot används för närvarande.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -647,4 +643,5 @@ StackTrace:
|
||||
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -565,10 +565,6 @@ Yığın izleme:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot şu anda kullanılıyor.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -677,4 +673,5 @@ Yığın izleme:
|
||||
<value>Steam keşif kuyruğu temizlenmesi bitti #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -544,10 +544,6 @@
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Бот зараз використовується.</value>
|
||||
</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">
|
||||
<value>Неможливо увійти до Steam: {0}/{1}</value>
|
||||
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
|
||||
@@ -650,4 +646,5 @@
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
</root>
|
||||
|
||||
@@ -565,10 +565,6 @@ StackTrace:
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>Bot hiện đang được sử dụng.</value>
|
||||
</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">
|
||||
<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>
|
||||
@@ -677,4 +673,5 @@ StackTrace:
|
||||
<value>Đã xóa hàng đợi khám phá Steam số #{0}.</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -541,10 +541,6 @@
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>当前帐号正在使用。</value>
|
||||
</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">
|
||||
<value>无法登录到 Steam:{0}/{1}</value>
|
||||
<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>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -541,10 +541,6 @@
|
||||
<data name="BotStatusPlayingNotAvailable" xml:space="preserve">
|
||||
<value>BOT 目前正被使用。</value>
|
||||
</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">
|
||||
<value>無法登入到 Steam︰{0}/{1}</value>
|
||||
<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>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
|
||||
@@ -70,13 +70,18 @@ namespace ArchiSteamFarm {
|
||||
|
||||
public void Dispose() => ConfirmationsSemaphore.Dispose();
|
||||
|
||||
internal void CorrectDeviceID(string deviceID) {
|
||||
internal bool CorrectDeviceID(string deviceID) {
|
||||
if (string.IsNullOrEmpty(deviceID)) {
|
||||
Bot.ArchiLogger.LogNullError(nameof(deviceID));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(DeviceID) && DeviceID.Equals(deviceID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceID = deviceID;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal async Task<string> GenerateToken() {
|
||||
|
||||
77
ArchiSteamFarm/ServerRecordEndPoint.cs
Normal file
77
ArchiSteamFarm/ServerRecordEndPoint.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace ArchiSteamFarm {
|
||||
|
||||
public void Dispose() => TradesSemaphore.Dispose();
|
||||
|
||||
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim();
|
||||
internal void OnDisconnected() => IgnoredTrades.Clear();
|
||||
|
||||
internal async Task OnNewTrade() {
|
||||
// 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() {
|
||||
HashSet<Steam.TradeOffer> tradeOffers = await Bot.ArchiWebHandler.GetActiveTradeOffers().ConfigureAwait(false);
|
||||
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));
|
||||
}
|
||||
|
||||
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 {
|
||||
internal readonly EResult Result;
|
||||
|
||||
|
||||
@@ -40,16 +40,6 @@ namespace ArchiSteamFarm {
|
||||
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
|
||||
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) {
|
||||
if ((cookieContainer == null) || string.IsNullOrEmpty(url) || string.IsNullOrEmpty(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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,6 @@
|
||||
"OptimizationMode": 0,
|
||||
"Statistics": true,
|
||||
"SteamOwnerID": 0,
|
||||
"SteamProtocol": 6,
|
||||
"SteamProtocols": 7,
|
||||
"UpdateChannel": 1
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"AcceptGifts": false,
|
||||
"AutoDiscoveryQueue": false,
|
||||
"CardDropsRestricted": true,
|
||||
"CustomGamePlayedWhileFarming": null,
|
||||
"CustomGamePlayedWhileIdle": null,
|
||||
@@ -9,6 +10,7 @@
|
||||
"FarmOffline": false,
|
||||
"GamesPlayedWhileIdle": [],
|
||||
"HandleOfflineMessages": false,
|
||||
"IdleRefundableGames": true,
|
||||
"IsBotAccount": false,
|
||||
"LootableTypes": [
|
||||
1,
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user