Compare commits

...

73 Commits

Author SHA1 Message Date
JustArchi
4ada8595bd Translations update 2017-08-01 19:14:20 +02:00
JustArchi
bc64c43748 Misc 2017-08-01 19:04:55 +02:00
JustArchi
f4f7935d4c Add a workaround for invalid scheme redirections
https://github.com/JustArchi/ArchiSteamFarm/issues/586#issuecomment-319422864
2017-08-01 18:59:10 +02:00
JustArchi
aacc0a1720 Bump 2017-08-01 13:23:57 +02:00
JustArchi
e5ee909b96 Remove debug leftovers 2017-08-01 13:23:19 +02:00
JustArchi
f3f444d0bd Test fix #605 2017-08-01 13:00:54 +02:00
JustArchi
43d18b6d49 Move debug to appropriate place 2017-08-01 12:44:22 +02:00
JustArchi
f24be67c8c Bunch of stuff for #605 2017-08-01 12:41:57 +02:00
JustArchi
6ce4f2941b Bump 2017-07-31 17:51:04 +02:00
JustArchi
ef5398fae8 Translations update 2017-07-31 17:39:40 +02:00
JustArchi
ce600fdf42 Packages update 2017-07-31 17:38:38 +02:00
JustArchi
7a6485775d Misc 2017-07-31 17:35:17 +02:00
JustArchi
d39e7a32d6 Add debug for #586 2017-07-31 17:22:18 +02:00
JustArchi
b4cbe85b36 Closes #605 2017-07-31 16:57:10 +02:00
JustArchi
4d83417c9d Misc 2017-07-24 12:18:40 +02:00
JustArchi
25bfbf4862 Misc optimization
I like how R# spots multiple enumerations.
2017-07-24 11:13:55 +02:00
JustArchi
e539e1bdc7 Fixes #537
Apparently Steam web is too stupid to handle redirections properly.
2017-07-24 10:39:51 +02:00
JustArchi
b0f83fcd6d AppVeyor: Misc 2017-07-24 10:08:51 +02:00
JustArchi
4880a4e60b Bump 2017-07-23 13:42:03 +02:00
JustArchi
1beb2e01d5 Translations update 2017-07-23 12:06:16 +02:00
JustArchi
a57dc7387c Fix FarmingOrder + add ordering by redeem datetimes
Since we already have logic for that, such farming order is cool too.
2017-07-23 11:55:44 +02:00
JustArchi
0289f65072 Correct caching of apps that weren't released yet 2017-07-23 10:29:57 +02:00
JustArchi
7bb3b38ea4 Closes #522 2017-07-23 10:27:20 +02:00
JustArchi
54d1b8211b Closes #564 2017-07-23 05:35:46 +02:00
JustArchi
f97ffc76b5 Closes #510 2017-07-23 05:06:11 +02:00
JustArchi
39891b835d Closes #509 2017-07-23 04:12:00 +02:00
JustArchi
c3d6a6103e Closes #537 2017-07-23 03:43:20 +02:00
JustArchi
32e005bde3 Closes #542 2017-07-23 03:15:35 +02:00
JustArchi
2db65d324a Closes #545 2017-07-23 02:11:16 +02:00
JustArchi
65c2b81d0a Closes #580 2017-07-23 01:34:02 +02:00
JustArchi
a216eb48af Packages update 2017-07-23 01:19:30 +02:00
JustArchi
6f1e230b81 Translations update 2017-07-21 14:35:01 +02:00
JustArchi
15ec01f447 Finish leftovers from previous commit 2017-07-21 14:25:25 +02:00
JustArchi
f564cc5ebb Update ASF base code to SK 2.0 alpha5
There is a lot of changes in this one, I like how we got rid of SteamProtocol and got SteamProtocols instead.
2017-07-21 02:13:03 +02:00
JustArchi
be349d0557 Closes #598
We can actually move asking for token into OnConnected() method, nicely putting it into login limiter instead of being executed on every connection attempt. This way we'll avoid executing this function on obvius network issues.
2017-07-21 01:24:59 +02:00
JustArchi
93bc8373f8 crowdin-cli 2.0.17 2017-07-15 22:32:36 +02:00
JustArchi
4b11f61ea1 Translations update 2017-07-15 22:31:43 +02:00
JustArchi
060db81f38 Bump 2017-07-15 22:30:24 +02:00
JustArchi
c27deb20a8 AppVeyor: Misc 2017-07-15 05:21:25 +02:00
JustArchi
b231abf19f AppVeyor: Webhook test 2017-07-15 04:45:44 +02:00
JustArchi
f17f2959b7 Don't put foil cards by default in matchable types, #586 2017-07-14 17:16:45 +02:00
JustArchi
9f7886df3f Travis: Cleanup 2017-07-14 16:55:34 +02:00
JustArchi
ecc0dac4d9 Closes #593 2017-07-14 16:24:30 +02:00
JustArchi
e2f9ebc715 Travis: bump to preview2 2017-07-14 16:08:13 +02:00
JustArchi
93716218a5 Misc 2017-07-13 06:08:52 +02:00
JustArchi
db149d16a7 Fix AutoUpdate for self-builds, #586 2017-07-13 05:37:17 +02:00
JustArchi
6d22b19ef3 Announce MatchableTypes 2017-07-10 23:04:33 +02:00
JustArchi
0f6cf10179 Bump 2017-07-10 17:48:36 +02:00
JustArchi
9a745ffb97 Translations update 2017-07-10 17:42:28 +02:00
JustArchi
df95c6c6b0 Remove debug spew 2017-07-10 17:40:18 +02:00
JustArchi
9711187940 Misc 2017-07-10 17:32:22 +02:00
JustArchi
f009f1b834 Bump 2017-07-10 17:14:31 +02:00
JustArchi
f4187e194e Various bugfixes 2017-07-10 17:07:48 +02:00
JustArchi
32b2ea9fb7 Add double fine adventure to global blacklist 2017-07-10 09:15:48 +02:00
JustArchi
6e87ee3718 Travis: final fixes 2017-07-10 09:02:57 +02:00
JustArchi
a718d1cefe Travis: Unit tests fixes 2017-07-10 08:52:59 +02:00
JustArchi
019f347be4 AppVeyor: Deploy after tests 2017-07-10 08:33:38 +02:00
JustArchi
5b2b00a9f0 Add MatchableTypes
As well as unit testing so I'm sure that part is not screwed up.
2017-07-10 08:20:15 +02:00
JustArchi
e6e1b6f061 Add pre-2.0 HttpClient max connections limit 2017-07-09 09:53:26 +02:00
JustArchi
88a7d2e7e9 Code cleanup 2017-07-09 09:09:46 +02:00
JustArchi
b223fc2d97 Misc 2017-07-08 00:11:58 +02:00
JustArchi
589f4f5746 Improve I/O database code
This should lead to less file corruptions.
2017-07-07 07:33:31 +02:00
JustArchi
c4e7bbbf0c Add missing linux-arm version for update 2017-07-07 07:06:25 +02:00
JustArchi
bbb43600e5 Misc 2017-07-07 07:02:06 +02:00
JustArchi
c602654800 Misc 2017-07-07 06:54:54 +02:00
JustArchi
7a49dc5188 Misc 2017-07-05 08:00:30 +02:00
JustArchi
f1b84e57e4 Bump 2017-07-05 07:59:04 +02:00
JustArchi
3db5830f7b Misc 2017-07-05 07:58:20 +02:00
JustArchi
4acc71485e Misc 2017-07-05 07:57:40 +02:00
JustArchi
7822210cc0 Misc + translations update 2017-07-05 07:49:40 +02:00
JustArchi
c46eee4499 Various auto-update improvements 2017-07-05 07:30:08 +02:00
JustArchi
ad09b1dec9 Fix possible early crash 2017-07-05 07:07:03 +02:00
JustArchi
e640f82712 Bump 2017-07-05 06:33:33 +02:00
76 changed files with 2836 additions and 1297 deletions

View File

@@ -17,40 +17,43 @@ mono: none
# ASF requires .NET Core 2.0+ # ASF requires .NET Core 2.0+
# TODO: We should target stable 2.0.0 once it's released # TODO: We should target stable 2.0.0 once it's released
dotnet: 2.0.0-preview1-005977 dotnet: 2.0.0-preview2-006497
env: env:
global: global:
- DOTNET_CLI_TELEMETRY_OPTOUT: 1 - DOTNET_CLI_TELEMETRY_OPTOUT: 1
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
script: before_script: dotnet restore
- set -e
- dotnet restore script:
- dotnet build -c Release - |
- dotnet publish -c Release -o out/generic set -e
- echo "generic" > "ArchiSteamFarm/out/generic/ArchiSteamFarm.version"
- dotnet publish -c Release -r win-x64 -o out/win-x64 RUNTIMES="generic win-x64 linux-x64 linux-arm osx-x64"
- echo "win-x64" > "ArchiSteamFarm/out/win-x64/ArchiSteamFarm.version"
- dotnet publish -c Release -r linux-x64 -o out/linux-x64 dotnet build -c Release
- echo "linux-x64" > "ArchiSteamFarm/out/linux-x64/ArchiSteamFarm.version" dotnet test -c Release --no-build --no-restore ArchiSteamFarm.Tests
- dotnet publish -c Release -r linux-arm -o out/linux-arm
- echo "linux-arm" > "ArchiSteamFarm/out/linux-arm/ArchiSteamFarm.version" for RUNTIME in $RUNTIMES; do
- dotnet publish -c Release -r osx-x64 -o out/osx-x64 if [ "$RUNTIME" = "generic" ]; then
- echo "osx-x64" > "ArchiSteamFarm/out/osx-x64/ArchiSteamFarm.version" dotnet publish -c Release -o "out/${RUNTIME}"
else
dotnet publish -c Release -r "$RUNTIME" -o "out/${RUNTIME}"
fi
echo "$RUNTIME" > "ArchiSteamFarm/out/${RUNTIME}/ArchiSteamFarm.version"
done
# This is our main build matrix
matrix: matrix:
# We can use fast finish, as we don't need to wait for all builds to mark it as failed/passed # We can use fast finish, as we don't need to wait for allow_failures builds to mark build as success
fast_finish: true fast_finish: true
allow_failures:
# We allow OS X to fail until https://github.com/travis-ci/travis-ci/issues/7757 is fixed
- os: osx
include: include:
# We're building ASF with dotnet on latest versions of Linux and OS X # We're building ASF with dotnet on latest versions of Linux and OS X
# Ref: https://docs.travis-ci.com/user/ci-environment/#Virtualization-environments
- os: linux - os: linux
# Ref: https://docs.travis-ci.com/user/reference/trusty/
dist: trusty dist: trusty
sudo: false sudo: false
- os: osx - os: osx
# Ref: https://docs.travis-ci.com/user/reference/osx/
osx_image: xcode9 osx_image: xcode9

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

View File

@@ -0,0 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ErrorReport>none</ErrorReport>
<ApplicationIcon>ASF.ico</ApplicationIcon>
<Copyright>Copyright © ArchiSteamFarm 2015-2017</Copyright>
<RuntimeIdentifiers>win-x64;linux-x64;linux-arm;osx-x64</RuntimeIdentifiers>
<Description>ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.</Description>
<Authors>JustArchi</Authors>
<Company>JustArchi</Company>
<PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/JustArchi/ArchiSteamFarm</PackageProjectUrl>
<RepositoryUrl>https://github.com/JustArchi/ArchiSteamFarm.git</RepositoryUrl>
<PackageIconUrl>https://github.com/JustArchi/ArchiSteamFarm/raw/master/resources/ASF.ico</PackageIconUrl>
<RepositoryType>Git</RepositoryType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0-preview-20170727-01" />
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0-beta" />
<PackageReference Include="MSTest.TestFramework" Version="1.2.0-beta" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ArchiSteamFarm\ArchiSteamFarm.csproj" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,175 @@
/*
_ _ _ ____ _ _____
/ \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
/ _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
/ ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
/_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
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.Generic;
using System.Reflection;
using ArchiSteamFarm.JSON;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ArchiSteamFarm.Tests {
[TestClass]
public sealed class Trading {
[TestMethod]
public void TradingMultiGameBadReject() {
Steam.Item item1Game1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item1Game1X9 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 9, 570, Steam.Item.EType.TradingCard);
Steam.Item item2Game1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item1Game2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 3, 1, 730, Steam.Item.EType.TradingCard);
Steam.Item item2Game2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 4, 1, 730, Steam.Item.EType.TradingCard);
HashSet<Steam.Item> inventory = new HashSet<Steam.Item> { item1Game1X9, item1Game2, item2Game2 };
HashSet<Steam.Item> itemsToGive = new HashSet<Steam.Item> { item1Game1, item1Game2 };
HashSet<Steam.Item> itemsToReceive = new HashSet<Steam.Item> { item2Game1, item2Game2 };
Assert.IsFalse(AcceptsTrade(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void TradingMultiGameMultiTypeBadReject() {
Steam.Item item1Type1Game1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item1Type1Game1X9 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 9, 570, Steam.Item.EType.TradingCard);
Steam.Item item2Type1Game1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item3Type2Game2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 3, 1, 730, Steam.Item.EType.Emoticon);
Steam.Item item3Type2Game2X9 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 3, 9, 730, Steam.Item.EType.Emoticon);
Steam.Item item4Type2Game2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 4, 1, 730, Steam.Item.EType.Emoticon);
HashSet<Steam.Item> inventory = new HashSet<Steam.Item> { item1Type1Game1X9, item3Type2Game2X9, item4Type2Game2 };
HashSet<Steam.Item> itemsToGive = new HashSet<Steam.Item> { item1Type1Game1, item4Type2Game2 };
HashSet<Steam.Item> itemsToReceive = new HashSet<Steam.Item> { item2Type1Game1, item3Type2Game2 };
Assert.IsFalse(AcceptsTrade(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void TradingMultiGameMultiTypeNeutralAccept() {
Steam.Item item1Type1Game1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item1Type1Game1X9 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 9, 570, Steam.Item.EType.TradingCard);
Steam.Item item2Type1Game1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item3Type2Game2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 3, 1, 730, Steam.Item.EType.Emoticon);
Steam.Item item4Type2Game2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 4, 1, 730, Steam.Item.EType.Emoticon);
HashSet<Steam.Item> inventory = new HashSet<Steam.Item> { item1Type1Game1X9, item3Type2Game2 };
HashSet<Steam.Item> itemsToGive = new HashSet<Steam.Item> { item1Type1Game1, item3Type2Game2 };
HashSet<Steam.Item> itemsToReceive = new HashSet<Steam.Item> { item2Type1Game1, item4Type2Game2 };
Assert.IsTrue(AcceptsTrade(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void TradingMultiGameNeutralAccept() {
Steam.Item item1Game1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item1Game1X2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 2, 570, Steam.Item.EType.TradingCard);
Steam.Item item2Game1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item1Game2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 730, Steam.Item.EType.TradingCard);
Steam.Item item2Game2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 730, Steam.Item.EType.TradingCard);
HashSet<Steam.Item> inventory = new HashSet<Steam.Item> { item1Game1X2, item1Game2 };
HashSet<Steam.Item> itemsToGive = new HashSet<Steam.Item> { item1Game1, item1Game2 };
HashSet<Steam.Item> itemsToReceive = new HashSet<Steam.Item> { item2Game1, item2Game2 };
Assert.IsTrue(AcceptsTrade(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void TradingSingleGameBadReject() {
Steam.Item item1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 570, Steam.Item.EType.TradingCard);
HashSet<Steam.Item> inventory = new HashSet<Steam.Item> { item1, item2 };
HashSet<Steam.Item> itemsToGive = new HashSet<Steam.Item> { item1 };
HashSet<Steam.Item> itemsToReceive = new HashSet<Steam.Item> { item2 };
Assert.IsFalse(AcceptsTrade(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void TradingSingleGameGoodAccept() {
Steam.Item item1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item1X2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 2, 570, Steam.Item.EType.TradingCard);
Steam.Item item2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 570, Steam.Item.EType.TradingCard);
HashSet<Steam.Item> inventory = new HashSet<Steam.Item> { item1X2 };
HashSet<Steam.Item> itemsToGive = new HashSet<Steam.Item> { item1 };
HashSet<Steam.Item> itemsToReceive = new HashSet<Steam.Item> { item2 };
Assert.IsTrue(AcceptsTrade(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void TradingSingleGameMultiTypeBadReject() {
Steam.Item item1Type1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item1Type1X9 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 9, 570, Steam.Item.EType.TradingCard);
Steam.Item item2Type1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item3Type2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 3, 1, 570, Steam.Item.EType.Emoticon);
Steam.Item item3Type2X9 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 3, 9, 570, Steam.Item.EType.Emoticon);
Steam.Item item4Type2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 4, 1, 570, Steam.Item.EType.Emoticon);
HashSet<Steam.Item> inventory = new HashSet<Steam.Item> { item1Type1X9, item3Type2X9, item4Type2 };
HashSet<Steam.Item> itemsToGive = new HashSet<Steam.Item> { item1Type1, item4Type2 };
HashSet<Steam.Item> itemsToReceive = new HashSet<Steam.Item> { item2Type1, item3Type2 };
Assert.IsFalse(AcceptsTrade(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void TradingSingleGameMultiTypeNeutralAccept() {
Steam.Item item1Type1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item1Type1X9 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 9, 570, Steam.Item.EType.TradingCard);
Steam.Item item2Type1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item3Type2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 3, 1, 570, Steam.Item.EType.Emoticon);
Steam.Item item4Type2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 4, 1, 570, Steam.Item.EType.Emoticon);
HashSet<Steam.Item> inventory = new HashSet<Steam.Item> { item1Type1X9, item3Type2 };
HashSet<Steam.Item> itemsToGive = new HashSet<Steam.Item> { item1Type1, item3Type2 };
HashSet<Steam.Item> itemsToReceive = new HashSet<Steam.Item> { item2Type1, item4Type2 };
Assert.IsTrue(AcceptsTrade(inventory, itemsToGive, itemsToReceive));
}
[TestMethod]
public void TradingSingleGameNeutralAccept() {
Steam.Item item1 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 1, 1, 570, Steam.Item.EType.TradingCard);
Steam.Item item2 = new Steam.Item(Steam.Item.SteamAppID, Steam.Item.SteamCommunityContextID, 2, 1, 570, Steam.Item.EType.TradingCard);
HashSet<Steam.Item> inventory = new HashSet<Steam.Item> { item1 };
HashSet<Steam.Item> itemsToGive = new HashSet<Steam.Item> { item1 };
HashSet<Steam.Item> itemsToReceive = new HashSet<Steam.Item> { item2 };
Assert.IsTrue(AcceptsTrade(inventory, itemsToGive, itemsToReceive));
}
private static bool AcceptsTrade(HashSet<Steam.Item> inventory, HashSet<Steam.Item> itemsToGive, HashSet<Steam.Item> itemsToReceive) {
Type trading = typeof(ArchiSteamFarm.Trading);
MethodInfo method = trading.GetMethod("IsTradeNeutralOrBetter", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
return (bool) method.Invoke(null, new object[] { inventory, itemsToGive, itemsToReceive });
}
}
}

View File

@@ -1,10 +1,12 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15 # Visual Studio 15
VisualStudioVersion = 15.0.26608.5 VisualStudioVersion = 15.0.26621.2
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm", "ArchiSteamFarm\ArchiSteamFarm.csproj", "{CF84911C-2C4C-4195-8AF3-ABBB6D3DE9AA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm", "ArchiSteamFarm\ArchiSteamFarm.csproj", "{CF84911C-2C4C-4195-8AF3-ABBB6D3DE9AA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArchiSteamFarm.Tests", "ArchiSteamFarm.Tests\ArchiSteamFarm.Tests.csproj", "{91DC4C4F-3C23-4716-8CB2-BC7EAB65D759}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -15,8 +17,15 @@ Global
{CF84911C-2C4C-4195-8AF3-ABBB6D3DE9AA}.Debug|Any CPU.Build.0 = Debug|Any CPU {CF84911C-2C4C-4195-8AF3-ABBB6D3DE9AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF84911C-2C4C-4195-8AF3-ABBB6D3DE9AA}.Release|Any CPU.ActiveCfg = Release|Any CPU {CF84911C-2C4C-4195-8AF3-ABBB6D3DE9AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CF84911C-2C4C-4195-8AF3-ABBB6D3DE9AA}.Release|Any CPU.Build.0 = Release|Any CPU {CF84911C-2C4C-4195-8AF3-ABBB6D3DE9AA}.Release|Any CPU.Build.0 = Release|Any CPU
{91DC4C4F-3C23-4716-8CB2-BC7EAB65D759}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{91DC4C4F-3C23-4716-8CB2-BC7EAB65D759}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91DC4C4F-3C23-4716-8CB2-BC7EAB65D759}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91DC4C4F-3C23-4716-8CB2-BC7EAB65D759}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D7D54143-C857-4B76-A219-0E98C5BC4895}
EndGlobalSection
EndGlobal EndGlobal

View File

@@ -117,7 +117,7 @@
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue">False</s:Boolean> <s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_CONTROL_TRANSFER_STATEMENTS/@EntryValue">1</s:Int64> <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_CONTROL_TRANSFER_STATEMENTS/@EntryValue">1</s:Int64>
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_FIELD/@EntryValue">0</s:Int64> <s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_FIELD/@EntryValue">1</s:Int64>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">END_OF_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">END_OF_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER_SAME_LINE</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">TOGETHER_SAME_LINE</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_STYLE/@EntryValue">Tab</s:String> <s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_STYLE/@EntryValue">Tab</s:String>
@@ -384,4 +384,5 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAlwaysTreatStructAsNotReorderableMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EXml_002ECodeStyle_002EFormatSettingsUpgrade_002EXmlMoveToCommonFormatterSettingsUpgrade/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002EXml_002ECodeStyle_002EFormatSettingsUpgrade_002EXmlMoveToCommonFormatterSettingsUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Int64 x:Key="/Default/Environment/UnitTesting/ParallelProcessesCount/@EntryValue">8</s:Int64></wpf:ResourceDictionary>

View File

@@ -37,6 +37,7 @@ using ArchiSteamFarm.Localization;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class ASF { internal static class ASF {
private const byte AutoUpdatePeriodInHours = 24; private const byte AutoUpdatePeriodInHours = 24;
private const string DefaultVersion = "source"; // Default entry of ArchiSteamFarm.version
internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF); internal static readonly ArchiLogger ArchiLogger = new ArchiLogger(SharedInfo.ASF);
@@ -46,32 +47,6 @@ namespace ArchiSteamFarm {
private static FileSystemWatcher FileSystemWatcher; private static FileSystemWatcher FileSystemWatcher;
internal static async Task CheckForUpdate(bool updateOverride = false) { internal static async Task CheckForUpdate(bool updateOverride = false) {
string targetDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
// Cleanup from previous update - update directory for old in-use runtime files
string backupDirectory = Path.Combine(targetDirectory, SharedInfo.UpdateDirectory);
if (Directory.Exists(backupDirectory)) {
// It's entirely possible that old process is still running, wait at least a second for eventual cleanup
await Task.Delay(1000).ConfigureAwait(false);
try {
Directory.Delete(backupDirectory, true);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
return;
}
}
// Cleanup from previous update - old non-runtime in-use files
foreach (string file in Directory.GetFiles(targetDirectory, "*.old", SearchOption.AllDirectories)) {
try {
File.Delete(file);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
return;
}
}
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.None) { if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.None) {
return; return;
} }
@@ -102,11 +77,15 @@ namespace ArchiSteamFarm {
} }
version = version.TrimEnd(); version = version.TrimEnd();
if (string.IsNullOrEmpty(version) || !IsVersionValid(version)) { if (string.IsNullOrEmpty(version) || !IsValidVersion(version)) {
ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, SharedInfo.VersionFile)); ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, SharedInfo.VersionFile));
return; return;
} }
if (version.Equals(DefaultVersion)) {
return;
}
if ((AutoUpdatesTimer == null) && Program.GlobalConfig.AutoUpdates) { if ((AutoUpdatesTimer == null) && Program.GlobalConfig.AutoUpdates) {
TimeSpan autoUpdatePeriod = TimeSpan.FromHours(AutoUpdatePeriodInHours); TimeSpan autoUpdatePeriod = TimeSpan.FromHours(AutoUpdatePeriodInHours);
@@ -120,13 +99,39 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericInfo(string.Format(Strings.AutoUpdateCheckInfo, autoUpdatePeriod.ToHumanReadable())); ArchiLogger.LogGenericInfo(string.Format(Strings.AutoUpdateCheckInfo, autoUpdatePeriod.ToHumanReadable()));
} }
ArchiLogger.LogGenericInfo(Strings.UpdateCheckingNewVersion);
string targetDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
// Cleanup from previous update - update directory for old in-use runtime files
string backupDirectory = Path.Combine(targetDirectory, SharedInfo.UpdateDirectory);
if (Directory.Exists(backupDirectory)) {
// It's entirely possible that old process is still running, wait a short moment for eventual cleanup
await Task.Delay(5000).ConfigureAwait(false);
try {
Directory.Delete(backupDirectory, true);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
return;
}
}
// Cleanup from previous update - old non-runtime in-use files
foreach (string file in Directory.EnumerateFiles(targetDirectory, "*.old", SearchOption.AllDirectories)) {
try {
File.Delete(file);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
return;
}
}
string releaseURL = SharedInfo.GithubReleaseURL; string releaseURL = SharedInfo.GithubReleaseURL;
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) { if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
releaseURL += "/latest"; releaseURL += "/latest";
} }
ArchiLogger.LogGenericInfo(Strings.UpdateCheckingNewVersion);
GitHub.ReleaseResponse releaseResponse; GitHub.ReleaseResponse releaseResponse;
if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) { if (Program.GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable) {
@@ -189,7 +194,7 @@ namespace ArchiSteamFarm {
return; return;
} }
ArchiLogger.LogGenericInfo(Strings.UpdateDownloadingNewVersion); ArchiLogger.LogGenericInfo(string.Format(Strings.UpdateDownloadingNewVersion, newVersion, binaryAsset.Size / 1024 / 1024));
byte[] result = await Program.WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false); byte[] result = await Program.WebBrowser.UrlGetToBytesRetry(binaryAsset.DownloadURL).ConfigureAwait(false);
if (result == null) { if (result == null) {
@@ -214,17 +219,10 @@ namespace ArchiSteamFarm {
return; return;
} }
// Before attempting to connect, initialize our list of CMs // Before attempting to connect, initialize our configuration
await Bot.InitializeCMs(Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider).ConfigureAwait(false); await Bot.InitializeSteamConfiguration(Program.GlobalConfig.SteamProtocols, Program.GlobalDatabase.CellID, Program.GlobalDatabase.ServerListProvider).ConfigureAwait(false);
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && (botName[0] != '.'))) {
switch (botName) {
case SharedInfo.ASF:
case "example":
case "minimal":
continue;
}
foreach (string botName in Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*.json").Select(Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && IsValidBotName(botName)).OrderBy(botName => botName)) {
Bot.RegisterBot(botName); Bot.RegisterBot(botName);
} }
@@ -270,17 +268,39 @@ namespace ArchiSteamFarm {
Bot.RegisterBot(botName); Bot.RegisterBot(botName);
} }
private static bool IsVersionValid(string version) { private static bool IsValidBotName(string botName) {
if (string.IsNullOrEmpty(botName)) {
ArchiLogger.LogNullError(nameof(botName));
return false;
}
if (botName[0] == '.') {
return false;
}
switch (botName) {
case SharedInfo.ASF:
case "example":
case "minimal":
return false;
default:
return true;
}
}
private static bool IsValidVersion(string version) {
if (string.IsNullOrEmpty(version)) { if (string.IsNullOrEmpty(version)) {
ArchiLogger.LogNullError(nameof(version)); ArchiLogger.LogNullError(nameof(version));
return false; return false;
} }
switch (version) { switch (version) {
case DefaultVersion:
case "generic": case "generic":
case "win-x64": case "linux-arm":
case "linux-x64": case "linux-x64":
case "osx-x64": case "osx-x64":
case "win-x64":
return true; return true;
default: default:
return false; return false;
@@ -438,7 +458,7 @@ namespace ArchiSteamFarm {
// Move top-level runtime in-use files to other directory // Move top-level runtime in-use files to other directory
// We must do it in order to not crash at later stage - all libraries/executables must keep original names // We must do it in order to not crash at later stage - all libraries/executables must keep original names
foreach (string file in Directory.GetFiles(targetDirectory)) { foreach (string file in Directory.EnumerateFiles(targetDirectory)) {
string target = Path.Combine(backupDirectory, Path.GetFileName(file)); string target = Path.Combine(backupDirectory, Path.GetFileName(file));
File.Move(file, target); File.Move(file, target);
} }
@@ -446,7 +466,7 @@ namespace ArchiSteamFarm {
// In generic ASF variant there can also be "runtimes" directory in need of same approach // In generic ASF variant there can also be "runtimes" directory in need of same approach
string runtimesDirectory = Path.Combine(targetDirectory, "runtimes"); string runtimesDirectory = Path.Combine(targetDirectory, "runtimes");
if (Directory.Exists(runtimesDirectory)) { if (Directory.Exists(runtimesDirectory)) {
foreach (string file in Directory.GetFiles(runtimesDirectory, "*", SearchOption.AllDirectories)) { foreach (string file in Directory.EnumerateFiles(runtimesDirectory, "*", SearchOption.AllDirectories)) {
string directory = Path.Combine(backupDirectory, Path.GetDirectoryName(Path.GetRelativePath(targetDirectory, file))); string directory = Path.Combine(backupDirectory, Path.GetDirectoryName(Path.GetRelativePath(targetDirectory, file)));
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);

View File

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

View File

@@ -3,14 +3,21 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp2.0</TargetFramework>
<AssemblyVersion>3.0.0.1</AssemblyVersion> <AssemblyVersion>3.0.0.9</AssemblyVersion>
<FileVersion>3.0.0.1</FileVersion> <FileVersion>3.0.0.9</FileVersion>
<LangVersion>7</LangVersion> <LangVersion>latest</LangVersion>
<ErrorReport>none</ErrorReport> <ErrorReport>none</ErrorReport>
<ApplicationIcon>ASF.ico</ApplicationIcon> <ApplicationIcon>ASF.ico</ApplicationIcon>
<Copyright>Copyright © ArchiSteamFarm 2015-2017</Copyright> <Copyright>Copyright © ArchiSteamFarm 2015-2017</Copyright>
<RuntimeIdentifiers>win-x64;linux-x64;linux-arm;osx-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;linux-x64;linux-arm;osx-x64</RuntimeIdentifiers>
<Description>ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.</Description> <Description>ASF is an application that allows you to farm steam cards using multiple steam accounts simultaneously.</Description>
<Authors>JustArchi</Authors>
<Company>JustArchi</Company>
<PackageLicenseUrl>http://www.apache.org/licenses/LICENSE-2.0</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/JustArchi/ArchiSteamFarm</PackageProjectUrl>
<RepositoryUrl>https://github.com/JustArchi/ArchiSteamFarm.git</RepositoryUrl>
<PackageIconUrl>https://github.com/JustArchi/ArchiSteamFarm/raw/master/resources/ASF.ico</PackageIconUrl>
<RepositoryType>Git</RepositoryType>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -19,12 +26,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.5.0" /> <PackageReference Include="HtmlAgilityPack" Version="1.5.2-beta3" />
<PackageReference Include="Humanizer" Version="2.2.0" /> <PackageReference Include="Humanizer" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Nito.AsyncEx.Coordination" Version="5.0.0-pre-02" />
<PackageReference Include="NLog" Version="5.0.0-beta09" /> <PackageReference Include="NLog" Version="5.0.0-beta09" />
<PackageReference Include="SteamKit2" Version="2.0.0-Alpha4" /> <PackageReference Include="SteamKit2" Version="2.0.0-Alpha5" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0-preview2-25405-01" /> <PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0-preview2-25405-01" />
</ItemGroup> </ItemGroup>
@@ -44,6 +50,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="ArchiSteamFarm.version">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="config\ASF.json"> <None Update="config\ASF.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

View File

@@ -0,0 +1 @@
source

View File

@@ -50,21 +50,23 @@ namespace ArchiSteamFarm {
// We must use HTTPS for SteamCommunity, as http would make certain POST requests failing (trades) // We must use HTTPS for SteamCommunity, as http would make certain POST requests failing (trades)
private const string SteamCommunityHost = "steamcommunity.com"; private const string SteamCommunityHost = "steamcommunity.com";
private const string SteamCommunityURL = "https://" + SteamCommunityHost; private const string SteamCommunityURL = "https://" + SteamCommunityHost;
// We could (and should) use HTTPS for SteamStore, but that would make certain POST requests failing // We could (and should) use HTTPS for SteamStore, but that would make certain POST requests failing
private const string SteamStoreHost = "store.steampowered.com"; private const string SteamStoreHost = "store.steampowered.com";
private const string SteamStoreURL = "http://" + SteamStoreHost; private const string SteamStoreURL = "http://" + SteamStoreHost;
private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1); private static readonly SemaphoreSlim InventorySemaphore = new SemaphoreSlim(1, 1);
private static int Timeout = GlobalConfig.DefaultConnectionTimeout * 1000; // This must be int type private static int Timeout = GlobalConfig.DefaultConnectionTimeout * 1000; // This must be int type
private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim ApiKeySemaphore = new SemaphoreSlim(1, 1);
private readonly Bot Bot; private readonly Bot Bot;
private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim PublicInventorySemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim SessionSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim TradeTokenSemaphore = new SemaphoreSlim(1, 1);
private readonly WebBrowser WebBrowser; private readonly WebBrowser WebBrowser;
private string CachedApiKey; private string CachedApiKey;
@@ -72,6 +74,7 @@ namespace ArchiSteamFarm {
private string CachedTradeToken; private string CachedTradeToken;
private DateTime LastSessionRefreshCheck = DateTime.MinValue; private DateTime LastSessionRefreshCheck = DateTime.MinValue;
private ulong SteamID; private ulong SteamID;
private string VanityURL;
internal ArchiWebHandler(Bot bot) { internal ArchiWebHandler(Bot bot) {
Bot = bot ?? throw new ArgumentNullException(nameof(bot)); Bot = bot ?? throw new ArgumentNullException(nameof(bot));
@@ -177,7 +180,7 @@ namespace ArchiSteamFarm {
} }
KeyValue response = null; KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
await Task.Run(() => { await Task.Run(() => {
using (dynamic iEconService = WebAPI.GetInterface(IEconService, steamApiKey)) { using (dynamic iEconService = WebAPI.GetInterface(IEconService, steamApiKey)) {
iEconService.Timeout = Timeout; iEconService.Timeout = Timeout;
@@ -196,7 +199,7 @@ namespace ArchiSteamFarm {
} }
if (response == null) { if (response == null) {
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxRetries)); Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries));
} }
} }
@@ -228,7 +231,7 @@ namespace ArchiSteamFarm {
} }
KeyValue response = null; KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
await Task.Run(() => { await Task.Run(() => {
using (dynamic iEconService = WebAPI.GetInterface(IEconService, steamApiKey)) { using (dynamic iEconService = WebAPI.GetInterface(IEconService, steamApiKey)) {
iEconService.Timeout = Timeout; iEconService.Timeout = Timeout;
@@ -249,7 +252,7 @@ namespace ArchiSteamFarm {
} }
if (response == null) { if (response == null) {
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxRetries)); Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries));
return null; return null;
} }
@@ -625,7 +628,7 @@ namespace ArchiSteamFarm {
} }
KeyValue response = null; KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
await Task.Run(() => { await Task.Run(() => {
using (dynamic iPlayerService = WebAPI.GetInterface(IPlayerService, steamApiKey)) { using (dynamic iPlayerService = WebAPI.GetInterface(IPlayerService, steamApiKey)) {
iPlayerService.Timeout = Timeout; iPlayerService.Timeout = Timeout;
@@ -644,7 +647,7 @@ namespace ArchiSteamFarm {
} }
if (response == null) { if (response == null) {
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxRetries)); Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries));
return null; return null;
} }
@@ -664,7 +667,7 @@ namespace ArchiSteamFarm {
internal async Task<uint> GetServerTime() { internal async Task<uint> GetServerTime() {
KeyValue response = null; KeyValue response = null;
for (byte i = 0; (i < WebBrowser.MaxRetries) && (response == null); i++) { for (byte i = 0; (i < WebBrowser.MaxTries) && (response == null); i++) {
await Task.Run(() => { await Task.Run(() => {
using (dynamic iTwoFactorService = WebAPI.GetInterface(ITwoFactorService)) { using (dynamic iTwoFactorService = WebAPI.GetInterface(ITwoFactorService)) {
iTwoFactorService.Timeout = Timeout; iTwoFactorService.Timeout = Timeout;
@@ -685,7 +688,7 @@ namespace ArchiSteamFarm {
return response["server_time"].AsUnsignedInteger(); return response["server_time"].AsUnsignedInteger();
} }
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxRetries)); Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries));
return 0; return 0;
} }
@@ -865,12 +868,16 @@ namespace ArchiSteamFarm {
internal static void Init() => Timeout = Program.GlobalConfig.ConnectionTimeout * 1000; internal static void Init() => Timeout = Program.GlobalConfig.ConnectionTimeout * 1000;
internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin) { internal async Task<bool> Init(ulong steamID, EUniverse universe, string webAPIUserNonce, string parentalPin, string vanityURL = null) {
if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) { if ((steamID == 0) || (universe == EUniverse.Invalid) || string.IsNullOrEmpty(webAPIUserNonce) || string.IsNullOrEmpty(parentalPin)) {
Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin)); Bot.ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(universe) + " || " + nameof(webAPIUserNonce) + " || " + nameof(parentalPin));
return false; return false;
} }
if (!string.IsNullOrEmpty(vanityURL)) {
VanityURL = vanityURL;
}
string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString())); string sessionID = Convert.ToBase64String(Encoding.UTF8.GetBytes(steamID.ToString()));
// Generate an AES session key // Generate an AES session key
@@ -1086,6 +1093,41 @@ namespace ArchiSteamFarm {
return true; return true;
} }
internal async Task<bool> UnpackBooster(uint appID, ulong itemID) {
if ((appID == 0) || (itemID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(itemID));
return false;
}
if (!await RefreshSessionIfNeeded().ConfigureAwait(false)) {
return false;
}
string sessionID = WebBrowser.CookieContainer.GetCookieValue(SteamCommunityURL, "sessionid");
if (string.IsNullOrEmpty(sessionID)) {
Bot.ArchiLogger.LogNullError(nameof(sessionID));
return false;
}
string request = GetAbsoluteProfileURL() + "/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 string GetAbsoluteProfileURL() {
if (!string.IsNullOrEmpty(VanityURL)) {
return SteamCommunityURL + "/id/" + VanityURL;
}
return SteamCommunityURL + "/profiles/" + SteamID;
}
private async Task<string> GetApiKey() { private async Task<string> GetApiKey() {
if (CachedApiKey != null) { if (CachedApiKey != null) {
// We fetched API key already, and either got valid one, or permanent AccessDenied // We fetched API key already, and either got valid one, or permanent AccessDenied

View File

@@ -22,23 +22,6 @@
*/ */
using System; using System.Runtime.CompilerServices;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm { [assembly: InternalsVisibleTo("ArchiSteamFarm.Tests")]
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());
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -41,6 +41,11 @@ namespace ArchiSteamFarm {
internal readonly bool AcceptGifts; internal readonly bool AcceptGifts;
#pragma warning restore 649 #pragma warning restore 649
#pragma warning disable 649
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AutoDiscoveryQueue;
#pragma warning restore 649
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool CardDropsRestricted = true; internal readonly bool CardDropsRestricted = true;
@@ -80,6 +85,11 @@ namespace ArchiSteamFarm {
internal readonly bool HandleOfflineMessages; internal readonly bool HandleOfflineMessages;
#pragma warning restore 649 #pragma warning restore 649
#pragma warning disable 649
[JsonProperty(Required = Required.DisallowNull)]
internal readonly bool IdleRefundableGames = true;
#pragma warning restore 649
#pragma warning disable 649 #pragma warning disable 649
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool IsBotAccount; internal readonly bool IsBotAccount;
@@ -92,6 +102,11 @@ namespace ArchiSteamFarm {
Steam.Item.EType.TradingCard Steam.Item.EType.TradingCard
}; };
[JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace, Required = Required.DisallowNull)]
internal readonly HashSet<Steam.Item.EType> MatchableTypes = new HashSet<Steam.Item.EType> {
Steam.Item.EType.TradingCard
};
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText; internal readonly CryptoHelper.ECryptoMethod PasswordFormat = CryptoHelper.ECryptoMethod.PlainText;
@@ -212,7 +227,11 @@ namespace ArchiSteamFarm {
HoursDescending, HoursDescending,
NamesAscending, NamesAscending,
NamesDescending, NamesDescending,
Random Random,
BadgeLevelsAscending,
BadgeLevelsDescending,
RedeemDateTimesAscending,
RedeemDateTimesDescending
} }
internal enum EPermission : byte { internal enum EPermission : byte {

View File

@@ -26,7 +26,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Threading;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
@@ -36,6 +35,9 @@ namespace ArchiSteamFarm {
private readonly object FileLock = new object(); private readonly object FileLock = new object();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<uint> IdlingPriorityAppIDs = new ConcurrentHashSet<uint>();
internal string LoginKey { internal string LoginKey {
get => _LoginKey; get => _LoginKey;
@@ -95,17 +97,51 @@ namespace ArchiSteamFarm {
} }
} }
internal IEnumerable<ulong> GetBlacklistedFromTradesSteamIDs() => BlacklistedFromTradesSteamIDs; internal void AddIdlingPriorityAppIDs(HashSet<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
internal bool IsBlacklistedFromTrades(ulong steamID) { ASF.ArchiLogger.LogNullError(nameof(appIDs));
if (steamID != 0) { return;
return BlacklistedFromTradesSteamIDs.Contains(steamID);
} }
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)); ASF.ArchiLogger.LogNullError(nameof(steamID));
return false; return false;
} }
bool result = BlacklistedFromTradesSteamIDs.Contains(steamID);
return result;
}
internal bool IsPriorityIdling(uint appID) {
if (appID == 0) {
ASF.ArchiLogger.LogNullError(nameof(appID));
return false;
}
bool result = IdlingPriorityAppIDs.Contains(appID);
return result;
}
internal static BotDatabase Load(string filePath) { internal static BotDatabase Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) { if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath)); ASF.ArchiLogger.LogNullError(nameof(filePath));
@@ -145,29 +181,38 @@ namespace ArchiSteamFarm {
} }
} }
internal void Save() { internal void RemoveIdlingPriorityAppIDs(HashSet<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
}
if (IdlingPriorityAppIDs.RemoveRange(appIDs)) {
Save();
}
}
private void Save() {
string json = JsonConvert.SerializeObject(this); string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) { if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json)); ASF.ArchiLogger.LogNullError(nameof(json));
return; return;
} }
// This call verifies if JSON is alright
// We don't wrap it in try catch as it should always be the case
// And if it's not, we want to know about it (in a crash) and correct it in future version
JsonConvert.DeserializeObject<BotDatabase>(json);
lock (FileLock) { lock (FileLock) {
for (byte i = 0; i < 5; i++) { string newFilePath = FilePath + ".new";
try { try {
File.WriteAllText(FilePath, json); File.WriteAllText(newFilePath, json);
break;
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) { } catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e); ASF.ArchiLogger.LogGenericException(e);
} }
Thread.Sleep(1000);
}
} }
} }
} }

View File

@@ -34,6 +34,7 @@ using System.Threading.Tasks;
using ArchiSteamFarm.Localization; using ArchiSteamFarm.Localization;
using HtmlAgilityPack; using HtmlAgilityPack;
using Newtonsoft.Json; using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class CardsFarmer : IDisposable { internal sealed class CardsFarmer : IDisposable {
@@ -47,7 +48,7 @@ namespace ArchiSteamFarm {
internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>(); internal readonly ConcurrentHashSet<Game> CurrentGamesFarming = new ConcurrentHashSet<Game>();
[JsonProperty] [JsonProperty]
internal readonly ConcurrentHashSet<Game> GamesToFarm = new ConcurrentHashSet<Game>(); internal readonly ConcurrentSortedHashSet<Game> GamesToFarm = new ConcurrentSortedHashSet<Game>();
[JsonProperty] [JsonProperty]
internal TimeSpan TimeRemaining => new TimeSpan( internal TimeSpan TimeRemaining => new TimeSpan(
@@ -57,9 +58,9 @@ namespace ArchiSteamFarm {
); );
private readonly Bot Bot; private readonly Bot Bot;
private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim EventSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim FarmingSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1);
private readonly ManualResetEventSlim FarmResetEvent = new ManualResetEventSlim(false); private readonly SemaphoreSlim FarmingResetSemaphore = new SemaphoreSlim(0, 1);
private readonly Timer IdleFarmingTimer; private readonly Timer IdleFarmingTimer;
[JsonProperty] [JsonProperty]
@@ -86,8 +87,8 @@ namespace ArchiSteamFarm {
public void Dispose() { public void Dispose() {
// Those are objects that are always being created if constructor doesn't throw exception // Those are objects that are always being created if constructor doesn't throw exception
EventSemaphore.Dispose(); EventSemaphore.Dispose();
FarmingSemaphore.Dispose(); FarmingInitializationSemaphore.Dispose();
FarmResetEvent.Dispose(); FarmingResetSemaphore.Dispose();
// Those are objects that might be null and the check should be in-place // Those are objects that might be null and the check should be in-place
IdleFarmingTimer?.Dispose(); IdleFarmingTimer?.Dispose();
@@ -139,7 +140,7 @@ namespace ArchiSteamFarm {
internal async Task OnNewItemsNotification() { internal async Task OnNewItemsNotification() {
if (NowFarming) { if (NowFarming) {
FarmResetEvent.Set(); FarmingResetSemaphore.Release();
return; return;
} }
@@ -187,7 +188,7 @@ namespace ArchiSteamFarm {
return; return;
} }
await FarmingSemaphore.WaitAsync().ConfigureAwait(false); await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try { try {
if (NowFarming || Paused || !Bot.IsPlayingPossible) { if (NowFarming || Paused || !Bot.IsPlayingPossible) {
@@ -230,7 +231,7 @@ namespace ArchiSteamFarm {
KeepFarming = NowFarming = true; KeepFarming = NowFarming = true;
Utilities.StartBackgroundFunction(Farm); Utilities.StartBackgroundFunction(Farm);
} finally { } finally {
FarmingSemaphore.Release(); FarmingInitializationSemaphore.Release();
} }
} }
@@ -239,7 +240,7 @@ namespace ArchiSteamFarm {
return; return;
} }
await FarmingSemaphore.WaitAsync().ConfigureAwait(false); await FarmingInitializationSemaphore.WaitAsync().ConfigureAwait(false);
try { try {
if (!NowFarming) { if (!NowFarming) {
@@ -247,7 +248,7 @@ namespace ArchiSteamFarm {
} }
KeepFarming = false; KeepFarming = false;
FarmResetEvent.Set(); FarmingResetSemaphore.Release();
for (byte i = 0; (i < 5) && NowFarming; i++) { for (byte i = 0; (i < 5) && NowFarming; i++) {
await Task.Delay(1000).ConfigureAwait(false); await Task.Delay(1000).ConfigureAwait(false);
@@ -260,11 +261,11 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(Strings.IdlingStopped); Bot.ArchiLogger.LogGenericInfo(Strings.IdlingStopped);
Bot.OnFarmingStopped(); Bot.OnFarmingStopped();
} finally { } finally {
FarmingSemaphore.Release(); FarmingInitializationSemaphore.Release();
} }
} }
private async Task CheckGame(uint appID, string name, float hours) { private async Task CheckGame(uint appID, string name, float hours, byte badgeLevel) {
if ((appID == 0) || string.IsNullOrEmpty(name) || (hours < 0)) { if ((appID == 0) || string.IsNullOrEmpty(name) || (hours < 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(name) + " || " + nameof(hours)); Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(name) + " || " + nameof(hours));
return; return;
@@ -280,7 +281,7 @@ namespace ArchiSteamFarm {
return; return;
} }
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining.Value)); GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining.Value, badgeLevel));
} }
private async Task CheckGamesForFarming() { private async Task CheckGamesForFarming() {
@@ -297,7 +298,7 @@ namespace ArchiSteamFarm {
return; return;
} }
HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_title_stats_content']"); HtmlNodeCollection htmlNodes = htmlDocument.DocumentNode.SelectNodes("//div[@class='badge_row_inner']");
if (htmlNodes == null) { if (htmlNodes == null) {
// No eligible badges whatsoever // No eligible badges whatsoever
return; return;
@@ -306,7 +307,9 @@ namespace ArchiSteamFarm {
HashSet<Task> backgroundTasks = new HashSet<Task>(); HashSet<Task> backgroundTasks = new HashSet<Task>();
foreach (HtmlNode htmlNode in htmlNodes) { foreach (HtmlNode htmlNode in htmlNodes) {
HtmlNode appIDNode = htmlNode.SelectSingleNode(".//div[@class='card_drop_info_dialog']"); HtmlNode statsNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_content']");
HtmlNode appIDNode = statsNode?.SelectSingleNode(".//div[@class='card_drop_info_dialog']");
if (appIDNode == null) { if (appIDNode == null) {
// It's just a badge, nothing more // It's just a badge, nothing more
continue; continue;
@@ -336,10 +339,10 @@ namespace ArchiSteamFarm {
continue; continue;
} }
if (IgnoredAppIDs.TryGetValue(appID, out DateTime lastPICSReport)) { if (IgnoredAppIDs.TryGetValue(appID, out DateTime ignoredUntil)) {
if (lastPICSReport.AddHours(HoursToIgnore) < DateTime.UtcNow) { if (ignoredUntil < DateTime.UtcNow) {
// This game served its time as being ignored // This game served its time as being ignored
IgnoredAppIDs.TryRemove(appID, out lastPICSReport); IgnoredAppIDs.TryRemove(appID, out _);
} else { } else {
// This game is still ignored // This game is still ignored
continue; continue;
@@ -347,7 +350,7 @@ namespace ArchiSteamFarm {
} }
// Cards // Cards
HtmlNode progressNode = htmlNode.SelectSingleNode(".//span[@class='progress_info_bold']"); HtmlNode progressNode = statsNode.SelectSingleNode(".//span[@class='progress_info_bold']");
if (progressNode == null) { if (progressNode == null) {
Bot.ArchiLogger.LogNullError(nameof(progressNode)); Bot.ArchiLogger.LogNullError(nameof(progressNode));
continue; continue;
@@ -380,7 +383,7 @@ namespace ArchiSteamFarm {
} }
// To save us on extra work, check cards earned so far first // To save us on extra work, check cards earned so far first
HtmlNode cardsEarnedNode = htmlNode.SelectSingleNode(".//div[@class='card_drop_info_header']"); HtmlNode cardsEarnedNode = statsNode.SelectSingleNode(".//div[@class='card_drop_info_header']");
if (cardsEarnedNode == null) { if (cardsEarnedNode == null) {
Bot.ArchiLogger.LogNullError(nameof(cardsEarnedNode)); Bot.ArchiLogger.LogNullError(nameof(cardsEarnedNode));
continue; continue;
@@ -419,7 +422,7 @@ namespace ArchiSteamFarm {
} }
// Hours // Hours
HtmlNode timeNode = htmlNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']"); HtmlNode timeNode = statsNode.SelectSingleNode(".//div[@class='badge_title_stats_playtime']");
if (timeNode == null) { if (timeNode == null) {
Bot.ArchiLogger.LogNullError(nameof(timeNode)); Bot.ArchiLogger.LogNullError(nameof(timeNode));
continue; continue;
@@ -443,7 +446,7 @@ namespace ArchiSteamFarm {
} }
// Names // Names
HtmlNode nameNode = htmlNode.SelectSingleNode("(.//div[@class='card_drop_info_body'])[last()]"); HtmlNode nameNode = statsNode.SelectSingleNode("(.//div[@class='card_drop_info_body'])[last()]");
if (nameNode == null) { if (nameNode == null) {
Bot.ArchiLogger.LogNullError(nameof(nameNode)); Bot.ArchiLogger.LogNullError(nameof(nameNode));
continue; continue;
@@ -477,14 +480,44 @@ namespace ArchiSteamFarm {
name = WebUtility.HtmlDecode(name.Substring(nameStartIndex, nameEndIndex - nameStartIndex)); name = WebUtility.HtmlDecode(name.Substring(nameStartIndex, nameEndIndex - nameStartIndex));
// We have two possible cases here // Levels
// Either we have decent info about appID, name, hours and cardsRemaining (cardsRemaining > 0) byte badgeLevel = 0;
// OR we strongly believe that Steam lied to us, in this case we will need to check game invidually (cardsRemaining == 0)
HtmlNode levelNode = htmlNode.SelectSingleNode(".//div[@class='badge_info_description']/div[2]");
if (levelNode != null) {
// There is no levelNode if we didn't craft that badge yet (level 0)
string levelString = levelNode.InnerText;
if (string.IsNullOrEmpty(levelString)) {
Bot.ArchiLogger.LogNullError(nameof(levelString));
continue;
}
int levelIndex = levelString.IndexOf("Level ", StringComparison.OrdinalIgnoreCase);
if (levelIndex < 0) {
Bot.ArchiLogger.LogNullError(nameof(levelIndex));
continue;
}
levelIndex += 6;
if (levelString.Length <= levelIndex) {
Bot.ArchiLogger.LogNullError(nameof(levelIndex));
continue;
}
levelString = levelString.Substring(levelIndex, 1);
if (!byte.TryParse(levelString, out badgeLevel) || (badgeLevel == 0) || (badgeLevel > 5)) {
Bot.ArchiLogger.LogNullError(nameof(badgeLevel));
continue;
}
}
// Done with parsing, we have two possible cases here
// Either we have decent info about appID, name, hours, cardsRemaining (cardsRemaining > 0) and level
// OR we strongly believe that Steam lied to us, in this case we will need to check game invidually (cardsRemaining == 0)
if (cardsRemaining > 0) { if (cardsRemaining > 0) {
GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining)); GamesToFarm.Add(new Game(appID, name, hours, cardsRemaining, badgeLevel));
} else { } else {
Task task = CheckGame(appID, name, hours); Task task = CheckGame(appID, name, hours, badgeLevel);
switch (Program.GlobalConfig.OptimizationMode) { switch (Program.GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage: case GlobalConfig.EOptimizationMode.MinMemoryUsage:
await task.ConfigureAwait(false); await task.ConfigureAwait(false);
@@ -523,31 +556,61 @@ namespace ArchiSteamFarm {
// If we have restricted card drops, we use complex algorithm // If we have restricted card drops, we use complex algorithm
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex")); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Complex"));
while (GamesToFarm.Count > 0) { while (GamesToFarm.Count > 0) {
HashSet<Game> gamesToFarmSolo = GamesToFarm.Count > 1 ? new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump)) : new HashSet<Game>(GamesToFarm); HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm.Where(game => game.HoursPlayed >= HoursToBump));
if (gamesToFarmSolo.Count > 0) {
while (gamesToFarmSolo.Count > 0) { foreach (Game game in gamesToCheck) {
Game game = gamesToFarmSolo.First(); if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
}
if (await FarmSolo(game).ConfigureAwait(false)) { if (await FarmSolo(game).ConfigureAwait(false)) {
gamesToFarmSolo.Remove(game); continue;
} else { }
NowFarming = false; NowFarming = false;
return; return;
} }
gamesToCheck = new HashSet<Game>(GamesToFarm.OrderByDescending(game => game.HoursPlayed));
HashSet<Game> playableGamesToFarmMultiple = new HashSet<Game>();
foreach (Game game in gamesToCheck) {
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
} }
} else {
if (FarmMultiple(GamesToFarm.OrderByDescending(game => game.HoursPlayed).Take(ArchiHandler.MaxGamesPlayedConcurrently))) { playableGamesToFarmMultiple.Add(game);
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", GamesToFarm.Select(game => game.AppID)))); if (playableGamesToFarmMultiple.Count >= ArchiHandler.MaxGamesPlayedConcurrently) {
break;
}
}
if (playableGamesToFarmMultiple.Count == 0) {
break;
}
if (await FarmMultiple(playableGamesToFarmMultiple).ConfigureAwait(false)) {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingFinishedForGames, string.Join(", ", playableGamesToFarmMultiple.Select(game => game.AppID))));
} else { } else {
NowFarming = false; NowFarming = false;
return; return;
} }
} }
}
} else { } else {
// If we have unrestricted card drops, we use simple algorithm // If we have unrestricted card drops, we use simple algorithm
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple")); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.ChosenFarmingAlgorithm, "Simple"));
while (GamesToFarm.Count > 0) { while (GamesToFarm.Count > 0) {
Game game = GamesToFarm.First(); HashSet<Game> gamesToCheck = new HashSet<Game>(GamesToFarm);
foreach (Game game in gamesToCheck) {
if (!await IsPlayableGame(game).ConfigureAwait(false)) {
GamesToFarm.Remove(game);
continue;
}
if (await FarmSolo(game).ConfigureAwait(false)) { if (await FarmSolo(game).ConfigureAwait(false)) {
continue; continue;
} }
@@ -556,9 +619,10 @@ namespace ArchiSteamFarm {
return; return;
} }
} }
}
} while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault()); } while ((await IsAnythingToFarm().ConfigureAwait(false)).GetValueOrDefault());
CurrentGamesFarming.ClearAndTrim(); CurrentGamesFarming.Clear();
NowFarming = false; NowFarming = false;
Bot.ArchiLogger.LogGenericInfo(Strings.IdlingFinished); Bot.ArchiLogger.LogGenericInfo(Strings.IdlingFinished);
@@ -573,13 +637,11 @@ namespace ArchiSteamFarm {
bool success = true; bool success = true;
uint appID = await Bot.GetAppIDForIdling(game.AppID).ConfigureAwait(false); if (game.AppID != game.PlayableAppID) {
if (appID != 0) { Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningIdlingGameMismatch, game.AppID, game.GameName, game.PlayableAppID));
if (appID != game.AppID) {
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.WarningIdlingGameMismatch, game.AppID, game.GameName, appID));
} }
Bot.PlayGame(appID, Bot.BotConfig.CustomGamePlayedWhileFarming); Bot.IdleGame(game.PlayableAppID);
DateTime endFarmingDate = DateTime.UtcNow.AddHours(Program.GlobalConfig.MaxFarmingTime); DateTime endFarmingDate = DateTime.UtcNow.AddHours(Program.GlobalConfig.MaxFarmingTime);
bool? keepFarming = await ShouldFarm(game).ConfigureAwait(false); bool? keepFarming = await ShouldFarm(game).ConfigureAwait(false);
@@ -587,8 +649,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName)); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName));
DateTime startFarmingPeriod = DateTime.UtcNow; DateTime startFarmingPeriod = DateTime.UtcNow;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
FarmResetEvent.Reset();
success = KeepFarming; success = KeepFarming;
} }
@@ -601,16 +662,12 @@ namespace ArchiSteamFarm {
keepFarming = await ShouldFarm(game).ConfigureAwait(false); keepFarming = await ShouldFarm(game).ConfigureAwait(false);
} }
} else {
IgnoredAppIDs[game.AppID] = DateTime.UtcNow;
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingGameNotPossible, game.AppID, game.GameName));
}
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StoppedIdling, game.AppID, game.GameName)); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StoppedIdling, game.AppID, game.GameName));
return success; return success;
} }
private bool FarmHours(ConcurrentHashSet<Game> games) { private async Task<bool> FarmHours(ConcurrentHashSet<Game> games) {
if ((games == null) || (games.Count == 0)) { if ((games == null) || (games.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(games)); Bot.ArchiLogger.LogNullError(nameof(games));
return false; return false;
@@ -627,15 +684,14 @@ namespace ArchiSteamFarm {
return true; return true;
} }
Bot.PlayGames(games.Select(game => game.AppID), Bot.BotConfig.CustomGamePlayedWhileFarming); Bot.IdleGames(games.Select(game => game.PlayableAppID));
bool success = true; bool success = true;
while (maxHour < 2) { while (maxHour < 2) {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID)))); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdlingList, string.Join(", ", games.Select(game => game.AppID))));
DateTime startFarmingPeriod = DateTime.UtcNow; DateTime startFarmingPeriod = DateTime.UtcNow;
if (FarmResetEvent.Wait(60 * 1000 * Program.GlobalConfig.FarmingDelay)) { if (await FarmingResetSemaphore.WaitAsync(60 * 1000 * Program.GlobalConfig.FarmingDelay).ConfigureAwait(false)) {
FarmResetEvent.Reset();
success = KeepFarming; success = KeepFarming;
} }
@@ -656,7 +712,7 @@ namespace ArchiSteamFarm {
return success; return success;
} }
private bool FarmMultiple(IEnumerable<Game> games) { private async Task<bool> FarmMultiple(IEnumerable<Game> games) {
if (games == null) { if (games == null) {
Bot.ArchiLogger.LogNullError(nameof(games)); Bot.ArchiLogger.LogNullError(nameof(games));
return false; return false;
@@ -666,8 +722,8 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdlingList, string.Join(", ", CurrentGamesFarming.Select(game => game.AppID)))); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdlingList, string.Join(", ", CurrentGamesFarming.Select(game => game.AppID))));
bool result = FarmHours(CurrentGamesFarming); bool result = await FarmHours(CurrentGamesFarming).ConfigureAwait(false);
CurrentGamesFarming.ClearAndTrim(); CurrentGamesFarming.Clear();
return result; return result;
} }
@@ -682,7 +738,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdling, game.AppID, game.GameName)); Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.NowIdling, game.AppID, game.GameName));
bool result = await Farm(game).ConfigureAwait(false); bool result = await Farm(game).ConfigureAwait(false);
CurrentGamesFarming.ClearAndTrim(); CurrentGamesFarming.Clear();
if (!result) { if (!result) {
return false; return false;
@@ -751,7 +807,7 @@ namespace ArchiSteamFarm {
} }
} }
GamesToFarm.ClearAndTrim(); GamesToFarm.Clear();
List<Task> tasks = new List<Task>(); List<Task> tasks = new List<Task>();
Task mainTask = CheckPage(htmlDocument); Task mainTask = CheckPage(htmlDocument);
@@ -798,6 +854,18 @@ namespace ArchiSteamFarm {
return true; return true;
} }
private async Task<bool> IsPlayableGame(Game game) {
(uint PlayableAppID, DateTime IgnoredUntil) appData = await Bot.GetAppDataForIdling(game.AppID).ConfigureAwait(false);
if (appData.PlayableAppID != 0) {
game.PlayableAppID = appData.PlayableAppID;
return true;
}
IgnoredAppIDs[game.AppID] = appData.IgnoredUntil != DateTime.MaxValue ? appData.IgnoredUntil : DateTime.UtcNow.AddHours(HoursToIgnore);
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.IdlingGameNotPossible, game.AppID, game.GameName));
return false;
}
private async Task<bool?> ShouldFarm(Game game) { private async Task<bool?> ShouldFarm(Game game) {
if (game == null) { if (game == null) {
Bot.ArchiLogger.LogNullError(nameof(game)); Bot.ArchiLogger.LogNullError(nameof(game));
@@ -817,50 +885,94 @@ namespace ArchiSteamFarm {
} }
private void SortGamesToFarm() { private void SortGamesToFarm() {
IOrderedEnumerable<Game> gamesToFarm; IOrderedEnumerable<Game> gamesToFarm = GamesToFarm.OrderBy(game => Bot.IsPriorityIdling(game.AppID) ? 1 : 0);
switch (Bot.BotConfig.FarmingOrder) { switch (Bot.BotConfig.FarmingOrder) {
case BotConfig.EFarmingOrder.Unordered: case BotConfig.EFarmingOrder.Unordered:
return; break;
case BotConfig.EFarmingOrder.AppIDsAscending: case BotConfig.EFarmingOrder.AppIDsAscending:
gamesToFarm = GamesToFarm.OrderBy(game => game.AppID); gamesToFarm = gamesToFarm.ThenBy(game => game.AppID);
break; break;
case BotConfig.EFarmingOrder.AppIDsDescending: case BotConfig.EFarmingOrder.AppIDsDescending:
gamesToFarm = GamesToFarm.OrderByDescending(game => game.AppID); gamesToFarm = gamesToFarm.ThenByDescending(game => game.AppID);
break;
case BotConfig.EFarmingOrder.BadgeLevelsAscending:
gamesToFarm = gamesToFarm.ThenBy(game => game.BadgeLevel);
break;
case BotConfig.EFarmingOrder.BadgeLevelsDescending:
gamesToFarm = gamesToFarm.ThenByDescending(game => game.BadgeLevel);
break; break;
case BotConfig.EFarmingOrder.CardDropsAscending: case BotConfig.EFarmingOrder.CardDropsAscending:
gamesToFarm = GamesToFarm.OrderBy(game => game.CardsRemaining); gamesToFarm = gamesToFarm.ThenBy(game => game.CardsRemaining);
break; break;
case BotConfig.EFarmingOrder.CardDropsDescending: case BotConfig.EFarmingOrder.CardDropsDescending:
gamesToFarm = GamesToFarm.OrderByDescending(game => game.CardsRemaining); gamesToFarm = gamesToFarm.ThenByDescending(game => game.CardsRemaining);
break; break;
case BotConfig.EFarmingOrder.HoursAscending: case BotConfig.EFarmingOrder.HoursAscending:
gamesToFarm = GamesToFarm.OrderBy(game => game.HoursPlayed); gamesToFarm = gamesToFarm.ThenBy(game => game.HoursPlayed);
break; break;
case BotConfig.EFarmingOrder.HoursDescending: case BotConfig.EFarmingOrder.HoursDescending:
gamesToFarm = GamesToFarm.OrderByDescending(game => game.HoursPlayed); gamesToFarm = gamesToFarm.ThenByDescending(game => game.HoursPlayed);
break; break;
case BotConfig.EFarmingOrder.NamesAscending: case BotConfig.EFarmingOrder.NamesAscending:
gamesToFarm = GamesToFarm.OrderBy(game => game.GameName); gamesToFarm = gamesToFarm.ThenBy(game => game.GameName);
break; break;
case BotConfig.EFarmingOrder.NamesDescending: case BotConfig.EFarmingOrder.NamesDescending:
gamesToFarm = GamesToFarm.OrderByDescending(game => game.GameName); gamesToFarm = gamesToFarm.ThenByDescending(game => game.GameName);
break; break;
case BotConfig.EFarmingOrder.Random: case BotConfig.EFarmingOrder.Random:
gamesToFarm = GamesToFarm.OrderBy(game => Utilities.RandomNext()); gamesToFarm = gamesToFarm.ThenBy(game => Utilities.RandomNext());
break;
case BotConfig.EFarmingOrder.RedeemDateTimesAscending:
case BotConfig.EFarmingOrder.RedeemDateTimesDescending:
Dictionary<uint, DateTime> redeemDates = new Dictionary<uint, DateTime>(GamesToFarm.Count);
foreach (Game game in GamesToFarm) {
DateTime redeemDate = DateTime.MinValue;
if (Program.GlobalDatabase.AppIDsToPackageIDs.TryGetValue(game.AppID, out ConcurrentHashSet<uint> packageIDs)) {
// ReSharper disable once LoopCanBePartlyConvertedToQuery - C# 7.0 out can't be used within LINQ query yet | https://github.com/dotnet/roslyn/issues/15619
foreach (uint packageID in packageIDs) {
if (!Bot.OwnedPackageIDs.TryGetValue(packageID, out (EPaymentMethod PaymentMethod, DateTime TimeCreated) packageData)) {
continue;
}
if (packageData.TimeCreated > redeemDate) {
redeemDate = packageData.TimeCreated;
}
}
}
redeemDates[game.AppID] = redeemDate;
}
switch (Bot.BotConfig.FarmingOrder) {
case BotConfig.EFarmingOrder.RedeemDateTimesAscending:
gamesToFarm = gamesToFarm.ThenBy(game => redeemDates[game.AppID]);
break;
case BotConfig.EFarmingOrder.RedeemDateTimesDescending:
gamesToFarm = gamesToFarm.ThenByDescending(game => redeemDates[game.AppID]);
break; break;
default: default:
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder))); Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder)));
return; return;
} }
GamesToFarm.ReplaceWith(gamesToFarm.ToList()); // We must call ToList() here as we can't enumerate during replacing break;
default:
Bot.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(Bot.BotConfig.FarmingOrder)));
return;
}
// We must call ToList() here as we can't enumerate during replacing
GamesToFarm.ReplaceWith(gamesToFarm.ToList());
} }
internal sealed class Game { internal sealed class Game {
[JsonProperty] [JsonProperty]
internal readonly uint AppID; internal readonly uint AppID;
internal readonly byte BadgeLevel;
[JsonProperty] [JsonProperty]
internal readonly string GameName; internal readonly string GameName;
@@ -870,9 +982,9 @@ namespace ArchiSteamFarm {
[JsonProperty] [JsonProperty]
internal float HoursPlayed { get; set; } internal float HoursPlayed { get; set; }
//internal string HeaderURL => "https://steamcdn-a.akamaihd.net/steam/apps/" + AppID + "/header.jpg"; internal uint PlayableAppID { get; set; }
internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining) { internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining, byte badgeLevel) {
if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) { if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) {
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining)); throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
} }
@@ -881,14 +993,17 @@ namespace ArchiSteamFarm {
GameName = gameName; GameName = gameName;
HoursPlayed = hoursPlayed; HoursPlayed = hoursPlayed;
CardsRemaining = cardsRemaining; CardsRemaining = cardsRemaining;
BadgeLevel = badgeLevel;
PlayableAppID = appID;
} }
public override bool Equals(object obj) { public override bool Equals(object obj) {
if (obj == null) { if (ReferenceEquals(null, obj)) {
return false; return false;
} }
if (obj == this) { if (ReferenceEquals(this, obj)) {
return true; return true;
} }

View File

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

View File

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

View File

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

View File

@@ -26,9 +26,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Net.Sockets;
using ArchiSteamFarm.Localization; using ArchiSteamFarm.Localization;
using Newtonsoft.Json; using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
@@ -41,7 +41,7 @@ namespace ArchiSteamFarm {
internal const string UlongStringPrefix = "s_"; internal const string UlongStringPrefix = "s_";
// This is hardcoded blacklist which should not be possible to change // This is hardcoded blacklist which should not be possible to change
internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 425280, 480730, 566020, 639900 }; internal static readonly HashSet<uint> GlobalBlacklist = new HashSet<uint> { 267420, 303700, 335590, 368020, 402590, 425280, 480730, 566020, 639900 };
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly bool AutoRestart = true; internal readonly bool AutoRestart = true;
@@ -102,7 +102,7 @@ namespace ArchiSteamFarm {
internal readonly bool Statistics = true; internal readonly bool Statistics = true;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly ProtocolType SteamProtocol = ProtocolType.Tcp; internal readonly ProtocolTypes SteamProtocols = ProtocolTypes.Tcp;
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable; internal readonly EUpdateChannel UpdateChannel = EUpdateChannel.Stable;
@@ -152,17 +152,6 @@ namespace ArchiSteamFarm {
return null; return null;
} }
// SK2 supports only TCP and UDP steam protocols
// Ensure that user can't screw this up
switch (globalConfig.SteamProtocol) {
case ProtocolType.Tcp:
case ProtocolType.Udp:
break;
default:
ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorConfigPropertyInvalid, nameof(globalConfig.SteamProtocol), globalConfig.SteamProtocol));
return null;
}
// User might not know what he's doing // User might not know what he's doing
// Ensure that he can't screw core ASF variables // Ensure that he can't screw core ASF variables
if (globalConfig.MaxFarmingTime == 0) { if (globalConfig.MaxFarmingTime == 0) {

View File

@@ -23,19 +23,18 @@
*/ */
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class GlobalDatabase : IDisposable { internal sealed class GlobalDatabase : IDisposable {
private static readonly JsonSerializerSettings CustomSerializerSettings = new JsonSerializerSettings { [JsonProperty(Required = Required.DisallowNull)]
Converters = new List<JsonConverter>(2) { internal readonly ConcurrentDictionary<uint, ConcurrentHashSet<uint>> AppIDsToPackageIDs = new ConcurrentDictionary<uint, ConcurrentHashSet<uint>>();
new IPAddressConverter(),
new IPEndPointConverter()
}
};
[JsonProperty(Required = Required.DisallowNull)] [JsonProperty(Required = Required.DisallowNull)]
internal readonly Guid Guid = Guid.NewGuid(); internal readonly Guid Guid = Guid.NewGuid();
@@ -45,6 +44,8 @@ namespace ArchiSteamFarm {
private readonly object FileLock = new object(); private readonly object FileLock = new object();
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1);
internal uint CellID { internal uint CellID {
get => _CellID; get => _CellID;
set { set {
@@ -90,7 +91,7 @@ namespace ArchiSteamFarm {
GlobalDatabase globalDatabase; GlobalDatabase globalDatabase;
try { try {
globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(filePath), CustomSerializerSettings); globalDatabase = JsonConvert.DeserializeObject<GlobalDatabase>(File.ReadAllText(filePath));
} catch (Exception e) { } catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e); ASF.ArchiLogger.LogGenericException(e);
return null; return null;
@@ -105,31 +106,65 @@ namespace ArchiSteamFarm {
return globalDatabase; return globalDatabase;
} }
internal async Task RefreshPackageIDs(Bot bot, ICollection<uint> packageIDs) {
if ((bot == null) || (packageIDs == null) || (packageIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(packageIDs));
return;
}
await PackagesRefreshSemaphore.WaitAsync().ConfigureAwait(false);
try {
HashSet<uint> missingPackageIDs = new HashSet<uint>(packageIDs.AsParallel().Where(packageID => AppIDsToPackageIDs.Values.All(packages => !packages.Contains(packageID))));
if (missingPackageIDs.Count == 0) {
return;
}
Dictionary<uint, HashSet<uint>> appIDsToPackageIDs = await bot.GetAppIDsToPackageIDs(missingPackageIDs);
if ((appIDsToPackageIDs == null) || (appIDsToPackageIDs.Count == 0)) {
return;
}
foreach (KeyValuePair<uint, HashSet<uint>> appIDtoPackageID in appIDsToPackageIDs) {
if (!AppIDsToPackageIDs.TryGetValue(appIDtoPackageID.Key, out ConcurrentHashSet<uint> packages)) {
packages = new ConcurrentHashSet<uint>();
AppIDsToPackageIDs[appIDtoPackageID.Key] = packages;
}
foreach (uint package in appIDtoPackageID.Value) {
packages.Add(package);
}
}
Save();
} finally {
PackagesRefreshSemaphore.Release();
}
}
private void OnServerListUpdated(object sender, EventArgs e) => Save(); private void OnServerListUpdated(object sender, EventArgs e) => Save();
private void Save() { private void Save() {
string json = JsonConvert.SerializeObject(this, CustomSerializerSettings); string json = JsonConvert.SerializeObject(this);
if (string.IsNullOrEmpty(json)) { if (string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(json)); ASF.ArchiLogger.LogNullError(nameof(json));
return; return;
} }
// This call verifies if JSON is alright
// We don't wrap it in try catch as it should always be the case
// And if it's not, we want to know about it (in a crash) and correct it in future version
JsonConvert.DeserializeObject<GlobalDatabase>(json, CustomSerializerSettings);
lock (FileLock) { lock (FileLock) {
for (byte i = 0; i < 5; i++) { string newFilePath = FilePath + ".new";
try { try {
File.WriteAllText(FilePath, json); File.WriteAllText(newFilePath, json);
break;
if (File.Exists(FilePath)) {
File.Replace(newFilePath, FilePath, null);
} else {
File.Move(newFilePath, FilePath);
}
} catch (Exception e) { } catch (Exception e) {
ASF.ArchiLogger.LogGenericException(e); ASF.ArchiLogger.LogGenericException(e);
} }
Thread.Sleep(1000);
}
} }
} }
} }

View File

@@ -50,11 +50,11 @@ namespace ArchiSteamFarm {
} }
internal static void Start() { internal static void Start() {
if (KeepRunning) { if (KeepRunning || (HttpListener.Prefixes.Count == 0)) {
return; return;
} }
ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCStarting, HttpListener.Prefixes.FirstOrDefault())); ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.IPCStarting, HttpListener.Prefixes.First()));
try { try {
HttpListener.Start(); HttpListener.Start();
@@ -84,8 +84,9 @@ namespace ArchiSteamFarm {
return; return;
} }
try {
if (Program.GlobalConfig.SteamOwnerID == 0) { if (Program.GlobalConfig.SteamOwnerID == 0) {
ASF.ArchiLogger.LogGenericInfo(Strings.ErrorIPCAccessDenied); ASF.ArchiLogger.LogGenericWarning(Strings.ErrorIPCAccessDenied);
await context.Response.WriteAsync(HttpStatusCode.Forbidden, Strings.ErrorIPCAccessDenied).ConfigureAwait(false); await context.Response.WriteAsync(HttpStatusCode.Forbidden, Strings.ErrorIPCAccessDenied).ConfigureAwait(false);
return; return;
} }
@@ -135,6 +136,9 @@ namespace ArchiSteamFarm {
if (context.Response.ContentLength64 == 0) { if (context.Response.ContentLength64 == 0) {
await context.Response.WriteAsync(HttpStatusCode.MethodNotAllowed, nameof(HttpStatusCode.MethodNotAllowed)).ConfigureAwait(false); await context.Response.WriteAsync(HttpStatusCode.MethodNotAllowed, nameof(HttpStatusCode.MethodNotAllowed)).ConfigureAwait(false);
} }
} finally {
context.Response.Close();
}
} }
private static async Task Run() { private static async Task Run() {
@@ -148,8 +152,7 @@ namespace ArchiSteamFarm {
continue; continue;
} }
await HandleRequest(context).ConfigureAwait(false); Utilities.StartBackgroundFunction(() => HandleRequest(context), false);
context.Response.Close();
} }
} }
@@ -164,8 +167,14 @@ namespace ArchiSteamFarm {
response.StatusCode = (ushort) statusCode; response.StatusCode = (ushort) statusCode;
} }
byte[] buffer = Encoding.UTF8.GetBytes(message); Encoding encoding = Encoding.UTF8;
response.ContentEncoding = encoding;
string html = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body><p>" + message + "</p></body></html>";
byte[] buffer = encoding.GetBytes(html);
response.ContentLength64 = buffer.Length; response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false); await response.OutputStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
} catch (Exception e) { } catch (Exception e) {
response.StatusCode = (ushort) HttpStatusCode.InternalServerError; response.StatusCode = (ushort) HttpStatusCode.InternalServerError;

View File

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

View File

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

View File

@@ -55,6 +55,11 @@ namespace ArchiSteamFarm.JSON {
internal readonly string Name; internal readonly string Name;
#pragma warning restore 649 #pragma warning restore 649
#pragma warning disable 649
[JsonProperty(PropertyName = "size", Required = Required.Always)]
internal readonly uint Size;
#pragma warning restore 649
// Deserialized from JSON // Deserialized from JSON
private Asset() { } private Asset() { }
} }

View File

@@ -220,6 +220,18 @@ namespace ArchiSteamFarm.JSON {
private ConfirmationResponse() { } private ConfirmationResponse() { }
} }
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
internal sealed class GenericResponse {
#pragma warning disable 649
[JsonProperty(PropertyName = "success", Required = Required.Always)]
internal readonly EResult Result;
#pragma warning restore 649
// Deserialized from JSON
private GenericResponse() { }
}
// REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset // REF: https://developer.valvesoftware.com/wiki/Steam_Web_API/IEconService#CEcon_Asset
internal sealed class Item { internal sealed class Item {
internal const ushort SteamAppID = 753; internal const ushort SteamAppID = 753;
@@ -227,13 +239,12 @@ namespace ArchiSteamFarm.JSON {
internal uint Amount { get; private set; } internal uint Amount { get; private set; }
internal uint AppID { get; set; } internal uint AppID { get; set; }
internal ulong AssetID { get; private set; }
internal ulong ClassID { get; private set; } internal ulong ClassID { get; private set; }
internal ulong ContextID { get; set; } internal ulong ContextID { get; set; }
internal uint RealAppID { get; set; } internal uint RealAppID { get; set; }
internal EType Type { get; set; } internal EType Type { get; set; }
private ulong AssetID;
[JsonProperty(PropertyName = "amount", Required = Required.Always)] [JsonProperty(PropertyName = "amount", Required = Required.Always)]
[SuppressMessage("ReSharper", "UnusedMember.Local")] [SuppressMessage("ReSharper", "UnusedMember.Local")]
private string AmountString { private string AmountString {
@@ -486,7 +497,15 @@ namespace ArchiSteamFarm.JSON {
return true; return true;
} }
internal bool IsSteamCardsRequest() => ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamCommunityContextID) && (item.Type == Item.EType.TradingCard)); internal bool IsValidSteamItemsRequest(HashSet<Item.EType> acceptedTypes) {
if ((acceptedTypes == null) || (acceptedTypes.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(acceptedTypes));
return false;
}
bool result = ItemsToGive.All(item => (item.AppID == Item.SteamAppID) && (item.ContextID == Item.SteamCommunityContextID) && acceptedTypes.Contains(item.Type));
return result;
}
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
internal enum ETradeOfferState : byte { internal enum ETradeOfferState : byte {

View File

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

View File

@@ -191,9 +191,7 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>جاري التحقيق من وجود إصدار جديد...</value> <value>جاري التحقيق من وجود إصدار جديد...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>جاري تحميل الإصدار الجديد... بينما تنتظر، تبرع إذا اعجبك عملنا! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>تم الانتهاء من عملية التحديث!</value> <value>تم الانتهاء من عملية التحديث!</value>
</data> </data>

View File

@@ -235,9 +235,7 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Проверяване за нова версия...</value> <value>Проверяване за нова версия...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>В момента се сваля новата версия... Докато чакате, помислете за дарение, ако оценявате свършената от нас работа! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Обновлението приключи!</value> <value>Обновлението приключи!</value>
</data> </data>
@@ -487,10 +485,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>В момента се ползва бот.</value> <value>В момента се ползва бот.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Не може да се свърже със Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Не може да се впише в Steam: {0}/{1}</value> <value>Не може да се впише в Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -553,4 +547,5 @@
</root> </root>

View File

@@ -184,14 +184,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Nelze zkontrolovat nejnovější verzi.</value> <value>Nelze zkontrolovat nejnovější verzi.</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Aktualizaci nelze provést, protože neexistuje žádný asset související s aktuálně spuštěnou verzí. Automatickou aktualizaci této verze nelze provést.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Aktualizace nemohla pokračovat, protože žádaná verze neobsahuje žádné assety.</value> <value>Aktualizace nemohla pokračovat, protože žádaná verze neobsahuje žádné assety.</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Obdržen vstup od uživatele, ale proces běží v automatickém režimu.</value> <value>Obdržen vstup od uživatele, ale proces běží v automatickém režimu.</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Požadavek byl zamítnut, protože není nastaven identifikátor SteamOwnerID.</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Ukončení...</value> <value>Ukončení...</value>
</data> </data>
@@ -242,7 +247,8 @@ StackTrace:
<value>Kontrola nové verze...</value> <value>Kontrola nové verze...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Probíhá stahování nové verze... Během čekání zvažte podpotu tohoto projektu! :)</value> <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>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Úspěšně aktualizováno.</value> <value>Úspěšně aktualizováno.</value>
@@ -282,7 +288,10 @@ StackTrace:
<value>Prosím, zadejte nezdokumentovanou hodnotu {0}: </value> <value>Prosím, zadejte nezdokumentovanou hodnotu {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Zadejte hostitele IPC:</value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Obdržena neznámá konfigurace pro {0}, nahlašte: {1}</value> <value>Obdržena neznámá konfigurace pro {0}, nahlašte: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -291,10 +300,20 @@ StackTrace:
<value>Spušt2ní více než {0} her není aktuálně možné, použito bude pouze prvních {0} položek z {1}.</value> <value>Spušt2ní více než {0} her není aktuálně možné, použito bude pouze prvních {0} položek z {1}.</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Služba IPC nemohla být spuštěna kvůli AddressAccessDeniedException. Pokud si přejete použít službu IPC, poskytovanou aplikací ASF, zvažte spuštění aplikace ASF jako správce, nebo aplikaci přidělte dostatečná oprávnění.</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Odpovězeno na příkaz IPC: {0} odpověď: {1}</value>
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
</data>
<data name="IPCReady" xml:space="preserve">
<value>IPC server je připravený.</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Spouštění serveru IPC na adrese {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Tento bot byl již zastaven.</value> <value>Tento bot byl již zastaven.</value>
</data> </data>
@@ -546,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot je právě použiván.</value> <value>Bot je právě použiván.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nelze se připojit ke Steamu: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nelze se příhlásit do Steamu: {0}/{1}</value> <value>Nelze se příhlásit do Steamu: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -658,4 +673,8 @@ StackTrace:
<value>Procházení fronty doporučení #{0} dokončeno.</value> <value>Procházení fronty doporučení #{0} dokončeno.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>{0}/{1} botů již vlastní všechny uvedené hry.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -240,9 +240,7 @@ StackTrace:
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Undersøger for ny version...</value> <value>Undersøger for ny version...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Downloader ny version... Mens du venter, overvej at donere hvis du sætter pris på det arbejde der bliver gjort! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Opdateringsprocessen er færdig!</value> <value>Opdateringsprocessen er færdig!</value>
</data> </data>
@@ -545,10 +543,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Botten bruges i øjeblikket.</value> <value>Botten bruges i øjeblikket.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Kan ikke forbinde til Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Kan ikke logge ind på Steam: {0}/{1}</value> <value>Kan ikke logge ind på Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -657,4 +651,5 @@ StackTrace:
<value>Færdig med rensning af Steam opdagelses kø #{0}.</value> <value>Færdig med rensning af Steam opdagelses kø #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -183,14 +183,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Neueste Version konnte nicht überprüft werden!</value> <value>Neueste Version konnte nicht überprüft werden!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Konnte mit der Aktualisierung nicht fortfahren, weil es keine Anlage gibt, welche sich auf die aktuell betriebene Version bezieht! Automatische Aktualisierung auf diese Version ist nicht möglich.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Konnte nicht mit Aktualisierung fortfahren, weil diese Version keine Anlage enthält!</value> <value>Konnte nicht mit Aktualisierung fortfahren, weil diese Version keine Anlage enthält!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Anfrage für Benutzereingabe erhalten, aber der Prozess läuft im Headless-Modus!</value> <value>Anfrage für Benutzereingabe erhalten, aber der Prozess läuft im Headless-Modus!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Verweigere die Bearbeitung dieser Anfrage, weil die SteamOwnerID nicht festgelegt wurde!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Vorgang wird beendet...</value> <value>Vorgang wird beendet...</value>
</data> </data>
@@ -241,7 +246,8 @@ StackTrace:
<value>Prüfe auf neue Version...</value> <value>Prüfe auf neue Version...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Lade neue Version herunter... Während du wartest, denk darüber nach zu spenden, wenn du die geleistete Arbeit zu schätzen weißt! :)</value> <value>Lade neue Version herunter: {0} ({1} MB)... Während du wartest, denk darüber nach zu spenden, wenn du die geleistete Arbeit zu schätzen weißt! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Aktualisierung abgeschlossen!</value> <value>Aktualisierung abgeschlossen!</value>
@@ -281,7 +287,10 @@ StackTrace:
<value>Bitte gebe undokumentierten Wert von {0} ein: </value> <value>Bitte gebe undokumentierten Wert von {0} ein: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Bitte gebe deinen IPC-Host ein: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Unbekannten Wert für {0} erhalten, bitte melde folgendes: {1}</value> <value>Unbekannten Wert für {0} erhalten, bitte melde folgendes: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -290,10 +299,20 @@ StackTrace:
<value>Das Spielen von mehr als {0} Spielen gleichzeitig ist nicht möglich, nur die ersten {0} Einträge von {1} werden verwendet!</value> <value>Das Spielen von mehr als {0} Spielen gleichzeitig ist nicht möglich, nur die ersten {0} Einträge von {1} werden verwendet!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Der IPC-Dienst konnte wegen einer AddressAccessDeniedException nicht gestartet werden! Wenn du den IPC-Service von ASF nutzen möchtest, erwäge es ASF als Administrator auszuführen oder die korrekten Berechtigungen zu erteilen!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Auf IPC-Befehl geantwortet: {0} mit {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>ICP-Server bereit!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Starte IPC-Server auf {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Dieser Bot hat bereits angehalten!</value> <value>Dieser Bot hat bereits angehalten!</value>
</data> </data>
@@ -545,10 +564,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Der Bot wird zurzeit benutzt.</value> <value>Der Bot wird zurzeit benutzt.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Verbindung zu Steam nicht möglich: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Anmeldung in Steam nicht möglich: {0}/{1}</value> <value>Anmeldung in Steam nicht möglich: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -657,4 +672,5 @@ StackTrace:
<value>Fertig mit Löschung der Steam Entdeckungsliste #{0}.</value> <value>Fertig mit Löschung der Steam Entdeckungsliste #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -184,14 +184,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Konnte aktuellste Version nicht überprüfen!</value> <value>Konnte aktuellste Version nicht überprüfen!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Konnte mit der Aktualisierung nicht fortfahren, weil es keine Anlage gibt, welche sich auf die aktuell betriebene Version bezieht! Automatische Aktualisierung auf diese Version ist nicht möglich.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Konnte nicht mit Aktualisierung fortfahren, weil diese Version keine Anlage enthält!</value> <value>Konnte nicht mit Aktualisierung fortfahren, weil diese Version keine Anlage enthält!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Anfrage für Benutzereingabe erhalten, aber der Prozess läuft im Headless-Modus!</value> <value>Anfrage für Benutzereingabe erhalten, aber der Prozess läuft im Headless-Modus!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Verweigere die Bearbeitung dieser Anfrage, weil die SteamOwnerID nicht festgelegt wurde!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Beende...</value> <value>Beende...</value>
</data> </data>
@@ -242,7 +247,8 @@ StackTrace:
<value>Prüfe auf neue Version...</value> <value>Prüfe auf neue Version...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Lade neue Version herunter... Während du wartest, denk darüber nach zu spenden, wenn du die geleistete Arbeit zu schätzen weißt! :)</value> <value>Lade neue Version herunter: {0} ({1} MB)... Während du wartest, denk darüber nach zu spenden, wenn du die geleistete Arbeit zu schätzen weißt! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Aktualisierung abgeschlossen!</value> <value>Aktualisierung abgeschlossen!</value>
@@ -282,7 +288,10 @@ StackTrace:
<value>Bitte gebe undokumentierten Wert von {0} ein: </value> <value>Bitte gebe undokumentierten Wert von {0} ein: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Bitte gebe deinen IPC-Host ein: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Unbekannten Wert für {0} erhalten, bitte melde folgendes: {1}</value> <value>Unbekannten Wert für {0} erhalten, bitte melde folgendes: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -291,10 +300,20 @@ StackTrace:
<value>Das Spielen von mehr als {0} Spielen gleichzeitig ist nicht möglich, nur die ersten {0} Einträge von {1} werden verwendet!</value> <value>Das Spielen von mehr als {0} Spielen gleichzeitig ist nicht möglich, nur die ersten {0} Einträge von {1} werden verwendet!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Der IPC-Dienst konnte wegen einer AddressAccessDeniedException nicht gestartet werden! Wenn du den IPC-Service von ASF nutzen möchtest, erwäge es ASF als Administrator auszuführen oder die korrekten Berechtigungen zu erteilen!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Auf IPC-Befehl geantwortet: {0} mit {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>ICP-Server bereit!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Starte IPC-Server auf {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Dieser Bot hat bereits angehalten!</value> <value>Dieser Bot hat bereits angehalten!</value>
</data> </data>
@@ -546,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot wird zurzeit benutzt.</value> <value>Bot wird zurzeit benutzt.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Verbindung zu Steam nicht möglich: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Anmeldung in Steam nicht möglich: {0}/{1}</value> <value>Anmeldung in Steam nicht möglich: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -658,4 +673,8 @@ StackTrace:
<value>Fertig mit Löschung der Steam Entdeckungsliste #{0}.</value> <value>Fertig mit Löschung der Steam Entdeckungsliste #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Es gibt {0}/{1}-Bots, die bereits alle geprüften Spiele besitzen.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -134,7 +134,10 @@
<value>Η ρυθμισμένη ιδιότητα {0} δεν είναι έγκυρη: {1}</value> <value>Η ρυθμισμένη ιδιότητα {0} δεν είναι έγκυρη: {1}</value>
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment> <comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
</data> </data>
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
<value>Το ASF V{0} αντιμετώπισε κρίσιμο σφάλμα πριν καν ξεκινήσει η κύρια μονάδα καταγραφής σφαλμάτων!</value>
<comment>{0} will be replaced by version number</comment>
</data>
<data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve"> <data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve">
<value>Εξαίρεση: {0}() {1} <value>Εξαίρεση: {0}() {1}
StackTrace: StackTrace:
@@ -166,7 +169,10 @@ StackTrace:
<value>Το {0} είναι null!</value> <value>Το {0} είναι null!</value>
<comment>{0} will be replaced by object's name</comment> <comment>{0} will be replaced by object's name</comment>
</data> </data>
<data name="ErrorParsingObject" xml:space="preserve">
<value>Η ανάλυση του {0} απέτυχε!</value>
<comment>{0} will be replaced by object's name</comment>
</data>
<data name="ErrorRemovingOldBinary" xml:space="preserve"> <data name="ErrorRemovingOldBinary" xml:space="preserve">
<value>Αδυναμία αφαίρεσης του παλιού ASF binary, αφαιρέστε το {0} χειροκίνητα ώστε να λειτουργήσει η λειτουργία ενημέρωσης!</value> <value>Αδυναμία αφαίρεσης του παλιού ASF binary, αφαιρέστε το {0} χειροκίνητα ώστε να λειτουργήσει η λειτουργία ενημέρωσης!</value>
<comment>{0} will be replaced by file's path</comment> <comment>{0} will be replaced by file's path</comment>
@@ -178,14 +184,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Αδυναμία ελέγχου για την τελευταία έκδοση!</value> <value>Αδυναμία ελέγχου για την τελευταία έκδοση!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Αδυναμία συνέχειας με την ενημέρωση καθώς δεν υπάρχουν τα απαραίτητα αρχεία που σχετίζονται με την τρέχουσα έκδοση. Η αυτόματη ενημέρωση σε αυτή την έκδοση δεν είναι δυνατή.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Αδυναμία συνέχειας με την ενημέρωση γιατί η συγκεκριμένη έκδοση δεν περιέχει καθόλου αρχεία!</value> <value>Αδυναμία συνέχειας με την ενημέρωση γιατί η συγκεκριμένη έκδοση δεν περιέχει καθόλου αρχεία!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Λήφθηκε αίτημα για είσοδο από τον χρήστη, αλλά η διεργασία εκτελείται σε σιωπηλή λειτουργία!</value> <value>Λήφθηκε αίτημα για είσοδο από τον χρήστη, αλλά η διεργασία εκτελείται σε σιωπηλή λειτουργία!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Άρνηση εκτέλεσης αυτού του αιτήματος επειδή το SteamOwnerID δεν έχει οριστεί!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Έξοδος...</value> <value>Έξοδος...</value>
</data> </data>
@@ -236,7 +247,8 @@ StackTrace:
<value>Έλεγχος για νέα έκδοση...</value> <value>Έλεγχος για νέα έκδοση...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Λήψη νέας έκδοσης... Όσο περιμένετε, σκεφτείτε να κάνετε μια δωρεά εάν εκτιμάτε τη δουλειά που γίνεται! :)</value> <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>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Η διαδικασία ενημέρωσης ολοκληρώθηκε!</value> <value>Η διαδικασία ενημέρωσης ολοκληρώθηκε!</value>
@@ -276,7 +288,10 @@ StackTrace:
<value>Εισάγετε τη μη τεκμηριωμένη τιμή {0}: </value> <value>Εισάγετε τη μη τεκμηριωμένη τιμή {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Παρακαλούμε εισάγετε τον διακομιστή IPC σας: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Λήφθηκε άγνωστη τιμή για το {0}, παρακαλούμε αναφέρετέ το: {1}</value> <value>Λήφθηκε άγνωστη τιμή για το {0}, παρακαλούμε αναφέρετέ το: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -285,10 +300,20 @@ StackTrace:
<value>Η συλλογή περισσότερων από {0} παιχνιδιών ταυτόχρονα δεν είναι δυνατή, μόνο οι πρώτες {0} καταχρήσεις από το {1} θα χρησιμοποιηθούν!</value> <value>Η συλλογή περισσότερων από {0} παιχνιδιών ταυτόχρονα δεν είναι δυνατή, μόνο οι πρώτες {0} καταχρήσεις από το {1} θα χρησιμοποιηθούν!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Η υπηρεσία IPC δεν ήταν δυνατό να εκκινηθεί λόγω σφάλματος AddressAccessDeniedException! Εάν θέλετε να χρησιμοποιήσετε την υπηρεσία IPC που παρέχεται από το ASF, δοκιμάστε να εκκινήσετε το ASF ως διαχειριστής ή να παραχωρήσετε τα κατάλληλα δικαιώματα!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Έγινε απάντηση στην εντολή IPC: {0} με: {1}</value>
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
</data>
<data name="IPCReady" xml:space="preserve">
<value>Ο διακομιστής IPC είναι έτοιμος!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Έναρξη διακομιστή IPC στο {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Το bot έχει ήδη σταματήσει!</value> <value>Το bot έχει ήδη σταματήσει!</value>
</data> </data>
@@ -449,7 +474,10 @@ StackTrace:
<data name="BotInstanceNotStartingBecauseDisabled" xml:space="preserve"> <data name="BotInstanceNotStartingBecauseDisabled" xml:space="preserve">
<value>Δεν γίνεται εκκίνηση αυτού του bot καθώς έχει απενεργοποιηθεί στο αρχείο διαμόρφωσης!</value> <value>Δεν γίνεται εκκίνηση αυτού του bot καθώς έχει απενεργοποιηθεί στο αρχείο διαμόρφωσης!</value>
</data> </data>
<data name="BotInvalidAuthenticatorDuringLogin" xml:space="preserve">
<value>Λήφθηκε κωδικός σφάλματος «TwoFactorCodeMismatch» {0} συνεχόμενες φορές. Αυτό σχεδόν πάντα υποδεικνύει μη έγκυρα στοιχεία εισόδου στο ASF 2FA. Γίνεται τερματισμός!</value>
<comment>{0} will be replaced by maximum allowed number of failed 2FA attempts</comment>
</data>
<data name="BotLoggedOff" xml:space="preserve"> <data name="BotLoggedOff" xml:space="preserve">
<value>Έγινε αποσύνδεση από το Steam: {0}</value> <value>Έγινε αποσύνδεση από το Steam: {0}</value>
<comment>{0} will be replaced by logging off reason (string)</comment> <comment>{0} will be replaced by logging off reason (string)</comment>
@@ -460,20 +488,36 @@ StackTrace:
<data name="BotLoggingIn" xml:space="preserve"> <data name="BotLoggingIn" xml:space="preserve">
<value>Σύνδεση...</value> <value>Σύνδεση...</value>
</data> </data>
<data name="BotLogonSessionReplaced" xml:space="preserve">
<value>Αυτός ο λογαριασμός φαίνεται να χρησιμοποιείται σε άλλη συνεδρία ASF, το οποίο προκαλεί απρόσμενη συμπεριφορά. Θα γίνει τερματισμός!</value>
</data>
<data name="BotLootingFailed" xml:space="preserve"> <data name="BotLootingFailed" xml:space="preserve">
<value>Η πρόταση ανταλλαγής απέτυχε!</value> <value>Η πρόταση ανταλλαγής απέτυχε!</value>
</data> </data>
<data name="BotLootingMasterNotDefined" xml:space="preserve">
<value>Η ανταλλαγή δεν μπορεί να αποσταλεί καθώς δεν υπάρχει κανένας χρήστης με ορισμένο το δικαίωμα «master»!</value>
</data>
<data name="BotLootingNoLootableTypes" xml:space="preserve">
<value>Δεν έχετε ορίσει τύπους αντικειμένων για μαζική συλλογή!</value>
</data>
<data name="BotLootingNowDisabled" xml:space="preserve">
<value>Η μαζική συλλογή απενεργοποιήθηκε!</value>
</data>
<data name="BotLootingNowEnabled" xml:space="preserve">
<value>Η μαζική συλλογή ενεργοποιήθηκε!</value>
</data>
<data name="BotLootingSuccess" xml:space="preserve"> <data name="BotLootingSuccess" xml:space="preserve">
<value>Η πρόταση ανταλλαγής στάλθηκε επιτυχώς!</value> <value>Η πρόταση ανταλλαγής στάλθηκε επιτυχώς!</value>
</data> </data>
<data name="BotLootingTemporarilyDisabled" xml:space="preserve">
<value>Η μαζική συλλογή είναι προσωρινά απενεργοποιημένη!</value>
</data>
<data name="BotLootingYourself" xml:space="preserve">
<value>Δεν μπορείτε να κάνετε μαζική συλλογή από τον εαυτό σας!</value>
</data>
<data name="BotNoASFAuthenticator" xml:space="preserve">
<value>Αυτό το bot δεν έχει ενεργοποιημένο το ASF 2FA! Μήπως ξεχάσατε να εισάγετε τον επαληθευτή σας ως ASF 2FA;</value>
</data>
<data name="BotNotConnected" xml:space="preserve"> <data name="BotNotConnected" xml:space="preserve">
<value>Αυτό το bot δεν έχει συνδεθεί!</value> <value>Αυτό το bot δεν έχει συνδεθεί!</value>
</data> </data>
@@ -521,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Το bot χρησιμοποιείται αυτή τη στιγμή.</value> <value>Το bot χρησιμοποιείται αυτή τη στιγμή.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Αδυναμία σύνδεσης στο Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Αδυναμία σύνδεσης στο Steam: {0}/{1}</value> <value>Αδυναμία σύνδεσης στο Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -633,4 +673,8 @@ StackTrace:
<value>Ολοκληρώθηκε η εκκαθάριση σειράς ανακαλύψεων Steam #{0}.</value> <value>Ολοκληρώθηκε η εκκαθάριση σειράς ανακαλύψεων Steam #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Υπάρχουν {0}/{1} bot που κατέχουν ήδη όλα τα παιχνίδια για τα οποία γίνεται έλεγχος.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -183,14 +183,19 @@ Trazo de pila:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>¡No se pudo comprobar la última versión!</value> <value>¡No se pudo comprobar la última versión!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>No se ha podido proceder con la actualización porque no hay ninguna opcion que se relacione con la version actual! La actualización automatica a esa version no es posible.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>¡No se puede continuar con una actualización porque esa versión no incluye ningún recurso!</value> <value>¡No se puede continuar con una actualización porque esa versión no incluye ningún recurso!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Recibida una solicitud de entrada del usuario, ¡pero el proceso se está ejecutando en modo servidor!</value> <value>Recibida una solicitud de entrada del usuario, ¡pero el proceso se está ejecutando en modo servidor!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>¡Solicitud denegada porque SteamOwnerID no esta establecido!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Saliendo...</value> <value>Saliendo...</value>
</data> </data>
@@ -241,7 +246,8 @@ Trazo de pila:
<value>Comprobando si existe una nueva versión...</value> <value>Comprobando si existe una nueva versión...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Descargando la nueva versión... ¡Mientras espera, considere donar si aprecia el trabajo que se está realizando! :)</value> <value>Descargando la nueva versión: {0} ({1} MB)... ¡Mientras espera, considere donar si aprecia el trabajo que se está realizando! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>¡Proceso de actualización finalizado!</value> <value>¡Proceso de actualización finalizado!</value>
@@ -281,7 +287,10 @@ Trazo de pila:
<value>Por favor ingresa el valor indocumentado de {0}: </value> <value>Por favor ingresa el valor indocumentado de {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Por favor ingresa tu host WFC: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Recibido valor desconocido para {0}. Por favor, informa de ello: {1}</value> <value>Recibido valor desconocido para {0}. Por favor, informa de ello: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -290,10 +299,20 @@ Trazo de pila:
<value>¡Ejecutar más de {0} juegos a la vez no es posible, sólo se usarán las primeras {0} entradas de {1}!</value> <value>¡Ejecutar más de {0} juegos a la vez no es posible, sólo se usarán las primeras {0} entradas de {1}!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>¡El servicio de WCF no pudo ser iniciado por un error en AddressAccessDeniedException! Si deseas usar el servicio de WCF proporcionado por ASF, intenta iniciar ASF como administrador, o dar los permisos adecuados!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Se ha respondido al comando WCF: {0} con: {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>¡Servidor WCF listo!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Iniciando servidor WCF en {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>¡Este bot ya se ha detenido!</value> <value>¡Este bot ya se ha detenido!</value>
</data> </data>
@@ -545,10 +564,6 @@ Trazo de pila:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>El bot se está utilizando actualmente.</value> <value>El bot se está utilizando actualmente.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>No se puede conectar a Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>No se puede iniciar sesión en Steam: {0}/{1}</value> <value>No se puede iniciar sesión en Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -649,6 +664,13 @@ Trazo de pila:
<value>Uso de memoria actual: {0} MB.</value> <value>Uso de memoria actual: {0} MB.</value>
<comment>{0} will be replaced by number (in megabytes) of memory being used</comment> <comment>{0} will be replaced by number (in megabytes) of memory being used</comment>
</data> </data>
<data name="ClearingDiscoveryQueue" xml:space="preserve">
<value>Haciendo la lista de descubrimientos de Steam #{0}...</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
<value>Lista de decubrimientos de Steam #{0} completada.</value>
<comment>{0} will be replaced by queue number</comment>
</data>
</root> </root>

View File

@@ -201,9 +201,7 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Tarkistetaan päivityksiä...</value> <value>Tarkistetaan päivityksiä...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Ladataan päivityksiä... Odottaessa, harkitse lahjoittamista kiittääksesi työhön käytetystä ajasta! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Päivitys valmis!</value> <value>Päivitys valmis!</value>
</data> </data>
@@ -370,7 +368,6 @@
<data name="ErrorIsEmpty" xml:space="preserve"> <data name="ErrorIsEmpty" xml:space="preserve">
<value>{0} on tyhjä!</value> <value>{0} on tyhjä!</value>
<comment>{0} will be replaced by object's name</comment> <comment>{0} will be replaced by object's name</comment>
@@ -416,4 +413,5 @@
</root> </root>

View File

@@ -191,7 +191,10 @@ StackTrace :
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Réception d'une demande d'entrée utilisateur, mais le processus en cours tourne en mode non-interactif!</value> <value>Réception d'une demande d'entrée utilisateur, mais le processus en cours tourne en mode non-interactif!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Refus de traiter la requête car le paramètre SteamOwnerID nest pas défini !</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Fermeture...</value> <value>Fermeture...</value>
</data> </data>
@@ -242,7 +245,8 @@ StackTrace :
<value>Recherche d'une nouvelle version...</value> <value>Recherche d'une nouvelle version...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Téléchargement de la nouvelle version en cours... En attendant, vous pouvez faire un don si vous appréciez le travail effectué ! :)</value> <value>Téléchargement de la nouvelle version en cours: {0} ({1} MB)... En attendant, envisagez de faire un don si vous appréciez le travail effectué ! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Mise à jour terminée !</value> <value>Mise à jour terminée !</value>
@@ -546,10 +550,6 @@ StackTrace :
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Le bot est actuellement utilisé.</value> <value>Le bot est actuellement utilisé.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Impossible de se connecter à Steam : {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Impossible de se connecter à Steam : {0}/{1}</value> <value>Impossible de se connecter à Steam : {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -650,6 +650,13 @@ StackTrace :
<value>Mémoire actuellement utilisée : {0} MB.</value> <value>Mémoire actuellement utilisée : {0} MB.</value>
<comment>{0} will be replaced by number (in megabytes) of memory being used</comment> <comment>{0} will be replaced by number (in megabytes) of memory being used</comment>
</data> </data>
<data name="ClearingDiscoveryQueue" xml:space="preserve">
<value>Consultation de la liste de découvertes en cours #{0}...</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
<value>Fini de consulter la liste de découvertes #{0}.</value>
<comment>{0} will be replaced by queue number</comment>
</data>
</root> </root>

View File

@@ -191,7 +191,10 @@ StackTrace :
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Réception d'une demande d'entrée utilisateur, mais le processus en cours tourne en mode non-interactif !</value> <value>Réception d'une demande d'entrée utilisateur, mais le processus en cours tourne en mode non-interactif !</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Refus de traiter la requête car le paramètre SteamOwnerID nest pas défini !</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Fermeture...</value> <value>Fermeture...</value>
</data> </data>
@@ -242,7 +245,8 @@ StackTrace :
<value>Recherche d'une nouvelle version...</value> <value>Recherche d'une nouvelle version...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Téléchargement de la nouvelle version en cours... En attendant, vous pouvez faire un don si vous appréciez le travail effectué ! :)</value> <value>Téléchargement de la nouvelle version en cours: {0} ({1} MB)... En attendant, envisagez de faire un don si vous appréciez le travail effectué ! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Mise à jour terminée !</value> <value>Mise à jour terminée !</value>
@@ -251,7 +255,7 @@ StackTrace :
<value>Une nouvelle version d'ASF est disponible ! Envisagez de la mettre à jour !</value> <value>Une nouvelle version d'ASF est disponible ! Envisagez de la mettre à jour !</value>
</data> </data>
<data name="UpdateVersionInfo" xml:space="preserve"> <data name="UpdateVersionInfo" xml:space="preserve">
<value>Version installée : {0} | Version la plus récente : {1}</value> <value>Version locale : {0} | Version la plus récente : {1}</value>
<comment>{0} will be replaced by current version, {1} will be replaced by remote version</comment> <comment>{0} will be replaced by current version, {1} will be replaced by remote version</comment>
</data> </data>
<data name="UserInputDeviceID" xml:space="preserve"> <data name="UserInputDeviceID" xml:space="preserve">
@@ -546,10 +550,6 @@ StackTrace :
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Le bot est actuellement utilisé.</value> <value>Le bot est actuellement utilisé.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Impossible de se connecter à Steam : {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Impossible de se connecter à Steam : {0}/{1}</value> <value>Impossible de se connecter à Steam : {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -650,6 +650,13 @@ StackTrace :
<value>Mémoire actuellement utilisée : {0} MB.</value> <value>Mémoire actuellement utilisée : {0} MB.</value>
<comment>{0} will be replaced by number (in megabytes) of memory being used</comment> <comment>{0} will be replaced by number (in megabytes) of memory being used</comment>
</data> </data>
<data name="ClearingDiscoveryQueue" xml:space="preserve">
<value>Consultation de la liste de découvertes en cours #{0}...</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
<value>Fini de consulter la liste de découvertes #{0}.</value>
<comment>{0} will be replaced by queue number</comment>
</data>
</root> </root>

View File

@@ -228,9 +228,7 @@ StackTrace:
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>מחפש גירסה חדשה...</value> <value>מחפש גירסה חדשה...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>מוריד גירסה חדשה... בזמן ההמתנה, אנא שקלו לתרום אם אתם מעריכים את העבודה שאנו עושים :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>תהליך העדכון הסתיים!</value> <value>תהליך העדכון הסתיים!</value>
</data> </data>
@@ -436,10 +434,6 @@ StackTrace:
<data name="BotUnableToConnect" xml:space="preserve">
<value>לא ניתן להתחבר לסטים: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>לא ניתן להתחבר לסטים: {0}/{1}</value> <value>לא ניתן להתחבר לסטים: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -490,4 +484,5 @@ StackTrace:
</root> </root>

View File

@@ -239,9 +239,7 @@ StackTrace: {2}</value>
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Új verzió keresése...</value> <value>Új verzió keresése...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Új verzió letöltése folyamatban... Mialatt várakozol, fontold meg, hogy pénzzel támogatod a munkámat, ha tetszik, amit csinálok! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Frissítés kész!</value> <value>Frissítés kész!</value>
</data> </data>
@@ -544,10 +542,6 @@ StackTrace: {2}</value>
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>A bot jelenleg használatban van.</value> <value>A bot jelenleg használatban van.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nem lehet csatlakozni a Steamhez: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nem lehet belépni a Steamre: {0}/{1}</value> <value>Nem lehet belépni a Steamre: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -656,4 +650,5 @@ StackTrace: {2}</value>
<value>{0}-s számú Steam Felfedezési Várólista kitsztítva.</value> <value>{0}-s számú Steam Felfedezési Várólista kitsztítva.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -239,9 +239,7 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Sedang mengecek versi terbaru...</value> <value>Sedang mengecek versi terbaru...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Mengunduh versi baru... Sambil menunggu, pertimbangkan untuk mengapresiasi seluruh kerja keras dengan mendonasi! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Proses update selesai!</value> <value>Proses update selesai!</value>
</data> </data>
@@ -291,7 +289,9 @@
</data> </data>
<data name="IPCReady" xml:space="preserve">
<value>Server IPC siap!</value>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Bot ini sudah berhenti!</value> <value>Bot ini sudah berhenti!</value>
@@ -544,10 +544,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot saat ini sedang digunakan.</value> <value>Bot saat ini sedang digunakan.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Tidak dapat terhubung ke Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Tidak dapat login ke Steam: {0}/{1}</value> <value>Tidak dapat login ke Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -656,4 +652,5 @@
<value>Selesai membersihkan antrian penemuan Steam #{0}.</value> <value>Selesai membersihkan antrian penemuan Steam #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -239,9 +239,7 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Verifica della nuova versione...</value> <value>Verifica della nuova versione...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Scaricando la nuova versione... Durante l'attesa, considera una donazione se apprezzi il lavoro svolto! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Aggiornamento completato!</value> <value>Aggiornamento completato!</value>
</data> </data>
@@ -544,10 +542,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Il bot è attualmente in uso.</value> <value>Il bot è attualmente in uso.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Impossibile connettersi a Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Impossibile effettuare l'accesso a Steam: {0}/{1}</value> <value>Impossibile effettuare l'accesso a Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -656,4 +650,5 @@
<value>Fine coda #{0} dell'elenco scoperte Steam.</value> <value>Fine coda #{0} dell'elenco scoperte Steam.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -238,9 +238,7 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>新しいバージョンをチェックしています...</value> <value>新しいバージョンをチェックしています...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>新しいバージョンをダウンロードしています... 待っている間、作者への寄付をご検討ください! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>アップデート完了!</value> <value>アップデート完了!</value>
</data> </data>
@@ -543,10 +541,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Botは現在使用されています。</value> <value>Botは現在使用されています。</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Steamに接続できませんでした: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Steamにログインできませんでした: {0}/{1}</value> <value>Steamにログインできませんでした: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -649,4 +643,5 @@
</data> </data>
</root> </root>

View File

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

View File

@@ -181,14 +181,19 @@
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Nepavyko patikrinti naujausios versijos!</value> <value>Nepavyko patikrinti naujausios versijos!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Nebuvo galima tęsti naujinimo, nes nėra jokio objekto, kuris yra susijęs su šiuo metu įdiegta versija! Automatinis naujinimas į tą versija yra neįmanoma.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Nebuvo galima tęsti su atnaujinimu, nes ta versija neapima jokios informacijos!</value> <value>Nebuvo galima tęsti su atnaujinimu, nes ta versija neapima jokios informacijos!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Gautas prašymas reikalaujantis naudotojo įvesties, bet procesas veikia "headless" mode!</value> <value>Gautas prašymas reikalaujantis naudotojo įvesties, bet procesas veikia "headless" mode!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Atsisakoma apdoroti užklausa, nes SteamOwnerID nėra nustatytas!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Stabdoma...</value> <value>Stabdoma...</value>
</data> </data>
@@ -239,7 +244,8 @@
<value>Ieškoma naujos versijos...</value> <value>Ieškoma naujos versijos...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Atsiunčiama nauja versija... Kol laukiate, apsvarstykite, gal norite paaukoti jei vertinate daromą darbą! :)</value> <value>Atsiunčiama nauja versija: {0} ({1} MB)... Kol laukiate, apsvarstykite, gal norite paaukoti jei vertinate daromą darbą! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Naujinimo procesas baigtas!</value> <value>Naujinimo procesas baigtas!</value>
@@ -279,7 +285,10 @@
<value>Prašome įvesti nedokumentuotą {0} reikšmę: </value> <value>Prašome įvesti nedokumentuotą {0} reikšmę: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Prašome įvesti savo IPC hostingą: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Gauta nežinoma reikšmė, {0}, prašome pranešti apie tai: {1}</value> <value>Gauta nežinoma reikšmė, {0}, prašome pranešti apie tai: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -288,10 +297,20 @@
<value>Žaisti daugiau negu {0} žaidimų vienu metu yra neįmanoma, bus naudojamas tik pirmasis {0} įrašas iš {1}!</value> <value>Žaisti daugiau negu {0} žaidimų vienu metu yra neįmanoma, bus naudojamas tik pirmasis {0} įrašas iš {1}!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>IPC tarnybos nepavyko paleisti dėl "AddressAccessDeniedException"! Jei norite naudotis IPC tarnybomis kurias teikia ASF, apsvarstykite pradėti ASF kaip administratorius, arba suteikti atitinkamus leidimus!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Atsakyta į IPC komandą: {0} su: {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 serveris paruoštas!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Paleidžiamas IPC serveris {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Šis botas jau sustabdytas!</value> <value>Šis botas jau sustabdytas!</value>
</data> </data>
@@ -543,10 +562,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Botas šiuo metu yra naudojamas.</value> <value>Botas šiuo metu yra naudojamas.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nepavyko prisijungti prie Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nepavyko prisijungti į Steam: {0}/{1}</value> <value>Nepavyko prisijungti į Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -655,4 +670,8 @@
<value>Baigta peržiūrėti Steam atradimo eilė #{0}.</value> <value>Baigta peržiūrėti Steam atradimo eilė #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Šiuo metu {0}/{1} botai turi visus tikrinamus žaidimus.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -183,14 +183,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Controle voor de laatste versie is mislukt!</value> <value>Controle voor de laatste versie is mislukt!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Kon niet verdergaan met updaten, omdat er geen bestand gerelateerd is aan de reeds werkende versie! Automatisch updaten van deze versie is niet mogelijk.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Kon niet verdergaan met updaten, omdat deze updateversie geen bestanden bevat!</value> <value>Kon niet verdergaan met updaten, omdat deze updateversie geen bestanden bevat!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Aanvraag voor gebruikersinvoer ontvangen, maar het proces draait in de headless mode!</value> <value>Aanvraag voor gebruikersinvoer ontvangen, maar het proces draait in de headless mode!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Het verzoek wordt niet in behandeling genomen omdat het SteamOwnerID niet is ingesteld!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Afsluiten...</value> <value>Afsluiten...</value>
</data> </data>
@@ -241,7 +246,8 @@ StackTrace:
<value>Controleren op nieuwe versie...</value> <value>Controleren op nieuwe versie...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Nieuwe versie wordt gedownload... Als je het gedane werk waardeert, overweeg dan tijdens het wachten om te doneren! :)</value> <value>Nieuwe versie wordt gedownload: {0} ({1} MB)... Als je het gedane werk waardeert, overweeg dan tijdens het wachten om te doneren! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Update is afgerond!</value> <value>Update is afgerond!</value>
@@ -282,7 +288,10 @@ StackTrace:
<value>Geef de ongedocumenteerde waarde van {0} op: </value> <value>Geef de ongedocumenteerde waarde van {0} op: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Voer je IPC host in: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Onbekende waarde ontvangen voor {0}, gelieven dit melden: {1}</value> <value>Onbekende waarde ontvangen voor {0}, gelieven dit melden: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -291,10 +300,20 @@ StackTrace:
<value>Het gelijktijdig spelen van meer dan {0} spellen is niet mogelijk, alleen de eerste {0} spellen van {1} worden gespeeld!</value> <value>Het gelijktijdig spelen van meer dan {0} spellen is niet mogelijk, alleen de eerste {0} spellen van {1} worden gespeeld!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>IPC service kan niet worden gestart vanwege de AddressAccessDeniedException! Als je gebruik wilt maken van de IPC service van ASF, start ASF als administrator of geef de juiste machtigingen!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Gereageerd op IPC opdracht: {0} met: {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 gereed!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>IPC server starten op {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Deze bot is al gestopt!</value> <value>Deze bot is al gestopt!</value>
</data> </data>
@@ -546,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot is momenteel in gebruik.</value> <value>Bot is momenteel in gebruik.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Kan geen verbinding maken met Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Kan niet inloggen op Steam: {0}/{1}</value> <value>Kan niet inloggen op Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -658,4 +673,8 @@ StackTrace:
<value>Steam-ontdekkingswachtrij voltooid #{0}.</value> <value>Steam-ontdekkingswachtrij voltooid #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Er zijn {0}/{1} bots die de gecontroleerde spellen al in bezit hebben.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -183,14 +183,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Laatste versie kon niet worden gecontroleerd!</value> <value>Laatste versie kon niet worden gecontroleerd!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Kon niet verdergaan met updaten, omdat er geen bestand gerelateerd is aan de reeds werkende versie! Automatisch updaten van deze versie is niet mogelijk.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Kon niet verdergaan met updaten, omdat deze updateversie geen bestanden bevat!</value> <value>Kon niet verdergaan met updaten, omdat deze updateversie geen bestanden bevat!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Aanvraag voor gebruikersinvoer ontvangen, maar het proces draait in de headless mode!</value> <value>Aanvraag voor gebruikersinvoer ontvangen, maar het proces draait in de headless mode!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Het verzoek wordt niet in behandeling genomen omdat het SteamOwnerID niet is ingesteld!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Afsluiten...</value> <value>Afsluiten...</value>
</data> </data>
@@ -241,7 +246,8 @@ StackTrace:
<value>Controleren op nieuwe versie...</value> <value>Controleren op nieuwe versie...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Nieuwe versie wordt gedownload... Als je het gedane werk waardeert, overweeg dan tijdens het wachten om te doneren! :)</value> <value>Nieuwe versie wordt gedownload: {0} ({1} MB)... Als je het gedane werk waardeert, overweeg dan tijdens het wachten om te doneren! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Update is afgerond!</value> <value>Update is afgerond!</value>
@@ -282,7 +288,10 @@ StackTrace:
<value>Geef de ongedocumenteerde waarde van {0} op: </value> <value>Geef de ongedocumenteerde waarde van {0} op: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Voer je IPC host in: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Onbekende waarde ontvangen voor {0}, graag dit melden: {1}</value> <value>Onbekende waarde ontvangen voor {0}, graag dit melden: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -291,10 +300,20 @@ StackTrace:
<value>Het gelijktijdig spelen van meer dan {0} spellen is niet mogelijk, alleen de eerste {0} spellen van {1} worden gespeeld!</value> <value>Het gelijktijdig spelen van meer dan {0} spellen is niet mogelijk, alleen de eerste {0} spellen van {1} worden gespeeld!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>IPC service kan niet worden gestart vanwege de AddressAccessDeniedException! Als je gebruik wilt maken van de IPC service van ASF, start ASF als administrator of geef de juiste machtigingen!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Gereageerd op IPC opdracht: {0} met: {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 gereed!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>IPC server starten op {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Deze bot is al gestopt!</value> <value>Deze bot is al gestopt!</value>
</data> </data>
@@ -546,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot is momenteel in gebruik.</value> <value>Bot is momenteel in gebruik.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Kan geen verbinding maken met Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Kan niet inloggen op Steam: {0}/{1}</value> <value>Kan niet inloggen op Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -658,4 +673,8 @@ StackTrace:
<value>Steam-ontdekkingswachtrij voltooid #{0}.</value> <value>Steam-ontdekkingswachtrij voltooid #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Er zijn {0}/{1} bots die de gecontroleerde spellen al in bezit hebben.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

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

View File

@@ -247,7 +247,8 @@ StackTrace:
<value>Wyszukiwanie nowej wersji...</value> <value>Wyszukiwanie nowej wersji...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Pobieranie nowej wersji... Podczas czekania rozważ dotację, jeśli doceniasz naszą pracę! :)</value> <value>Pobieranie nowej wersji: {0} ({1} MB)... Podczas czekania rozważ dotację, jeśli doceniasz naszą pracę! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Aktualizacja została zakończona!</value> <value>Aktualizacja została zakończona!</value>
@@ -564,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot jest aktualnie używany.</value> <value>Bot jest aktualnie używany.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nie można połączyć ze Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nie można zalogować do Steam: {0}/{1}</value> <value>Nie można zalogować do Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -676,4 +673,8 @@ StackTrace:
<value>Ukończono czyszczenie #{0} kolejki odkryć Steam.</value> <value>Ukończono czyszczenie #{0} kolejki odkryć Steam.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>W tej chwili {0}/{1} botów posiada już wszystkie sprawdzane gry.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -184,14 +184,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Não foi possível verificar a última versão!</value> <value>Não foi possível verificar a última versão!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Não foi possível prosseguir com a atualização porque não há nenhum recurso relacionado a versão atualmente em execução! Atualização automática para esta versão não é possível.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Não foi possível prosseguir com a atualização pois esta versão não inclui nenhum arquivo!</value> <value>Não foi possível prosseguir com a atualização pois esta versão não inclui nenhum arquivo!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Recebido um pedido de entrada feito pelo usuário, mas o processo está sendo executado em modo headless!</value> <value>Recebido um pedido de entrada feito pelo usuário, mas o processo está sendo executado em modo headless!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Recusando-se a lidar com a requisição porque SteamOwnerID não está configurado!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Saindo...</value> <value>Saindo...</value>
</data> </data>
@@ -242,7 +247,8 @@ StackTrace:
<value>Verificando se há atualizações...</value> <value>Verificando se há atualizações...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Baixando nova versão... Enquanto aguarda, considere fazer uma doação caso aprecie o trabalho que está sendo feito! :)</value> <value>Baixando uma nova versão: {0} ({1} MB)... Enquanto aguarda, considere doar se você aprecia o nosso trabalho! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Processo de atualização finalizado!</value> <value>Processo de atualização finalizado!</value>
@@ -282,7 +288,10 @@ StackTrace:
<value>Por favor, insira o valor não documentado de {0}: </value> <value>Por favor, insira o valor não documentado de {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Por favor insira o seu host IPC: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Valor desconhecido recebido para {0}. Por favor, reporte isto: {1}</value> <value>Valor desconhecido recebido para {0}. Por favor, reporte isto: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -291,10 +300,20 @@ StackTrace:
<value>Não é possível jogar mais de {0} jogos ao mesmo tempo, apenas os primeiros {0} jogos de {1} serão usados!</value> <value>Não é possível jogar mais de {0} jogos ao mesmo tempo, apenas os primeiros {0} jogos de {1} serão usados!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Não foi possível iniciar o serviço IPC devido a AddressAccessDeniedException! Se você quer utilizar o serviço IPC fornecido pelo ASF, considere iniciar o ASF como administrador, ou dando as permissões necessárias!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Respondeu ao comando IPC: {0} com: {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>Servidor IPC pronto!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Iniciando servidor IPC em {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Este bot já parou!</value> <value>Este bot já parou!</value>
</data> </data>
@@ -546,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot está sendo usado no momento.</value> <value>Bot está sendo usado no momento.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Não foi possível conectar-se ao Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Não foi possível iniciar a sessão no Steam: {0}/{1}</value> <value>Não foi possível iniciar a sessão no Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -658,4 +673,8 @@ StackTrace:
<value>Limpeza da lista de descoberta da Steam #{0} concluída.</value> <value>Limpeza da lista de descoberta da Steam #{0} concluída.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Os bots {0}/{1} já possuem todos os jogos que estão sendo verificados.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -121,7 +121,10 @@
<value>Aceitando a troca: {0}</value> <value>Aceitando a troca: {0}</value>
<comment>{0} will be replaced by trade number</comment> <comment>{0} will be replaced by trade number</comment>
</data> </data>
<data name="AutoUpdateCheckInfo" xml:space="preserve">
<value>ASF vai automaticamente procurar por novas versões a cada {0}.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "24 hours")</comment>
</data>
<data name="Content" xml:space="preserve"> <data name="Content" xml:space="preserve">
<value>Conteúdo: <value>Conteúdo:
{0}</value> {0}</value>
@@ -132,7 +135,7 @@
<comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment> <comment>{0} will be replaced by name of the configuration property, {1} will be replaced by invalid value</comment>
</data> </data>
<data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve"> <data name="ErrorEarlyFatalExceptionInfo" xml:space="preserve">
<value>ASF V{0} foi executado em exceção fatal antes do núcleo do módulo de log foi capaz de inicializar!</value> <value>ASF V{0} encontrou uma exceção fatal antes que o núcleo do módulo de log foi sequer capaz de inicializar!</value>
<comment>{0} will be replaced by version number</comment> <comment>{0} will be replaced by version number</comment>
</data> </data>
<data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve"> <data name="ErrorEarlyFatalExceptionPrint" xml:space="preserve">
@@ -181,16 +184,21 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Não foi possível verificar a ultima versão!</value> <value>Não foi possível verificar a ultima versão!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Não se conseguiu prosseguir com a atualização porque não há nenhum recurso que se relacione com a versão atual! Foi impossível atualizar automaticamente para essa versão.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Não foi possível prosseguir com uma atualização!</value> <value>Não foi possível prosseguir com a atualização porque essa versão não incluí recursos!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>O pedido de entrada do usuário foi recebido, mas o processo está sendo executado no modo não-interativo!</value> <value>O pedido de entrada do usuário foi recebido, mas o processo está sendo executado no modo não-interativo!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Recusando-se a lidar com o pedido, porque SteamOwnerID não está definido!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>A sair...</value> <value>Saindo...</value>
</data> </data>
<data name="WarningFailed" xml:space="preserve"> <data name="WarningFailed" xml:space="preserve">
<value>Erro!</value> <value>Erro!</value>
@@ -206,27 +214,27 @@ StackTrace:
<comment>{0} will be replaced by trade number</comment> <comment>{0} will be replaced by trade number</comment>
</data> </data>
<data name="LoggingIn" xml:space="preserve"> <data name="LoggingIn" xml:space="preserve">
<value>A entrar em {0}...</value> <value>A iniciar sessão em {0}...</value>
<comment>{0} will be replaced by service's name</comment> <comment>{0} will be replaced by service's name</comment>
</data> </data>
<data name="NoBotsAreRunning" xml:space="preserve"> <data name="NoBotsAreRunning" xml:space="preserve">
<value>Sem bots ligados, a sair...</value> <value>Sem bots ligados, a sair...</value>
</data> </data>
<data name="RefreshingOurSession" xml:space="preserve"> <data name="RefreshingOurSession" xml:space="preserve">
<value>Atualizando a nossa sessão!</value> <value>A atualizar a nossa sessão!</value>
</data> </data>
<data name="RejectingTrade" xml:space="preserve"> <data name="RejectingTrade" xml:space="preserve">
<value>Rejeitando a troca: {0}</value> <value>Rejeitando a troca: {0}</value>
<comment>{0} will be replaced by trade number</comment> <comment>{0} will be replaced by trade number</comment>
</data> </data>
<data name="Restarting" xml:space="preserve"> <data name="Restarting" xml:space="preserve">
<value>Reniciando...</value> <value>Reiniciando...</value>
</data> </data>
<data name="Starting" xml:space="preserve"> <data name="Starting" xml:space="preserve">
<value>A iniciar...</value> <value>Iniciando...</value>
</data> </data>
<data name="StatusCode" xml:space="preserve"> <data name="StatusCode" xml:space="preserve">
<value>Estado do código: {0}</value> <value>Código de estado: {0}</value>
<comment>{0} will be replaced by status code number/name</comment> <comment>{0} will be replaced by status code number/name</comment>
</data> </data>
<data name="Success" xml:space="preserve"> <data name="Success" xml:space="preserve">
@@ -239,13 +247,14 @@ StackTrace:
<value>A verificar por novas versões...</value> <value>A verificar por novas versões...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>A obter a nova versão... Enquanto espera, considere doar algo se aprecias o trabalho feito! :)</value> <value>Obtendo a nova versão: {0} ({1} MB)... Enquanto esperas, considera doar algo se aprecias o trabalho feito! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Processo de actualização terminada!</value> <value>Processo de actualização terminada!</value>
</data> </data>
<data name="UpdateNewVersionAvailable" xml:space="preserve"> <data name="UpdateNewVersionAvailable" xml:space="preserve">
<value>Nova versão do ASF está disponível! Considere obter-la!</value> <value>Há uma nova versão do ASF disponível! Considere atualizar!</value>
</data> </data>
<data name="UpdateVersionInfo" xml:space="preserve"> <data name="UpdateVersionInfo" xml:space="preserve">
<value>Versão local: {0} | Versão remota: {1}</value> <value>Versão local: {0} | Versão remota: {1}</value>
@@ -279,7 +288,10 @@ StackTrace:
<value>Por favor, insira o valor não-documentado de {0}: </value> <value>Por favor, insira o valor não-documentado de {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Por favor insira o seu anfitrião de IPC: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Valor desconhecido recebido por {0}, por favor reporte isto: {1}</value> <value>Valor desconhecido recebido por {0}, por favor reporte isto: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -288,10 +300,20 @@ StackTrace:
<value>Não é possível jogar {0} ao mesmo tempo, apenas {0} jogos vão usados com {1}!</value> <value>Não é possível jogar {0} ao mesmo tempo, apenas {0} jogos vão usados com {1}!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Serviço de IPC não pode ser iniciado devido a AddressAccessDeniedException! Se quiser usar o serviço IPC fornecido pelo ASF, considere iniciar o ASF como administrador, ou dando permissões adequadas!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Respondeu ao comando do IPC: {0} com: {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>Servidor IPC pronto!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Iniciando o servidor IPC em {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Este bot já parou!</value> <value>Este bot já parou!</value>
</data> </data>
@@ -429,7 +451,10 @@ StackTrace:
<data name="BotAutomaticIdlingPausedAlready" xml:space="preserve"> <data name="BotAutomaticIdlingPausedAlready" xml:space="preserve">
<value>A coleta automática está pausada!</value> <value>A coleta automática está pausada!</value>
</data> </data>
<data name="BotAutomaticIdlingPausedWithCountdown" xml:space="preserve">
<value>A coleta automática está agora em pausa. Tens {0} para iniciar um jogo.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "5 minutes")</comment>
</data>
<data name="BotAutomaticIdlingResumedAlready" xml:space="preserve"> <data name="BotAutomaticIdlingResumedAlready" xml:space="preserve">
<value>A coleta automática já foi resumida!</value> <value>A coleta automática já foi resumida!</value>
</data> </data>
@@ -489,7 +514,7 @@ inválidas, abortando!</value>
<value>Looting está temporariamente desligado!</value> <value>Looting está temporariamente desligado!</value>
</data> </data>
<data name="BotLootingYourself" xml:space="preserve"> <data name="BotLootingYourself" xml:space="preserve">
<value>Não se pode bloquear a si mesmo!</value> <value>Não podes colecionar os seus próprios itens!</value>
</data> </data>
<data name="BotNoASFAuthenticator" xml:space="preserve"> <data name="BotNoASFAuthenticator" xml:space="preserve">
<value>Este bot não tem a ASF 2FA ligada! Esqueceu-se de importar seu autenticador como ASF 2FA?</value> <value>Este bot não tem a ASF 2FA ligada! Esqueceu-se de importar seu autenticador como ASF 2FA?</value>
@@ -505,7 +530,10 @@ inválidas, abortando!</value>
<value>Já adquirido: {0} | {1}</value> <value>Já adquirido: {0} | {1}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment> <comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data> </data>
<data name="BotRateLimitExceeded" xml:space="preserve">
<value>Taxa limite excedida, tentaremos novamente daqui a {0}...</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
</data>
<data name="BotReconnecting" xml:space="preserve"> <data name="BotReconnecting" xml:space="preserve">
<value>Reconectando...</value> <value>Reconectando...</value>
</data> </data>
@@ -538,10 +566,6 @@ inválidas, abortando!</value>
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>O Bot está a ser usado atualmente.</value> <value>O Bot está a ser usado atualmente.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Não foi possível estabelecer ligação à Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Não foi possível efectuar o login para a Steam: {0}/{1}</value> <value>Não foi possível efectuar o login para a Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -635,8 +659,23 @@ inválidas, abortando!</value>
<data name="ErrorAccessDenied" xml:space="preserve"> <data name="ErrorAccessDenied" xml:space="preserve">
<value>Acesso negado!</value> <value>Acesso negado!</value>
</data> </data>
<data name="WarningPreReleaseVersion" xml:space="preserve">
<value>Estás a usar uma versão mais recente que a última versão do teu canal de atualização. Por favor considera que as versões de pré-lançamento são dedicados a utilizadores que sabem reportar erros, lidar com problemas e dar feedback - não irás receber suporte técnico.</value>
</data>
<data name="BotStats" xml:space="preserve">
<value>Uso de memória atual: {0} MB.</value>
<comment>{0} will be replaced by number (in megabytes) of memory being used</comment>
</data>
<data name="ClearingDiscoveryQueue" xml:space="preserve">
<value>Passando a Fila de Exploração da Steam #{0}...</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
<value>Fila de exploração da Steam feita #{0}.</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Os bots {0}/{1} já possuem todos os jogos que está sendo verificados.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -247,7 +247,8 @@ StackTrace:
<value>Checking for new version...</value> <value>Checking for new version...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Downloading new version... While waiting, consider donating if you appreciate the work being done! :)</value> <value>Downloading new version: {0} ({1} MB)... While waiting, consider donating if you appreciate the work being done! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Update process finished!</value> <value>Update process finished!</value>
@@ -564,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot is currently being used.</value> <value>Bot is currently being used.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Unable to connect to Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Unable to login to Steam: {0}/{1}</value> <value>Unable to login to Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -676,4 +673,8 @@ StackTrace:
<value>Done clearing Steam discovery queue #{0}.</value> <value>Done clearing Steam discovery queue #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>There are {0}/{1} bots that already own all of the games being checked.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -184,14 +184,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Nu a fost posibilă verificarea celei mai recente versiuni!</value> <value>Nu a fost posibilă verificarea celei mai recente versiuni!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Nu s-a putut continua cu actualizarea deoarece nu există niciun fișier asemănător versiunii care rulează! Actualizarea automată către acea versiune nu este posibilă.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Nu putem continua cu actualizarea deoarece acea versiune nu conține niciun fișier!</value> <value>Nu putem continua cu actualizarea deoarece acea versiune nu conține niciun fișier!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>S-a primit o cerere de date introduse de utilizator, dar procesul se execută în modul fără cap (serviciu)!</value> <value>S-a primit o cerere de date introduse de utilizator, dar procesul se execută în modul fără cap (serviciu)!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Se refuză gestionarea cererii, deoarece SteamOwnerID nu este setat!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Ieșire...</value> <value>Ieșire...</value>
</data> </data>
@@ -242,7 +247,8 @@ StackTrace:
<value>Se caută versiune nouă...</value> <value>Se caută versiune nouă...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Se descarcă o versiune nouă... Cât timp aștepți, dacă consideri, poți dona dacă apreciezi toată munca depusă! :)</value> <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>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Proces de actualizare finalizat!</value> <value>Proces de actualizare finalizat!</value>
@@ -282,7 +288,10 @@ StackTrace:
<value>Te rog să introduci valoarea nedocumentată a {0}: </value> <value>Te rog să introduci valoarea nedocumentată a {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Te rugăm să introduci gazda IPC-ului tău: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Am primit o valoare necunoscută pentru {0}, te rog să raportezi acest lucru: {1}</value> <value>Am primit o valoare necunoscută pentru {0}, te rog să raportezi acest lucru: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -291,10 +300,20 @@ StackTrace:
<value>Jucarea a mai mult de {0} jocuri concomitent nu este posibil, numai primele {0} intrări de la {1} vor fi folosite!</value> <value>Jucarea a mai mult de {0} jocuri concomitent nu este posibil, numai primele {0} intrări de la {1} vor fi folosite!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Serviciul IPC nu a putut fi pornit din cauza AddressAccessDeniedException! Dacă dorești să utilizezi serviciul IPC oferit de ASF, ia în considerare rularea ASF-ului ca administrator sau acordarea de permisiuni adecvate!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Se răspunde la comanda IPC: {0} cu: {1}</value>
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
</data>
<data name="IPCReady" xml:space="preserve">
<value>Serverul IPC este pregătit!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Pornește serverul IPC pe {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Acest bot s-a oprit deja!</value> <value>Acest bot s-a oprit deja!</value>
</data> </data>
@@ -546,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Botul este folosit în prezent.</value> <value>Botul este folosit în prezent.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nu s-a putut realiza conexiunea la Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nu s-a putut autentifica la Steam: {0}/{1}</value> <value>Nu s-a putut autentifica la Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -658,4 +673,8 @@ StackTrace:
<value>S-a terminat ștergerea cozii pentru lista de descoperiri Steam #{0}.</value> <value>S-a terminat ștergerea cozii pentru lista de descoperiri Steam #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>Există {0}/{1} boți care dețin deja toate jocurile verificate.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -184,14 +184,19 @@
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Не удалось проверить последнюю версию!</value> <value>Не удалось проверить последнюю версию!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Невозможно произвести обновление, так как нет подходящих файлов, которые относятся к текущей запущенной версии! Автоматическое обновление до этой версии невозможно.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Не могу обновиться, поскольку эта версия не содержит никаких файлов!</value> <value>Не могу обновиться, поскольку эта версия не содержит никаких файлов!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Получен запрос на ввод данных пользователем, однако процесс идёт в безынтерфейсном режиме!</value> <value>Получен запрос на ввод данных пользователем, однако процесс идёт в безынтерфейсном режиме!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Отказ от обработки запроса, поскольку SteamOwnerID не задан!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Выход...</value> <value>Выход...</value>
</data> </data>
@@ -242,7 +247,8 @@
<value>Проверка новой версии...</value> <value>Проверка новой версии...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Загрузка новой версии... Во время ожидания подумайте о пожертвовании в пользу разработчиков, если вы цените проделанную работу! :)</value> <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>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Процесс обновления завершён!</value> <value>Процесс обновления завершён!</value>
@@ -282,7 +288,10 @@
<value>Пожалуйста, введите недокументированное значение {0}: </value> <value>Пожалуйста, введите недокументированное значение {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Пожалуйста, введите ваш хост IPC: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Получено неизвестное значение для {0}, пожалуйста, сообщите об этом: {1}</value> <value>Получено неизвестное значение для {0}, пожалуйста, сообщите об этом: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -291,10 +300,20 @@
<value>Невозможен запуск более {0} игр одновременно, будут использованы только первые {0} записей из параметра {1}!</value> <value>Невозможен запуск более {0} игр одновременно, будут использованы только первые {0} записей из параметра {1}!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Сервис IPC не может быть запущен, из-за "AddressAccessDeniedException" (исключение: отказ в доступе к адресу)! Если Вы желаете использовать сервис IPC, предоставляемый ASF, то попробуйте запустить ASF от имени администратора, или выдать необходимые права!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>На команду IPC: {0} был ответ: {1}</value>
<comment>{0} will be replaced by IPC command, {1} will be replaced by IPC answer</comment>
</data>
<data name="IPCReady" xml:space="preserve">
<value>IPC сервер готов!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Запуск IPC сервера на {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Этот бот уже остановлен!</value> <value>Этот бот уже остановлен!</value>
</data> </data>
@@ -546,10 +565,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Бот сейчас используется.</value> <value>Бот сейчас используется.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Не удалось подключиться к Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Не удалось войти в Steam: {0}/{1}</value> <value>Не удалось войти в Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -658,4 +673,5 @@
<value>Очищен список рекомендаций #{0}.</value> <value>Очищен список рекомендаций #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

@@ -241,9 +241,7 @@ StackTrace:
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Kontrola najnovšej verzie...</value> <value>Kontrola najnovšej verzie...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Sťahovanie najnovšej verzie... Zváž podporu tohto projektu počas čakania! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Aktualizácia úspešne dokončená!</value> <value>Aktualizácia úspešne dokončená!</value>
</data> </data>
@@ -546,10 +544,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot je práve používaný.</value> <value>Bot je práve používaný.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nie je možné pripojiť sa k službe Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nie je možné prihlásenie do služby Steam: {0}/{1}</value> <value>Nie je možné prihlásenie do služby Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -652,4 +646,5 @@ StackTrace:
</data> </data>
</root> </root>

View File

@@ -225,9 +225,7 @@ StackTrace:
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Traženje nove verzije...</value> <value>Traženje nove verzije...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Skidanje nove verzije... Dok čekate, razmislite o podržavanju ovoh programa putem donacija ako cenite uložen rad! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Proces ažuriranja je završen!</value> <value>Proces ažuriranja je završen!</value>
</data> </data>
@@ -448,10 +446,6 @@ StackTrace:
<data name="BotUnableToConnect" xml:space="preserve">
<value>Nije moguće povezati se na Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Nije moguće prijavljivanje na Steam: {0}{1}</value> <value>Nije moguće prijavljivanje na Steam: {0}{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -512,4 +506,5 @@ StackTrace:
</root> </root>

View File

@@ -241,10 +241,7 @@ StackTrace:
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Söker efter senaste versionen...</value> <value>Söker efter senaste versionen...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Laddar ner senaste versionen... Medan du väntar, fundera på att donera om du uppskattar arbetet som görs!
:)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Uppdateringsprocessen klar!</value> <value>Uppdateringsprocessen klar!</value>
</data> </data>
@@ -547,10 +544,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot används för närvarande.</value> <value>Bot används för närvarande.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Kan inte ansluta till Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Det går inte att logga in på Steam: {0}/{1}</value> <value>Det går inte att logga in på Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -650,4 +643,5 @@ StackTrace:
</root> </root>

View File

@@ -184,14 +184,19 @@ Yığın izleme:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>En son sürüm kontrol edilemedi!</value> <value>En son sürüm kontrol edilemedi!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Şu anda çalışan sürüme ait hiçbir dosya olmadığı için güncelleme işlemine devam edemedi! Bu sürüme otomatik güncelleme yapmak mümkün değil.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Sürüm herhangi bir öğe içermediğinden güncelleme ile devam edemedi!</value> <value>Sürüm herhangi bir öğe içermediğinden güncelleme ile devam edemedi!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Kullanıcı girdisi için bir istek alındı; ancak işlem headless modda çalışıyor!</value> <value>Kullanıcı girdisi için bir istek alındı; ancak işlem headless modda çalışıyor!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>SteamOwnerID ayarlanmadığı için istek reddedildi!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Çıkılıyor...</value> <value>Çıkılıyor...</value>
</data> </data>
@@ -242,7 +247,8 @@ Yığın izleme:
<value>Yeni sürüm kontrol ediliyor...</value> <value>Yeni sürüm kontrol ediliyor...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Yeni sürüm indiriliyor... Beklerken, yaptığımız çalışmayı takdir ediyorsanız bağış yapmayı düşünün! :)</value> <value>Yeni sürüm indiriliyor: {0} ({1} MB)... Beklerken, yaptığımız çalışmayı takdir ediyorsanız bağış yapmayı düşünün! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Güncelleme işlemi tamamlandı!</value> <value>Güncelleme işlemi tamamlandı!</value>
@@ -282,7 +288,10 @@ Yığın izleme:
<value>Lütfen belgelenmemiş {0} değerini girin: </value> <value>Lütfen belgelenmemiş {0} değerini girin: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Lütfen IPC sunucunuzu girin: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>{0} için bilinmeyen değer alındı, lütfen bunu bildirin: {1}</value> <value>{0} için bilinmeyen değer alındı, lütfen bunu bildirin: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -291,10 +300,20 @@ Yığın izleme:
<value>Eşzamanlı olarak {0} oyundan fazlasını oynamak mümkün değildir, yalnızca {1} yapılandırmasından ilk {0} girdisi kullanılacaktır!</value> <value>Eşzamanlı olarak {0} oyundan fazlasını oynamak mümkün değildir, yalnızca {1} yapılandırmasından ilk {0} girdisi kullanılacaktır!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>IPC hizmeti AddressAccessDeniedException nedeniyle başlatılamadı! ASF tarafından sağlanan IPC hizmetini kullanmak istiyorsanız, ASF'yi yönetici olarak başlatmayı veya uygun izinler vermeyi düşünün!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>IPC komutu {0} için cevap: {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 sunucusu hazır!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>{0} sunucusunda IPC sunucusu başlatılıyor...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Bu bot zaten durdurulmuş!</value> <value>Bu bot zaten durdurulmuş!</value>
</data> </data>
@@ -546,10 +565,6 @@ Yığın izleme:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot şu anda kullanılıyor.</value> <value>Bot şu anda kullanılıyor.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Steam'e bağlanılamadı: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Steam'e giriş yapılamıyor: {0}/{1}</value> <value>Steam'e giriş yapılamıyor: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -658,4 +673,8 @@ Yığın izleme:
<value>Steam keşif kuyruğu temizlenmesi bitti #{0}.</value> <value>Steam keşif kuyruğu temizlenmesi bitti #{0}.</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
<data name="BotOwnsOverview" xml:space="preserve">
<value>{0}/{1} bot zaten sahip olunan tüm oyunları denetliyor.</value>
<comment>{0} will be replaced by number of bots that already own games being checked, {1} will be replaced by total number of bots that were checked during the process</comment>
</data>
</root> </root>

View File

@@ -241,9 +241,7 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>Перевірка наявності нової версії...</value> <value>Перевірка наявності нової версії...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Завантаження нової версії... Під час очікування подумайте про пожертвування на користь розробників, якщо ви цінуєте виконану роботу! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Процесс оновлення закінчено!</value> <value>Процесс оновлення закінчено!</value>
</data> </data>
@@ -546,10 +544,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Бот зараз використовується.</value> <value>Бот зараз використовується.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Неможливо з'єднатися зі Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Неможливо увійти до Steam: {0}/{1}</value> <value>Неможливо увійти до Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -652,4 +646,5 @@
</data> </data>
</root> </root>

View File

@@ -121,7 +121,10 @@
<value>Chấp nhận giao dịch: {0}</value> <value>Chấp nhận giao dịch: {0}</value>
<comment>{0} will be replaced by trade number</comment> <comment>{0} will be replaced by trade number</comment>
</data> </data>
<data name="AutoUpdateCheckInfo" xml:space="preserve">
<value>ASF sẽ tự động kiểm tra phiên bản mới mỗi {0}.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "24 hours")</comment>
</data>
<data name="Content" xml:space="preserve"> <data name="Content" xml:space="preserve">
<value>Nội dung: <value>Nội dung:
{0}</value> {0}</value>
@@ -141,7 +144,9 @@ StackTrace:
{2}</value> {2}</value>
<comment>{0} will be replaced by function name, {1} will be replaced by exception message, {2} will be replaced by entire stack trace. Please note that this string should include newlines for formatting.</comment> <comment>{0} will be replaced by function name, {1} will be replaced by exception message, {2} will be replaced by entire stack trace. Please note that this string should include newlines for formatting.</comment>
</data> </data>
<data name="ErrorExitingWithNonZeroErrorCode" xml:space="preserve">
<value>Đang thoát với mã lỗi khác không!</value>
</data>
<data name="ErrorFailingRequest" xml:space="preserve"> <data name="ErrorFailingRequest" xml:space="preserve">
<value>Yêu cầu thất bại: {0}</value> <value>Yêu cầu thất bại: {0}</value>
<comment>{0} will be replaced by URL of the request</comment> <comment>{0} will be replaced by URL of the request</comment>
@@ -179,14 +184,19 @@ StackTrace:
<data name="ErrorUpdateCheckFailed" xml:space="preserve"> <data name="ErrorUpdateCheckFailed" xml:space="preserve">
<value>Không thể kiểm tra phiên bản mới nhất!</value> <value>Không thể kiểm tra phiên bản mới nhất!</value>
</data> </data>
<data name="ErrorUpdateNoAssetForThisVersion" xml:space="preserve">
<value>Không thể tiến hành cập nhật vì không có asset liên quan đến phiên bản đang chạy! Tự động cập nhật lên phiên bản đó là không thể.</value>
</data>
<data name="ErrorUpdateNoAssets" xml:space="preserve"> <data name="ErrorUpdateNoAssets" xml:space="preserve">
<value>Không thể tiến hành với bản cập nhật vì phiên bản đó không gồm bất cứ tài sản nào!</value> <value>Không thể tiến hành với bản cập nhật vì phiên bản đó không gồm bất cứ tài sản nào!</value>
</data> </data>
<data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve"> <data name="ErrorUserInputRunningInHeadlessMode" xml:space="preserve">
<value>Nhận được yêu cầu nhập của người dùng, nhưng quá trình đang chạy trong chế độ không kiểm soát!</value> <value>Nhận được yêu cầu nhập của người dùng, nhưng quá trình đang chạy trong chế độ không kiểm soát!</value>
</data> </data>
<data name="ErrorIPCAccessDenied" xml:space="preserve">
<value>Từ chối xử lý yêu cầu do SteamOwnerID chưa được đặt!</value>
<comment>SteamOwnerID is name of bot config property, it should not be translated</comment>
</data>
<data name="Exiting" xml:space="preserve"> <data name="Exiting" xml:space="preserve">
<value>Đang thoát...</value> <value>Đang thoát...</value>
</data> </data>
@@ -218,7 +228,7 @@ StackTrace:
<comment>{0} will be replaced by trade number</comment> <comment>{0} will be replaced by trade number</comment>
</data> </data>
<data name="Restarting" xml:space="preserve"> <data name="Restarting" xml:space="preserve">
<value>Khởi động lại...</value> <value>Đang khởi động lại...</value>
</data> </data>
<data name="Starting" xml:space="preserve"> <data name="Starting" xml:space="preserve">
<value>Bắt đầu...</value> <value>Bắt đầu...</value>
@@ -237,7 +247,8 @@ StackTrace:
<value>Kiểm tra phiên bản mới...</value> <value>Kiểm tra phiên bản mới...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve"> <data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>Tải xuống phiên bản mới... Trong khi chờ đợi, hãy xem xét quyên góp nếu bạn đánh giá cao công việc được thực hiện! :)</value> <value>Đang tải phiên bản mới: {0} ({1} MB)... Trong khi chờ, hãy xem xét quyên góp nếu bạn đánh giá cao sản phẩm được thực hiện! :)</value>
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
</data> </data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>Quá trình cập nhật hoàn tất!</value> <value>Quá trình cập nhật hoàn tất!</value>
@@ -277,7 +288,10 @@ StackTrace:
<value>Xin vui lòng nhập các giá trị không có giấy tờ của {0}: </value> <value>Xin vui lòng nhập các giá trị không có giấy tờ của {0}: </value>
<comment>{0} will be replaced by property name. Please note that this translation should end with space</comment> <comment>{0} will be replaced by property name. Please note that this translation should end with space</comment>
</data> </data>
<data name="UserInputIPCHost" xml:space="preserve">
<value>Vui lòng nhập IPC host của bạn: </value>
<comment>Please note that this translation should end with space</comment>
</data>
<data name="WarningUnknownValuePleaseReport" xml:space="preserve"> <data name="WarningUnknownValuePleaseReport" xml:space="preserve">
<value>Nhận được giá trị không rõ cho {0}, xin vui lòng báo cáo điều này: {1}</value> <value>Nhận được giá trị không rõ cho {0}, xin vui lòng báo cáo điều này: {1}</value>
<comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment> <comment>{0} will be replaced by object's name, {1} will be replaced by value for that object</comment>
@@ -286,10 +300,20 @@ StackTrace:
<value>Chơi nhiều hơn {0} trò chơi đồng thời là không thể, chỉ {0} trò chơi đầu tiên {1} sẽ được sử dụng!</value> <value>Chơi nhiều hơn {0} trò chơi đồng thời là không thể, chỉ {0} trò chơi đầu tiên {1} sẽ được sử dụng!</value>
<comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment> <comment>{0} will be replaced by max number of games, {1} will be replaced by name of the configuration property</comment>
</data> </data>
<data name="ErrorIPCAddressAccessDeniedException" xml:space="preserve">
<value>Dịch vụ IPC không thể chạy do AddressAccessDeniedException! Nếu bạn muốn dùng dịch vụ IPC được cung cấp bởi ASF, hãy chạy ASF bằng quyền admin, hoặc cấp quyền thích hợp!</value>
</data>
<data name="IPCAnswered" xml:space="preserve">
<value>Đã trả lời lệnh IPC: {0} với: {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 đã sẵn sàng!</value>
</data>
<data name="IPCStarting" xml:space="preserve">
<value>Khởi động IPC server trên {0}...</value>
<comment>{0} will be replaced by IPC hostname</comment>
</data>
<data name="BotAlreadyStopped" xml:space="preserve"> <data name="BotAlreadyStopped" xml:space="preserve">
<value>Bot này đã được dừng lại!</value> <value>Bot này đã được dừng lại!</value>
</data> </data>
@@ -301,8 +325,14 @@ StackTrace:
<value>Đang có {0}/{1} bot đang chạy, với tổng số {2} games ({3} cards) còn lại để chạy không.</value> <value>Đang có {0}/{1} bot đang chạy, với tổng số {2} games ({3} cards) còn lại để chạy không.</value>
<comment>{0} will be replaced by number of active bots, {1} will be replaced by total number of bots, {2} will be replaced by total number of games left to idle, {3} will be replaced by total number of cards left to idle</comment> <comment>{0} will be replaced by number of active bots, {1} will be replaced by total number of bots, {2} will be replaced by total number of games left to idle, {3} will be replaced by total number of cards left to idle</comment>
</data> </data>
<data name="BotStatusIdling" xml:space="preserve">
<value>Bot đang cày game: {0} ({1}, {2} card còn lại) từ tổng cộng {3} game ({4} card) còn lại để cày (~{5} còn lại).</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by number of cards left to idle, {3} will be replaced by total number of games to idle, {4} will be replaced by total number of cards to idle, {5} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="BotStatusIdlingList" xml:space="preserve">
<value>Bot đang cày game: {0} từ tổng cộng {1} game ({2} card) còn lại để cày (~{3} còn lại).</value>
<comment>{0} will be replaced by list of the games (IDs, numbers), {1} will be replaced by total number of games to idle, {2} will be replaced by total number of cards to idle, {3} will be replaced by translated TimeSpan string (such as "1 day, 5 hours and 30 minutes")</comment>
</data>
<data name="CheckingFirstBadgePage" xml:space="preserve"> <data name="CheckingFirstBadgePage" xml:space="preserve">
<value>Kiểm tra trang huy hiệu đầu tiên...</value> <value>Kiểm tra trang huy hiệu đầu tiên...</value>
</data> </data>
@@ -388,8 +418,14 @@ StackTrace:
<data name="BotAccountLimited" xml:space="preserve"> <data name="BotAccountLimited" xml:space="preserve">
<value>Tài khoản này bị giới hạn, quá trình chạy không sẽ không khả dụng cho đến khi những hạn chế sẽ bị gỡ bỏ!</value> <value>Tài khoản này bị giới hạn, quá trình chạy không sẽ không khả dụng cho đến khi những hạn chế sẽ bị gỡ bỏ!</value>
</data> </data>
<data name="BotAddLicense" xml:space="preserve">
<value>ID: {0} | Trạng thái: {1}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string</comment>
</data>
<data name="BotAddLicenseWithItems" xml:space="preserve">
<value>ID: {0} | Trạng thái: {1} | Mục: {2}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by status string, {2} will be replaced by list of granted IDs (numbers), separated by a comma</comment>
</data>
<data name="BotAlreadyRunning" xml:space="preserve"> <data name="BotAlreadyRunning" xml:space="preserve">
<value>Bot này đang được chạy!</value> <value>Bot này đang được chạy!</value>
</data> </data>
@@ -406,12 +442,19 @@ StackTrace:
<value>2FA Token: {0}</value> <value>2FA Token: {0}</value>
<comment>{0} will be replaced by generated 2FA token (string)</comment> <comment>{0} will be replaced by generated 2FA token (string)</comment>
</data> </data>
<data name="BotAutomaticIdlingNowPaused" xml:space="preserve">
<value>Tự động idling đã tạm dừng!</value>
</data>
<data name="BotAutomaticIdlingNowResumed" xml:space="preserve">
<value>Tự động idling đã tiếp tục trở lại!</value>
</data>
<data name="BotAutomaticIdlingPausedAlready" xml:space="preserve"> <data name="BotAutomaticIdlingPausedAlready" xml:space="preserve">
<value>Tự động chạy không đã tạm dừng!</value> <value>Tự động chạy không đã tạm dừng!</value>
</data> </data>
<data name="BotAutomaticIdlingPausedWithCountdown" xml:space="preserve">
<value>Tự động cày đã tạm dừng! Bạn có {0} để bắt đầu 1 game.</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "5 minutes")</comment>
</data>
<data name="BotAutomaticIdlingResumedAlready" xml:space="preserve"> <data name="BotAutomaticIdlingResumedAlready" xml:space="preserve">
<value>Tự động chạy không đã tiếp tục!</value> <value>Tự động chạy không đã tiếp tục!</value>
</data> </data>
@@ -451,7 +494,9 @@ StackTrace:
<data name="BotLootingFailed" xml:space="preserve"> <data name="BotLootingFailed" xml:space="preserve">
<value>Lời mời giao dịch thất bại!</value> <value>Lời mời giao dịch thất bại!</value>
</data> </data>
<data name="BotLootingMasterNotDefined" xml:space="preserve">
<value>Giao dịch không thể gửi do không có user có quyền master xác định!</value>
</data>
<data name="BotLootingNoLootableTypes" xml:space="preserve"> <data name="BotLootingNoLootableTypes" xml:space="preserve">
<value>Bạn không có bất kỳ cái gì có thể luộc được!</value> <value>Bạn không có bất kỳ cái gì có thể luộc được!</value>
</data> </data>
@@ -476,17 +521,29 @@ StackTrace:
<data name="BotNotConnected" xml:space="preserve"> <data name="BotNotConnected" xml:space="preserve">
<value>Bot này không được kết nối!</value> <value>Bot này không được kết nối!</value>
</data> </data>
<data name="BotNotOwnedYet" xml:space="preserve">
<value>Chưa sở hữu: {0}</value>
<comment>{0} will be replaced by query (string)</comment>
</data>
<data name="BotOwnedAlreadyWithName" xml:space="preserve"> <data name="BotOwnedAlreadyWithName" xml:space="preserve">
<value>Đã sở hữu: {0} | {1}</value> <value>Đã sở hữu: {0} | {1}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment> <comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data> </data>
<data name="BotRateLimitExceeded" xml:space="preserve">
<value>Vượt quá số lượng giới hạn; chúng tôi sẽ thử lại sau {0} đếm ngược...</value>
<comment>{0} will be replaced by translated TimeSpan string (such as "25 minutes")</comment>
</data>
<data name="BotReconnecting" xml:space="preserve"> <data name="BotReconnecting" xml:space="preserve">
<value>Đang kết nối lại...</value> <value>Đang kết nối lại...</value>
</data> </data>
<data name="BotRedeem" xml:space="preserve">
<value>Key: {0} | Status: {1}</value>
<comment>{0} will be replaced by cd-key (string), {1} will be replaced by status string</comment>
</data>
<data name="BotRedeemWithItems" xml:space="preserve">
<value>Key: {0} | Status: {1} | Items: {2}</value>
<comment>{0} will be replaced by cd-key (string), {1} will be replaced by status string, {2} will be replaced by list of key-value pairs, separated by a comma</comment>
</data>
<data name="BotRemovedExpiredLoginKey" xml:space="preserve"> <data name="BotRemovedExpiredLoginKey" xml:space="preserve">
<value>Gỡ bỏ key đăng nhập hết hạn!</value> <value>Gỡ bỏ key đăng nhập hết hạn!</value>
</data> </data>
@@ -496,7 +553,9 @@ StackTrace:
<data name="BotStatusLimited" xml:space="preserve"> <data name="BotStatusLimited" xml:space="preserve">
<value>Bot bị hạn chế và không thể rớt bất kỳ thẻ thông qua chạy không.</value> <value>Bot bị hạn chế và không thể rớt bất kỳ thẻ thông qua chạy không.</value>
</data> </data>
<data name="BotStatusConnecting" xml:space="preserve">
<value>Bot đang kết nối với Steam network.</value>
</data>
<data name="BotStatusNotRunning" xml:space="preserve"> <data name="BotStatusNotRunning" xml:space="preserve">
<value>Bot không chạy.</value> <value>Bot không chạy.</value>
</data> </data>
@@ -506,10 +565,6 @@ StackTrace:
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>Bot hiện đang được sử dụng.</value> <value>Bot hiện đang được sử dụng.</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>Không thể kết nối với Steam: {0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>Không thể đăng nhập Steam: {0}/{1}</value> <value>Không thể đăng nhập Steam: {0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -575,7 +630,10 @@ StackTrace:
<value>ASF sẽ cố gắng sử dụng mã ngôn ngữ {0} ưa thích của bạn, nhưng bản dịch ngôn ngữ đó hoàn thiện chỉ được {1}. Có lẽ bạn có thể giúp chúng tôi cải thiện bản dịch ASF cho ngôn ngữ của bạn?</value> <value>ASF sẽ cố gắng sử dụng mã ngôn ngữ {0} ưa thích của bạn, nhưng bản dịch ngôn ngữ đó hoàn thiện chỉ được {1}. Có lẽ bạn có thể giúp chúng tôi cải thiện bản dịch ASF cho ngôn ngữ của bạn?</value>
<comment>{0} will be replaced by culture code, such as "en-US", {1} will be replaced by completeness percentage, such as "78.5%"</comment> <comment>{0} will be replaced by culture code, such as "en-US", {1} will be replaced by completeness percentage, such as "78.5%"</comment>
</data> </data>
<data name="IdlingGameNotPossible" xml:space="preserve">
<value>Việc cày {0} ({1}) tạm thời bị vô hiệu hóa, ASF không thể mở game đó lúc này.</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data>
<data name="WarningIdlingGameMismatch" xml:space="preserve"> <data name="WarningIdlingGameMismatch" xml:space="preserve">
<value>ASF phát hiện ID không khớp với {0} ({1}) và sẽ sử dụng ID {2} thay thế.</value> <value>ASF phát hiện ID không khớp với {0} ({1}) và sẽ sử dụng ID {2} thay thế.</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by game's ID (number)</comment> <comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name, {2} will be replaced by game's ID (number)</comment>
@@ -584,18 +642,36 @@ StackTrace:
<value>{0} V{1}</value> <value>{0} V{1}</value>
<comment>{0} will be replaced by program's name (e.g. "ASF"), {1} will be replaced by program's version (e.g. "1.0.0.0"). This string typically has nothing to translate and you should leave it as it is, unless you need to change the format, e.g. in RTL languages.</comment> <comment>{0} will be replaced by program's name (e.g. "ASF"), {1} will be replaced by program's version (e.g. "1.0.0.0"). This string typically has nothing to translate and you should leave it as it is, unless you need to change the format, e.g. in RTL languages.</comment>
</data> </data>
<data name="BotAccountLocked" xml:space="preserve">
<value>Tài khoản này bị khóa, quá trình cày card không chạy!</value>
</data>
<data name="BotStatusLocked" xml:space="preserve"> <data name="BotStatusLocked" xml:space="preserve">
<value>Bot bị khóa và không thể rớt bất kỳ thẻ thông qua chạy không.</value> <value>Bot bị khóa và không thể rớt bất kỳ thẻ thông qua chạy không.</value>
</data> </data>
<data name="ErrorFunctionOnlyInHeadlessMode" xml:space="preserve">
<value>Chức năng này chỉ có sẵn trong headless mode!</value>
</data>
<data name="BotOwnedAlready" xml:space="preserve"> <data name="BotOwnedAlready" xml:space="preserve">
<value>Đã sở hữu: {0}</value> <value>Đã sở hữu: {0}</value>
<comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment> <comment>{0} will be replaced by game's ID (number), {1} will be replaced by game's name</comment>
</data> </data>
<data name="ErrorAccessDenied" xml:space="preserve">
<value>Truy cập bị từ chối!</value>
</data>
<data name="WarningPreReleaseVersion" xml:space="preserve">
<value>Bạn đang dùng phiên bản mới hơn phiên bản chính thức mới nhất. Hãy chú ý rằng phiên bản phát hành trước là dành cho người dùng biết cách báo lỗi, xử lý vấn đề và gửi phản hồi - sẽ không có hỗ trợ kỹ thuật.</value>
</data>
<data name="BotStats" xml:space="preserve">
<value>Bộ nhớ đang được sử dụng: {0} MB.</value>
<comment>{0} will be replaced by number (in megabytes) of memory being used</comment>
</data>
<data name="ClearingDiscoveryQueue" xml:space="preserve">
<value>Đã xóa hàng đợi khám phá Steam số #{0}...</value>
<comment>{0} will be replaced by queue number</comment>
</data>
<data name="DoneClearingDiscoveryQueue" xml:space="preserve">
<value>Đã xóa hàng đợi khám phá Steam số #{0}.</value>
<comment>{0} will be replaced by queue number</comment>
</data>
</root> </root>

View File

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

View File

@@ -238,9 +238,7 @@
<data name="UpdateCheckingNewVersion" xml:space="preserve"> <data name="UpdateCheckingNewVersion" xml:space="preserve">
<value>正在檢查新版本...</value> <value>正在檢查新版本...</value>
</data> </data>
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
<value>正在下載新版本... 等待期間如果喜歡這個軟體請考慮捐助ASF! :)</value>
</data>
<data name="UpdateFinished" xml:space="preserve"> <data name="UpdateFinished" xml:space="preserve">
<value>更新完成 </value> <value>更新完成 </value>
</data> </data>
@@ -543,10 +541,6 @@
<data name="BotStatusPlayingNotAvailable" xml:space="preserve"> <data name="BotStatusPlayingNotAvailable" xml:space="preserve">
<value>BOT 目前正被使用。</value> <value>BOT 目前正被使用。</value>
</data> </data>
<data name="BotUnableToConnect" xml:space="preserve">
<value>無法連線至 Steam{0}</value>
<comment>{0} will be replaced by failure reason (string)</comment>
</data>
<data name="BotUnableToLogin" xml:space="preserve"> <data name="BotUnableToLogin" xml:space="preserve">
<value>無法登入到 Steam{0}/{1}</value> <value>無法登入到 Steam{0}/{1}</value>
<comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment> <comment>{0} will be replaced by failure reason (string), {1} will be replaced by extended failure reason (string)</comment>
@@ -655,4 +649,5 @@
<value>已完成 Steam 探索佇列 #{0}。</value> <value>已完成 Steam 探索佇列 #{0}。</value>
<comment>{0} will be replaced by queue number</comment> <comment>{0} will be replaced by queue number</comment>
</data> </data>
</root> </root>

View File

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

View File

@@ -28,10 +28,8 @@ using ArchiSteamFarm.Localization;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal static class OS { internal static class OS {
private static readonly PlatformID PlatformID = Environment.OSVersion.Platform;
internal static void Init(bool headless) { internal static void Init(bool headless) {
switch (PlatformID) { switch (Environment.OSVersion.Platform) {
case PlatformID.Win32NT: case PlatformID.Win32NT:
case PlatformID.Win32S: case PlatformID.Win32S:
case PlatformID.Win32Windows: case PlatformID.Win32Windows:
@@ -76,7 +74,7 @@ namespace ArchiSteamFarm {
private static class NativeMethods { private static class NativeMethods {
internal const uint EnableQuickEditMode = 0x0040; internal const uint EnableQuickEditMode = 0x0040;
internal const int StandardInputHandle = -10; internal const sbyte StandardInputHandle = -10;
[DllImport("kernel32.dll", CharSet = CharSet.Auto)] [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
internal static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); internal static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
@@ -94,8 +92,6 @@ namespace ArchiSteamFarm {
internal enum EExecutionState : uint { internal enum EExecutionState : uint {
Error = 0, Error = 0,
SystemRequired = 0x00000001, SystemRequired = 0x00000001,
//DisplayRequired = 0x00000002,
//UserPresent = 0x00000004,
AwayModeRequired = 0x00000040, AwayModeRequired = 0x00000040,
Continuous = 0x80000000 Continuous = 0x80000000
} }

View File

@@ -135,13 +135,17 @@ namespace ArchiSteamFarm {
ASF.ArchiLogger.LogGenericException(e); ASF.ArchiLogger.LogGenericException(e);
} }
// Give new process some time to take over the window (if needed)
await Task.Delay(2000).ConfigureAwait(false);
ShutdownResetEvent.Set(); ShutdownResetEvent.Set();
Environment.Exit(0); Environment.Exit(0);
} }
private static async Task Init(string[] args) { private static async Task Init(string[] args) {
AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
TaskScheduler.UnobservedTaskException += UnobservedTaskExceptionHandler; AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
// We must register our logging target as soon as possible // We must register our logging target as soon as possible
Target.Register<SteamTarget>(SteamTarget.TargetName); Target.Register<SteamTarget>(SteamTarget.TargetName);
@@ -180,9 +184,7 @@ namespace ArchiSteamFarm {
ParsePostInitArgs(args); ParsePostInitArgs(args);
} }
if (!Debugging.IsDebugBuild) {
await ASF.CheckForUpdate().ConfigureAwait(false); await ASF.CheckForUpdate().ConfigureAwait(false);
}
await ASF.InitBots().ConfigureAwait(false); await ASF.InitBots().ConfigureAwait(false);
ASF.InitEvents(); ASF.InitEvents();
@@ -306,7 +308,7 @@ namespace ArchiSteamFarm {
OS.Init(GlobalConfig.Headless); OS.Init(GlobalConfig.Headless);
WebBrowser.Init(); WebBrowser.Init();
WebBrowser = new WebBrowser(ASF.ArchiLogger); WebBrowser = new WebBrowser(ASF.ArchiLogger, true);
} }
private static async Task<bool> InitShutdownSequence() { private static async Task<bool> InitShutdownSequence() {
@@ -316,8 +318,6 @@ namespace ArchiSteamFarm {
ShutdownSequenceInitialized = true; ShutdownSequenceInitialized = true;
IPC.Stop();
if (Bot.Bots.Count == 0) { if (Bot.Bots.Count == 0) {
return true; return true;
} }
@@ -327,12 +327,12 @@ namespace ArchiSteamFarm {
switch (GlobalConfig.OptimizationMode) { switch (GlobalConfig.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage: case GlobalConfig.EOptimizationMode.MinMemoryUsage:
foreach (Task task in tasks) { foreach (Task task in tasks) {
await Task.WhenAny(task, Task.Delay(WebBrowser.MaxRetries * 1000)).ConfigureAwait(false); await Task.WhenAny(task, Task.Delay(WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
} }
break; break;
default: default:
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(Bot.Bots.Count * WebBrowser.MaxRetries * 1000)).ConfigureAwait(false); await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(Bot.Bots.Count * WebBrowser.MaxTries * 1000)).ConfigureAwait(false);
break; break;
} }
@@ -350,6 +350,31 @@ namespace ArchiSteamFarm {
Exit().Wait(); Exit().Wait();
} }
private static void OnProcessExit(object sender, EventArgs e) => IPC.Stop();
private static async void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) {
if (e?.ExceptionObject == null) {
ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.ExceptionObject));
return;
}
ASF.ArchiLogger.LogFatalException((Exception) e.ExceptionObject);
await Exit(1).ConfigureAwait(false);
}
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) {
if (e?.Exception == null) {
ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.Exception));
return;
}
ASF.ArchiLogger.LogFatalException(e.Exception);
// Normally we should abort the application here, but many tasks are in fact failing in SK2 code which we can't easily fix
// Thanks Valve.
e.SetObserved();
}
private static void ParsePostInitArgs(IEnumerable<string> args) { private static void ParsePostInitArgs(IEnumerable<string> args) {
if (args == null) { if (args == null) {
ASF.ArchiLogger.LogNullError(nameof(args)); ASF.ArchiLogger.LogNullError(nameof(args));
@@ -404,28 +429,5 @@ namespace ArchiSteamFarm {
ShutdownResetEvent.Set(); ShutdownResetEvent.Set();
} }
private static async void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs e) {
if (e?.ExceptionObject == null) {
ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.ExceptionObject));
return;
}
ASF.ArchiLogger.LogFatalException((Exception) e.ExceptionObject);
await Exit(1).ConfigureAwait(false);
}
private static void UnobservedTaskExceptionHandler(object sender, UnobservedTaskExceptionEventArgs e) {
if (e?.Exception == null) {
ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.Exception));
return;
}
ASF.ArchiLogger.LogFatalException(e.Exception);
// Normally we should abort the application here, but many tasks are in fact failing in SK2 code which we can't easily fix
// Thanks Valve.
e.SetObserved();
}
} }
} }

View File

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

View File

@@ -28,19 +28,20 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ArchiSteamFarm.JSON; using ArchiSteamFarm.JSON;
using Newtonsoft.Json;
using SteamKit2; using SteamKit2;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class Statistics : IDisposable { internal sealed class Statistics : IDisposable {
private const byte MinAnnouncementCheckTTL = 6; // Minimum amount of hours we must wait before checking eligibility for Announcement, should be lower than MinPersonaStateTTL private const byte MinAnnouncementCheckTTL = 6; // Minimum amount of hours we must wait before checking eligibility for Announcement, should be lower than MinPersonaStateTTL
private const byte MinCardsCount = 100; // Minimum amount of cards to be eligible for public listing
private const byte MinHeartBeatTTL = 10; // Minimum amount of minutes we must wait before sending next HeartBeat private const byte MinHeartBeatTTL = 10; // Minimum amount of minutes we must wait before sending next HeartBeat
private const byte MinItemsCount = 100; // Minimum amount of items to be eligible for public listing
private const byte MinPersonaStateTTL = 8; // Minimum amount of hours we must wait before requesting persona state update private const byte MinPersonaStateTTL = 8; // Minimum amount of hours we must wait before requesting persona state update
private const string URL = "https://" + SharedInfo.StatisticsServer; private const string URL = "https://" + SharedInfo.StatisticsServer;
private readonly Bot Bot; private readonly Bot Bot;
private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim Semaphore = new SemaphoreSlim(1, 1);
private DateTime LastAnnouncementCheck = DateTime.MinValue; private DateTime LastAnnouncementCheck = DateTime.MinValue;
private DateTime LastHeartBeat = DateTime.MinValue; private DateTime LastHeartBeat = DateTime.MinValue;
private DateTime LastPersonaStateRequest = DateTime.MinValue; private DateTime LastPersonaStateRequest = DateTime.MinValue;
@@ -97,7 +98,7 @@ namespace ArchiSteamFarm {
// Don't announce if we don't meet conditions // Don't announce if we don't meet conditions
string tradeToken; string tradeToken;
if (!Bot.HasMobileAuthenticator || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher) || !await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false) || !await Bot.ArchiWebHandler.HasPublicInventory().ConfigureAwait(false) || string.IsNullOrEmpty(tradeToken = await Bot.ArchiWebHandler.GetTradeToken().ConfigureAwait(false))) { if (!Bot.HasMobileAuthenticator || !Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.SteamTradeMatcher) || (Bot.BotConfig.MatchableTypes.Count == 0) || !await Bot.ArchiWebHandler.HasValidApiKey().ConfigureAwait(false) || !await Bot.ArchiWebHandler.HasPublicInventory().ConfigureAwait(false) || string.IsNullOrEmpty(tradeToken = await Bot.ArchiWebHandler.GetTradeToken().ConfigureAwait(false))) {
LastAnnouncementCheck = DateTime.UtcNow; LastAnnouncementCheck = DateTime.UtcNow;
ShouldSendHeartBeats = false; ShouldSendHeartBeats = false;
return; return;
@@ -113,8 +114,6 @@ namespace ArchiSteamFarm {
} }
} }
bool matchEverything = Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything);
await Semaphore.WaitAsync().ConfigureAwait(false); await Semaphore.WaitAsync().ConfigureAwait(false);
try { try {
@@ -122,7 +121,7 @@ namespace ArchiSteamFarm {
return; return;
} }
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(true, new HashSet<Steam.Item.EType> { Steam.Item.EType.TradingCard }).ConfigureAwait(false); HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(true, Bot.BotConfig.MatchableTypes).ConfigureAwait(false);
// This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check // This is actually inventory failure, so we'll stop sending heartbeats but not record it as valid check
if (inventory == null) { if (inventory == null) {
@@ -131,21 +130,22 @@ namespace ArchiSteamFarm {
} }
// This is actual inventory // This is actual inventory
if (inventory.Count < MinCardsCount) { if (inventory.Count < MinItemsCount) {
LastAnnouncementCheck = DateTime.UtcNow; LastAnnouncementCheck = DateTime.UtcNow;
ShouldSendHeartBeats = false; ShouldSendHeartBeats = false;
return; return;
} }
const string request = URL + "/api/Announce"; const string request = URL + "/api/Announce";
Dictionary<string, string> data = new Dictionary<string, string>(7) { Dictionary<string, string> data = new Dictionary<string, string>(8) {
{ "SteamID", Bot.SteamID.ToString() }, { "SteamID", Bot.SteamID.ToString() },
{ "Guid", Program.GlobalDatabase.Guid.ToString("N") }, { "Guid", Program.GlobalDatabase.Guid.ToString("N") },
{ "Nickname", nickname }, { "Nickname", nickname },
{ "AvatarHash", avatarHash }, { "AvatarHash", avatarHash },
{ "MatchEverything", matchEverything ? "1" : "0" }, { "MatchableTypes", JsonConvert.SerializeObject(Bot.BotConfig.MatchableTypes) },
{ "MatchEverything", Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) ? "1" : "0" },
{ "TradeToken", tradeToken }, { "TradeToken", tradeToken },
{ "CardsCount", inventory.Count.ToString() } { "ItemsCount", inventory.Count.ToString() }
}; };
// We don't need retry logic here // We don't need retry logic here

View File

@@ -32,12 +32,12 @@ using ArchiSteamFarm.Localization;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class Trading : IDisposable { internal sealed class Trading : IDisposable {
internal const byte MaxItemsPerTrade = 150; // This is due to limit on POST size in WebBrowser internal const byte MaxItemsPerTrade = byte.MaxValue; // This is due to limit on POST size in WebBrowser
internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve internal const byte MaxTradesPerAccount = 5; // This is limit introduced by Valve
private readonly Bot Bot; private readonly Bot Bot;
private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>(); private readonly ConcurrentHashSet<ulong> IgnoredTrades = new ConcurrentHashSet<ulong>();
private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1); private readonly SemaphoreSlim TradesSemaphore = new SemaphoreSlim(1, 1);
private bool ParsingScheduled; private bool ParsingScheduled;
@@ -45,7 +45,7 @@ namespace ArchiSteamFarm {
public void Dispose() => TradesSemaphore.Dispose(); public void Dispose() => TradesSemaphore.Dispose();
internal void OnDisconnected() => IgnoredTrades.ClearAndTrim(); internal void OnDisconnected() => IgnoredTrades.Clear();
internal async Task OnNewTrade() { internal async Task OnNewTrade() {
// We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue // We aim to have a maximum of 2 tasks, one already parsing, and one waiting in the queue
@@ -71,6 +71,76 @@ namespace ArchiSteamFarm {
} }
} }
private static bool IsTradeNeutralOrBetter(HashSet<Steam.Item> inventory, HashSet<Steam.Item> itemsToGive, HashSet<Steam.Item> itemsToReceive) {
if ((inventory == null) || (inventory.Count == 0) || (itemsToGive == null) || (itemsToGive.Count == 0) || (itemsToReceive == null) || (itemsToReceive.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(itemsToGive) + " || " + nameof(itemsToReceive));
return false;
}
// Now let's create a map which maps items to their amount in our EQ
// This has to be done as we might have multiple items of given ClassID with multiple amounts
Dictionary<ulong, uint> itemAmounts = new Dictionary<ulong, uint>();
foreach (Steam.Item item in inventory) {
if (itemAmounts.TryGetValue(item.ClassID, out uint amount)) {
itemAmounts[item.ClassID] = amount + item.Amount;
} else {
itemAmounts[item.ClassID] = item.Amount;
}
}
// Calculate our value of items to give on per-game basis
Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>> itemAmountToGivePerGame = new Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>>();
Dictionary<ulong, uint> itemAmountsToGive = new Dictionary<ulong, uint>(itemAmounts);
foreach (Steam.Item item in itemsToGive) {
if (!itemAmountToGivePerGame.TryGetValue((item.Type, item.RealAppID), out List<uint> amountsToGive)) {
amountsToGive = new List<uint>();
itemAmountToGivePerGame[(item.Type, item.RealAppID)] = amountsToGive;
}
if (!itemAmountsToGive.TryGetValue(item.ClassID, out uint amount)) {
amountsToGive.Add(0);
continue;
}
amountsToGive.Add(amount);
itemAmountsToGive[item.ClassID] = amount - 1; // We're giving one, so we have one less
}
// Sort all the lists of amounts to give on per-game basis ascending
foreach (List<uint> amountsToGive in itemAmountToGivePerGame.Values) {
amountsToGive.Sort();
}
// Calculate our value of items to receive on per-game basis
Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>> itemAmountToReceivePerGame = new Dictionary<(Steam.Item.EType Type, uint AppID), List<uint>>();
Dictionary<ulong, uint> itemAmountsToReceive = new Dictionary<ulong, uint>(itemAmounts);
foreach (Steam.Item item in itemsToReceive) {
if (!itemAmountToReceivePerGame.TryGetValue((item.Type, item.RealAppID), out List<uint> amountsToReceive)) {
amountsToReceive = new List<uint>();
itemAmountToReceivePerGame[(item.Type, item.RealAppID)] = amountsToReceive;
}
if (!itemAmountsToReceive.TryGetValue(item.ClassID, out uint amount)) {
amountsToReceive.Add(0);
continue;
}
amountsToReceive.Add(amount);
itemAmountsToReceive[item.ClassID] = amount + 1; // We're getting one, so we have one more
}
// Sort all the lists of amounts to receive on per-game basis ascending
foreach (List<uint> amountsToReceive in itemAmountToReceivePerGame.Values) {
amountsToReceive.Sort();
}
// Calculate final neutrality result
// This is quite complex operation of taking minimum difference from all differences on per-game basis
// When calculating per-game difference, we sum only amounts at proper indexes, because user might be overpaying
int difference = itemAmountToGivePerGame.Min(kv => kv.Value.Select((t, i) => (int) (t - itemAmountToReceivePerGame[kv.Key][i])).Sum());
return difference > 0;
}
private async Task ParseActiveTrades() { private async Task ParseActiveTrades() {
HashSet<Steam.TradeOffer> tradeOffers = await Bot.ArchiWebHandler.GetActiveTradeOffers().ConfigureAwait(false); HashSet<Steam.TradeOffer> tradeOffers = await Bot.ArchiWebHandler.GetActiveTradeOffers().ConfigureAwait(false);
if ((tradeOffers == null) || (tradeOffers.Count == 0)) { if ((tradeOffers == null) || (tradeOffers.Count == 0)) {
@@ -173,7 +243,7 @@ namespace ArchiSteamFarm {
return new ParseTradeResult(tradeOffer.TradeOfferID, tradeOffer.ItemsToGive.Count > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : ParseTradeResult.EResult.AcceptedWithoutItemLose); return new ParseTradeResult(tradeOffer.TradeOfferID, tradeOffer.ItemsToGive.Count > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : ParseTradeResult.EResult.AcceptedWithoutItemLose);
} }
// Always deny trades from blacklistem steamIDs // Always deny trades from blacklisted steamIDs
if (Bot.IsBlacklistedFromTrades(tradeOffer.OtherSteamID64)) { if (Bot.IsBlacklistedFromTrades(tradeOffer.OtherSteamID64)) {
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently); return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
} }
@@ -214,8 +284,8 @@ namespace ArchiSteamFarm {
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently); return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
} }
// Decline trade if we're losing anything but steam cards, or if it's non-dupes trade // Decline trade if it's not fair games/types exchange or if we're requested to handle any not-accepted item type
if (!tradeOffer.IsSteamCardsRequest() || !tradeOffer.IsFairTypesExchange()) { if (!tradeOffer.IsFairTypesExchange() || !tradeOffer.IsValidSteamItemsRequest(Bot.BotConfig.MatchableTypes)) {
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently); return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedPermanently);
} }
@@ -241,82 +311,27 @@ namespace ArchiSteamFarm {
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose); return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.AcceptedWithItemLose);
} }
// Get appIDs we're interested in // Get appIDs/types we're interested in
HashSet<uint> appIDs = new HashSet<uint>(tradeOffer.ItemsToGive.Select(item => item.RealAppID)); HashSet<uint> appIDs = new HashSet<uint>();
HashSet<Steam.Item.EType> types = new HashSet<Steam.Item.EType>();
foreach (Steam.Item item in tradeOffer.ItemsToGive) {
appIDs.Add(item.RealAppID);
types.Add(item.Type);
}
// Now check if it's worth for us to do the trade // Now check if it's worth for us to do the trade
HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false, new HashSet<Steam.Item.EType> { Steam.Item.EType.TradingCard }, appIDs).ConfigureAwait(false); HashSet<Steam.Item> inventory = await Bot.ArchiWebHandler.GetMySteamInventory(false, types, appIDs).ConfigureAwait(false);
if ((inventory == null) || (inventory.Count == 0)) { if ((inventory == null) || (inventory.Count == 0)) {
// If we can't check our inventory when not using MatchEverything, this is a temporary failure // If we can't check our inventory when not using MatchEverything, this is a temporary failure
Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(inventory))); Bot.ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(inventory)));
return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily); return new ParseTradeResult(tradeOffer.TradeOfferID, ParseTradeResult.EResult.RejectedTemporarily);
} }
// Now let's create a map which maps items to their amount in our EQ bool accept = IsTradeNeutralOrBetter(inventory, tradeOffer.ItemsToGive, tradeOffer.ItemsToReceive);
// 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 // Even if trade is not neutral+ for us right now, it might be in the future, unless we're bot account where we assume that inventory doesn't change
Dictionary<uint, List<uint>> itemAmountToGivePerGame = new Dictionary<uint, List<uint>>(appIDs.Count); return new ParseTradeResult(tradeOffer.TradeOfferID, accept ? ParseTradeResult.EResult.AcceptedWithItemLose : (Bot.BotConfig.IsBotAccount ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily));
Dictionary<ulong, uint> itemAmountsToGive = new Dictionary<ulong, uint>(itemAmounts);
foreach (Steam.Item item in tradeOffer.ItemsToGive) {
if (!itemAmountToGivePerGame.TryGetValue(item.RealAppID, out List<uint> amountsToGive)) {
amountsToGive = new List<uint>();
itemAmountToGivePerGame[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<uint, List<uint>> itemAmountToReceivePerGame = new Dictionary<uint, List<uint>>(appIDs.Count);
Dictionary<ulong, uint> itemAmountsToReceive = new Dictionary<ulong, uint>(itemAmounts);
foreach (Steam.Item item in tradeOffer.ItemsToReceive) {
if (!itemAmountToReceivePerGame.TryGetValue(item.RealAppID, out List<uint> amountsToReceive)) {
amountsToReceive = new List<uint>();
itemAmountToReceivePerGame[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());
// Trade is neutral+ for us if the difference is greater than 0
// If not, we assume that the trade might be good for us in the future, unless we're bot account where we assume that inventory doesn't change
return new ParseTradeResult(tradeOffer.TradeOfferID, difference > 0 ? ParseTradeResult.EResult.AcceptedWithItemLose : (Bot.BotConfig.IsBotAccount ? ParseTradeResult.EResult.RejectedPermanently : ParseTradeResult.EResult.RejectedTemporarily));
} }
private sealed class ParseTradeResult { private sealed class ParseTradeResult {

View File

@@ -40,16 +40,6 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "UnusedParameter.Global")] [SuppressMessage("ReSharper", "UnusedParameter.Global")]
internal static void Forget(this object obj) { } internal static void Forget(this object obj) { }
internal static string GetArgsAsString(this string[] args, byte argsToSkip = 1) {
if ((args == null) || (args.Length < argsToSkip)) {
ASF.ArchiLogger.LogNullError(nameof(args));
return null;
}
string result = string.Join(" ", args.GetArgs(argsToSkip));
return result;
}
internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) { internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
if ((cookieContainer == null) || string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) { if ((cookieContainer == null) || string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) {
ASF.ArchiLogger.LogNullError(nameof(cookieContainer) + " || " + nameof(url) + " || " + nameof(name)); ASF.ArchiLogger.LogNullError(nameof(cookieContainer) + " || " + nameof(url) + " || " + nameof(name));
@@ -95,22 +85,34 @@ namespace ArchiSteamFarm {
} }
} }
internal static void StartBackgroundAction(Action action) { internal static void StartBackgroundAction(Action action, bool longRunning = true) {
if (action == null) { if (action == null) {
ASF.ArchiLogger.LogNullError(nameof(action)); ASF.ArchiLogger.LogNullError(nameof(action));
return; return;
} }
Task.Factory.StartNew(action, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning).Forget(); TaskCreationOptions options = TaskCreationOptions.DenyChildAttach;
if (longRunning) {
options |= TaskCreationOptions.LongRunning;
} }
internal static void StartBackgroundFunction(Func<Task> function) { Task.Factory.StartNew(action, options).Forget();
}
internal static void StartBackgroundFunction(Func<Task> function, bool longRunning = true) {
if (function == null) { if (function == null) {
ASF.ArchiLogger.LogNullError(nameof(function)); ASF.ArchiLogger.LogNullError(nameof(function));
return; return;
} }
Task.Factory.StartNew(function, TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning).Forget(); TaskCreationOptions options = TaskCreationOptions.DenyChildAttach;
if (longRunning) {
options |= TaskCreationOptions.LongRunning;
}
Task.Factory.StartNew(function, options).Forget();
} }
internal static IEnumerable<T> ToEnumerable<T>(this T item) where T : struct { internal static IEnumerable<T> ToEnumerable<T>(this T item) where T : struct {
@@ -118,22 +120,5 @@ namespace ArchiSteamFarm {
} }
internal static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3); internal static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3);
private static string[] GetArgs(this string[] args, byte argsToSkip = 1) {
if ((args == null) || (args.Length < argsToSkip)) {
ASF.ArchiLogger.LogNullError(nameof(args));
return null;
}
byte argsToCopy = (byte) (args.Length - argsToSkip);
string[] result = new string[argsToCopy];
if (argsToCopy > 0) {
Array.Copy(args, argsToSkip, result, 0, argsToCopy);
}
return result;
}
} }
} }

View File

@@ -35,26 +35,28 @@ using Newtonsoft.Json.Linq;
namespace ArchiSteamFarm { namespace ArchiSteamFarm {
internal sealed class WebBrowser { internal sealed class WebBrowser {
internal const byte MaxRetries = 5; // Defines maximum number of retries, UrlRequest() does not handle retry by itself (it's app responsibility) internal const byte MaxTries = 5; // Defines maximum number of recommended tries for a single request
private const byte MaxConnections = ServicePointManager.DefaultNonPersistentConnectionLimit; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state private const byte ExtendedTimeoutMultiplier = 10; // Defines multiplier of timeout for WebBrowsers dealing with huge data (ASF update)
private const byte MaxIdleTime = 15; // In seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it private const byte MaxConnections = 10; // Defines maximum number of connections per ServicePoint. Be careful, as it also defines maximum number of sockets in CLOSE_WAIT state
private const byte MaxIdleTime = 15; // Defines in seconds, how long socket is allowed to stay in CLOSE_WAIT state after there are no connections to it
internal readonly CookieContainer CookieContainer = new CookieContainer(); internal readonly CookieContainer CookieContainer = new CookieContainer();
private readonly ArchiLogger ArchiLogger; private readonly ArchiLogger ArchiLogger;
private readonly HttpClient HttpClient; private readonly HttpClient HttpClient;
internal WebBrowser(ArchiLogger archiLogger) { internal WebBrowser(ArchiLogger archiLogger, bool extendedTimeout = false) {
ArchiLogger = archiLogger ?? throw new ArgumentNullException(nameof(archiLogger)); ArchiLogger = archiLogger ?? throw new ArgumentNullException(nameof(archiLogger));
HttpClientHandler httpClientHandler = new HttpClientHandler { HttpClientHandler httpClientHandler = new HttpClientHandler {
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
CookieContainer = CookieContainer CookieContainer = CookieContainer,
MaxConnectionsPerServer = MaxConnections
}; };
HttpClient = new HttpClient(httpClientHandler) { HttpClient = new HttpClient(httpClientHandler) {
Timeout = TimeSpan.FromSeconds(Program.GlobalConfig.ConnectionTimeout) Timeout = TimeSpan.FromSeconds(extendedTimeout ? ExtendedTimeoutMultiplier * Program.GlobalConfig.ConnectionTimeout : Program.GlobalConfig.ConnectionTimeout)
}; };
// Most web services expect that UserAgent is set, so we declare it globally // Most web services expect that UserAgent is set, so we declare it globally
@@ -94,7 +96,7 @@ namespace ArchiSteamFarm {
} }
byte[] result = null; byte[] result = null;
for (byte i = 0; (i < MaxRetries) && (result == null); i++) { for (byte i = 0; (i < MaxTries) && (result == null); i++) {
result = await UrlGetToBytes(request, referer).ConfigureAwait(false); result = await UrlGetToBytes(request, referer).ConfigureAwait(false);
} }
@@ -102,7 +104,7 @@ namespace ArchiSteamFarm {
return result; return result;
} }
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxRetries)); ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
return null; return null;
} }
@@ -114,7 +116,7 @@ namespace ArchiSteamFarm {
} }
HtmlDocument result = null; HtmlDocument result = null;
for (byte i = 0; (i < MaxRetries) && (result == null); i++) { for (byte i = 0; (i < MaxTries) && (result == null); i++) {
result = await UrlGetToHtmlDocument(request, referer).ConfigureAwait(false); result = await UrlGetToHtmlDocument(request, referer).ConfigureAwait(false);
} }
@@ -122,7 +124,7 @@ namespace ArchiSteamFarm {
return result; return result;
} }
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxRetries)); ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
return null; return null;
} }
@@ -134,7 +136,7 @@ namespace ArchiSteamFarm {
} }
JObject result = null; JObject result = null;
for (byte i = 0; (i < MaxRetries) && (result == null); i++) { for (byte i = 0; (i < MaxTries) && (result == null); i++) {
result = await UrlGetToJObject(request, referer).ConfigureAwait(false); result = await UrlGetToJObject(request, referer).ConfigureAwait(false);
} }
@@ -142,7 +144,7 @@ namespace ArchiSteamFarm {
return result; return result;
} }
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxRetries)); ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
return null; return null;
} }
@@ -150,12 +152,12 @@ namespace ArchiSteamFarm {
internal async Task<T> UrlGetToJsonResultRetry<T>(string request, string referer = null) { internal async Task<T> UrlGetToJsonResultRetry<T>(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request)); ArchiLogger.LogNullError(nameof(request));
return default(T); return default;
} }
string json = await UrlGetToContentRetry(request, referer).ConfigureAwait(false); string json = await UrlGetToContentRetry(request, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) { if (string.IsNullOrEmpty(json)) {
return default(T); return default;
} }
try { try {
@@ -167,7 +169,7 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericDebug(string.Format(Strings.Content, json)); ArchiLogger.LogGenericDebug(string.Format(Strings.Content, json));
} }
return default(T); return default;
} }
} }
@@ -178,7 +180,7 @@ namespace ArchiSteamFarm {
} }
XmlDocument result = null; XmlDocument result = null;
for (byte i = 0; (i < MaxRetries) && (result == null); i++) { for (byte i = 0; (i < MaxTries) && (result == null); i++) {
result = await UrlGetToXML(request, referer).ConfigureAwait(false); result = await UrlGetToXML(request, referer).ConfigureAwait(false);
} }
@@ -186,7 +188,7 @@ namespace ArchiSteamFarm {
return result; return result;
} }
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxRetries)); ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
return null; return null;
} }
@@ -198,7 +200,7 @@ namespace ArchiSteamFarm {
} }
bool result = false; bool result = false;
for (byte i = 0; (i < MaxRetries) && !result; i++) { for (byte i = 0; (i < MaxTries) && !result; i++) {
result = await UrlHead(request, referer).ConfigureAwait(false); result = await UrlHead(request, referer).ConfigureAwait(false);
} }
@@ -206,7 +208,7 @@ namespace ArchiSteamFarm {
return true; return true;
} }
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxRetries)); ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
return false; return false;
} }
@@ -218,7 +220,7 @@ namespace ArchiSteamFarm {
} }
Uri result = null; Uri result = null;
for (byte i = 0; (i < MaxRetries) && (result == null); i++) { for (byte i = 0; (i < MaxTries) && (result == null); i++) {
result = await UrlHeadToUri(request, referer).ConfigureAwait(false); result = await UrlHeadToUri(request, referer).ConfigureAwait(false);
} }
@@ -226,7 +228,7 @@ namespace ArchiSteamFarm {
return result; return result;
} }
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxRetries)); ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
return null; return null;
} }
@@ -249,7 +251,7 @@ namespace ArchiSteamFarm {
} }
bool result = false; bool result = false;
for (byte i = 0; (i < MaxRetries) && !result; i++) { for (byte i = 0; (i < MaxTries) && !result; i++) {
result = await UrlPost(request, data, referer).ConfigureAwait(false); result = await UrlPost(request, data, referer).ConfigureAwait(false);
} }
@@ -257,7 +259,7 @@ namespace ArchiSteamFarm {
return true; return true;
} }
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxRetries)); ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
return false; return false;
} }
@@ -275,12 +277,12 @@ namespace ArchiSteamFarm {
internal async Task<T> UrlPostToJsonResultRetry<T>(string request, ICollection<KeyValuePair<string, string>> data = null, string referer = null) { internal async Task<T> UrlPostToJsonResultRetry<T>(string request, ICollection<KeyValuePair<string, string>> data = null, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request)); ArchiLogger.LogNullError(nameof(request));
return default(T); return default;
} }
string json = await UrlPostToContentRetry(request, data, referer).ConfigureAwait(false); string json = await UrlPostToContentRetry(request, data, referer).ConfigureAwait(false);
if (string.IsNullOrEmpty(json)) { if (string.IsNullOrEmpty(json)) {
return default(T); return default;
} }
try { try {
@@ -292,7 +294,7 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericDebug(string.Format(Strings.Content, json)); ArchiLogger.LogGenericDebug(string.Format(Strings.Content, json));
} }
return default(T); return default;
} }
} }
@@ -333,7 +335,7 @@ namespace ArchiSteamFarm {
} }
string result = null; string result = null;
for (byte i = 0; (i < MaxRetries) && string.IsNullOrEmpty(result); i++) { for (byte i = 0; (i < MaxTries) && string.IsNullOrEmpty(result); i++) {
result = await UrlGetToContent(request, referer).ConfigureAwait(false); result = await UrlGetToContent(request, referer).ConfigureAwait(false);
} }
@@ -341,7 +343,7 @@ namespace ArchiSteamFarm {
return result; return result;
} }
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxRetries)); ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
return null; return null;
} }
@@ -380,14 +382,15 @@ namespace ArchiSteamFarm {
} }
private async Task<HttpResponseMessage> UrlGetToResponse(string request, string referer = null) { private async Task<HttpResponseMessage> UrlGetToResponse(string request, string referer = null) {
if (!string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
return await UrlRequest(new Uri(request), HttpMethod.Get, null, referer).ConfigureAwait(false);
}
ArchiLogger.LogNullError(nameof(request)); ArchiLogger.LogNullError(nameof(request));
return null; return null;
} }
HttpResponseMessage result = await UrlRequest(new Uri(request), HttpMethod.Get, null, referer).ConfigureAwait(false);
return result;
}
private async Task<XmlDocument> UrlGetToXML(string request, string referer = null) { private async Task<XmlDocument> UrlGetToXML(string request, string referer = null) {
if (string.IsNullOrEmpty(request)) { if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request)); ArchiLogger.LogNullError(nameof(request));
@@ -464,7 +467,7 @@ namespace ArchiSteamFarm {
} }
string result = null; string result = null;
for (byte i = 0; (i < MaxRetries) && string.IsNullOrEmpty(result); i++) { for (byte i = 0; (i < MaxTries) && string.IsNullOrEmpty(result); i++) {
result = await UrlPostToContent(request, data, referer).ConfigureAwait(false); result = await UrlPostToContent(request, data, referer).ConfigureAwait(false);
} }
@@ -472,7 +475,7 @@ namespace ArchiSteamFarm {
return result; return result;
} }
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxRetries)); ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorRequestFailedTooManyTimes, MaxTries));
ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request)); ArchiLogger.LogGenericDebug(string.Format(Strings.ErrorFailingRequest, request));
return null; return null;
} }
@@ -486,7 +489,7 @@ namespace ArchiSteamFarm {
return null; return null;
} }
private async Task<HttpResponseMessage> UrlRequest(Uri requestUri, HttpMethod httpMethod, ICollection<KeyValuePair<string, string>> data = null, string referer = null, byte maxRedirections = MaxRetries) { private async Task<HttpResponseMessage> UrlRequest(Uri requestUri, HttpMethod httpMethod, ICollection<KeyValuePair<string, string>> data = null, string referer = null, byte maxRedirections = MaxTries) {
if ((requestUri == null) || (httpMethod == null)) { if ((requestUri == null) || (httpMethod == null)) {
ArchiLogger.LogNullError(nameof(requestUri) + " || " + nameof(httpMethod)); ArchiLogger.LogNullError(nameof(requestUri) + " || " + nameof(httpMethod));
return null; return null;
@@ -532,7 +535,17 @@ namespace ArchiSteamFarm {
ushort status = (ushort) responseMessage.StatusCode; ushort status = (ushort) responseMessage.StatusCode;
if ((status >= 300) && (status <= 399) && (maxRedirections > 0)) { if ((status >= 300) && (status <= 399) && (maxRedirections > 0)) {
redirectUri = responseMessage.Headers.Location; redirectUri = responseMessage.Headers.Location;
if (!redirectUri.IsAbsoluteUri) {
if (redirectUri.IsAbsoluteUri) {
switch (redirectUri.Scheme) {
case "http":
case "https":
break;
default:
// Invalid ones such as "steammobile"
return null;
}
} else {
redirectUri = new Uri(requestUri.GetLeftPart(UriPartial.Authority) + redirectUri); redirectUri = new Uri(requestUri.GetLeftPart(UriPartial.Authority) + redirectUri);
} }
} else { } else {

View File

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

View File

@@ -1,5 +1,6 @@
{ {
"AcceptGifts": false, "AcceptGifts": false,
"AutoDiscoveryQueue": false,
"CardDropsRestricted": true, "CardDropsRestricted": true,
"CustomGamePlayedWhileFarming": null, "CustomGamePlayedWhileFarming": null,
"CustomGamePlayedWhileIdle": null, "CustomGamePlayedWhileIdle": null,
@@ -9,12 +10,16 @@
"FarmOffline": false, "FarmOffline": false,
"GamesPlayedWhileIdle": [], "GamesPlayedWhileIdle": [],
"HandleOfflineMessages": false, "HandleOfflineMessages": false,
"IdleRefundableGames": true,
"IsBotAccount": false, "IsBotAccount": false,
"LootableTypes": [ "LootableTypes": [
1, 1,
3, 3,
5 5
], ],
"MatchableTypes": [
5
],
"PasswordFormat": 0, "PasswordFormat": 0,
"Paused": false, "Paused": false,
"RedeemingPreferences": 0, "RedeemingPreferences": 0,

View File

@@ -18,7 +18,7 @@ build:
project: ArchiSteamFarm.sln project: ArchiSteamFarm.sln
parallel: true parallel: true
verbosity: minimal verbosity: minimal
after_build: after_test:
- ps: >- - ps: >-
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
@@ -33,16 +33,16 @@ after_build:
dotnet publish -c "$env:CONFIGURATION" -r "$RUNTIME" -o "out\$RUNTIME" dotnet publish -c "$env:CONFIGURATION" -r "$RUNTIME" -o "out\$RUNTIME"
} }
Add-Content "ArchiSteamFarm\out\$RUNTIME\ArchiSteamFarm.version" "$RUNTIME" Set-Content -Path "ArchiSteamFarm\out\$RUNTIME\ArchiSteamFarm.version" -Value "$RUNTIME"
7z a -bd -tzip -mm=Deflate64 -mx=1 "ArchiSteamFarm\out\ASF-$RUNTIME.zip" "$env:APPVEYOR_BUILD_FOLDER\ArchiSteamFarm\out\$RUNTIME\*" 7z a -bd -tzip -mm=Deflate64 -mx=5 "ArchiSteamFarm\out\ASF-$RUNTIME.zip" "$env:APPVEYOR_BUILD_FOLDER\ArchiSteamFarm\out\$RUNTIME\*"
Push-AppveyorArtifact "ArchiSteamFarm\out\ASF-$RUNTIME.zip" -FileName "ASF-$RUNTIME.zip" -DeploymentName "ASF-$RUNTIME.zip" Push-AppveyorArtifact "ArchiSteamFarm\out\ASF-$RUNTIME.zip" -FileName "ASF-$RUNTIME.zip" -DeploymentName "ASF-$RUNTIME.zip"
} }
deploy: deploy:
- provider: GitHub - provider: GitHub
tag: $(appveyor_repo_tag_name) tag: $(appveyor_repo_tag_name)
release: ASF V$(appveyor_repo_tag_name) release: ArchiSteamFarm V$(appveyor_repo_tag_name)
description: '**NOTICE:** Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features or rewritten implementations. If you don''t consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchi/ArchiSteamFarm/wiki/Release-cycle)** if you''d like to learn more.\n\n---\n\nThis is automated AppVeyor GitHub deployment, human-readable changelog should be available soon. In the meantime please refer to **[GitHub commits](https://github.com/JustArchi/ArchiSteamFarm/commits/$(appveyor_repo_tag_name))**.\n\n---\n\nASF is available for free. If you''re grateful for what we''re doing, please consider donating. Developing ASF requires massive amount of time and knowledge, especially when it comes to Steam (and its problems). Even 1$ is highly appreciated and shows that you care!\n\n [![Patreon support](https://img.shields.io/badge/Patreon-support-yellow.svg)](https://www.patreon.com/JustArchi) [![Paypal.me donate](https://img.shields.io/badge/Paypal.me-donate-yellow.svg)](https://www.paypal.me/JustArchi/1usd) [![Paypal donate](https://img.shields.io/badge/Paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [![Bitcoin donate](https://img.shields.io/badge/Bitcoin-donate-yellow.svg)](https://blockchain.info/payment_request?address=1Archi6M1r5b41Rvn1SY2FfJAzsrEUT7aT) [![Steam donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)' description: '### **NOTICE:** Pre-releases are experimental versions that often contain unpatched bugs, work-in-progress features or rewritten implementations. If you don''t consider yourself advanced user, please download **[latest stable release](https://github.com/JustArchi/ArchiSteamFarm/releases/latest)** instead. Pre-release versions are dedicated to users who know how to report bugs, deal with issues and give feedback - no technical support will be given. Check out ASF **[release cycle](https://github.com/JustArchi/ArchiSteamFarm/wiki/Release-cycle)** if you''d like to learn more.\n\n---\n\nThis is automated AppVeyor GitHub deployment, human-readable changelog should be available soon. In the meantime please refer to **[GitHub commits](https://github.com/JustArchi/ArchiSteamFarm/commits/$(appveyor_repo_tag_name))**.\n\n---\n\nASF is available for free. If you''re grateful for what we''re doing, please consider donating. Developing ASF requires massive amount of time and knowledge, especially when it comes to Steam (and its problems). Even 1$ is highly appreciated and shows that you care!\n\n [![Patreon support](https://img.shields.io/badge/Patreon-support-yellow.svg)](https://www.patreon.com/JustArchi) [![Paypal.me donate](https://img.shields.io/badge/Paypal.me-donate-yellow.svg)](https://www.paypal.me/JustArchi/1usd) [![Paypal donate](https://img.shields.io/badge/Paypal-donate-yellow.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=HD2P2P3WGS5Y4) [![Bitcoin donate](https://img.shields.io/badge/Bitcoin-donate-yellow.svg)](https://blockchain.info/payment_request?address=1Archi6M1r5b41Rvn1SY2FfJAzsrEUT7aT) [![Steam donate](https://img.shields.io/badge/Steam-donate-yellow.svg)](https://steamcommunity.com/tradeoffer/new/?partner=46697991&token=0ix2Ruv_)'
auth_token: auth_token:
secure: QC5gIDMvSpd43EG6qW8d1E3ZHiVU4aR7pbKQonXstjj/JtAABf5S1IbtoY4OsnOR secure: QC5gIDMvSpd43EG6qW8d1E3ZHiVU4aR7pbKQonXstjj/JtAABf5S1IbtoY4OsnOR
artifact: /.*/ artifact: /.*/
@@ -53,3 +53,17 @@ deploy:
branch: master branch: master
configuration: Release configuration: Release
appveyor_repo_tag: true appveyor_repo_tag: true
notifications:
- provider: Webhook
url:
secure: i/og7KzkpbcWcKoUubrLH+KB6bkfqA55FHUlGxLepLmgZNQeNMiMoAFICOFY795fFrFfUNUKqwk7ApXE6HUyWMoiijLj7G/JBLTPkBiTCu8fZMTMqwQm6FiHB3+/0h0C+ukcrBEqnXYSQUh6znpKqJSTrIfXUQ7ftNuC966kBAw=
method: POST
body: >-
{
"avatar_url": "https://www.appveyor.com/assets/img/appveyor-logo-256.png",
"username": "AppVeyor",
"content": "[{{projectName}}:{{branch}}] {{commitMessage}} - {{committerName}} ({{commitId}}) | {{buildUrl}}/artifacts | **{{status}}**"
}
on_build_success: true
on_build_failure: true
on_build_status_changed: false

View File

@@ -15,9 +15,9 @@ This tool is being used by ASF developers for synchronization of strings/transla
## Before you begin ## Before you begin
- Make sure that your ```crowdin_identity.yaml``` file exists - this is the file with login credentials that is not being committed to GitHub. If it doesn't exist yet (e.g. because you've just cloned the repo), create it from ```crowdin_identity_example.yaml``` and fill ```api_key``` that can be found **[here](http://l10n.asf.justarchi.net/project/archisteamfarm/settings#api)**. - Make sure that your `crowdin_identity.yaml` file exists - this is the file with login credentials that is not being committed to GitHub. If it doesn't exist yet (e.g. because you've just cloned the repo), create it from `crowdin_identity_example.yaml` and fill `api_key` that can be found **[here](http://l10n.asf.justarchi.net/project/archisteamfarm/settings#api)**.
- Ensure that ```crowdin``` command is recognized by your OS. - Ensure that `crowdin` command is recognized by your OS.
--- ---
@@ -25,14 +25,16 @@ This tool is being used by ASF developers for synchronization of strings/transla
- Install **[Java JDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html)**. - Install **[Java JDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html)**.
- **[Set JAVA_HOME properly](https://confluence.atlassian.com/doc/setting-the-java_home-variable-in-windows-8895.html)**. - **[Set JAVA_HOME properly](https://confluence.atlassian.com/doc/setting-the-java_home-variable-in-windows-8895.html)**.
- Launch ```setup_crowdin.bat``` as administrator. - Launch `setup_crowdin.bat` as administrator.
- Open new ```cmd``` prompt and verify that ```crowdin help``` indeed works. - Open new `cmd` prompt and verify that `crowdin help` indeed works.
--- ---
## Usage ## Usage
- ```archi_download.bat``` for downloading translations from Crowdin (typically last commit before release). - `archi_upload.bat` for pushing strings to Crowdin (when any `*Strings.resx` file gets modified).
- ```archi_upload.bat``` for pushing strings to Crowdin (when any ```*Strings.resx``` file gets modified). - `archi_download.bat` for downloading translations from Crowdin (typically last commit before release).
- `archi_sync.bat` for upload + download (tree sync).

View File

@@ -0,0 +1,5 @@
@echo off
cd ..\\..
call crowdin -b master --identity tools\\crowdin-cli\\crowdin_identity.yaml upload sources
call crowdin -b master --identity tools\\crowdin-cli\\crowdin_identity.yaml download
pause

Binary file not shown.